引入
如下图所示,可能存在来自桌面端和移动端的用户有 1000 的并发请求,同一时刻来访问的获取文章列表的接口,获取前 20 条信息,如果这时服务直接去访问 redis 出现 cache miss, 那就会去请求 1000 次数据库,这时可能会给数据库带来较大的压力(这里的1000 只是一个例子,实际上可能远大于这个值)导致服务异常或者超时。
这时就可以使用singleflight库了,直译过来就是单飞
实际开发中常见的做法是在查数据库前先去查缓存,如果缓存Miss(未命中)就去数据库中查到数据并放到缓存里。这是正常情况,然而缓存击穿则是指在高并发系统中,大量请求同时查询一个缓存的key,假如这个key刚好过期就会导致大量的请求都打到数据库上。在绝大多数情况下,可以考虑使用singleflight来抑制重复函数调用。
SingleFlight的作用是在处理多个goroutine同时调用同一个函数的时候,只让一个goroutine去实际调用这个函数,等到这个goroutine返回结果的时,再把结果返回给其他几个同时调用了相同函数的goroutine,这样可以减少并发调用的数量。
如果在某些场景下允许第一个调用失败后再次尝试调用该函数,而不希望同一时间内的多次请求都因第一个调用返回失败而失败,那么可以通过调用Forget
方法来忘记这个key。
源码剖析
Group 是 singleflight 的核心,代表一个组,用于执行具有重复抑制的工作单元。
type Group struct {mu sync.Mutex m map[string]*call
}
SingleFlight 是使用互斥锁 Mutex 和 Map 来实现的。互斥锁 Mutex 提供并发时的读写保护,而 Map 用于保存同一个 key 正在处理的请求。
主要作用是合并并发请求的场景,针对于同一时刻相同的读请求。
而对于并发写请求的场景,如果是多次写只需要一次的情况,那么也是满足的。例如:每个 http 请求都会携带 token,每次请求都需要把 token 存入缓存或者写入数据库,如果多次并发请求同时进来,只需要写一次即可。
https://www.jb51.net/jiaoben/307907bv2.htm
特点
总结
- Do和DoChan 一个用于同步阻塞调用传入的函数,一个用于异步调用传入的参数并通过 Channel 接收函数的返回值
- Forget可以通知 Group 在持有的映射表中删除某个键,接下来对该键的调用就不会等待前面的函数返回了
- 一旦调用的函数返回了错误,所有在等待的 Goroutine 也都会接收到同样的错误