业务中我们经常遇到一些重复使用的轮子代码,本篇介绍了 singleflight 和 backoff 以及本地缓存!来提高我们平时业务开发的效率和代码的精简度!
singleflight
介绍
源码位置: https://github.com/golang/groupcache/tree/master/singleflight 或者 golang.org/x/sync/singleflight
主要是google 开源的group cache 封装的sdk,目的是为了解决 cache 回源的时候,容易出现并发加载一个或者多个key,导致缓存击穿
其中 golang.org/x/sync/singleflight 它提供了更多方法,比如异步加载等!但是代码量增加了很多,比如很多异步的bug之类的!
简单使用
- 简单模拟100个并发请求去加载
k1
1 | package main |
可以看到输出中,其中有2次去 loadKeyFromRemote
去加载,并没有做到完全防止得到的作用
- 如何解决上诉问题了,问题出在哪了?我们进行简单的源码分析
源码分析
- 数据结构
1 | // call is an in-flight or completed Do call |
- 主逻辑
1 | func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { |
- (1) 首先会去初始化一个 caller,然后waitgroup ++ ,然后set [锁]
- (2) 然后调用方法,再done [无锁]
- (3) 最后删除 key [锁]
- (4) 其他同一个key并发请求,会发现key存在,则直接wait了!
假如现在并发请求,那么此时假如都加载同一个key,那么只有一个key先经过,但是计算机执行的很快,在第(2)和(3)步执行的很快,导致key已经删除,但是还有请求未开始 Do
方法或者到了 g.m[key]
这一步,都是会再次重新走一遍
- 问题? 能不能使用读写锁优化加锁了?
假如读取key加的读锁,那么此时最长流程变为: 读锁 + 写锁 + 写锁, 最短流程变为: 读锁, 当特别高的并发才会有较为大的提升!
优化后用法
1 | package main |
backoff
介绍
主要是解决补偿的操作,当业务/方法遇到异常的情况,通常会有补偿的操作,一般就是业务继续重试
我经常使用这个包做重试,感觉比较好用!不用自己写for循环了
简单使用
- 模拟一个异常,去加载一个data数据,当遇到偶数的时候就爆异常!
1 | package main |
结果可以看到很好的解决了重试的问题!代码很优雅!
- 关于为啥业务中重试都喜欢等待一下,其实比较佛学!
sdk介绍
- back off
1 | type BackOff interface { |
- 封装了四个基本的Backoff
1 | // 不需要等待,继续重试 |
- 自适应backoff
整个时间 < 15min,重试时间从500ms开始增长,每次增长1.5倍,直到60s每次!
1 | // NewExponentialBackOff creates an instance of ExponentialBackOff using default values. |
组合使用,构建一个本地缓存!
这个应该是日常开发中经常用到的,本地缓存可以有效解决高频数据但是数据整体占用并不是特别的大,但是每次加载都需要额外的开销,所以基于本地缓存去构建一个可用性比较高的缓存框架!
- 核心代码
1 | package main |
- cache 实现
这里介绍一下sync.Map
为一个无过期的本地缓存和 go-cache
有ttl的缓存框架!或者你自己去实现一个也可以!
1 | import ( |
- 测试用例
1 | import ( |