在一般的接口请求中,一般的获取获取的逻辑为:从memcache或者 redis 中获取 缓存 ,如果缓存失效,则从数据库中获取,这种获取数据的逻辑在qps低的时候也没有什么不良的后果。 但是想象这样的qps较高场景:做活动在最后时刻冲榜单是如果缓存失效,则大量的请求会落到数据库中,很容易就吧数据库打垮,此时上面的获取数据的逻辑就会有问题
解决方案
- 定时把数据刷到缓存中,这个对数据规模小且对数据敏感度不高的是一个比较好方案
- 在缓存失效获取数据时添加,添加全局锁,这样只读一次数据库,减少并发对数据库的压力,添加锁可以借助memcahe,redis 的incr 等命令来执行
3、可以在各个服务器实例中添加锁,也是一种方法,对并发量不太大的业务也是可行的
栗子-golang
package singleflight import "sync" // 一个执行主体 type call struct { wg sync.WaitGroup val interface{} err error } type Group struct { mu sync.Mutex // protects m m map[string]*call // lazily initialized } func (g *Group) Do (key string, fn func() (interface{}, error)) (interface{}, error) { g.mu.Lock() if g.m == nil { g.m = make(map[string]*call) } // 如果已经有任务存在 if c, ok := g.m[key]; ok { g.mu.Unlock() // 等待执行结束 c.wg.Wait() return c.val, c.err } // 新建一个执行主体 c := new(call) c.wg.Add(1) g.m[key] = c g.mu.Unlock() //获取定义函数的执行接口 c.val, c.err = fn() // 做完任务释放 c.wg.Done() g.mu.Lock() // 要删除组 delete(g.m, key) g.mu.Unlock() return c.val, c.err } 注意 do要求的存入的func 为 func() (interface{}, error) 类型