得物客户端直播间APM压测实践

一、背景

随着直播行业的飞速发展,越来越多的企业涉足这一领域,直播间的稳定性和用户体验成为了直播平台竞争的重要因素。但是,由于直播间涉及到多个复杂的技术环节,如视频传输、网络通讯、数据处理等,因此直播间的性能压测显得尤为重要。在客户端直播间的压测实践中,APM压测技术是一种常用的性能测试方法,通过对应用程序的性能进行实时监控和诊断,可以快速定位和解决性能瓶颈,提升直播间的稳定性和用户体验。

APM压测的重要性

  1. 检测系统的稳定性:APM压测可以帮助测试人员评估直播间在高并发情况下的性能和稳定性,以确保系统能够正常运行,并且不会崩溃或出现故障。
  2. 提升用户体验:高APM值通常表示直播间可以流畅地处理更多的操作,从而提高用户的体验。如果APM值较低,则可能会导致用户在直播间中遇到卡顿和延迟,影响用户的使用体验。
  3. 发现系统瓶颈:APM压测可以帮助测试人员和开发人员发现系统的瓶颈和问题,从而可以针对性地进行优化和改进。例如,如果在APM压测中发现了数据库读写性能的问题,可以通过升级数据库或者采用其他优化措施来提高系统的性能
  4. 优化系统性能:通过APM压测,开发人员可以识别系统的性能问题并针对性地进行优化。例如,可以采用负载均衡技术来分散流量,采用缓存技术来减少数据库负载,或者采用异步处理来提高系统的并发能力。

由此可知,APM压测对于保证直播间的稳定性、提升用户体验、发现系统瓶颈和优化系统性能都非常重要。

二、直播间常见的压测方式

  1. 负载测试:通过模拟大量用户访问直播间,测试直播间在高并发情况下的性能和稳定性。可以使用工具如JMeter或LoadRunner等来模拟用户请求,以便评估直播间在不同负载下的表现。
  2. 带宽测试:直播间需要保证足够的带宽来支持高清视频的实时传输,因此需要进行带宽测试以确保直播间具备足够的带宽。可以使用网速测试工具来评估带宽的实际带宽和稳定性。
  3. 性能测试:通过模拟不同场景下的用户访问,测试直播间在不同场景下的性能表现,例如同时观看直播、同时发送弹幕等情况。可以使用性能测试工具如WebLOAD等来模拟并发请求,以便评估直播间在不同场景下的性能表现。
  4. 安全测试:直播间需要保证用户信息和隐私的安全,因此需要进行安全测试以确保直播间没有安全漏洞。可以使用工具如Burp Suite等进行渗透测试,以评估直播间的安全性。
  5. 可靠性测试:通过模拟不同的故障和异常情况,测试直播间在异常情况下的表现和恢复能力。可以使用工具如Chaos Monkey等来模拟异常情况,以评估直播间的可靠性和恢复能力。

综上所述,通过负载测试、带宽测试、性能测试、安全测试和可靠性测试等压测方式,可以全面评估直播间的性能、稳定性、安全性和可靠性,从而确保直播间能够满足用户的需求和期望。

得物直播间主要采用的压测方式是负载测试、性能测试。

三、实现方式

首先我们压测的目标是【基于直播间的IM性能压测】,压测的主要目的是监测当客户端某个直播间长时间接收到大量IM消息的时候,是否会出现卡顿、crash或者OOM等性能问题。在每次发版前跑一轮压测,提前在线下暴露直播间的性能问题,避免性能问题被带到线上。

在具体的压测手段上我们希望能够满足以下几个条件:

  1. 尽量覆盖更多的IM消息类型
  2. 压测自动化程度高,省去较多的手动操作麻烦
  3. 维护成本低
  4. 压测尽量不依赖服务端,能够直接实现本地端上的消息压测

基于上面几点要求,在探索压测的方式,我们直播业务组大概经历了下面三个阶段:

四、压测阶段

4.1 第一阶段

直播间压测的第一阶段采用的方式比较简单,通过脚本模拟用户发送评论、点赞等IM到需要压测的房间。需要自己编写相应的python代码,发送相对应的IM消息到某个直播间,以下是部分Python脚本的部分内容:

class APIUtils:
""" 仅适用于测试环境 """


