最近一直在优化行情推送系统,有不少优化心得跟大家分享下。性能方面提升最明显的是时延,在单节点8万客户端时,时延从1500ms优化到40ms,这里是内网mock客户端的得到的压测数据。

对于订阅客户端数没有太执着量级的测试,弱网络下单机8w客户端是没问题的。当前采用的是kubenetes部署方案,可灵活地扩展扩容。

架构图

push-gateway是推送的网关,有这么几个功能:第一点是为了做鉴权;第二点是为了做接入多协议,我们这里实现了websocket, grpc, grpc-web,sse的支持;第三点是为了实现策略调度及亲和绑定等。

push-server 是推送服务,这里维护了订阅关系及监听mq的新消息,继而推送到网关。

问题一:并发操作map带来的锁竞争及时延

推送的服务需要维护订阅关系,一般是用嵌套的map结构来表示,这样造成map并发竞争下带来的锁竞争和时延高的问题。

// xiaorui.cc
{"topic1": {"uuid1": client1, "uuid2": client2}, "topic2": {"uuid3": client3,  "uuid4": client4}   ... }

已经根据业务拆分了4个map,但是该订阅关系是嵌套的,直接上锁会让其他协程都阻塞,阻塞就会造成时延高。

加锁操作map本应该很快,为什么会阻塞?上面我们有说过该map是用来存topic和客户端列表的订阅关系,当我进行推送时,必然是需要拿到该topic的所有客户端,然后进行一个个的send通知。(这里的send不是io.send,而是chan send,每个客户端都绑定了缓冲的chan)

解决方法:在每个业务里划分256个map和读写锁,这样锁的粒度降低到1/256。除了该方法,开始有尝试过把客户端列表放到一个新的slice里返回,但造成了 GC 的压力,经过测试不可取。

// xiaorui.ccsync.RWMutex
map[string]map[string]client改成这样m *shardMap.shardMap

分段map的库已经推到github[1]了,有兴趣的可以看看。

问题二:串行消息通知改成并发模式

简单说,我们在推送服务维护了某个topic和1w个客户端chan的映射,当从mq收到该topic消息后,再通知给这1w个客户端chan。

客户端的chan本身是有大buffer,另外发送的函数也使用 select default 来避免阻塞。但事实上这样串行发送chan耗时不小。对于channel底层来说,需要goready等待channel的goroutine,推送到runq里。

下面是我写的benchmark[2],可以对比串行和并发的耗时对比。在mac下效果不是太明显,因为mac cpu频率较高,在服务器里效果明显。

串行通知,拿到所有客户端的chan,然后进行send发送。

for _, notifier := range notifiers {s.directSendMesg(notifier, mesg)
}

并发send,这里使用协程池来规避morestack的消耗,另外使用sync.waitgroup里实现异步下的等待。

// xiaorui.ccnotifiers := []*mapping.StreamNotifier{}
// conv slice
for _, notifier := range notifierMap {notifiers = append(notifiers, notifier)
}// optimize: direct map struct
taskChunks := b.splitChunks(notifiers, batchChunkSize)// concurrent send chan
wg := sync.WaitGroup{}
for _, chunk := range taskChunks {chunkCopy := chunk // slice replicawg.Add(1)b.SubmitBlock(func() {for _, notifier := range chunkCopy {b.directSendMesg(notifier, mesg)}wg.Done()},)
}
wg.Wait()

按线上的监控表现来看,时延从200ms降到30ms。这里可以做一个更深入的优化,对于少于5000的客户端,可直接串行调用,反之可并发调用。

问题三:过多的定时器造成cpu开销加大

行情推送里有大量的心跳检测,及任务时间控速,这些都依赖于定时器。go在1.9之后把单个timerproc改成多个timerproc,减少了锁竞争,但四叉堆数据结构的时间复杂度依旧复杂,高精度引起的树和锁的操作也依然频繁。

所以,这里改用时间轮解决上述的问题。数据结构改用简单的循环数组和map,时间的精度弱化到秒的级别,业务上对于时间差是可以接受的。

Golang时间轮的代码已经推到github[3]了,时间轮很多方法都兼容了golang time原生库。有兴趣的可以看下。

问题四:多协程读写chan会出现send closed panic的问题

解决的方法很简单,就是不要直接使用channel,而是封装一个触发器,当客户端关闭时,不主动去close chan,而是关闭触发器里的ctx,然后直接删除topic跟触发器的映射。

// xiaorui.cc// 触发器的结构
type StreamNotifier struct {Guid  stringQueue chan interface{}closed int32ctx    context.Contextcancel context.CancelFunc
}func (sc *StreamNotifier) IsClosed() bool {if sc.ctx.Err() == nil {return false}return true
}...

问题五:提高grpc的吞吐性能

grpc是基于http2协议来实现的,http2本身实现流的多路复用。通常来说,内网的两个节点使用单连接就可以跑满网络带宽,无性能问题。但在golang里实现的grpc会有各种锁竞争的问题。

如何优化?多开grpc客户端,规避锁竞争的冲突概率。测试下来qps提升很明显,从8w可以提到20w左右。

可参考以前写过的grpc性能测试[4]

问题六:减少协程数量

有朋友认为等待事件的协程多了无所谓,只是占内存,协程拿不到调度,不会对runtime性能产生消耗。这个说法是错误的。虽然拿不到调度,看起来只是占内存,但是会对 GC 有很大的开销。所以,不要开太多的空闲的协程,比如协程池开的很大。

