引言:
长连接对于大部分公司的意义点?
实时的响应总是让人兴奋的,就如你在微信里看到对方正在输入,如你在王者峡谷里一呼百应,如你们在直播弹幕里不约而同的 666,它们的背后都离不开长连接技术的加持。 每个互联网公司里几乎都有一套长连接系统,它们被应用在消息提醒、即时通讯、推送、直播弹幕、游戏、共享定位、股票行情等等场景。而当公司发展到一定规模,业务场景变得更复杂后,更有可能是多个业务都需要同时使用长连接系统。
长连接是什么?
长连接,在百度上的定义:指在一个连接上可以连续发送多个数据包,在连接保持期间,如果没有数据包发送,需要双方发链路检测包。
严格上来说,长连接是一种概念,它指的是在网络发送,接收双方保持一个持续连接的状态,双方都可以发送或接收消息(全双工)
当然,长连接系统听起来好像高深莫测,但实际不可能说完全脱离于我们实际公司的业务去出发设计,这就引出来我们今天的主题------ 长连接技术是在37手游内是如何设计以及实践的?
痛点和背景:
痛点:
-
从服务端而言,缺乏在SDK内实时推送通知用户的能力,如防沉迷的弹窗通过http定时轮询来实现,给服务造成很大的压力
-
从客户端而言,也缺乏低成本告知服务端自身在线的能力,如维持用户在线状态靠客户端定时上报心跳到防沉迷服务实现。
搭建背景:
-
业务背景:手游的SDK需要提供内嵌直播弹幕的功能,供玩家在看直播的时候进行沟通发言,业界一般会用到长连接来进行实时的推送,以降低服务的轮训和请求
-
从业务上而言,直播弹幕需求由于初始量级很少,不用通过长连接的形式也能实现具体业务逻辑;但出于平台的拓展和能力考虑,长连接的能力是必须具备的
-
长连接系统的这次搭建,本质是通过SDK内嵌直播弹幕为切入点,从0到1,为平台提供了完整的一套实时推送触达用户的能力,
从以上角度出发,我们便着手想要设计一个高可用且高性能的长连接系统提供给我们使用
方案选型------该如何选择适合我们业务的方案
从实际上而言,我们考虑三个方向
云服务:
市面上可购买的服务如open-IM,环信等,大多数都以即时聊天通讯为主,长连接往往只是附带产品,过于偏向于社交业务,在花钱的同时也很难适应到自身游戏业务。
开源框架:
b站的goim框架,NettyChat框架等,好处是免费且能快速接入,但业务还是不相适应,且语言栈和技术栈不一定相契合
自研:
-
弊:开发成本高,且容易踩坑
-
利:
1,基础组件完善,统一框架好进行监控和问题查询
2,能充分契合自身业务进行拓展,并将数据源掌控在自己手中
结论:
出于扩展性的考虑,肯定是自研的方式更加合适,但完全自研相应的成本和不确定性会非常高,因此最终的选型方案为借鉴b站的goim框架设计和部分代码,并做了符合自身技术栈和业务架构的改善。
可认为是基于开源框架的自研方案的形式完成了长连接这个系统的落地
总体设计与架构:
goim是什么?为什么借鉴goim?
-
goim是b站开源研发的一个支持集群的im及实时推送服务
-
业务上为直播间的弹幕发送场景
-
技术栈契合:语言栈为Golang ,消息队列为kafka,缓存设计为redis
-
高性能:有压测报告,性能设计有保障。
-
多协议支持:websocket,tcp
goim的模块?
- comet:用于跟端上保连,在内存中存储订阅信息
- logic:用于处理鉴权,在线房间等业务逻辑
- business:业务服务
- balancer:负载均衡模块
- discovery:服务发现模块
- job,kafka用于削峰。redis用于缓存房间信息
- logic和comet的通讯采用rpc,优化性能
从上面的goim的设计而言,我们可以总结出长连接的设计原则
长连接架构的设计总是大同小异的,通用的有这几点
-
业务层次分明:分接入层,逻辑层,存储层,服务发现层;通过这几层,实现业务解耦
-
协议通用:传输的数据协议必须在所有服务内进行通讯,并支持扩展
-
上行和下行收敛:上行和下行都会有一个对应的网关来进行收敛消息的收敛
-
消息削峰:通过队列来将发送信息削峰。并使接入层和逻辑层解藕
37手游长连接系统总体介绍
根据以上的通用设计原则,我们不难得出,需要针对goim的架构如何做适配才能实际满足我们的业务需求,下面我们将会一一介绍
架构改动
从架构而言,我们改动的点不多
-
cient获取连接节点:37手游内无统一的服务发现模块,但有外层LB,因此通过LB来实现comet节点负载均衡
-
推送消息:同上,消费者获取节点时需要到logic服务中查询,而不是discovery
-
服务通讯协议:由于37手游内架构通讯协议的统一,接入层和逻辑层的通讯由RPC转为http
逻辑改动
鉴权机制:
-
客户端第一次连接上comet的时候,会发送鉴权上行
-
comet会解析客户端传输的数据,若包体协议或对应的签名错误,comet会将会直接主动断连
心跳机制:
-
comet会有心跳计时器,若客户端无定时上报心跳,则认为该连接已经超时,直接断开
-
这种业务的心跳主要是为了防止僵尸连接的存在
回包机制:
-
客户端每发送一次上行操作,comet都会有对应的消息回包给端上。
-
端上可根据回包,来知道自己鉴权,心跳,或者订阅,退订的操作是否成功,从而决定是否进行重试
发布/订阅机制:
我们在观看goim的源码时,发现goim适用于直播,群体推送以房间为维度,带有强业务属性,因此我们针对该部分抽象出发布/订阅机制
-
将房间抽象为topic,修改进房/出房动作为 订阅/退订
-
将所有的通知抽象为业务事件,客户端想要接受到哪个事件过来的消息时,可发送对应的订阅上行。单个连接订阅的事件不做限制,对某个用户或某个事件范围内的用户推送消息时,comet会根据事件去取到推送的用户,只有用户订阅了才会收到消息。
内存设计:
-
bucket维护消息通道和事件的信息
-
一个session对应一个用户连接
-
根据sessionid做一致性哈希来选择落到那个bucket上
-
bucket有两个map,一个是session map,一个是topic map
-
所有bucket都会开启一个chan做监听,广播的时候,会通知到所有bucket,所有bucket再取出某个事件的所有连接进行下发
总体交互
总体特点
因此,我们可以总结出来,我们37手游长连接系统的总体特点
-
纯golang实现
-
多协议支持:websocket和tcp
-
可拓扑结构:主要模块均无状态,可横向扩展
-
消息支持单推/群推,消息协议业务可自定义
-
发布/订阅机制,事件可业务自定义
性能评估
在说性能之前,我们先抛出一个疑问:
- 协程数过多实际占用的是什么?连接数过多是否影响CPU?
从实际上来说
-
长连接的性能瓶颈一般卡在接入层,
-
因此以接入层为评估维度,通过全链路压测得出来结果,
bash(1)压测参数:3000连接+ 3000 qps的实时推送 (2)推送内容:{msg:test} 推送类型:群推 推送持续时长:5分钟 (3)资源使用:1核2G的容器 (4)CPU的使用:达到100%
目前采用的长连接系统,通过压测结果发现,本身的连接数的维持和实时推送实际影响到是机器两个维度的性能,推送量影响到的是CPU ,连接数影响到的是内存。分别提高两个参数对相应的内存,CPU的影响并不大。
实际实现的业务
-
直播弹幕
-
悬浮球实时红点
从实际上来说,接入的业务虽小,但基础能力已经具备
总结
长连接系统从根本上来说,对应的设计都是大同小异的,我们应该更加关注的是对于自身业务的适配以及实现。
此文章来自于37手游------黄子键