基于 SPP 模块的优化实践

作者:袁浩

导语

SPP框架的微线程模式在网络密集型Server开发中优势明显,用同步的方式写异步的代码真的很爽。QQ消息系统这边目前也有若干模块都在使用SPP框架,新增模块也首选SPP。在使用过程中,也遇到过一些性能问题,下面跟大家分享下解决思路。

proxy的性能瓶颈

SPP是单proxy + 多worker架构,随着CPU的核心数越来越多,M1是24核心,M10是48核心,为了充分利用机器的计算资源,就必须扩展越来越多的worker,而proxy只能有一个,所以proxy会成为业务瓶颈。下面来聊聊怎么解决proxy瓶颈。

1. worker代替proxy回包

图:创建msg的时候,设置来源地址

图:回包时,由worker直接回包,并调用SendToClient断开proxy连接

2. 优化proxy路由函数spp_handle_route

一般来讲,proxy的路由函数只需随机选一个worker保持worker负载均衡即可。对于某些业务,需要对uin分shard处理,则proxy需要解析出请求的uin来计算shard。以oidb协议为例,对于proxy只关心uin和command,就可以把其他字段删除,用简化版的oidb head,其他字段在PB解析时,则直接放到unknown字段(PB的解析可以参考PB解析原理)。如果使用的PB版本支持lazy字段就更简单了,把不需要的字段设置lazy选项,proxy和worker就可以使用同样的协议,同时保持proxy的高效了。

图:proxy和worker协议对比

下图是我们群系统消息存储模块的CPU占用情况,单proxy CPU占用率25%,而每个worker则最多只占了14%。这是在经过1,2方法优化之后,优化前proxy CPU占用35%以上,差不多是每个worker的3倍。

图:单proxy + 23个worker的CPU占用情况

3. 绕过proxy,worker直接监听收包

如果以上方法仍然不能解决proxy的瓶颈,那么可以绕过proxy,由worker直接监听收包。这样,既解决proxy瓶颈问题,也减少了大量内存拷贝和共享队列锁的抢占,一举多得。可参考thomas同学的文章《一种SPP性能改良方法》

图:spp_handle_init启动监听微线程

图:监听函数处理收包,并创建微线程和msg处理请求

不过这种方式,有一个不爽的地方就是不能批量监听端口,SPP没有提供mt_select方法,因为微线程底层的就是用select来实现的。

这种去proxy化,有两个弊端:

a. SPP的proxy具有防雪崩的设计,去proxy就意味着没有防雪崩; b. proxy和worker之间的共享队列,可以缓存请求,在模块发布时,使用热重启,可以减少甚至避免丢包。去proxy化,重启进程必然会丢失请求; c. 同group下的worker在proxy + worker模式下,具有容灾功能,即一个worker挂了,同一group下其他worker可以顶上;在去proxy化的情况下,注意同一端口多worker共同监听。 总之,去proxy化慎用。

worker性能优化

1. 缓存action等对象

使用SPP框架时,处理每个msg,难以避免会new很多对象出来,最明显的就是action的创建,甚至有时候,一个msg请求,就会有数个action的创建,内存new和delete消耗了大量的性能。由于同类action有大量的相同信息,我们能不能把action缓存起来,每次需要变化的东西,重新传入?

图:对象池类

需要缓存的对象,只需继承CObjectPool即可。使用智能指针操作对象,在智能指针释放时,则自动调用FreeObj,把对象放回对象池。每个对象都有自己的对象池,使用者不必关心对象池的存在,也不用自己释放对象,简单易用,居家旅行必备。其他类似的对象,都可以用这种方式进行优化。

图:对象池使用方法

2. 缓存msg,由用户自己管理msg,而不是托管给框架

既然action可以缓存,那么msg可不可以呢?答案是肯定的,msg相对复杂,每个msg占用的内存可能达到几k或几十k以上,不用重复创建和释放,肯定能得到更大的收益。但有以下几个问题: a. msg比较复杂,里面脏数据比较难以控制; b. msg是由用户创建,spp框架释放,我们怎么回收到对象池中?