在推送的架构里,push-gateway到push-server不仅几个连接就可以,且几十个stream就可以。我们自己实现大量消息在十几个stream里跑,然后调度通知。在golang grpc streaming的实现里,每个streaming请求都需要一个协程去等待事件。所以,共享stream通道也能减少协程的数量。

问题七:GC 问题

对于频繁创建的结构体采用sync.Pool进行缓存。有些业务的缓存先前使用list链表来存储,在不断更新新数据时,会不断的创建新对象,对 GC 造成影响,所以改用可复用的循环数组来实现热缓存。

后记

有坑不怕,填上就可以了。

参考资料

[1]

github: https://github.com/rfyiamcool/ccmap/blob/master/syncmap.go

[2]

benchmark: https://github.com/rfyiamcool/go-benchmark/tree/master/batch_notify_channel

[3]

github: https://github.com/rfyiamcool/go-timewheel

[4]

测试: https://github.com/rfyiamcool/grpc_batch_test

优化 Golang 分布式行情推送的性能瓶颈相关推荐

  1. socket.io搭建分布式Web推送服务器

    socket.io是目前较为流行的web实时推送框架,其基于nodejs语言开发,底层用engine.io实现. 借助nodejs语言异步的特性,其获得了不错的性能.但单个实例的socket.io依然 ...

  2. 百度搜索引擎优化(PHP自动推送连接到百度搜索引擎)代码

    百度搜索引擎优化工具(百度站长平台)以及网站流量监控 1.提交本站地址到百度:首先在百度站长平台(https://ziyuan.baidu.com)注册账户登录,将域名新建-关联主体-先手动提交域名首 ...

  3. java 消息推送_hanbo-push分布式消息推送、IM服务

    系统概览 app接入除了接入restApi(push-admin)之外,还需要兼容connector(push-admin的client,用于和push-server通信)的通信协议. 基于proto ...

  4. SEO优化之147SEO搜索引擎推送工具

    什么是推送? 对于SEO人员应该都不陌生吧,推送就是把网站的链接主动提交给搜索引擎, 确保新的链接可以尽快被收录,同时保护原创(防止别人抄袭,以及转载后导致自己的网站没收录) 为什么我们要去做主动推送 ...

  5. Redis 在 vivo 推送平台的应用与优化实践

    一.推送平台特点 vivo推送平台是vivo公司向开发者提供的消息推送服务,通过在云端与客户端之间建立一条稳定.可靠的长连接,为开发者提供向客户端应用实时推送消息的服务,支持百亿级的通知/消息推送,秒 ...

  6. 触达率提升 20%,融云推送优化实践

    不稳定的产品表现和不友好的使用体验,会让社交用户很快失去耐心.关注[融云全球互联网通信云]了解更多 去中心化社交产品 Damus 已经沦为互联网垃圾场.它在人们对去中心化的欢呼声中引爆,但也因违背用户 ...

  7. vivo 推送平台架构演进

    本文根据Li Qingxin老师在"2021 vivo开发者大会"现场演讲内容整理而成.公众号回复**[2021VDC]**获取互联网技术分会场议题相关资料. 一.vivo推送平台 ...

  8. 京东大规模消息推送平台搭建实践

    背景 每个app或者业务都有将信息推送到用户客户端的需求.作为中台的推送平台,需要为公司内部许多个不同app同时提供可用,稳定的推送服务,因此我们消息推送平台应运而生. 推送平台架构 名词解释: dt ...

  9. HTTP/2之服务器推送(Server Push)最佳实践

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由mariolu 发表于云+社区专栏 HTTP/1.X出色地满足互联网的普遍访问需求,但随着互联网的不断发展,其性能越来越成为瓶颈.IE ...

最新文章

  1. java 抽象工厂 类图_spring学习中常用到的工厂,抽象工厂学习 | 学步园
  2. 如何写一个优秀的GitHub项目README文档?
  3. 安卓JNI使用C++类
  4. Exchange 邮件投递被拒的问题分析
  5. jni c call java_Java通过-jni调用c语言
  6. Python html 代码转成图片、PDF
  7. 周二直播丨数据库上云趋势下,如何面对海量数据迁移及落地实践
  8. spring-boot(2)--环境搭建
  9. 95-220-020-源码-Idle-Flink中Idle停滞流机制
  10. php里建立数据库和表,PHP 创建数据库和表 | w3cschool菜鸟教程
  11. linux内存源码分析 - 内存池
  12. 随笔——我悄悄地更新了我的博客
  13. FCKeditor编辑器详解
  14. Hive基础04、Hive建表语句详解
  15. Centos8安装Redis
  16. 【IoT】产品设计:硬件成本核算,这篇文章就够了
  17. antd权限管理_Antd Pro的权限组件
  18. html5淘宝注册界面设计,电商登陆注册页设计分析
  19. WTP 线程池管理系统,修改配置后能够实时刷新
  20. 百度地图导航的接入(包含三种选择方式驾车、公交、步行)

热门文章

  1. python输出星期名缩写_在Python中解析带有时区缩写名称的日期/时间字符串?
  2. python中showinfo_python – Tkinter中的非阻塞信息对话框
  3. 光耦的CTR(Current Transfer Ratio)值概念及计算方法
  4. Linux下静态库的创立与使用
  5. Python 爬虫利器 Beautiful Soup 4 之文档树的搜索
  6. [笔记][mooc]《程序设计入门—C语言》
  7. 查看数据库系统字符集
  8. 微软发布架构师期刊阅读器
  9. 牛客 - 动物森友会(二分+最大流)
  10. POJ - 2513 Colored Sticks(字典树+并查集+欧拉回路)