前言
2018年伊始,各种答题赚钱热流席卷了朋友圈,先有百万英雄、冲顶大会成为风口浪尖,又有全民答题推波助澜。 公司适时推出了 《XX答题助手》采用“真人工 · 智能“的方式在直播答题的同时为大家提供参考答案,帮助大家稳、准、快的答对12道题目,分得真金白银。接下来我们以xx答题助手为例聊一聊websocket的应用
背景介绍

答题助手目前的planA如上 客户端采用轮询的方式每秒向服务器发送2次http请求,若有正确答案返回则展示在页面上。这种方式给服务器带来了很大的压力 答题直播时,每一个使用答题助手的用户会给服务器带来2qps的压力。经过优化目前每台服务器能抗约1w/qps的并发,当用户数上来后只能加机器。
分析业务场景,其实大多数的请求是无效的,客户端只需要在答题结果出来后,接收答案并展示即可。因此完全可以采用websocket的方法,在服务端获取到答案时,主动push消息到客户端即可。
Websocket协议
Websocket协议是基于tcp协议的应用层协议 主要是为了实现客户端与服务端的全双工通信。
Websocket与HTTP
Websocket与HTTP都是基于TCP协议的应用层协议,Websocket在建立连接时需要先发送HTTP请求与服务器握手,待服务器返回101进行协议转换,从HTTP切成Websocket协议进行通信

握手过程如上:
一、客户端:申请协议升级
客户端发起协议升级请求。采用HTTP报文,且仅支持GET方式
含义:
Request Method: GET 使用get的方式
Connection: Upgrade 表示要升级协议
Upgrade: websocket 表示要升级的协议是websocket
Sec-WebScoket-Version: 13 websocket协议支持的版本号。如果服务端不支持该版本,服务端需要返回一个Sec-WebScoket-Version Header,里面包含支持的版本号
Sec-WebSocket-Key: 7PMYxFH/jxrVsZvKeSTW1Q== 采用base64编码的随机16字节长的字符序列
Sec-WebScocket-Extensions: permessage-deflate; client_max_window_bits 希望采用的扩展协议
二、服务端:响应协议升级

Connection: upgrade 同意升级
Sec-WebSocket-Accept: E1rL2SuyYrDeuDYc5kUQApGBsyg= 服务端根据请求首部的Sec-WebSocket-Key计算出来的 计算方式如下:
1、将Sec-WebSocket-Key和 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 进行拼接
2、经过SHA1计算并转成base64
Sec-WebSocket-Version: 13 升级版本为13
Upgrade: websocket 升级的协议是websocket
三、客户端验证
客户端同样通过将Sec-WebSocket-Key和258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接经过SHA1计算,转为base64后与Sec-WebSocket-Accept对比,相等则验证成功
至此连接建立,由HTTP协议切换为WebSocket协议
技术方案
在了解WebSocket协议原理后 可基于TCP socket通过处理协议及数据帧搭建WebSocket服务。因swoole扩展已经对WebSocket进行了很好的封装以及进程的管理,同时是以C来实现的WebSocket,性能及稳定性都经过了很多大公司的检验,最终选用Swoole进行开发
Swoole扩展安装:下载swoole-2.0.12 table版 解压 进入解压目录 按以下步骤安装

(swoole2.0.12版本起不再支持php5,扩展编译需gcc-4.4+版本)
安装完毕在php.ini配置文件中加上extension=swoole.so即可启用swoole扩展
可使用下面几行代码实现一个最简单的WebSocket Server:

在on方法中注册事件以及其对应的回调函数进行处理。其中onMessage回调函数为必选,否则服务不会启动。用户可以onHandShake回调自定义握手协议,否则将使用Swoole默认的协议握手
广播:直接发http请求能触发onRequest回调,可在回调中遍历connections属性广播请求

注意:connections是一个迭代器对象 并且依赖pcre库,若编译时为安装prce库,此属性无法使用。需yum install pcre-devel 后重新编译swoole扩展使用
动态路由:搭建通用服务,使用一个websocket server提供多种类型的服务,需要根据路由动态选择服务类型和处理逻辑 可在onOpen时获取request对象的request_uri属性来根据url选择不同的路由做处理

onMessage时,无法获取request 需要在消息体内指定request_uri选择路由

性能压测
主要测试WebSocket Server能抗住多少长连接和并发,以及push消息时的速度和消息到达率
fork N个进程使用异步非阻塞客户端进行压测

在32核 128G机器上测得部分数据如下:
并发数为55000时,cpu的idle峰值约为87%,链接建立后保持连接时约为99% push消息时97%
push55000条消息平均需要200ms,消息送达率为100%
可见WebSocket长连接对机器资源的消耗非常小。
监控
为了能在系统负载过大、无法申请到内存、程序被误杀等情况下 能重新拉起server需要有脚本监测 自动启动主进程,脚本如下

停止脚本

需要在Server启动后设置进程名称

至此WebSocket服务的搭建及压测监控都已完成。接下来在完成服务的稳定性、上下线等运维相关的工作后会计划灰度上线 后期结论会继续同步