请选择 进入手机版 | 继续访问电脑版

网赚研究院-致富网赚论坛-网赚宝盒-华夏网赚论坛-28网赚-贵族网赚论坛-日付网赚联盟

 找回密码
 立即注册
查看: 65|回复: 0

毒侠孟雪歌 Golang实现单机百万长连接服务 - 美图的三年优化经验 重生之嫡女上位

发表于 2019-11-10 17:20 | 650 显示全部楼层 |阅读模式

[复制链接]

1万

主题

3万

帖子

6万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
66899
发表于 2019-11-10 17:20 | 显示全部楼层 |阅读模式
这是写在帖子头部的内容导读:美图长毗连办事用时三年,在内存优化上堆集比力丰富的理论履历,本文将会先容我们团队这些年在内存优化门路上做的一些尝试。

作者简介:王鸿佳,系统研发工程师,现任职于美图公司,严重处置通讯及存储关连范围的研发。加入了通用长毗连通道、美图推送、散布式数据库(Titan 已开源)、路由分发器等项目研发。对根柢研发技术及开源项目有ㄖ氐陌。

美图长毗连办事简介

随着科技的飞速成长,技术的日新月异,长毗连的应用处景日益增加。不单在后端办事中被普遍应用,比力常见的稀有据库的拜候、办事内部状态的和谐等,而且在 App 真个消息推送、聊天信息、直播弹字幕等场景长毗连办事也是优选计划。长毗连办事的严重性也在各个场所被业界专家不停说起,与此同时也引发了更加普遍地关注和会商,各至公司也起头构建自己的长毗连办事。
美图公司于2016 年头起头构建长毗连办事,与此同时, Go 在编程说话范围异军突起,考虑到其丰富的编程库,完竣的工具链,简单高效的并发模子等上风,使我们趾Λ挑选 Go 去作为实现长毗连办事的说话。在通讯协议的挑选上,考虑到 MQTT 协议的轻量、简单、易于实现的优点,挑选了 MQTT 协议作为数据交互的载体。其团体的架构会鄙人文中做响应地先容。
美图长毗连办事(项目内部代号为bifrost )已履用时三年,在这三年的时候里,长毗连办事经过了营业的检验,同时也履历了办事的重构,存储的升级等,长毗连办事从之前支持单机二十几万毗连到现在可以支持单机百万毗连。在大大都长毗连办事中存在一个共性题目,那就是内存占用太高,我们经常发现单个节点几十万的长毗连,内存却占用十几G 以致更多,有哪些本事能低落内存呢?
本文将从多个角度先容长毗连办事在内存优化路上的摸索,首先会先经过先容当前办事的架构模子,Go 说话的内存治理,让大家清楚地了解我们内存优化的偏向和关注的严重数据。后背会重点先容我们在内存优化上做的一些尝试以及具体的优化本事,渴望对大家有必定的鉴戒意义。

架构模子

一个好的架构模子计划不单能让系统有很好的可扩大性,同时也能在办事本事上有很好的表示。除此之外,在计划上多考虑数据的笼统、模块的别离、工具链的完竣,这样不单能让软件具有更灵活的扩大本事、办事本事更高,也进步系统的安定性和坚忍性以及可保护性。
在数据笼统层面笼统pubsub 数据聚集,用于消息的分发和处置赏罚。模块别离层面我们将办事一分为三:内部通讯(grpcsrv)、内部办事(mqttsrv)、毗连治理(session)。工具链的方面我们构建了自动化测试,系统 mock ,压测工具。美图长毗连办事架构计划以下:
Golang实现单机百万长连接服务 - 美图的三年优化经验  新闻
图一架构图
从架构图中我们可以清楚地看到由7 个模块组成,别离是:conf 、grpcsrv 、mqttsrv、session、pubsub、packet、util ,每个模块的感化以下:

  • conf :设备治理中心,负责办事设备的初始化,底子字段校验。
  • grpcsrv :grpc 办事,集群内部信息交互和谐。
  • mqttsrv :mqtt 办事,吸收客户端毗连,同时支持单进程多端口 MQTT 办事。
  • session :会话模块,治理客户端状态变化,MQTT 信息的收发。
  • pubsub :公布定阅模块,依照 Topic 维度保存 session 并公布 Topic 看护给 session。
  • packet:协议分析模块,负责 MQTT 协议包分析。
  • util :工具包,现在集成监控、日志、grpc 客户端、调理上报四个子模块。

