OpenIM压力及可靠性测试
背景
为了全面测试OpenIM,首先需要明确其核心功能和架构。OpenIM不是一个独立的聊天应用程序,如WeChat或Slack,而是一个包含IMSDK(用于客户端集成)和IMServer(用于私有化部署)的开源即时通讯解决方案。这使得开发者可以在自己的应用程序中轻松集成即时通讯功能,提供一个替代Twilio或Sendbird等即时通讯云服务的选择。由于IMSDK底层是用Go语言编写的openim-sdk-core,模拟大规模用户在移动设备上的使用场景具有一定的挑战。实际上,使用成千上万台手机进行测试是不现实的。为此,我们设计了两套测试程序:
- 测试程序A-可靠性:
- 该程序在服务器上加载并运行openim-sdk-core来模拟IMSDK实例。每个实例都使用SQLite进行消息存储。这种方法可以全面模拟客户端的行为,包括消息的发送、接收、存储和回调,有效验证消息收发的可靠性和时延。但是,如果在单个服务器上运行过多的SDK实例,可能会导致客户端IMSDK性能下降。
- 测试程序B-压力:
- 此程序精简了IMSDK的功能,只保留登录和消息收发功能,旨在通过启动大量实例模拟高并发场景,以评估服务器在重负载下的表现和稳定性。虽然这种方法不能完全验证客户端SDK的全部流程,但它对于评估服务器的承载能力和资源管理策略至关重要。它的缺点是:不能完整模拟IMSDK的功能,也就无法验证消息收发可靠性。
综合这两种测试程序,程序B主要用于模拟大量用户同时在线并进行消息交互,以增加服务器的负荷;而程序A则用于加载IMSDK,通过抽样统计来评估消息的可靠性和时延。两种程序的联合使用可以更好地模拟真实用户场景,并客观地提供IMSDK的测试报告。这种双重测试策略确保了从不同角度对系统进行全面的评估和验证。
可靠性和时延
消息的可靠性通常指的是消息投递的可靠性,即所谓的“消息必达”。这意味着一旦消息被发送,它必须能够被接收者成功收到。考虑到网络环境的复杂性和用户在线状态的不确定性,消息的可靠性无疑成为IM系统的一个核心性能指标,也是实现上的一大挑战。通常所说的IM系统的“可靠性”,主要是指聊天消息的投递可靠性。需要说明的是,这里的“消息”是广义的,包括用户看不见的各种指令和通知,如进群退群通知、好友添加通知等。
消息时延则指从客户端A创建并发送消息,到客户端B成功接收并入库这一消息的耗时。有些IM系统仅将消息从客户端发送到服务器的接收计入消息时延的统计,这种做法并不全面。正确的做法应该包括从客户端A发送到客户端B接收的整体耗时。
测试资源
服务器1:Ubuntu 22.04.2,16Core,64GB RAM,150GB 机械磁盘:部署组件和IMServer;同时部署测试程序B,可能也会部署测试程序A在共享内存/dev/shm上
服务器2:Ubuntu 18.04.5,4Core,8GB RAM,40GB 机械磁盘:部署测试程序A在共享内存/dev/shm上
在服务端start-config.yml中,把服务的实例调整为openim-push: 8,openim-msgtransfer: 8,其他保持1个实例不变。
测试场景和结果
测试一:200个用户
测试程序A,模拟200个用户,其中100个立刻登录,向小群、好友发送消息
测试程序A统计消息完整性和时延:go run main.go -lgr 0.5 -imf -crg -ckgn -ckcon -sem -ckmsn -u 200 -su 10 -lg 2 -cg 2 -cgm 5 -sm 5 -gm 5 -reg
此时,测试程序A部署在服务器1的共享内存/dev/shm上
参数/结果 | 描述 |
---|---|
测试目的 | 少量用户情况下测试消息可靠性和耗时 |
用户数量 | 200用户,其中100人立刻登录,另外100个延迟登录 |
群数量及规模 | 每人加入0-10个常规群,群成员人数为5人 |
发消息频率 | 峰值150条/s |
消息总数 | 112350 |
消息完整性 | 100%(所有消息精准送达) |
消息平均时延 | 0.231秒 |
消息最大时延 | 1.703秒 |
测试二:5万在线用户+小群
测试程序B:模拟5万在线用户,随机发送消息
测试程序A:模拟100个用户,其中80个立刻登录,向小群、好友发送消息
测试程序A注册10万用户:go run main.go -reg -u 100000
测试程序B启动5万在线用户:go run main.go -s 49500 -e 99500 -c 100 -i 500 -rs 1000 -rr 1000
测试程序A统计消息完整性和时延: go run main.go -lgr 0.8 -imf -crg -ckgn -ckcon -sem -ckmsn -u 100 -su 3 -lg 0 -cg 4 -cgm 5 -sm 100 -gm 100
此时,测试程序A部署在服务器2的共享内存/dev/shm上,测试程序B部署在服务器1上
参数/结果 | 描述 |
---|---|
压力情况 | 50000用户在线,每秒发送约1700条消息 |
抽样统计用户数 | 100用户,其中80人立刻登录,另外20个延迟登录 |
抽样统计用户的群数量及规模 | 每人加入0-20个常规群,群成员人数为5人 |
抽样统计消息发送频率 | 峰值160条/s |
抽样统计消息数 | 170800 |
抽样统计消息完整性 | 100%(所有消息精准送达) |
抽样统计消息平均时延 | 0.202秒 |
抽样统计消息最大时延 | 3.641秒 |
测试三:5万在线用户+5万大群
测试程序B:模拟5万在线用户,随机发送消息
测试程序A:模拟20个用户,其中16个立刻登录,向10个5万人群聊、好友发送消息
测试程序A注册10万用户:go run main.go -reg -u 100000
测试程序B启动5万在线用户:go run main.go -o 50000 -s 49500 -e 99500 -c 100 -i 500 -rs 1000 -rr 1000
测试程序A统计消息完整性和时延:go run main.go -lgr 0.8 -imf -crg -ckgn -ckcon -sem -ckmsn -u 20 -su 3 -lg 10 -cg 0 -cgm 5 -sm 0 -gm 10
此时,测试程序A部署在服务器2的共享内存/dev/shm上,测试程序B部署在服务器1上
参数/结果 | 描述 |
---|---|
压力情况 | 50000用户在线,每秒发送约1700条消息 |
抽样统计用户数 | 20用户,其中16人立刻登录,另外4个延迟登录 |
抽样统计用户的群数量及规模 | 10个大群,每个群50000人,其中500人在线 |
抽样统计消息发送频率 | 峰值32条/s |
抽样统计消息数 | 24000 |
抽样统计消息完整性 | 100%(所有消息精准送达) |
抽样统计消息平均时延 | 0.022秒 |
抽样统计消息最大时延 | 1.664秒 |
测试二服务器资源消耗
登录用户数:
消息压力:(下图指标表示1分钟服务器接收到的消息量)
CPU使用情况:
进程 | CPU占用 |
---|---|
openim-msggateway | 210% |
mongo | 100% |
kafka | 84% |
redis | 67% |
openim-rpc-msg | 56% |
openim-msgtransfer | 27%*8 |
openim-push | 13%*8 |
其他OpenIM 服务以及组件 | 65% |
总计 | 902% |
物理内存占用情况:
进程 | 内存占用 |
---|---|
openim-msggateway | 2.1 GiB |
mongo | 717 MiB |
kafka | 1.1GiB |
redis | 85MiB |
openim-rpc-msg | 162MiB |
openim-msgtransfer | 74MiB*8 |
openim-push | 126MiB*8 |
其他OpenIM 服务以及组件 | 457MiB |
总计(所有OpenIM 服务以及组件) | 6.986GiB |
备注:以上表格内容是粗略统计,在总计中不包括docker转发数据到容器的资源消耗,仅供参考。
结果分析
OpenIM 支持同时在线 5 万人,能够处理多个 5 万人超级大群,面对每秒 1700 条消息的压力时,消息可达率达到 100%。平均消息时延低于 1 秒,最大时延不超过 3 秒,展现出卓越的性能和可靠性。
配置建议
按照10万注册用户,日常在线10%用户,支持5万大群计算,每秒600条消息,建议配置:
名称 | 配置 |
---|---|
内存 | 16G |
CPU | 8核 |
网络带宽 | 10M |
备注:消息包大小按照2KB计算。实际消息包大小根据发送内容而异,常规的一句文字消息消息包大小为700字节左右。
补充说明
本次测试用到了两个测试程序:
压力测试程序,路径:openim-sdk-core/msgtest/
可靠性测试程序,路径:openim-sdk-core/integration_test/
以下是可靠性测试程序的使用说明以及检测逻辑的说明。
参数说明
测试程序支持通过配置参数来指定不同的测试场景。通过灵活设置参数,用户可以自由地模拟各种复杂场景,涵盖不同的网络状态和操作流程,从而更精确地评估消息通道的可靠性。
参数 | 含义 | 类型 |
---|---|---|
u | 用户数量 | int |
su | 拥有所有好友的用户数量 | int |
lg | 包含大群的数量 | int |
lgm | 大群人数 | int |
cg | 每个人创建的常规群聊数量 | int |
cgm | 常规群人数 | int |
sm | 每人发送的私聊消息数量 | int |
gm | 每人发送的群聊消息数量 | int |
reg | 是否注册 | bool |
imf | 是否导入好友 | bool |
crg | 是否创建群组 | bool |
sem | 是否发送消息 | bool |
ckgn | 是否检查群组数量 | bool |
ckcon | 是否检查会话数量 | bool |
ckmsn | 是否检查消息数量 | bool |
ckuni | 是否模拟卸载和重新安装并再次检查 | bool |
lgr | 登录用户比例/登录率 | float |
以下为一个运行命令的示例:
go run main.go -u 10 -su 3 -lg 2 -cg 4 -cgm 5 -sm 6 -gm 7 -reg -lgr 0.7 -imf -crg -ckgn -ckcon -sem -ckmsn -ckuni
该命令的参数含义如下:
-u 10
:总共创建10个用户-su 3
:创建3个超级用户-lg 2
:创建2个大群聊-cg 4
:每个登录用户创建4个小群聊-cgm 5
:每个小群聊包含5个成员-sm 6
:发送6条私聊消息-gm 7
:发送7条群聊消息-reg
:执行用户注册-lgr 0.7
:70%的用户登录-imf
:导入好友-crg
:创建群聊-ckgn
:检测群聊数量-ckcon
:检测会话数量-sem
:发送消息-ckmsn
:检测消息数量-ckuni
:模拟卸载重装操作
配置文件说明
配置文件位于 internal/config/
目录中,基础配置文件为 config.go
,配置如下:
TestIP = "127.0.0.1" // ip 地址
APIAddr = "http://" + TestIP + ":10002"
WsAddr = "ws://" + TestIP + ":10001"
AdminUserID = "imAdmin" // 服务端管理员ID
Secret = "openIM123" // 服务端管理员密码
PlatformID = constant.WindowsPlatformID // 模拟的登录平台类型
LogLevel = 3 // 日志级别
DataDir = "./data/" // 数据文件路径
LogFilePath = "./logs/" // 日志文件路径
IsLogStandardOutput = false // 是否在控制条输出日志,建议false
具体实现方案
测试程序的核心操作可以分为两类:模拟操作和检测操作。模拟操作用于模拟实际场景中的用户行为,如注册、登录、消息发送等。由于模拟操作涉及异步执行,为确保所有操作正常完成,测试程序会执行检测操作,以验证结果的正确性。例如,在登录过程中,必须确保每个客户端成功连接至服务器,才能继续后续操作,此时进行用户登录数量的检测尤为重要。
模拟操作说明
模拟操作逻辑位于 internal/manager
目录下,目前包含以下几项核心操作:
用户注册
通过调用服务端 API 来模拟用户注册。用户分为超级用户和普通用户。超级用户拥有所有用户作为好友,普通用户仅添加超级用户为好友。
- 假设系统中存在
u
个用户,其中su
个为超级用户,则用户的编号范围为0~u-1
。编号为0~su-1
的用户为超级用户,剩余用户为普通用户。
用户登录
用户登录的数量通过计算 用户总数 * 登录率
,并向下取整得到。被选中的用户编号范围为 [0, LoginUserNum)
。
好友导入
好友导入操作同样通过调用服务端 API 模拟完成。每个超级用户会向其编号之后的所有用户发送好友申请,服务端自动同意申请并发送通知。
群聊创建
群聊分为大群聊和常规群聊:
- 大群聊:大群包含所有用户,创建数量由参数
gl
指定。创建者按编号从0
开始轮流分配,超出登录用户人数时重新从编号0
开始。 - 常规群聊:每个登录用户都会创建常规群聊,创建数量由参数
gs
指定,群成员数量由gms
参数控制。用户编号为n
的用户会选择编号范围为[n+1, n+gms)
的用户作为群成员,超出最大编号时从0
重新开始选择。
消息发送
消息发送分为群聊消息和私聊消息:
- 群聊消息:每个登录用户会向大群聊及其创建的群聊发送
gm
条消息(待实现频率控制:最多每秒 3000 条消息)。 - 私聊消息:每个登录用户会向其所有好友发送
sm
条私聊消息(待实现频率控制:最多每秒 1000 条消息)。
卸载重装(待完成)
卸载操作通过删除本地数据文件并暂停一定时间来模拟,确保客户端停止发送心跳包,服务器检测到客户端下线并断开连接。
重装通过 SDK 初始化来模拟,卸载重装后程序会重新登录并执行后续检测操作。
检测操作说明
检测操作在 internal/checker
目录下实现,并采取循环检测的方式。在触发检测后,程序会进行一轮数据验证,只有当所有数据与预期结果一致时,才会继续执行下一步操作;否则将重复检测,直至数据符合预期。
检测操作的主要目的有两个:
- 阻塞主进程:确保异步的模拟操作已全部完成。
- 验证结果正确性:通过一系列正确性检测操作,验证模拟操作是否达到预期效果。
当前包含以下检测操作:
用户登录检测
用户登录检测发生在初始化或注册之后,以及每次正确性检测操作之前。通过计算与服务器成功建立连接的实际用户数量,比较预期登录用户数量。
预期用户登录数:
用户总数 * 登录率
,向下取整。- 所有用户总数。
好友数量检测
好友数量检测在导入好友操作完成后执行。由于离线用户无法同步数据,此检测仅针对在线用户的好友数量进行验证。
实际好友数量通过调用相应 SDK 获取,预期好友数量如下:
- 超级用户:好友数量应为所有用户数。
- 普通用户:好友数量应为所有超级用户数。
群聊数量检测
群聊数量检测属于正确性检测操作,通过调用 SDK 获取实际群聊数量。
预期群聊数量为:
- 大群数量 + 常规群数量(常规群数量依据用户登录情况可能有所不同)。
会话数量检测
会话数量检测也属于正确性检测,通过 SDK 获取实际会话数量。
预期会话数量为:
- 所有群聊会话数量 + 所有好友会话数量。
未读消息数检测
未读消息检测同样属于正确性检测,通过调用 SDK 获取实际未读消息数量,并与预期值进行对比。
预期未读消息数计算方式为:
- 所有群通知消息 + 所有群消息 - (自己创建的群通知消息 + 自己发送的群消息) + 好友申请通过通知消息 + 好友消息。
需要注意的是,只有申请发起人会收到好友申请通过的未读通知消息,且根据导入好友操作逻辑,只有超级用户的通知消息数量会有所不同。
限制修改
- 在模拟操作过程中,多个 SDK 实例同时运行,可能会对服务器造成较大压力,进而引发超时或其他问题。为确保系统在进行大规模数据量测试时能够平稳运行,需对以下几个关键指标进行调整:
- 修改请求超时时间
- 位置:
openim-sdk-core/pkg/network/http_client.go
- 调整内容:将请求的默认超时时间适当延长,以减少大数据量情况下的请求超时问题。建议根据测试规模和实际网络情况进行合理配置。
- 位置:
- 设置通知消息未读状态
- 位置:
open-im-server/config/notification.yml
- 调整内容:将
groupCreated
和friendApplicationApproved
的unreadCount
参数设置为true
。此配置将确保在线用户接收到群创建和好友申请通过的通知消息时,依然将其标记为未读消息,方便后续的未读消息数量计算。
- 位置:
- 最大文件描述符数量
- 位置:
open-im-server/start-config.yml
- 调整内容:根据测试时需要登录的用户数量,将
maxFileDescriptors
的值适当变大。
- 位置:
- 修改请求超时时间