0%

全文约9000字,预计阅读时间50分钟。

背景

IM作为目前移动互联网领域份额最高的流量入口之一,几乎深入到每个人的学习工作和生活当中,而在其他领域产品流量中,IM会作为产品能力的一部分为产品提供诸如社交、客服等核心能力。

掌门技术公众号《SocketIO高性能事件驱动模型探索》中,背景介绍中掌门在基于Socket的实时互动业务领域覆盖之广,这其中即时通信(IM)作为核心功能的覆盖是不可或缺的能力支撑,同时IM也是作为实时交互中台最核心的业务领域能力,业务触角广,接入群体大。

阅读全文 »

3、功能篇

3.1 ClientAPI设计思路

1
2
3
4
5
emitToSession
emitToUser
emitToGroup
emitToGroupExcludeSelf
broadcast

目标:友好设计

img

使用异步事件通知模型:

发送伪代码1

Client.addQueue(msg);

发送伪代码2

1
2
3
4
5
6
7
8
Client -> queue<Message>
while(true) {
msg = queue.poll()
if (msg == null) {
break;
}
channe.writeAndFlush(msg);
}

基于引用计数方式实现内存零拷贝

阅读全文 »

2、性能篇

本篇主要关注通过压测和参数配置、技术选型,基于理论和原理提升Netty-SocketIO性能,尽量让SocketIO服务器性能符合理论上的曲线趋势,内容中会涉及较多基础原理(节选出处见参考)。

2.1 环境设定

系统环境:CentOS7.0,已经对fd、网络等关键内核参数进行了优化

硬件环境:2C4G

因整体环境区别,以下数据仅供趋势上的参考分析对照。

Netty-socketIO部分配置参数

1
2
3
4
5
6
7
8
9
10
bossThreads=2
workerThreads=4
pingInterval=10000
pingTimeout=20000
acceptbacklog=65535
tcpNoDelay = true
tcpKeepAlive = true
preferDirectBuffer=false
httpCompression = true
websocketCompression = true

压测工具:JMeter

G1默认配置

1
-XX:SoftRefLRUPolicyMSPerMB=0 -Xmx2048m -XX:-OmitStackTraceInFastThrow -Xss256k -Xms2048m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+UseG1GC -XX:MaxGCPauseMillis=200

初步SocketIO网关压测结果

测试场景 websocket连接数 发送频率 qps 网关cpu 网关内存/堆内存 网络流量(up/down) 备注
订阅推送服务 8000 1/5s 4000 11% 2.71G/1850m 4.45mb/4.21mb 服务器每个jmeter只能承受4000
广播 5000 1/5s 2500 4% 2.88G/1992m 2.5mb/1.34mb 服务器每个jmeter只能承受2500
广播 5000 1/2s 5200 7% 2.67G/1874m 5.81b/2.61mb 服务器每个jmeter只能承受2500

该数据由@梁兵玉 大佬提供,号称压测一时爽,一直压测一直爽。

2.2 目标与分析

程序优化目标:功能扩展、QPS提升、GC友好,避免过早优化

优化阶段分析:

GC在G1下的GC阶段分别为YGC、MGC、FGC。

阅读全文 »

全文约8000字,预计阅读时间40分钟。

编者话:本次压测是场景压测中比较重要的环节,除此之外还有并发会话压测(同时在线人数),并发多组压测(同时在线群组数),推送点对点、群组发送、广播发送等场景,同时针对限流、发包大小限制、熔断标记等环节都有涉及,非常感谢@梁兵玉(大佬的原话:压测一时爽,一直压测一直爽)同学在压测环节提供的各种压测流程编排和数据提供,后续会陆续讲述这其中的精彩故事。

(一)背景

针对第一篇关于Socket长连接场景高性能事件驱动探索中,我们进行了部分核心场景的性能压测,高性能驱动凝聚了中台Socket团队很多生产经验的积累,我们希望通过一些场景压测来验证和发现那些已知和未知的问题,这次我们选择了广播场景,广播消息是Socket长连接业务常用的一种技术手段,推送系统的广播,IM系统的群聊都是针对一类特定人群的消息派发,在业务上的实现通过有状态的会话进行循环点对点式的主动消息上传。

broadcast

(图1-1)广播类消息概念图
压测1800人大群以10秒每人次的速度进行广播聊天消息,在压测大概10分钟左右,服务器流量会突然下降,并且内存耗尽,而10分钟的过程则显得非常平淡,内存正常,CPU正常,网络几乎无波动,没有异常日志,没有生成dump文件,压测机器流量正常,笔者怀疑每个人是不是都在正常开车,但是却没有证据。

硬件环境:4C8G

系统环境:

