全文约7000字,预计阅读时间30分钟。
该文章首发于《掌门技术》公众号,更多技术好文,欢迎关注掌门技术。
背景
SocketIO原生基于NodeJS实现的Web长连接技术方案,H5原生场景下通常使用websocket作为基础协议进行网络通信(客户端支持多语言),SocketIO对于长连接场景下的业务形态进行了很多方面的抽象和实现,比如:命名空间、用户、房间等关系模型,技术形态下同样也进行了多方面的快速支持,比如ssl证书、websocket文本、二进制、双向Ack、心跳等API,作为一个Web长连接解决方案,SocketIO不失为一个很棒的基础协议支持框架,接入快速,模型简单,掌门在Socket通信接入侧选型上也选择了SocketIO协议。
如果是一个擅长Java技术栈的后端来说,netty-socketio(官方地址: https://github.com/mrniko/netty-socketio)(4.4k star)的确是实现socketio服务的不二之选,这个项目由近几年比较火的redis官方推荐Java客户端连接工具redisson(11.6k star)作者(mrniko)于13年开发,已经有7年之久,已经处于事实上的停更状态,这给使用该项目作为web长连接后端框架来说,选择它通常并不是那么容易接受,而我们希望通过一系列的性能改造使地它能够更好地被我们所用。
业务背景
基于Socket的业务场景大致可以分为以下部分(其中加粗部分为掌门在Socket业务领域覆盖):
- 聊天场景: 即时通信
- 直播场景:互动、弹幕
- 智能家居IoT:监控、远程控制
- 游戏场景:互动
- 交通场景:位置共享
- 教学场景:在线白板
- 音视频:WebRTC信令协商
- SLB长连接场景:网关
从分类上,掌门在Socket领域覆盖度很高,其中最核心场景为在线白板,在线白板单个会话信令最高可达80帧QPS,百兆带宽下上课高峰时间对服务器冲击很大,高性能、可控制、可度量、可伸缩是生产系统服务提供的基础要求,如何设计一套高性能高可扩展性系统对团队提出了很大的考验,高性能事件驱动模型的探索则在意料之中。
建议面向读者:对SocketIO或者对事件驱动设计有兴趣的开发人员。
SocketIO在本篇中通常指的是Netty-SocketIO。
概念
事件本是GUI领域最常用的概念,前端开发人员最常接触的一些GUI事件和事件模型框架比后端开发人员相对使用的更多,GUI中通常定义的一些事件,比如client、touch、doubleclick、multitouch、open、close等等都是对于GUI层面一些交互的抽象,这些具体的事件注册和响应也通常由GUI系统本身提供,而背后的实现逻辑,则无外乎下图所描述的事件模型实现。

(图2-1)事件驱动模型图
这其中主要包括4个基本组件:
- 事件队列(event queue):接收事件的入口,存储待处理事件;
- 分发器(event mediator):将不同的事件分发到不同的业务逻辑单元;
- 事件通道(event channel):分发器与处理器之间的联系渠道;
- 事件处理器(event processor):实现业务逻辑,处理完成后会发出事件,触发下一步操作。
事件驱动模型的三要素:
- 事件源:能够接收外部事件的源体;
- 侦听器:能够接收事件源通知的对象;
- 事件处理程序:用于处理事件的对象。
HTML中对于Body的标签预埋的事件,除了浏览器本身提供可声明行为监听,还可以针对框架本身进行扩展。

(图2-2)github的body标签对应的事件
抛开GUI领域,事件驱动模型在其他领域发挥的作用也远比想象的要多,基于笔者的理解,大致能抽象一下如下的场景:
- 可声明的行为(静态的)
- 可状态化的行为抽象描述(动态的)
通常可状态化的行为,可以通过状态图来描述,而状态图通常是事件驱动模型设计中非常重要的建模模型。作为面向对象语言的Java开发人员,面向对象设计和状态图之间总会有一个成员变量表示这个对象的当前状态,而状态的变化(生命周期)用事件驱动模型设计思路不谋而合,比如下图展示的是课堂中的熔断逻辑状态图。

(图2-3)单个会话生命周期信令熔断状态图
事件驱动在Java编程领域常用的基于guava的EventBus、RxJava的RxBus都是使用率较高的事件驱动框架,为了和SocketIO框架整合,本篇也重复造了一个轮子。