singleflight 是啥

lxf2023-05-20 00:59:27

hello,大家好呀,我就是小院。

近期在工作上对 Go 的 singleflight 包做了一下提高,克服了一个兼容性问题,这儿记录下来,希望对大家也有一定的帮助。

singleflight 是啥

singleflight 直译为”单(次)飞(行)“,这是对同一种请求抑止,确保同一时时刻刻同样的要求只有一个在实施,且在它们执行过程中的同样要求都是会 Hold 直至实行进行,这种 hold 请求也应用此次实施的结论。

举例说明,当程序流程含有读(如 Redis、MySQL、Http、RPC等)要求,且高并发很高的状况,应用 singleflight 能够得到比良好的效果,它阻碍了同一时间只有一个要求在实施,其实就是高并发始终为1。

singleflight 是啥

singleflight 的基本原理

最开始 singleflight 出现在了 groupcache 工程中,这样的项目都是 Go 精英团队所作,之后该抱被挪到 Go 源代码中,在 Go 源代码里的版本号通过几场迭代更新,稍微有一点繁杂,就以最原始源代码来介绍基本原理,比较方便地看清本质。

github.com/golang/grou…

singleflight 把每一次要求界定为 call,每一个 call 目标包括了一个 waitGroup,一个 val,即请求传参,一个 err,即要求返回不正确。

type call struct {
wg  sync.WaitGroup
val interface{}
err error
}

再定义全局性 Group,包括一个互斥锁 Mutex,一个 key 为 string,value 为 call 的 map。

type Group struct {
mu sync.Mutex       
m  map[string]*call
}

Group 目标有一个 Do 方式,其第一个主要参数是 string 类别的 key,这一 key 其实就是上面所说的 map 的 key,同样的 key 意味着她们是一样的要求,仅有同样的要求能被抑止;第二个主要参数是一个函数公式 fn,这一函数是真正想实施的函数公式,比如启用 MySQL;传参比较合适了解,即最后调用的传参和错误报告。

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
}

把整个编码分为三块:

  • ① 懒加载方法复位 map;
  • ② 假如现阶段 key 存有,即同样要求已经启用中,就等着它进行,结束后直接用它 value 和 error;
  • ③ 假如现阶段 key 不会有,即并没有同样要求已经启用中,就创建一个 call 目标,并将其放入 map,然后实行 fn 函数公式,当函数公式执行完唤起 waitGroup,并删掉 map 对应的 key,回到 value 和 error。

读能够抑制,写呢?

我们可以通过以上的介绍能够了解,singleflight 可以解决高并发念的难题,但我又遇到一个高并发所写的难题。为了能会让大家迅速达到最佳状态,来花一点篇数描述一下碰到的现实问题:

微服务架构里的认证中心相信大家都有一定的了解,假如不掌握,能去查下有关定义,或是翻阅我之前的帖子,老阅读者应该可以发现自己写了很多相关的文章。

服务提供方在申请以后,会把变动事情传送到交易方,消息推送事件处理程序是:接受到事情,查看拼装出最新数据信息,随后推送到订阅者。存有这两种情况可能会致使短时间申请注册要求特别多,消息推送事情多就会影响到认证中心性能

  • 插口级申请注册(相近 Dubbo),每一台设备会申请注册N数次
  • 服务项目高并发公布,比如每一次公布重新启动100台机器,那样登记注册的高并发就有可能是100

取得这类问题,第一能想到的打法是:合拼消息推送。但,怎么合并呢?

是否每一次发送的情况下等一等,事件后也来了再一把推过去就行了?但等多长时间?什么时候等呢?粗鲁点,每秒钟消息推送一次,这样就可以将一秒内时间都汇聚,但这时候危害发送的及时性,显而易见不符大家追求卓越的规定。

直接用 singleflight,能行吗?

套入上边 singleflight ,在第一个事情消息推送环节中,别的同样的事情被 Hold 住,等第一个事情消息推送结束后,这种 Hold 事件不会再实行消息推送立即回到。

略微想一下就了解那样是错误的,假定有三个事情 A、B、C,分别代表到三个版本数据信息A1、B1、C1,A 最开始抵达,在 A 逐渐消息推送后但是没有结束时 B、C 事情抵达,A 事件触发消息推送了 A1 版本数据信息,B、C 事情在 A 事情消息推送结束后,立即丢掉,最后传送到顾客上的数据版本号为 A1,但是我们毫无疑问期待发送的数据信息版本号为 C1,画个图线感受一下:

singleflight 是啥

提高一点点