Go 的内存治理

众所周知,Go 是一门自带渣滓采纳机制的说话,内存治理参照 tcmalloc 实现,利用连续捏造地址,以页( 8k )为单元、多级缓存举行治理。针对小于16 byte 间接利用Go的高低文P中的mcache分派,大于 32 kb 间接在 mheap 申请,剩下的先利用当前 P 的 mcache 中对应的 size class 分派 ,假如 mcache 对应的 size class 的 span 已经没有可用的块,则向 mcentral 请求。假如 mcentral 也没有可用的块,则向 mheap 申请,并切分。假如 mheap 也没有合适的 span,则向操纵系统申请。
Go 在内存统计方面做的也是相当出色,供给细粒度的内存分派、GC 采纳、goroutine 治理等统计数据。在优化进程中,一些数据能帮助我们发现和分析题目,在先容优化之前,我们先来看看哪些参数需要关注,其统计参数以下:

  • go_memstats_sys_bytes :进程从操纵系统获得的内存的总字节数 ,其中包含 Go 运转时的堆、栈和其他内部数据结构保存的捏造地址空间。
  • go_memstats_heap_inuse_bytes:在 spans 中正在利用的字节。其中不包含大要已经返回到操纵系统,大要可以重用举行堆分派,大要可以将作为仓库内存重用的字节。
  • go_memstats_heap_idle_bytes:在 spans 中余暇的字节。
  • go_memstats_stack_sys_bytes:栈内存字节,严重用于 goroutine 栈内存的分派。
在内存监控中按照Go 将堆的捏造地址空间别离为 span ,即对内存8K或更大的连续地域举行统计。span 大要处于以下三种状态之一 :

  • idle 不包含工具或其他数据,余暇空间的物理内存可以开释回 OS (但捏造地址空间永久不会开释),大要可以将其转换为利用中或栈空间;
  • inuse 最少包含一个堆工具,而且大要不足暇空间来分派更多的堆工具;
  • stack span 用于 goroutine 栈,栈不被以为是堆的一部分。span 可以在堆和仓库内存之间变动,但它历来不会同时用于两者。
此外有一部分统计没有从堆内存平分派的运转时内部结构(凡是由于它们是实现堆的一部分),与仓库内存差别,分派给这些结构的任何内存都公用于这些结构,这些严重用于调试运转时内存开销。
固然Go 具有了丰富的标准库、说话层面支持并发、内置runtime,但相比C/C++ 完成类似逻辑的情况下 Go 消耗内存相对增加。在步伐的运转进程中,它的 stack 内存会随着利用而自动扩容,但在 stack 内存采纳采纳惰性采纳方式,必定水平的致使内存消耗增加,此外另有GC 机制也会带来额外内存的消耗。
Go 供给了三种内存采纳机制:按时触发,按量触发,手动触发。在内存渣滓少许的情况下,Go 可以杰出的运转。可是不管采纳哪类触发方式,由于在海量用户办事的情况下酿成的渣滓内存是庞大的,在 GC 尝试进程中办事城市感受明显的卡顿。这些也是现在长毗连办事面临的困难,鄙人文中我将会逐一先容我们怎样淘汰和治理题目标发生的具体理论。

优化之路

在了解架构计划、Go 的内存治理、根柢监控后,信赖大家已经对当前系统有了一个大略的熟悉,先给大家展现一下内存优化的成果,下表一是内存优化前后的对照表,在线毗连数底子类似的情况下,进程内存占用大幅度低落,其中 stack 申请内存低落约 5.9 G,其次 heap 利用内存低落 0.9 G,other 申请内存也小幅下降。那末我们是怎样做到内存低落的呢?那接下来我将会把我们团队关于举行内存优化的摸索和大家聊一聊。

