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

发表于:2023-4-12 09:43

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:大胜 Herman    来源:得物科技

  一、背景
  随着直播行业的飞速发展,越来越多的企业涉足这一领域,直播间的稳定性和用户体验成为了直播平台竞争的重要因素。但是,由于直播间涉及到多个复杂的技术环节,如视频传输、网络通讯、数据处理等,因此直播间的性能压测显得尤为重要。在客户端直播间的压测实践中,APM压测技术是一种常用的性能测试方法,通过对应用程序的性能进行实时监控和诊断,可以快速定位和解决性能瓶颈,提升直播间的稳定性和用户体验。
  APM压测的重要性
  检测系统的稳定性:APM压测可以帮助测试人员评估直播间在高并发情况下的性能和稳定性,以确保系统能够正常运行,并且不会崩溃或出现故障。
  提升用户体验:高APM值通常表示直播间可以流畅地处理更多的操作,从而提高用户的体验。如果APM值较低,则可能会导致用户在直播间中遇到卡顿和延迟,影响用户的使用体验。
  发现系统瓶颈:APM压测可以帮助测试人员和开发人员发现系统的瓶颈和问题,从而可以针对性地进行优化和改进。例如,如果在APM压测中发现了数据库读写性能的问题,可以通过升级数据库或者采用其他优化措施来提高系统的性能。
  优化系统性能:通过APM压测,开发人员可以识别系统的性能问题并针对性地进行优化。例如,可以采用负载均衡技术来分散流量,采用缓存技术来减少数据库负载,或者采用异步处理来提高系统的并发能力。
  由此可知,APM压测对于保证直播间的稳定性、提升用户体验、发现系统瓶颈和优化系统性能都非常重要。
  二、直播间常见的压测方式
  负载测试:通过模拟大量用户访问直播间,测试直播间在高并发情况下的性能和稳定性。可以使用工具如JMeter或LoadRunner等来模拟用户请求,以便评估直播间在不同负载下的表现。
  带宽测试:直播间需要保证足够的带宽来支持高清视频的实时传输,因此需要进行带宽测试以确保直播间具备足够的带宽。可以使用网速测试工具来评估带宽的实际带宽和稳定性。
  性能测试:通过模拟不同场景下的用户访问,测试直播间在不同场景下的性能表现,例如同时观看直播、同时发送弹幕等情况。可以使用性能测试工具如WebLOAD等来模拟并发请求,以便评估直播间在不同场景下的性能表现。
  安全测试:直播间需要保证用户信息和隐私的安全,因此需要进行安全测试以确保直播间没有安全漏洞。可以使用工具如Burp Suite等进行渗透测试,以评估直播间的安全性。
  可靠性测试:通过模拟不同的故障和异常情况,测试直播间在异常情况下的表现和恢复能力。可以使用工具如Chaos Monkey等来模拟异常情况,以评估直播间的可靠性和恢复能力。
  综上所述,通过负载测试、带宽测试、性能测试、安全测试和可靠性测试等压测方式,可以全面评估直播间的性能、稳定性、安全性和可靠性,从而确保直播间能够满足用户的需求和期望。
  得物直播间主要采用的压测方式是负载测试、性能测试。
  三、实现方式
  首先我们压测的目标是【基于直播间的IM性能压测】,压测的主要目的是监测当客户端某个直播间长时间接收到大量IM消息的时候,是否会出现卡顿、crash或者OOM等性能问题。在每次发版前跑一轮压测,提前在线下暴露直播间的性能问题,避免性能问题被带到线上。
  在具体的压测手段上我们希望能够满足以下几个条件:
  ·尽量覆盖更多的IM消息类型
  · 压测自动化程度高,省去较多的手动操作麻烦
  · 维护成本低
  · 压测尽量不依赖服务端,能够直接实现本地端上的消息压测
  基于上面几点要求,在探索压测的方式,我们直播业务组大概经历了下面三个阶段:
  四、压测阶段
  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消息,但是也有几个比较明显的缺点:
  压测某个直播间,需要知道房间的ID或者IM的topic,获取这个信息就要去抓包或者查开播记录,比较麻烦。
  客户端代码每次新增一个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,语法非常简易,维护成本很低。
  客户端内部备齐所有的IM压测类型。在进行IM压测时,客户端应当支持各种类型的IM消息,例如文本消息、语音消息、图片消息、礼物消息等等。同时,客户端还应当支持各种不同的IM操作,如点赞、评论、送礼等,以全面测试IM功能的稳定性和性能。
  直播debug工具接通了kylin,kylin组件已经打通了amp平台。为了更好地收集和记录压测指标,我们需要将直播debug工具与kylin组件和amp平台进行打通,确保能够快速地收集和分析压测数据。在这个过程中,kylin组件将负责接收客户端发送的压测数据,并将这些数据传递给amp平台进行进一步处理和分析。
  apm平台收到了直播IM压测记录飞书通知到固定的群。为了及时发现和解决潜在的性能问题,我们需要将压测记录及时通知到相应的人员,例如开发人员、测试人员等。在这个过程中,我们可以利用飞书等即时通讯工具,将压测记录发送到固定的群,以便相关人员及时查看并进行分析。
  综上,第三阶段的压测策略通过客户端发起的方式,实现了IM压测使用方式方便、支持多设备压测和压测指标有记录的目标。同时,我们还需要在实际实施过程中不断优化和改进,以进一步提高压测效率和结果的可靠性。
  压测流程图:
  五、压测效果
  六、收益
  压测只是一个手段,最重要的是发现问题,解决问题,通过三个阶段的压测也发现了不少问题。
  通过三个阶段的压测,团队成功地发现并解决了一些iOS方面的问题。其中,最重要的是发现了压测时长超过20分钟时,CPU异常高并伴随着界面卡死的情况。经过排查,发现问题源于消息逐条往业务层分发,导致CPU消耗太大和UI界面刷新太频繁(每秒钟刷新大几十次)。针对这个问题,团队采取了两个解决方案:一是通过定时器向业务层分发消息组,而不是逐条分发消息;二是在定时器内部做线程切换,保证在一段时间内只有一次的线程切换。
  此外,团队还在压测过程中发现了内存持续上涨产生的OOM情况,原因是某些IM有动画执行时间,一段时间内只会执行一次,高并发情况下就会不断累积导致内存溢出。对于这个问题,团队采取了对动画执行的优化方案,避免了内存溢出的情况。
  另外,通过kylin组件,团队还发现了若干内存泄漏问题,并及时解决了这些问题,保证了直播应用的稳定性和可靠性。总之,通过三个阶段的压测,团队成功地发现和解决了多个问题,不仅提升了应用的性能和稳定性,也为团队的技术积累和发展提供了有益的经验和启示。
  七、结束语
  性能压测的确是保证直播间稳定、高效运行的重要手段,但我们不能把它看作是代码开发的终点。好的代码应该是能够被整个团队共同维护的,代码的可读性、可维护性和可扩展性同样重要。只有在开发和维护过程中,不断注重代码质量和团队协作,才能让直播间持续地为用户提供优质的服务。
  在进行直播间性能压测的同时,也需要关注代码的可读性和可维护性。我们应该建立严格的代码审核机制,对代码质量进行监控和控制,以确保代码的可靠性和可扩展性。同时,注重团队协作,建立团队内部沟通和合作的机制,让团队成员能够共同维护好直播间,提供更好的用户体验。
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号