likes
comments
collection
share

缓存击穿以及singleflight

作者站长头像
站长
· 阅读数 11

什么是缓存击穿

缓存击穿,这里击穿的是缓存,遭受压力的是DB。

在提到缓存击穿时,其实有三个关于缓存和DB的问题场景:缓存击穿、缓存穿透、缓存雪崩。

缓存击穿

问题场景

在高并发的情况下,某个热门的缓存数据过期,导致大量请求在缓存中未找到数据,直接到DB请求数据,从而导致DB的压力瞬间暴增。

解决方案

缓存击穿一般情况下不会出现DB宕机的情况,但是会导致DB出现周期性的压力。既然出现问题的是因为热数据过期,那么解决方案就是让它不过期,直接设置为永久不过期或者为其实现续期。

将数据设置为永久不过期时,需要注意数据的冷热变更,也就是原来“热key”变为“冷key”,比如一份配置信息,之前是热数据,设为永久不过期是合理的;但是随着业务的变化,它可能渐渐地变为冷数据,也就是由原来的“高并发”变为“低并发”,这时候就不适合永久放在缓存中。可以通过业务上的设计,使用手动清除或者引入lru进行清除。

永久有效的缓存,还涉及如何设计更新的问题,简单来说就是更新和读取要实现互斥,如果是分布式系统,需要引入分布式锁。可以选择同步更新或者异步更新。同步更新就是使用互斥阻塞,更新时锁住资源,可能带来读业务的延迟;异步更新就是周期性更新缓存,可能会读到过期的缓存数据。

缓存穿透

问题场景

缓存穿透一般会存在于被攻击的场景,例如正常的业务id都是大于0的,但是攻击者会尝试发出id=-1的请求,这时候对应的数据在缓存和DB中都不存在,这类请求多了,DB就会承受很大的压力。

解决方案

缓存穿透是因为DB不存在请求的数据,针对这种数据,一个方案是在业务上做好非法请求的校验,例如对于id<=0的请求,直接拒绝,避免直通DB的压力。另一个方案是不存在的数据直接在缓存中为其生成一个缓存数据,并且设置该缓存的过期时间,例如60s。这样就很好地缓解了短时间缓存穿透带来的DB压力。

缓存雪崩

问题场景

缓存雪崩指当大量的缓存数据在某一个时刻过期时,同时出现了大量的缓存击穿,大量数据请求落在DB上,导致DB压力过大或者宕机。

解决方案

缓存雪崩的解决方案一个是减少同时过期的情况发生,分析业务场景,将缓存数据的过期进行削峰处理;另一个是参考缓存击穿的处理,对缓存数据进行冷热区分,热数据进行永久缓存+更新。

上述的缓存问题和解决方案,都是针对缓存数据层。对于缓存击穿的问题场景,除了直接处理缓存数据,还可以在业务逻辑层对逻辑进行缓存处理。这时候就可以引出基于go语言开发的singleflight库。

singleflight实现机制

缓存击穿以及singleflight

singleflight其实原理很简单,其核心结构如下:

// Group represents a class of work and forms a namespace in
// which units of work can be executed with duplicate suppression.
type Group struct {
    mu sync.Mutex       // protects m
    m  map[string]*call // lazily initialized
}

其中的map[string]*call 表示请求的缓存列表,同类请求,key是相同的。当一个业务请求到达时,如果已经有正在处理中的同类请求,则就阻塞等待其返回结果,而不是直接跑业务逻辑。 singleflight解决的问题场景比缓存击穿更宽泛些,不只是针对数据访问。

总结

对于优化高并发请求场景,缓解服务压力,singleflight是个很好的工具库,它能解决包括缓存击穿在内的同参高并发请求问题,值得在项目推广。