背景
在做项目大屏项目,和图形可视化项目时需要前端界面需要并发实时请求服务端数据,实现数据平滑刷新。
- 建立并发送多个http请求,并且定时轮询,返回不及时,浪费带宽资源,且会导致浏览器内存占有高;
- 服务端需要快速处理大量的数据请求,进程级别占用太多CPU,需要线程级别的并发,减少服务器负担。
- WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
技术选型(本人技术有限,目前只会以下三种)
NodeJS:单线程模型,推送性能有限
PHP:多进程模型,需要安装扩展才能实现多线程;
GO: 多线程,基于协成模型并发;成熟的WebSocket标准库,无需造轮子
什么是WebSocket
WebSocket是一种在单个TCP连接上进行全双工通信的协议。
什么是线程安全
线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。
Golang-Websokcet包
开发websocket服务时,首先要基于http库对外暴露接口,然后由websocket库接管TCP连接进行协议升级,然后进行websocket协议的数据交换,所以开发时总是要用到http库和websocket库。上述websocket文档中对开发websocket服务有明确的注意事项要求,主要是指:
- 读和写API不是并发安全的,需要启动单个goroutine串行处理。
- 关闭API是线程安全的,一旦调用则阻塞的读和写API会出错返回,从而终止处理。
所以要用实现websoket必须考虑线程安全问题
技术实现
1.工程化设计,封装Websoket连接,隐藏WebSoket底层连接
2.封装Websoket的API,提供Send/Read/Close等线程安全接口
封装Websoket连接:
封装Websocket的API: ReadMessage/WriteMessage/Close
- WriteMessage将消息投递到out channel
- ReadMessage将消息投递到in channel
- websocket的默认Close方法是线程安全的
- 启动读协程,循环读取WebSocket,将长连接的消息投递到in channel
- 启动写协程,循环读取out channel,将消息写回长连接
- 连接异常关闭,会导致读写channel阻塞,加入close chanel,连接异常关闭则关闭close chanel,通过close channel的关闭来判断读写操作是否需要退出,防止阻塞
- 因为关闭一个channel是非线程安全的,只能关闭一次,所以需要加锁和变量来判断
性能测试
场景对比:Workman 和 GO;
测试环境:服务端:4核8G,Centos环境;客户端:4核8G,Centos环境
测试工具:
websocket-bench -a 20000 -c 5000 -t primus -p websockets ws://10.176.124.200:7777
-a-c-o
测试报告:
并发连接数 | 测试次数 | 测试消息长度 | GO耗时 | Workman耗时 |
---|---|---|---|---|
100 | 1000 | Hello Word! (11) | 500ms | |
1000 | 1000 | 500 |
使用场景
内核瓶颈:Linux内核发送TCP的极限包频 约=100万/秒——消息合并减少发送量,每秒推送次数只等于在线连接数
锁频颈:维护用户字典集合
CPU频颈:浏览器和服务端通常采用JSON格式通讯,编码非常耗费CPU资源——减少重复计算,JSON编码前置