优化前
优化后
在线链接数
225 K
225 K
进程占用内存
13.4 G
4.7 G
heap 利用内存
5.2 G
3.4 G
stack 申请内存
7.25 G
1.02 G
other 申请内存
0.9 G
0.37 G
表一内存优化前后的对照表
备注:进程占用内存≈ 捏造内存- 未归还内存
在优化前随机抽取线上一台呆板举行分析内存,经过监控发现当前节点进程占用捏造内存为22.3 G,堆区利用的内存占用 5.2 G ,堆区未归还内存为 8.9 G,栈区内存为 7.25 G,此外约占用 0.9 G,毗连数为 225 K。
我们简单举行换算,可以看出均匀一个链接占用的内存别离为:堆:23K,栈:32K。经过对照业内长毗连办事的数据可以看出单个链接占用的内存偏大,按照监控数据和内存分派道理分析严重原因原由在:goroutine 占用、session 状态信息、pubsub 模块占用,我们计划从营业、步伐、收集形式三个方面举行优化。

营业优化

上文中提到 session 模块严重是用于处置赏罚消息的收发,在实现时考虑到在凡是场景中营业的消息生产大于客户端消息的消耗速度的情况,为了减缓这类状态,计划时引入消息的缓冲行列,这类做法一样也有助于做客户端消息的流控。
缓冲消息行列借助chan 实现 ,chan 巨细按照履历将初始化默许设备为 128 。但在今朝方上推送的场景中,我们发现,消息的生产一样平常小于消耗的速度,128 缓冲巨细明显偏大,是以我们把长度调解为 16 ,淘汰内存的分派。
在计划中依照topic 对客户端举行分组治理的算法中,采纳空间换时候的方式,组合 map 和 list 两种数据结构对于客户端聚集操纵供给O(1)的删除、O(1)的增加、O(n)的遍历。数据的删除采纳标志删除方式,利用帮助 slice 结构举行记录,只要到达预设阈值才会举行实在的删除。固然标志删除进步了遍历和增加的性能,但也一样带来了内存消耗题目。
大家必定猎奇什么样的场景需要供给这样的复杂度,在现实中其场景有以下两种情况:

  • 在现实的收集场景中,客户端随时都大要由于收集的不安定断开大要重新建联,是以聚集的增加和删除需要在常数范围内。
  • 在消息公布的流程中,采纳遍历聚集逐一公布看护方式,但随着单个topic 上的用户量的增加,经常会出现单个 topic 用户聚集消息过热的题目,耗时太久致使消息挤压,是以针对聚集的遍历固然也要求尽管快。
经过benchamrk 数据分析,在标志采纳 slice 长度在 1000 时,可以供给最好的性能,是以默许设备阈值为 1000。在线上办事中,无出格情况都是采纳默许设备。但在当前推送办事的利用中,发现标志删除和迟误采纳机制好处甚微,严重是由于 topic 和客户端为 1 : 1 方式,也就是不存在客户端聚集,是以调解采纳阈值巨细为 2,淘汰无效内存占用。
上述全数优化,只要简单调解设备后办事灰度上线即可,在计划实现时经过conf 模块静态设备,低落了办事的斥地和保护资本。经过监控对照优化结果以下表,在优化后在线毗连数比优化的在线毗连更多的情况下, heap 利用内存利用数目由本来的 4.16G 下降到了 3.5G ,低落了约 0.66 G。
Golang实现单机百万长连接服务 - 美图的三年优化经验  新闻

golang 代码优化

在实现上面展现的架构的时候发现在session 模块 和 mqttsrv 模块之间存在很多同享变量,现在实现方式都是采纳指针大要值拷贝的,由于 session的数目和客户端数据量成反比也就致使消耗大量内存用于同享数据,这不单仅增加 GC 压力,一样对于内存的消耗也是庞大的。就此题目思考再三,参考系统的库 context 的计划在架构中也笼统 context 包负责模块之间交互信息转达,同一分派内存。此外还参考他人淘汰临时变量的分派的优化方式,进步系统运转服从。严重优化角度参考以下:

  • 在频仍申请内存的地方,利用pool 方式举行内存治理
  • 小工具合并成结构体一次分派,淘汰内存分派次数
  • 缓存区内容一次分派富足巨细空间,并适当复用
  • slice 和 map 采 make 建立时,预估巨细指定容量
  • 挪用栈禁止申请较多的临时工具
  • 淘汰byte 与 string 之间转换,尽管采纳 byte 来字符串处置赏罚