第一个问题,定义reset方法,由对象池调用,清空脏数据; 第二个问题,看过SPP源码的同学可能知道,框架处理msg其实只是调用msg->HandleProcess, 然后delete msg;那我们自己启动微线程,处理msg即可。

那么带来另一个问题,智能指针对象无法通过微线程函数传递;我们搞一个裸的对象池类,不使用智能指针:

图:msg的对象池类, msg类直接继承即可

图:启动微线程处理msg

图:创建msg智能指针时,设置删除器,删除器的作用是把msg放归对象池

3. 避免socket的重复创建

看spp源码的时候发现,mt_udpsendrcv的实现是,不断创建和释放socket,根据缓存的思想,那么我们可不可以把socket缓存起来呢?把socket收归到action里面,缓存action的同时,socket也会被缓存起来。

图:spp的sendrecv实现

接下来自己实现一个sendrecv函数。

图:自己实现一个sendrecv

别急还没完:测试发现,缓存socket的方式是有问题的。假设msg A使用一个socket发出去一个请求,恰好下游超时,A处理完毕后,msg B又通过这个socket发出去请求,从该socket的读到的就是A的脏数据。这样形成错位效应,后面所有请求读到的都是脏数据。怎么解决呢?

串包校验,请求和回复不符的,继续处理socket里面的后续数据:

图:解决脏数据问题

图:优化前

图:优化后

利用以上worker优化方法1, 2,3对我们消息上行模块进行优化,优化前单worker压测值为325k/min,优化后压测值385k/min,大约提升18%。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏云计算

JClouds的命令行界面

我已经使用jclouds一年多了,也一直为它的进步做贡献。目前为止,我已经在很多领域广泛地使用它,特别是在 Fuse Ecosystem 。总之,它是一个特别棒...

2597
来自专栏落影的专栏

iOS电商类APP的研发

前言 本文是研发一个在线超市的电商类APP过程中,对架构的整理。 ? 功能: 1、浏览商品、购买商品、切换商店; 2、查看订单、订单投诉、意见反馈; 3、...

80310
来自专栏SeanCheney的专栏

《Python分布式计算》 第6章 超级计算机群使用Python (Distributed Computing with Python)典型的HPC群任务规划器使用HTCondor运行Python任务

本章,我们学习另一种部署分布式Python应用的的方法。即使用高性能计算机(HPC)群(也叫作超级计算机),它们通常价值数百万美元(或欧元),占地庞大。 真正的...

1.5K10
来自专栏IT派

用Click编写Python命令行工具

在编写Python命令行(CLI)应用程序时,使用Click库进行参数解析的深入教程

2291
来自专栏Golang语言社区

Golang-简洁的并发

转载原文:http://www.yankay.com/go-clear-concurreny/ 多核处理器越来越普及。有没有一种简单的办法,能够让我们写的软件释...

3904
来自专栏JackieZheng

Nodejs学习笔记(四)——支持Mongodb

前言:回顾前面零零碎碎写的三篇挂着Nodejs学习笔记的文章,着实有点名不副实,当然,这篇可能还是要继续走着离主线越走越远的路子,从简短的介绍什么是Nodejs...

2175
来自专栏信安之路

用150行python代码来做代码审计笔记

通过审计源代码,也就是查看源代码,来发现其中存在的隐患,代码审计需要对被审计的语言有充分的了解,不仅是能读懂源代码,还要了解语言本身的缺陷。很多时候代码审计的突...

1130
来自专栏芋道源码1024

从客户端的角度设计后端的接口

2.请求Path,http://www.online.com/api/ [path]

1823
来自专栏Python爬虫与数据挖掘

网络爬虫过程中5种网页去重方法简要介绍

一般的,我们想抓取一个网站所有的URL,首先通过起始URL,之后通过网络爬虫提取出该网页中所有的URL链接,之后再对提取出来的每个URL进行爬取,提取出...

851
来自专栏游戏杂谈

关于seajs

虽然已经有很长时间没写JavaScript,但很多时候看到一些应用还是会带着好奇心去研究一下。之前是看腾讯的朋友网,它的webchat做的很不错(虽然ff下有b...

3363

扫码关注云+社区

领取腾讯云代金券