去年做外包时接了一单模拟实时交易的系统。在外包领域,难得接到这种高并发项目,分享下经验。
技术栈: Node + MySQL + 小程序 + WebSocket
服务器:腾讯云 4C8G
性能目标:能承受 500 人同时交易
主要技术难点
需求的难点:
- 需要实时根据一定的规则撮合交易发生(价优者优先,同价情况,先到者优先)
- 即使一个用户没有进行买卖,他的资产和可用资金也会实时变化
- 需要实时计算所有用户的资产,并作出排行榜
- 所有变化要实时推送到客户端
有这么几个坑:
- 实时系统的设计与实现
- 非常容易发生性能问题
- 非常容易发生并发冲突
最终系统的大致设计如下:
- 每秒进行一次撮合交易,相比真的实时,更容易优化性能问题。
- 为了处理并发问题,所有委托默认隐藏状态,每次进行撮合交易时,先读取所有委托,撮合它们进行交易,之后未成交的委托取消隐藏。
- 每秒撮合交易完成后,对所有客户端推送最新数据。一秒一次批量推送,能节省带宽和计算资源,也差不多刚好是小程序能处理的频率。
由于这个交易撮合的流程是单线程的,导致无法暴力地通过加核加主机来优化性能。 对高并发带来了更多的挑战。
下面就开始优化性能了。
上缓存
系统中,除了推送的数据之外,还有各种行情图,排行榜等,用户查看时通过 http 请求轮询。
这些请求要查询大量数据库和进行大量运算,消耗资源不少。
上一个 1 秒过期的缓存就能减小很多压力。
精简掉新连接时拉取数据环节
最初的设计中,每个用户连接时,系统会查询当前的实时数据并返回用户,当同时有大量用户连接时,就会卡。
解决方案:直接去掉这一步,用户连接时服务器不推送任何数据,而是在下一次撮合交易完后再批量推送数据。
好处是,只要系统能承载 500 人同时交易,就能承载 500 人同时加入交易。nice。
多线程处理重计算业务
虽然不能直接开多个进程,但我们还是可以把多核利用起来的,只要把重计算的部分交给其它核就行了。
给你安利一个库:worker-farm,基本上来讲,它可以把一个异步函数变成多进程的,同时调用多个时,会智能分配给各个 cpu,在不能简单粗暴地使用 cluster 时,可以用这种解决方案。
这下四个 CPU 能跑满了。
异步编程大法好,node 大法好,多线程编程一窍不通,也能写多线程程序。
增加 mysql 连接数
不解释。
使用主机商提供的 MySQL 云服务
之前 MySQL 是跑在本地的。众所周知,云服务器的磁盘读写性能堪忧。直接换成主机商的 MySQL 云服务,性能提升不少。
性能瓶颈竟然是…… Array.sort
考虑到会有很多用户登录一次后不再登录,上线后很可能是:五百用户同时在线,总用户数千个。
为此,往数据库里添加了数千个用户再次测试,果然出现了性能问题,排查后发现,性能问题竟然是:排行榜进行排序时,使用 Array.sort,这个函数跑了数百毫秒。好吧,毕竟是数千笔数据的排序。
解决方案:npm 上搜一个快速排序的库,替换掉,时间直接缩减到几十毫秒,美滋滋。
至此,差不多已经可以承受 500 用户同时访问了,上线!
数据库加索引
上线后没多久就开始卡了,直觉告诉我是因为数据库没加索引,而用户太过热情导致瞬间委托量就达到数万。
登录数据库加上索引,搞定。
总结
并发并不是这个系统中最困难的部分。
最困难的部分是交易撮合系统的设计开发和调试,很多 bug 只在非常特定的情况和时间点才会出现,交易一次没问题,两次没问题,但在不特定的 n 次时会出 bug,非常难定位。
此外,系统设计之初必须要考虑到并发问题,如果最初的设计是,每有用户下单时就推送一次数据,那么十几个在线用户就可以造成每秒数十次推送,需要推翻重新设计。
不过,这个系统的并发量并不算很高,可能这也是难度不大的原因。希望我司的产品能大卖,让我有机会做个千万级的并发,再回来吹牛逼说高并发不难,哈哈。