跳到主要内容

OpenIM压力及可靠性测试

背景

为了全面测试OpenIM,首先需要明确其核心功能和架构。OpenIM不是一个独立的聊天应用程序,如WeChat或Slack,而是一个包含IMSDK(用于客户端集成)和IMServer(用于私有化部署)的开源即时通讯解决方案。这使得开发者可以在自己的应用程序中轻松集成即时通讯功能,提供一个替代Twilio或Sendbird等即时通讯云服务的选择。由于IMSDK底层是用Go语言编写的openim-sdk-core,模拟大规模用户在移动设备上的使用场景具有一定的挑战。实际上,使用成千上万台手机进行测试是不现实的。为此,我们设计了两套测试程序:

  1. 测试程序A-可靠性
    • 该程序在服务器上加载并运行openim-sdk-core来模拟IMSDK实例。每个实例都使用SQLite进行消息存储。这种方法可以全面模拟客户端的行为,包括消息的发送、接收、存储和回调,有效验证消息收发的可靠性和时延。但是,如果在单个服务器上运行过多的SDK实例,可能会导致客户端IMSDK性能下降。
  2. 测试程序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秒

测试二服务器资源消耗

登录用户数:

br-login

消息压力:(下图指标表示1分钟服务器接收到的消息量)

br-msg

CPU使用情况:

br-cpu
进程CPU占用
openim-msggateway210%
mongo100%
kafka84%
redis67%
openim-rpc-msg56%
openim-msgtransfer27%*8
openim-push13%*8
其他OpenIM服务以及组件65%
总计902%

物理内存占用情况:

br-mem
进程内存占用
openim-msggateway2.1 GiB
mongo717 MiB
kafka1.1GiB
redis85MiB
openim-rpc-msg162MiB
openim-msgtransfer74MiB*8
openim-push126MiB*8
其他OpenIM服务以及组件457MiB
总计(所有OpenIM服务以及组件)6.986GiB

备注:以上表格内容是粗略统计,在总计中不包括docker转发数据到容器的资源消耗,仅供参考。

结果分析

OpenIM 支持同时在线 5 万人,能够处理多个 5 万人超级大群,面对每秒 1700 条消息的压力时,消息可达率达到 100%。平均消息时延低于 1 秒,最大时延不超过 3 秒,展现出卓越的性能和可靠性。

配置建议

按照10万注册用户,日常在线10%用户,支持5万大群计算,每秒600条消息,建议配置:

名称配置
内存16G
CPU8核
网络带宽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 -gl 2 -gs 4 -gsm 5 -sm 6 -gm 7 -reg -lgr 0.7 -imf -crg -ckgn -ckcon -sem -ckmsn -ckuni

该命令的参数含义如下:

  • -u 10:总共创建10个用户
  • -su 3:创建3个超级用户
  • -gl 2:创建2个大群聊
  • -gs 4:每个登录用户创建4个小群聊
  • -gsm 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 目录下实现,并采取循环检测的方式。在触发检测后,程序会进行一轮数据验证,只有当所有数据与预期结果一致时,才会继续执行下一步操作;否则将重复检测,直至数据符合预期。

检测操作的主要目的有两个:

  1. 阻塞主进程:确保异步的模拟操作已全部完成。
  2. 验证结果正确性:通过一系列正确性检测操作,验证模拟操作是否达到预期效果。

当前包含以下检测操作:

用户登录检测

用户登录检测发生在初始化或注册之后,以及每次正确性检测操作之前。通过计算与服务器成功建立连接的实际用户数量,比较预期登录用户数量。

预期用户登录数:

  1. 用户总数 * 登录率,向下取整。
  2. 所有用户总数。
好友数量检测

好友数量检测在导入好友操作完成后执行。由于离线用户无法同步数据,此检测仅针对在线用户的好友数量进行验证。

实际好友数量通过调用相应 SDK 获取,预期好友数量如下:

  • 超级用户:好友数量应为所有用户数。
  • 普通用户:好友数量应为所有超级用户数。
群聊数量检测

群聊数量检测属于正确性检测操作,通过调用 SDK 获取实际群聊数量。

预期群聊数量为:

  • 大群数量 + 常规群数量(常规群数量依据用户登录情况可能有所不同)。
会话数量检测

会话数量检测也属于正确性检测,通过 SDK 获取实际会话数量。

预期会话数量为:

  • 所有群聊会话数量 + 所有好友会话数量。
未读消息数检测

未读消息检测同样属于正确性检测,通过调用 SDK 获取实际未读消息数量,并与预期值进行对比。

预期未读消息数计算方式为:

  • 所有群通知消息 + 所有群消息 - (自己创建的群通知消息 + 自己发送的群消息) + 好友申请通过通知消息 + 好友消息。

需要注意的是,只有申请发起人会收到好友申请通过的未读通知消息,且根据导入好友操作逻辑,只有超级用户的通知消息数量会有所不同。

限制修改

  • 在模拟操作过程中,多个 SDK 实例同时运行,可能会对服务器造成较大压力,进而引发超时或其他问题。为确保系统在进行大规模数据量测试时能够平稳运行,需对以下几个关键指标进行调整:
    1. 修改请求超时时间
      • 位置:openim-sdk-core/pkg/network/http_client.go
      • 调整内容:将请求的默认超时时间适当延长,以减少大数据量情况下的请求超时问题。建议根据测试规模和实际网络情况进行合理配置。
    2. 设置通知消息未读状态
      • 位置:open-im-server/config/notification.yml
      • 调整内容:将 groupCreatedfriendApplicationApprovedunreadCount 参数设置为 true。此配置将确保在线用户接收到群创建和好友申请通过的通知消息时,依然将其标记为未读消息,方便后续的未读消息数量计算。
    3. 最大文件描述符数量
      • 位置:open-im-server/start-config.yml
      • 调整内容:根据测试时需要登录的用户数量,将maxFileDescriptors的值适当变大。