使用增强版 singleflight 合并事件推送,效果炸裂

科技资讯 投稿 5900 0 评论

使用增强版 singleflight 合并事件推送,效果炸裂

最近在工作中对 Go 的 singleflight 包做了下增强,解决了一个性能问题,这里记录下,希望对你也有所帮助。

singleflight 是什么

举个例子,当程序中有读(如 Redis、MySQL、Http、RPC等)请求,且并发非常高的情况,使用 singleflight 能得到比较好的效果,它限制了同一时刻只有一个请求在执行,也就是并发永远为1。

singleflight 的原理

https://github.com/golang/groupcache/blob/master/singleflight/singleflight.go

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。

读可以抑制,写呢

微服务中的注册中心想必大家都有所了解,如果不了解,可以去查查相关概念,或者翻看我以前的文章,老读者应该能发现我写了很多相关的文章。

    接口级注册(类似 Dubbo),每台机器会注册N多次
  • 服务并发发布,例如每次发布重启100台机器,那么注册的并发就可能是100

拿到这种问题,第一想到的解法是:合并推送。但,怎么合并呢?

直接使用 singleflight,能行吗?

套用上面 singleflight,在第一个事件推送过程中,其他相同的事件被 Hold 住,等第一个事件推送完成后,这些 Hold 的事件不再执行推送直接返回。

增强一点点 🤏🏻

增强代码参考

package singleflight

import (
	"sync"


type WriteGroup struct {
	mu    sync.Mutex
	wgs   map[string]*sync.WaitGroup
	group Group
}

func (g *WriteGroup Do(key string, fn func( error error {
	g.mu.Lock(
	if g.wgs == nil {
		g.wgs = make(map[string]*sync.WaitGroup
	}
	wg, ok := g.wgs[key]
	if !ok {
		wg = &sync.WaitGroup{}
		wg.Add(1
		g.wgs[key] = wg
	}
	g.mu.Unlock(

	if !ok {
		err := fn(

		g.mu.Lock(
		wg.Done(
		delete(g.wgs, key
		g.mu.Unlock(
		return err
	}

	wg.Wait(
	_, err := g.group.Do(key, func( (interface{}, error {
		return nil, fn(
	}
	return err
}

效果如何?

理论上,如果没有并发,事件和以前一样推送,没有合并,当然这也没毛病。当并发大于 2 时,开始发挥威力。在实际的压测上,注册并发 1500 时,合并的事件达到 99.9%,效果相当炸裂!

最后感谢能抽空看到这里,如果你能点赞在看分享,我会更加感激不尽~


    搜索关注微信公众号"捉虫大师",后端技术分享,架构设计、性能优化、源码阅读、问题排查、踩坑实践
  • 进技术交流群加微信 MrRoshi

编程笔记 » 使用增强版 singleflight 合并事件推送,效果炸裂

赞同 (31) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