现在系统具被完整的单元测试、集成测试,是以经过一周的快速的斥地重构后灰度上线监控数据对照以下表:在底子类似的毗连数上,heap 利用内存约占用低落 0.27G,stack 申请内存占用低落 3.81G。为什么 stack 会大幅度低落呢?
经过设备stackDebug 重新编译步伐清查步伐运转进程,优化前 goroutine 栈的大大都在内存为 16K,经过淘汰临时变量的分派,拆分大函数处置赏罚逻辑,有用的淘汰触发栈的内存扩容,优化后 goroutine 栈内存低落到 8 K。一个毗连需要启动两个 goroutine 负责数据的读和写,大略盘算一个毗连淘汰约 16 K 的内存,23 w 毗连约低落 3.68 G 内存。
Golang实现单机百万长连接服务 - 美图的三年优化经验  新闻

收集模子优化

在Go 说话的收集编程中典范的实现都是采纳同步处置赏罚方式,启动两个 goroutine 别离处置赏罚读和写请求,goroutine 也不像 thread ,它是轻量级的。但对于一百万毗连的情况,这类计划形式最少要启动两百万的 goroutine,其中一个 goroutine 利用栈的巨细在 2 KB 到 8KB, 对于资本的消耗也是极大的。在大大都场景中,只要少数毗连是稀有据处置赏罚,大部分 goroutine 阻塞 IO 处置赏罚中。在是以可以鉴戒 C 说话的计划,在步伐中利用 epoll 模子办事变分发,只要活泼毗连才会启动 goroutine 处置赏罚营业,基于这类脑筋点窜收集处置赏罚流程。
收集模子点窜测试完成后起头灰度上线,经过监控数据对照以下表:在优化后比优化前的毗连数多10 K的情况下,heap 利用内存低落 0.33 G,stack 申请内存低落 2.34 G,优化结果明显。
Golang实现单机百万长连接服务 - 美图的三年优化经验  新闻

总结

在经过营业优化,临时内存优化,收集模子优化操纵后,线上办事保证21w 长毗连在线现实内存占用约为 5.1 G。简单举行压测 100w 毗连只完成建立毗连,不举行其他操纵约占用 10 G。长毗连办事内存优化已经获得阶段性的乐成,可是这仅仅是我们团队的一小步,未来另有更多的工作要做:收集链路、办事本事,存储优化等,这些都是亟待摸索的偏向。假如大家有什么好的想法,接待与我们团队分享,配合探讨。
bifrost项目现在我们有开源筹划,敬请大家期待。

参考文章

go tool pprof 利用先容 :https://segmentfault.com/a/1190000016412013
Go 内存监控先容:https://golang.org/src/runtime/mstats.go
Go 内存优化先容:https://blog.golang.org/profiling-go-programs
高性能Go办事内存分派:https://segment.com/blog/allocation-efficiency-in-high-performance-go-services
Go stack 优化分析:https://studygolang.com/articles/10597
参考阅读:

  • 正式支持多线程!Redis 6.0与老版性能对照评测
  • 你真的了解性能压测中的SLA吗?
  • 一个Netflix斥地的微办事编排引擎,支持可视化工作流界说
  • 你真的了解压测吗?实战报告性能测试场景计划和实现
  • 关于Golang GC的一些误解--真的比Java算法更领先吗?
高可用架构
改变互联网的构建方式

免责声明:假如加害了您的权益,请联系站长,我们会实时删除侵权内容,感谢合作!
感激您的阅读
回复

使用道具 举报

0条回复
跳转到指定楼层
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Copyright © 2001-2015 忽悠兄 X3.2 © 2001-2013 Comsenz Inc.

Archiver|手机版|小黑屋| Comsenz Inc.  |网站地图

快速回复 返回顶部 返回列表