@staticmethod
def token(user_id: int):
resp = requests.get('https://xxxx.com', params={'user_id': user_id})
return resp.json().get('token')


@staticmethod
def change_rc_im(user_id: int):
try:
im_info = requests.post(
'http://xxxx.com',
headers={'userId': '1'},
data={'kolUserId': user_id}
)
im_id = im_info.json().get('data', {}).get('list', [{}])[0].get('id', 0)
requests.post(
'http://xxxx.com',
headers={'userId': '1'},
data={'kolUserId': user_id, 'id': im_id}
)
except:
pass


time.sleep(3)


data = {
"startTime": int(time.time()) + 1,
"endTime": int(time.time()) + 600 * 6,
"kolUserId": user_id,
"imSwitch": 1,
"id": 0
}
requests.post('xxxx.com',
headers={'userId': '1'}, data=data)


@staticmethod
def get_topic(user_id: int, room_id: int):
""" 获取房间号 """
headers = {
'POIZON-USERID': str(user_id),
'POIZON-ISGUEST': 'false',
'platform': 'iPhone',
'v': '4.78.0'
}
try:
resp = requests.get('xxxx.com',
headers=headers, params={'roomId': room_id})
return resp.json().get('data').get('room').get('imInfo').get('chatRoomId')
except Exception as e:
raise e

主要流程如下图:

图片


这种方式实现的压测比较简单,也能覆盖一些比较重要的IM消息,但是也有几个比较明显的缺点:

  1. 压测某个直播间,需要知道房间的ID或者IM的topic,获取这个信息就要去抓包或者查开播记录,比较麻烦。
  2. 客户端代码每次新增一个IM消息就需要去手动维护python脚本去新加对应的IM号,对于后期的维护有一定的要求,需要维护的同学会写python,并且在后续的需求维护者要主动了解每个版本迭代新加的IM消息,主动去更新脚本的IM消息类型,这一块无疑增加了比较大的维护成本。

4.2 第二阶段

在本阶段着重于解决上个阶段遗留下来的问题,针对获取房间ID的问题,这个只需要后端提供相应的开播列表接口即可,问题是如何使得压测这个流程操作起来更方便?这里我们就想到了可视化,鼠标点一下就能压测岂不是非常简单!于是我们基于前端技术,使用Vue3搭建了一个简易的IM消息操作页面,可以在这个可视化界面选择自己想要发送的房间和IM号,并且在做这个工具的同时丰富了IM消息发送的一些逻辑,可以针对消息优先级、房间消息还是全站消息做了个性化处理,顺便为IM的mock调试做了一些工作。

图片

然后在这个基础上,调接口告诉后端需要压测的房间,再让后端去调用第一阶段的脚本去压测相应的房间。

图片

该方式省去了之前需要自己去手动获取房间ID的麻烦,并且在做这个可视化Mock平台的时候加入了mock IM的功能和压测关系不大,本质上和脚本实现的压测方式并无区别。

4.3 第三阶段

这个阶段解决了上面遗留的随着功能迭代,消息类型覆盖的问题,同时为了进一步解放人工介入,基于Teslab自动化平台,用UI脚本的方式定时去跑我们的压测功能,实现了真正的自动化压测功能。下面分别解释每个步骤的具体操作

4.3.1 消息类型覆盖

在客户端每个IM消息类型,都有一个对应的IM消息Java类,每增加一个IM消息类型,都会有一个实体类去对应,这些类都继承于基类BaseLiveChatMessage,因此我们在BaseLiveChatMessage里面加了一个接口抽象方法,用于产生此消息类型的mock数据。

图片

那么我们在新加IM数据的时候,继承BaseLiveChatMessage,就需要强制覆盖这个方法,去生成自己的mock消息,非常好的解决了维护性的问题,因为不覆盖这个mock方法是无法通过编译的。

下面是警告消息和抽奖消息的Mock代码:

图片

图片

有了上面的基础,在测试工程里面加一个IMTest测试类,主要逻辑是扫描所有继承BaseLiveChatMessage类的子类,然后反射构造函数,调用mock接口即可获取到相应IM类的mock消息实体,伪代码如下:

