Web Websocket心跳应用
websocket 稳定 push 设计方案
User story
1000 台客户端同时在线, 突然发现 bug, 导致大面积瘫痪, server 使用 websocket 做更新推送.
版本紧急修复后,上线实现步骤:
- 修复版本
- admin 登录 portal
- 上传当前补丁信息, 设置发布条件: push 时间, 车机 id, 地理限制, 人数限制, push 频率…等等.
- 设置完成后, 页面显示 push 目标人数, 即当前补丁正式 push 前的在线人数.
- push 操作,点击后开始执行 push, 后台按设定策略开始通知在线车机.
当前实现,可以解决在 push 期间出现 client 异常掉线/新用户上线的场景.
- 用 python list 存储 [ client object1 ,… ],client 增加属性 flag=0
- push 时,对每个 client 发送命令. 发送完毕的 client flag=1. 循环检测 list 里 dict 元素的 flag,直到最后 1 个 flag 不为 0,即所有 client 被 push 了,才 push end. push 期间, 再次 push 时 trylock push 期间 ,重连上线或者新上线的 client 可以处理(client 会 append 到 list) push 期间 ,如果 client 掉线, remove(client).
不足分析
上线步骤 4 和 5 期间, 考虑可能发生在以下任意时间段(t0,t1,t2,t3)的掉线/重新上线:
—t0 —– tinker ready — t1 —push start —t2— push end —–t3——
t0 之后的新用户 由 app 自己处理; 在 t2 之前因为掉线而重新上线的,都由上面已经实现的机制处理; 即主要考虑在 t0/t1/t2 掉线,但在 t3 又重连的客户,如何识别?
t3 连上的客户可能包括已下 3 种:
- t0,t1,t2 断开,t3 重连的客户;
- t3 新来的客户; //ignore
- t3 断开又重连的客户;
client 角度出发,理论上自己每次重连时访问 tinker 即可,但是访问量可能巨大. 通过把 vid 给后台,由后台来判断并告诉 client 是否需要访问 tinker.
逻辑实现
1. 每次connected后,客户端通过wss协议主动发送vid, 后台存储redis key HASH
'0': no need push after connect
'1': need push after connect
if wx:vid:xx key exsits and push_end and key.value is '1':
push this client
set wx:vid:123xxx '0'
set wx:peer:12131 'wx:vid:123xxxx'
2. pushed end时, push_end = True
3. disconnected时:
if push_end is not True:
# 根据client.peer的value查找对应的vehicle id, 再修改响应的status 需要用到2组key, peer:vid, vid:status
get wx:peer:1231 = wx:vid:123xxx
set wx:vid:123xxx '1'
del wx:peer:port
可能隐患: wx:vid:123xx 的k-v对会越来越多, 不过不是大问题.
新的问题
发现问题:
client —nginx—websocket client 和 nginx 断开,没有通知到 web service
用手机模拟掉线的 client, 都飞行模式,但是 tcp 还是 established 的状态?
看来并不能把 websocket 等同与 tcp:
TCP keepalive dont get passed through a web proxy. The websocket ping/pong will be forwarded by through web proxies. TCP
keepalive is designed to supervise a connection between TCP endpoints. Web socket endpoints is not equal to TCP endpoints.
A websocket connection can use several TCP connections between two websocket endpoints.
另外 nginx 里和 websocket 有关的超时设置, 是为了防止 nginx proxy websocket 的自动断开.
proxy_connect_timeout 5s;
proxy_send_timeout 15s;
proxy_read_timeout 86400s;
证明 proxy_read_timeout 是有影响的,但是也不能不要,否则 nginx 代理的 websocket 会断.
怎么办?看来还需要增加 1 层心跳功能. 考虑应用层加心跳或者 websocket 有自带的心跳 ping pong
研究 autobahn ,很简单就可以设置:
global_factory.setProtocolOptions(autoPingInterval=5, autoPingTimeout=10)
而且 client 端一般都打开了 pong 功能,上述开启后, wireshark 可以轻松抓到 5s/次的心跳包
这样再再断网时,websocket 就能在 10s 内自动断开了.结合上面的机制,可以实现断线后仍然稳健 push.