eout
发布于

go singleflight 缓存击穿 缓存一致性

一致性

一致性问题

2个线程 协程,因为先后顺序不同,导致 最后update cache时候,不一致

缓存中常涉及到三个概念:缓存击穿,缓存雪崩,缓存穿透。我们先来区分一下:

  • 缓存穿透 指的是数据库本就没有这个数据,请求直奔数据库,缓存系统形同虚设,对数据库产生很大压力从而影响正常服务。
  • 缓存击穿 (失效)指的是数据库有数据,缓存本应该也有数据,但是缓存过期了,这层流量防护屏障被击穿了,大量高并发请求直奔数据库。
  • 缓存雪崩 指的是大量的热点数据无法在缓存中处理(大面积热点数据缓存失效、缓存服务宕机),流量全部打到数据库,导致数据库极大压力。

缓存击穿和缓存雪崩有点像,又有点不一样,缓存雪崩是指大面积的缓存失效,打崩了DB,而缓存击穿则是指一个热点key,在不停的扛着高并发,高并发集中对着这一个点进行访问,如果这个key在失效的瞬间,持续的并发到来就会穿破缓存,直接请求到数据库,就像一个完好无损的桶上凿开了一个洞,造成某一时刻数据库请求量过大,压力剧增。

针对【穿透】,我们经常考虑的方案是 缓存零值,也就是如果数据库没数据,我们也会缓存一个零值,这样当有请求来的时候,缓存这一层可以清晰的告知对方,不是我这儿没有,而是本来就是零值,你回源也没用,这里是对数据库的保护。

一堆 key 因为种种原因没有被缓存拦住,直接访问DB,这叫【雪崩】,对应的我们可能会考虑是不是打散过期时间,避免同时大量key都需要回源。而一个key失效,大量并发请求同时访问DB 拉这一个key的数据,描述的就是【击穿】。

singleflight 解决的其实就是【缓存击穿】的问题。


singleflight 类似java 中 类级别的 synchronized . 基于key 锁住fetch方法

必须共用一个 singleflight
func main() {
 
  var singleSetCache singleflight.Group // 必须共用一个 singleflight
 
  getAndSetCache := func (requestID int, cacheKey string) (string, error) {
      //do的入参key,可以直接使用缓存的key,这样同一个缓存,只有一个协程会去读DB
      value, _, _ := singleSetCache.Do(cacheKey, func() (ret interface{}, err error) {
          return "VALUE", nil
      })
      return value.(string),nil
  }
 
  cacheKey := "cacheKey"
 
  for i:=1;i<10;i++{//模拟多个协程同时请求
      go func(requestID int) {
         value, _:= getAndSetCache(requestID,cacheKey)
       }(i)
  }
}

https://github.com/dtm-labs/rockscache 这个cache中用

singleflight 源码分析参考
https://juejin.cn/post/7123495892572700702/

浏览 (1809)
点赞
收藏
评论