//获取BaseLiveChatMessage子类
if (allSubClass == null) {
allSubClass = ClassUtils.getAllSubClass(BaseApplication.getInstance(), BaseLiveChatMessage::class.java)
val iterator = allSubClass?.iterator()
while (iterator?.hasNext() == true) {
val next = iterator.next()
try {
next.getDeclaredMethod("mock", Int::class.java)
} catch (e: NoSuchMethodException) {
}
}
}
// ....
allSubClass?.forEach {
val o = constructorMap[it]?.newInstance() as BaseLiveChatMessage
var message: BaseLiveChatMessage? = null
message = o.mock(0)
justPostIM(message) //发送IM
}

之后的压测就是控制发送频率、压测时间即可实现本地的压测,无需依赖服务端实现。

图片

到此为止,基本已经解决了文章最开始的几个问题,IM消息的覆盖率和可维护性也得到了保证。

4.3.2 自动化

在现有的基础上,为了使得压测更加自动化,我们接入了Teslab自动化测试平台,可以定时启动自动化UI脚本,提升压测效率,自动化脚本是基于UiAutomator,语法非常简易,维护成本很低。

图片

  1. 客户端内部备齐所有的IM压测类型。在进行IM压测时,客户端应当支持各种类型的IM消息,例如文本消息、语音消息、图片消息、礼物消息等等。同时,客户端还应当支持各种不同的IM操作,如点赞、评论、送礼等,以全面测试IM功能的稳定性和性能
  2. 直播debug工具接通了kylin,kylin组件已经打通了amp平台。为了更好地收集和记录压测指标,我们需要将直播debug工具与kylin组件和amp平台进行打通,确保能够快速地收集和分析压测数据。在这个过程中,kylin组件将负责接收客户端发送的压测数据,并将这些数据传递给amp平台进行进一步处理和分析。
  3. apm平台收到了直播IM压测记录飞书通知到固定的群。为了及时发现和解决潜在的性能问题,我们需要将压测记录及时通知到相应的人员,例如开发人员、测试人员等。在这个过程中,我们可以利用飞书等即时通讯工具,将压测记录发送到固定的群,以便相关人员及时查看并进行分析。

综上,第三阶段的压测策略通过客户端发起的方式,实现了IM压测使用方式方便、支持多设备压测和压测指标有记录的目标。同时,我们还需要在实际实施过程中不断优化和改进,以进一步提高压测效率和结果的可靠性。

压测流程图:

图片

五、压测效果

得物客户端直播间APM压测实践

六、收益

压测只是一个手段,最重要的是发现问题,解决问题,通过三个阶段的压测也发现了不少问题。

通过三个阶段的压测,团队成功地发现并解决了一些iOS方面的问题。其中,最重要的是发现了压测时长超过20分钟时,CPU异常高并伴随着界面卡死的情况。经过排查,发现问题源于消息逐条往业务层分发,导致CPU消耗太大和UI界面刷新太频繁(每秒钟刷新大几十次)。针对这个问题,团队采取了两个解决方案:一是通过定时器向业务层分发消息组,而不是逐条分发消息;二是在定时器内部做线程切换,保证在一段时间内只有一次的线程切换。

此外,团队还在压测过程中发现了内存持续上涨产生的OOM情况,原因是某些IM有动画执行时间,一段时间内只会执行一次,高并发情况下就会不断累积导致内存溢出。对于这个问题,团队采取了对动画执行的优化方案,避免了内存溢出的情况。

另外,通过kylin组件,团队还发现了若干内存泄漏问题,并及时解决了这些问题,保证了直播应用的稳定性和可靠性。总之,通过三个阶段的压测,团队成功地发现和解决了多个问题,不仅提升了应用的性能和稳定性,也为团队的技术积累和发展提供了有益的经验和启示。

七、结束语

性能压测的确是保证直播间稳定、高效运行的重要手段,但我们不能把它看作是代码开发的终点。好的代码应该是能够被整个团队共同维护的,代码的可读性、可维护性和可扩展性同样重要。只有在开发和维护过程中,不断注重代码质量和团队协作,才能让直播间持续地为用户提供优质的服务。

在进行直播间性能压测的同时,也需要关注代码的可读性和可维护性。我们应该建立严格的代码审核机制,对代码质量进行监控和控制,以确保代码的可靠性和可扩展性。同时,注重团队协作,建立团队内部沟通和合作的机制,让团队成员能够共同维护好直播间,提供更好的用户体验。


以上就是得物客户端直播间APM压测实践的详细内容,更多请关注其它相关文章!