[GoCache] 缓存击穿

3 篇文章 0 订阅
3 篇文章 0 订阅

防止缓存击穿

1. 缓存事故

缓存雪崩、缓存击穿和缓存穿透是缓存系统中常见的比较严重的问题。

缓存雪崩: 缓存在同一时刻全部失效,造成瞬间DB请求量过大、压力骤增,引起雪崩。缓存雪崩通常因为缓存服务器宕机、缓存的 key 设置了相同的过期时间等引起。

缓存击穿:一个存在的热点key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬间DB请求量过大、压力骤增。

缓存穿透:查询一个不存在的数据,因为不存在则不会写到缓存中,所以每次都会去请求 DB,如果瞬间流量过大,穿透到 DB,导致宕机。

2. singleflight的实现

当并发地向同一个节点发起N个请求时,如果对数据库的访问没有做任何限制,很可能向数据库也会发起N次请求,容易造成缓存击穿和穿透。即使对数据库做了防护,HTTP请求也是十分消耗资源的操作,针对相同的key,向同一个节点发起多次请求是完全没有必要的。

singleflight

GoCache中设计了名为singleflightpackage来解决这个问题。

// call 表示正在进行中或者已结束的请求
type call struct {
    // 使用WaitGroup锁避免重入
    wg  sync.WaitGroup
    val interface{}
    err error
}

// Group 管理不同key的请求
type Group struct {
    mu       sync.Mutex //保护requests不被并发读写
    requests map[string]*call //requests中存放正在进行中的请求
}

实现Do方法,对相同key发起的多次请求,只会调用一次请求函数。

func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
    g.mu.Lock()
    if g.requests == nil {
      g.requests = make(map[string]*call)
    }
    // 存在进行中的请求,c等待
    if c, ok := g.requests[key]; ok {
      g.mu.Unlock()
      c.wg.Wait()
      return c.val, c.err //请求结束,返回结果
    }
    // 没有进行中的请求
    c := new(call)
    c.wg.Add(1) //发起请求前加锁
    g.requests[key] = c //添加到requests表中
    g.mu.Unlock()
    
    c.val, c.err = fn()
    c.wg.Done() //请求结束
    
    g.mu.Lock()
    delete(g.requests, key) //删除表中请求,更新requests 
    g.mu.Unlock()
    
    return c.val, c.err
}

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值