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

这篇具有很好参考价值的文章主要介绍了使用增强版 singleflight 合并事件推送,效果炸裂!。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

hello,大家好啊,我是小楼。

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

singleflight 是什么

singleflight 直接翻译为”单(次)飞(行)“,它是对同一种请求的抑制,保证同一时刻相同的请求只有一个在执行,且在它执行期间的相同请求都会 Hold 直到执行完成,这些 hold 的请求也使用这次执行的结果。

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

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

singleflight 的原理

最初 singleflight 出现在 groupcache 项目中,这个项目也是 Go 团队所写,后来该包被移到 Go 源码中,在 Go 源码中的版本经过几轮迭代,稍微有点复杂,我们以最原始的源码来讲解原理,更方便地看清本质。

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

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 合并事件推送,效果炸裂!

增强一点点 🤏🏻

假设有事件 A、B、C、D 先后到达,A 事件仍然先正常执行推送,在 A 事件推送的时候,B、C、D 事件 Hlod 住,当 A 事件推送完成后,B 事件开始推送,B 事件将把 A 事件推送时期积攒的事件都一起推送掉,即 B、C、D 一次性推送完成。

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

增强代码参考

增强的定义为 WriteGroup,借用 singleflight 原先的实现,具体代码就不必解读了,对照上面的例子应该很好理解。

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%,效果相当炸裂!

最后感谢能抽空看到这里,如果你能点赞在看分享,我会更加感激不尽~文章来源地址https://www.toymoban.com/news/detail-450462.html


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

到了这里,关于使用增强版 singleflight 合并事件推送,效果炸裂!的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Git管理神器SourceTree使用教程详解(连接远程仓库,克隆,拉取,提交,推送,新建/切换/合并分支,冲突解决,提交PR)

    俗话说的好工欲善其事必先利其器,Git分布式版本控制系统是我们日常开发中不可或缺的。目前市面上比较流行的Git可视化管理工具有SourceTree、Github Desktop、TortoiseGit,综合网上的一些文章分析和自己的日常开发实践心得个人比较推荐开发者使用SourceTree,因为SourceTree同时支持

    2024年02月03日
    浏览(163)
  • JavaScript鼠标拖动事件监听使用方法及实例效果

    首先鼠标拖动事件需要与标签的 draggable属性配合使用,在标签中设置draggable属性为true则表示允许拖动该元素 鼠标拖动事件,当元素被拖动时该事件会持续重复触发,可以用于实时定位鼠标位置以让某元素跟随鼠标 当拖动开始时触发一次该事件,可以用于拖动前对元素进行一

    2024年02月05日
    浏览(29)
  • 工具系列(七) 本文(4万字) | Git入门教程 | 初始本地仓库-推送合并到远程仓库 | 解读VScode与PyCharm配置与使用git | CodeGeeX与Tabnine使用 |

    点击进入专栏: 《人工智能专栏》 Python与Python | 机器学习 | 深度学习 | 目标检测 | YOLOv5及其改进 | YOLOv8及其改进 | 关键知识点 | 各种工具教程

    2024年02月21日
    浏览(42)
  • 撤消尚未推送的 Git 合并

    我不小心在本地主分支上运行了 git merge some_other_branch。我还没有将更改推送到原始主机。如何撤消合并? 合并后,git status 说: 我如何撤消所有这些提交? huntsbot.com精选全球7大洲远程工作机会,涵盖各领域,帮助想要远程工作的数字游民们能更精准、更高效的找到对方。

    2024年02月09日
    浏览(22)
  • 达到chatgpt 90%效果的llama,Chinese-Alpaca-Plus-13B合并使用全过程分享

    基于llama的开源项目有很多,本次测试的是一个基于 7b的llama二次训练 的项目,本项目开源了 中文LLaMA模型 和 指令精调的Alpaca大模型 。这些模型在原版LLaMA的基础上扩充了中文词表并使用了中文数据进行二次预训练,进一步提升了中文基础语义理解能力。同时,中文Alpaca模型

    2024年02月11日
    浏览(26)
  • git控制台-分支的合并与推送

    博主 DTcode7 带您 溺亖在知识的海洋里,嘿嘿嘿.~ 🐒 个人主页—— DTcode7 的博客 🐒 《微信小程序相关博客》 《Vue相关博客》 《前端开发习惯与小技巧相关博客》 《AIGC相关博客》 《photoshop相关博客》 😚 吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤

    2024年04月17日
    浏览(28)
  • 公众号接收事件推送

    洛塔服务号回复005获取代码。 用户订阅公众号、取消订阅、扫码公众号、开启地理位置、自定义菜单事件等,都会有收到微信发送的事件推送。本篇测试的事件推送 订阅公众号:含扫码关注和其他关注方式 取消订阅 扫描带参数二维码 上报地理位置:公众号后台需开启 自定

    2024年02月09日
    浏览(23)
  • Git的基础命令大全,拉取推送,分支,合并

    命令如下: 1. clone(克隆): 从远程仓库中克隆代码到本地仓库 2. checkout (检出):从本地仓库中检出一个仓库分支然后进行修订 3. add(添加): 在提交前先将代码提交到暂存区 4. commit(提交): 提交到本地仓库。本地仓库中保存修改的各个历史版本 5. fetch (抓取) : 从远程库

    2024年02月12日
    浏览(36)
  • rime中州韵 输入效果一览 100+增强功能效果

    rime是一个定制化程度很高的输入法框架, 我们可以在该框架上搭建适合自己的输入法程序。我们将在专栏 小狼毫 Rime 保姆教程 中完成以下近百种定制化效果的配置与演示。欢迎订阅。 以下为个性化定制的输入效果: 👇 中文(五笔)输入, 英文(easy-english)输入, latex输入 单词首

    2024年02月03日
    浏览(33)
  • SSE(服务器推送事件)规范

    SSE 是指 \\\"Server-Sent Events\\\",即服务器推送事件。它是一种基于 HTTP 的服务器推送技术,允许服务器实时向客户端推送数据。SSE 规范定义了一种在客户端和服务器之间单向实时通信的方式,通常用于实现服务器向客户端推送更新、通知或实时数据。 使用 SSE,客户端可以通过简单

    2024年01月18日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包