前言
我们知道电商平台首页有许多数据都来自缓存,请求并不经过数据库,但假设某一时刻大流量访问某商品时,该商品的缓存正好过期就会造成缓存击穿,给数据库造成巨大压力。
解决思路
在面对秒杀等大并发请求的场景,而且这些请求都是读请求时,你可以把这些请求合并为一个请求
很幸运,Golang的扩展并发原语为我们提供了这一个机制。
下载包
1 |
go get golang.org/x/sync/singleflight |
直接上代码与效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
package main import ( "fmt" "time" "golang.org/x/sync/singleflight" ) var sf singleflight.Group // GetCache 获取缓存的方法 func GetCache() (interface{}, error) { // 先查 redis 缓存,有则直接返回 // .... // 当 redis 没有数据时 fmt.Println("缓存击穿") // 模拟读取数据库的数据的时间 time.Sleep(time.Second * 3) // 再次写入缓存 // ..... return "数据库的数据", nil } func main() { // 模拟 10 个用户同时请求 for i := 0; i < 10; i++ { go func(i int) { v, _, _ := sf.Do("key", GetCache) fmt.Printf("协程 %d 获取到了 %s \n", i, v.(string)) }(i) } // 防止主协程退出 time.Sleep(time.Second * 100) } |

我们能看到,只发生了一次缓存穿透。
这里我们主要使用到了 singleflight 包
singleflight
singleflight类的使用方法就新建一个singleflight.Group,使用其方法Do或者DoChan来包装方法,被包装的方法在对于同一个key,只会有一个协程执行,其他协程等待那个协程执行结束后,拿到同样的结果。
介绍如下:
Group结构体
1 |
代表一类工作,同一个 group 中,同样的 key 同时只能被执行一次。(当然你可以调用 Forget 方法,删除这个key) |
Do方法
1 |
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) |
- key:同一个key,同时只有一个协程执行。
- fn:被包装的函数。
- v:返回值,即执行的结果。其他等待的协程都会拿到。
- shared:表示是否有其他协程得到了这个结果v。
DoChan方法
1 |
func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result |
与Do方法一样,只是返回的是一个channel,执行结果会发送到channel中,其他等待的协程都可以从channel中拿到结果
完。
© 著作权归作者所有
文章评论(0)