皇冠体育寻求亚洲战略合作伙伴,皇冠代理招募中,皇冠平台开放会员注册、充值、提现、电脑版下载、APP下载。

首页快讯正文

2022世界杯比分(www.x2w99.com):优化 Golang 漫衍式行情推送的性能瓶颈

admin2021-07-1172

2021年欧洲杯

www.x2w99.com)实时更新发布最新最快最有效的2021欧洲杯资讯。

,

责编 | 张红月

出品 | 码农桃花源

最近一直在优化行情推送系统,有不少优化心得跟人人下。性能方面提升最显著的是时延,在单节点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.RWMutexmap[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发送。

2022世界杯比分

www.x2w99.com)实时更新发布最新最快最有效的2022世界杯比分资讯。

for _, notifier := range notifiers { s.directSendMesg(notifier, mesg)}并发send,这里使用协程池来规避morestack的消耗,另外使用sync.waitgroup里实现异步下的守候。

// xiaorui.ccnotifiers := *mapping.StreamNotifier{}// conv slicefor _, notifier := range notifierMap { notifiers = append(notifiers, notifier)}// optimize: direct map structtaskChunks := b.splitChunks(notifiers, batchChunkSize)// concurrent send chanwg := sync.WaitGroup{}for _, chunk := range taskChunks { chunkCopy := chunk // slice replica wg.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 string Queue chan interface{} closed int32 ctx context.Context cancel context.CancelFunc}func (sc *StreamNotifier) IsClosed bool { if sc.ctx.Err == nil { return false } return true}...

问题五:提高grpc的吞吐性能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 造成影响,以是改用可复用的循环数组来实现热缓存。

后记有坑不怕,填上就可以了。

参考资料

网友评论