1
2
3
CentOS Linux release 7.6.1810 (Core)
uname:Linux 3.10.0-957.5.1.el7.x86_64 #1 SMP Fri Feb 1 14:54:57 UTC 2019 x86_64
libc:glibc 2.17 NPTL 2.17

网络环境:模拟公网带宽100Mbps

压测数据:

群人数:1800

QPS:20

上行QPS=20*1800=36,000/s

消息大小:78Byte

协议头大小:22Byte(粗估)+socketio二进制头40Byte

每秒包大小:36,000*(78+22+40) = 3,600,000Byte=4.8MB

窗口大小和吞吐量:按照RTT0.1s计算,1个连接的最大吞吐量为5.2Mbps,在一般网络压测中,使用多个TCP连接提高网络吞吐量,web浏览器中通常使用4个TCP连接。

(二)分析

对于服务器性能迷之自信的我肯定是无法接受这种没有证据的开车一说,临近下班,没有太清晰的思路去偌大车祸现场研究细节,于是对广播消息进行限流,数据对比如下:

序号 人数 限流次数 广播次数 上行网络带宽 持续时间 GC
1 1800 30 54,000 ~80Mbps 20m G1
2 1800 20 36,000 ~65Mbps 30m G1
3 1800 30 54,000 ~80Mbps 23m G1

通过前2次分析和GC的内存走势上面分析,老年代突然加大,必定是内存中的QPS达到峰值无法销毁导致年轻代来不及回收,本该需要回收的对象,在CPU允许的情况下并没有GC掉,第三次试图通过G1的垃圾回收参数通过CPU换取QPS效率,

img

(图2-1) 车祸现场内存图

img

(图2-2) 广播消息限流图
阅读全文 »

正文

1、使用篇

SocketIO使用,不同场景上可以通过不同的技术栈组件匹配不同的场景和改造方向,本篇主要介绍一些基础的SocketIO概念和接入,大致思路入思维导图所示:

img

(图1-1)默认协议接入流程时序图

1.1 初步使用时的一些小姿势

img

(图1-2)默认协议接入流程时序图

前端通过简单的io.connect完成一次Demo连接

1
2
3
var socket =  io.connect('http://localhost:9092', {
transports: ['websocket']
});

默认的连接大致分为三步

  1. 连接获取sid
  2. 默认握手返回

img

(图1-3)Http升级Ws成功的响应

  1. sid再连接+升级到ws

在源码层,一个会话有多个channel,分别用于多种协议交互,client会随机使用一个端口和socketio服务器进行连接

1.2 通信事件的定义

1
2
3
4
5
6
7
'CONNECT',
'DISCONNECT',
'EVENT',
'ACK',
'ERROR',
'BINARY_EVENT',
'BINARY_ACK'

1.3 连接参数定义

参考socketio.js, line 1882: function Manager(uri, opts)

1
2
3
4
5
6
7
8
9
10
11
this.reconnection(opts.reconnection !== false);
this.reconnectionAttempts(opts.reconnectionAttempts || Infinity);
this.reconnectionDelay(opts.reconnectionDelay || 1000);
this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000);
this.randomizationFactor(opts.randomizationFactor || 0.5);
this.backoff = new Backoff({
min: this.reconnectionDelay(),
max: this.reconnectionDelayMax(),
jitter: this.randomizationFactor()
});
this.timeout(null == opts.timeout ? 20000 : opts.timeout);

1.4 接入和安全协议

HTTPDNS、浏览器下https降级和ip策略,HTTPDNS对于移动设备基本上已经是标配了,不可控因素在于LocalDNS的一致性同步问题,阿里云和腾讯云DNSPort都已经提供快捷接入方式。

https/wss降级主要针对DNS劫持后的自保手段,在能接受一定安全降级的情况下使用IP接入,是降级策略的前提。

多域名也是DNS防劫持手段之一,这是在多通配符证书的价格面前,需要掂量投入产出比。

价格参考:阿里云1年的多域名通配符,假设20台服务器,配置3个域名,需要60个域名证书支持。

img

(图1-3)阿里云多通配符域名价格参考

1.5 集群化部署与集群灰度

集群化部署跟公司整体现有技术栈对接即可,最大问题在于准备阶段需要做到可控的上线监听,如果端口通信开放,而系统还处于未完全准备阶段,会造成不可预测的结果,实例级别灰度通常需要跟接入流程息息相关,此时需要一个前置LB服务负责Socket实例的负载,一般能做到Zone+Region级别能够满足绝大部分场景。

阅读全文 »

全文约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开发人员,面向对象设计和状态图之间总会有一个成员变量表示这个对象的当前状态,而状态的变化(生命周期)用事件驱动模型设计思路不谋而合,比如下图展示的是课堂中的熔断逻辑状态图。

img

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