热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

几行代码为老板省百万某高并发服务GOGC及UDPPool优化

20万QPS-臣妾办不到前两天一位同学找我,说他一个需要几百万qps的go重构的服务下周要上线,但随着容器核数增加,不具有线性ScaleUp,他担心上线后扛不住,心里没底.8核(容

20万QPS-臣妾办不到

前两天一位同学找我, 说他一个需要几百万qps的go重构的服务下周要上线, 但随着容器核数增加, 不具有线性Scale Up, 他担心上线后扛不住,心里没底. 8核(容器虚拟核, 可以认为是4个物理核, 以下都为虚拟核)时QPS 2.3万/s, 他单容器加到40核时, qps只有8万(按我看来, 这种单容器搞这么大核, 不太合理) 他的目标是能够大致对标原先的cpp服务, 40核物理核50万/s, 即对应到虚拟核40核, 25万/s, go能跑到15-20万即可(原先cpp数据读共享内存, go读redis).

以下代码均为精简过的示意代码, 与实际略有不同.

屡试不爽GOGC: 8万/s到14万/s

我大概了解了一下他的服务逻辑模型, 接收客户端UDP请求, 请求一次redis, 然后再单向发UDP包给其他机器.

服务的内存占用不大, 200m. 这种情况, 有一种屡试不爽的方法: 在main里面分配一个1024M不用的[]byte, 全局占着, 根据允许的内存情况, 然后把服务GOGC设置大一些.

我这里设置了300, 也就是正常跑的时候, 占用内存4G, 一测QPS从8万到了14万, gc消耗在火焰图中也基本看不见了. 3行代码为马化腾节省上百万, 离目标不远了. 足以看出go的gc在这种高QPS小内存情况下的损耗之大.

UDP问题

接下来跑一发火焰图. 有几处可以优化的地方. 我觉得到20万/s应该没啥问题. 其中有一点比较有意思, UDP单发的client调用, 占用CPU竟然比同样数量的redis调用tcp一来一回的还要多. 不是说UDP简单, 这咋比TCP还耗性能呢?

火焰图中显示占用了竟然达到30%的CPU时间.

RPC框架中UDP发包的代码精简了一下大概是这样. 

火焰图中的1对应net.DialUDP, 2对应udpconn.Write, 3对应conn.Close.

大致浏览了下代码, 其实这里主要是系统调用耗费了时间 DialUDP: SYSSOCKET创建socket fd, SYSFCNTL获取fd属性和设置fd为nonblock, SYSSETSOCKOPT设置socket fd的一些属性, SYSCONNECT绑定远端ip port, 最大头的还是poll.runtimepollOpen里把这个UDP fd放入到epoll中EPOLLCTLADD.

Write: 基本是SYSWRITE.

Close: 基本是SYSCLOSE.

由此看出, 这个代码发一次udp发包, 这么多系统调用. 最大的问题就是每次发包, 都创建一个实例, 然后用一下, 就关掉了. UDP实例能否复用?

UDP实例复用

其实代码思路倒不难, 就是对于同一个对端地址的udp实例, 使用一个sync.Pool保存, 这样同时对一个ip port的请求可以使用不同的实例, 比用一个实例减少锁等待.

不同的地址用一个sync.Map保存各个对应的sync.Pool. 使用时, 从sync.Map获取对应的sync.Pool, pool中有则使用, 没有则创建一个.

使用结束, 放回pool中.

sync.Pool的特性使得在GC时, 可以释放不再使用的fd.

结构体包装定义

Pool代码

这里其实会有一点内存泄露的味道, sync.Map中对端地址对应的sync.Pool实例本身没法释放. 不过这里问题不大, 对于服务端之间调用, 就算你对端节点有几万个, 我估计也就只有几M内存而已.

是否有并发问题, 串包问题?

对于这种并发网络编程, 最怕的就是并发不安全的操作并发了, 状态乱了, 或者串包. 初看起来没啥问题: 不同协程不会同时使用一个UDP实例.

但仔细一思考, 这样的使用方式还是存在一点局限性的, 大家都知道UDP是无状态的, 也就是说, 你用udp往ip1:p1发个包, 那么你本地也必然开了一个端口local:p2, 然后你不收回包了, 但服务端回了包.

接下来, 另一个协程如果也往ip1:p1发包, 正好也用了local:p2, 但它收回包, 这个时候上一次服务端的回包就被这个协程接收了, 导致串包了. 不复用UDP实例, 每次new, write, close, 这种情况都有可能偶尔发生. 那用池子的话, 更加加剧了这种情况. 所以

  1. UDP客户端要和服务端的行为一致, 服务端不回包的话, 客户端就只发不收. 服务端回包的话, 客户端就即发又收.

  2. 如果udp服务端回包, 客户端也不想收, 那么在一个客户端机器上, 不要对同一个对端udp服务, 存在收包和不收包的两种udp client模型.

经过压测, 的确没有串包问题.

UDP改进效果 17万/s

QPS从14万涨到了17万. 之前dial占了绝大部分, 现在已经很少了, close也很少了. 而write在现有代码调用下, 是没减少的.

其他处理

看了下火焰图, 还有其他一些地方可以优化一下.

  1. 日志精简

  2. 上报合并

  3. 客户端服务发现的select node性能需要优化

做了1,2步后, 基本上到QPS到20万问题不大, 后面我也没关注了. 当然正式环境跑的时候, 跑个一半多点的负载就行了.

关于UDP connect

可能没接触过UDP的人会觉得很奇怪, go的udp怎么会有dial呢? 其实底层调的是udp connect, 作用当然不是三次握手, 建立连接, UDP中调用connect内核仅仅把对端ip&port记录下来, 发包时无需指定对端addr, 使用 funcWrite(b[]byte)(int,error), 同一个实例后面多次发包都无需再做connect.

go里面也还有not connect udp, 使用net.ListenPacket创建, 然后发包时指定对端addr, 使用方法 WriteTo(p[]byte,addrAddr)(nint,err error), 这种情况下, 每次发包内核都要绑定对端addr, 发包完, 解绑. 多次发包, 都会进行绑定和解绑.

除此之外, 对于connected udp, 收包的时候也会减少应用层出现串包的问题. 因为它只收connect的对端ip的包返回给应用层.

总结

UDP以前用的多, 框架直接通过配置就能切换udp/tcp, 内网网络环境好, UDP用起来没啥问题, 不过现在用的不多了. 我不太熟, 做这个优化我也是现学的UDP.

抓大放小, 先从参数改起. 20行代码, 帮马化腾节省几百万. 

参考阅读

  • 一文搞懂 etcd 3.5 核心特性

  • 谈谈实体的 ID 与“键”

  • 消息中间件:为什么我们选择 RocketMQ

  • 分布式一致性算法Raft

  • dubbogo 是如何炼成的?

  • 差之毫厘:etcd 3 完美支持 HTTP 访问

原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。

2021年GIAC调整到7月30-31日在深圳举行,点击阅读原文了解更多详情。


推荐阅读
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • 本文分享了一位Android开发者多年来对于Android开发所需掌握的技能的笔记,包括架构师基础、高级UI开源框架、Android Framework开发、性能优化、音视频精编源码解析、Flutter学习进阶、微信小程序开发以及百大框架源码解读等方面的知识。文章强调了技术栈和布局的重要性,鼓励开发者做好学习规划和技术布局,以提升自己的竞争力和市场价值。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 本文介绍了Java的公式汇总及相关知识,包括定义变量的语法格式、类型转换公式、三元表达式、定义新的实例的格式、引用类型的方法以及数组静态初始化等内容。希望对读者有一定的参考价值。 ... [详细]
  • C++语言入门:数组的基本知识和应用领域
    本文介绍了C++语言的基本知识和应用领域,包括C++语言与Python语言的区别、C++语言的结构化特点、关键字和控制语句的使用、运算符的种类和表达式的灵活性、各种数据类型的运算以及指针概念的引入。同时,还探讨了C++语言在代码效率方面的优势和与汇编语言的比较。对于想要学习C++语言的初学者来说,本文提供了一个简洁而全面的入门指南。 ... [详细]
  • 超级简单加解密工具的方案和功能
    本文介绍了一个超级简单的加解密工具的方案和功能。该工具可以读取文件头,并根据特定长度进行加密,加密后将加密部分写入源文件。同时,该工具也支持解密操作。加密和解密过程是可逆的。本文还提到了一些相关的功能和使用方法,并给出了Python代码示例。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
author-avatar
fdsfdsfsfsfsfsfsfsfsafsf
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有