前言
玩物得志的直播电商起源于19年年中,是一个非常年轻的业务了。我们在实际开发运营过程当中面临反馈最多的就是直播各个方面的卡顿及延时问题,从而严重影响客户体验。本文我们简单叙述下我们在碰到各类卡顿延时场景下,如何优化我们的客户体验的。
目录
- Bad Case
- 视频卡顿优化
- 拍卖延时优化
Bad Case
视频卡帧了
卡卡卡……看过直播的都知道卡成幻灯片有多难受🌚
明明看着是我的出价最高怎么中拍的不是我?
还有十秒,偷塔出了一手价,嘿嘿应该捡到漏了😁。
居然中拍的不是我😥,一定是系统有BUG😡
为什么他们的直播比我快?
同样一件拍卖商品,居然安卓/IOS的倒计时不一致?甚至我拿两台一样的手机放着放着都不一致了?😳
优化三板斧来了
视频相关
衡量视频直播的性能有三个指标:第一个是延迟,第二个是卡顿,第三个就是首屏耗时。那么能不能低延时+少卡顿+高清晰度呢?作为一个成年人,正常的想法肯定是:
那么能不能呢?看看后面的一些基本概念。
聊聊直播那些参数
gop:
gop是啥?GOP(Group of picture) 关键帧的周期,也就是两个IDR帧之间的距离。一般我们都想要视频越流畅且延时越低就好,然而流畅度和延时鱼和熊掌不可兼得,gop越大时延时越高但是流畅度会有提升,gop越小延时越小但是会变得卡顿。
拍卖的场景下延时不可以太长,4、5s会明显感觉出延时,所以宁可降低一些帧率,在不太卡顿的情况下降到更低的延时。找到一个最适宜的gop,根据我们的经验在2左右比较合适的。
fps:
理论上fps越高,动画越连续。比如我们最喜欢的农药的fps就会到60。但是注意一定不要混淆场景:游戏追求高帧率是渲染帧率,其目的是为了尽可能让 3D 模型渲染出来的运动效果更加接近真实运动轨迹,所以帧率越高越好,但采集帧率不需要这么高,例如手机摄像头,它采集的目标是真实世界的物体,真实世界的物体本来就是连续运动的,而不是用一阵阵画面刷新来模拟的,所以 20FPS 的采集就足以。并且过高的fps会导致系统开销的上升,让一些本就已经性能落伍的手机负载更高,反而影响了推流的效果。所以目前我们采取了比20略高的fps配置。
清晰度:
看起来是一个和卡顿延时不太相关的参数,但是同样的,清晰度上升对应的码率也需要配合上升。所以卖货的直播也不能追求过高的清晰度,否则反而会出现马赛克造成观感的下降。下面是某服务商的一些推荐指标,大家可以参照着进行调整
档位 | 分辨率(Resolution) | FPS(FPS) | 码率(Bitrate) |
---|---|---|---|
标清 | 360*640 | 20 | 800kbps |
高清 | 540*960 | 20 | 1000kbps |
超清 | 720*1280 | 20 | 1800kbps |
所以看的多了就发现在硬件条件一定情况下选择提升哪一个性能指标往往有时候必须做出选择。
那么要清晰度还是流畅度?
根据我们的业务场景来说可能清晰度来说更重要,毕竟连货都看不清谁敢买?所以还是在高清及以上的基础上进行参数优化。
当然这样也只是一般的一刀切而已,我们的目标应该是根据网络和硬件情况动态设置推流参数,这样才能大家好才是真的好啊。
HttpDNS功能的使用
这是一个能正向提速的功能,首先我们了解下不用httpdns可能会出现的问题
- 域名劫持造成的无法正常访问业务域名,访问超时或返回错误;
- 域名劫持造成的访问到旧文件,未更新;
- 域名劫持造成的访问后返回涉政、涉黄等敏感、违法页面;
- 域名劫持造成的访问业务域名后返回广告、导航等第三方页面;
- 因为DNS多出口,转发解析等因素,造成的终端访问过慢。
不考虑域名劫持的问题,我们主要是用httpdns解决多出口的问题,具体是什么一个意思呢?
传统的 DNS 有很多问题,例如解析慢、更新不及时。因为缓存、转发、NAT 问题导致客户端误会自己所在的位置和运营商,从而影响流量的调度。
比如你家在西南小镇,用了长城宽带等类似小公司的宽带,那边运营商极有可能根据播放域名把你转到一个大服务商比如移动联通,并且解析到了最近的移动或者联调的服务器,这样就会造成跨服务器的时延;另外也有情况是运营商的服务器有缓存,导致没有访问最快的服务器。
但是一些服务商搭建基于 HTTP 协议的 DNS 服务器集群,分布在多个地点和多个运营商。那么我们使用httpdns以后,当客户端需要 DNS 解析的时候,直接通过 HTTP 协议进行请求这个服务器集群,得到就近的地址。把域名替换为IP地址,就可以缩短转发造成的时延。
出价相关优化
服务端
- 缩小同步代码块
拍卖当中最核心的消息就是出价消息,会影响每个用户对于商品是否要出价的判断,也会影响到主播对于当前直播间的火热程度和后续发拍的决策。
我们在运行一段时间业务量逐渐增长后后遇到的情景是客户发现自己出价总是落后于别人,被别人抢了自己的出价。
对此我们排查了后端出价的代码,原因是之前的synchronized同步块锁的代码段太长了,核心需要加锁的只有商品加价和增加出价记录这两块代码,但是同步的代码块却包含了和比对当前价格、发消息等不需要同步的代码块,导致锁的时间加长,在锁的代码内比对当前价格(redis)这一步会大量失败。所以对此我们的处理很简单,缩小同步代码块只锁住最核心的加价逻辑就可以了。
原始代码:
public void handleAuctionV1(Long userId, Integer price) {
//参数检查等一系列检查函数
...;
//同步代码块 tryLockMILLISECONDS(String lockname, Long waitTime, Long lockTime)
if (lockUtil.tryLockMILLISECONDS(lockKey, 200L, 1000L)) {
//快速点击校验
if(doubleClickCheck()) {
return;
}
//设置Redis当前出价
setRedisNowPrice();
//添加拍卖记录
addAuctionRecord();
//添加关注操作
addFav();
//分佣操作
addCps();
//消息通知
auctionSuccessNotify();
}
}
其实在上述代码中快速点击校验以及添加拍卖记录的操作都是不需要在同步块中的,尤其是一些比较耗时的方法,保证变量线程安全其他的方法都可在同步块外操作并且缩短锁占有的时间。
将非核心代码移出同步块后,减少了大量出价失败的case。
-
增加心跳数据修正
当然完全依赖消息做出价的更新还是会有问题。虽然我们用的是某在IM方面很有经验的云厂商的服务,但是不可避免的有时候会出现云厂商的服务过载、或者网络的抖动、以及偶尔客户端的一些BUG导致出出价消息最终丢失了,或者是没有成功的展示出来。直接导致的后果就是用户自以为自己是最高的出价了,但是其实已经有更高的出价了;或者主播端看不到最新的出价影响拍卖的效率。
所以要在“推”消息的基础上做“拉”的补偿操作,通过心跳讲最新在拍的商品的价格返回,可以在出现推消息异常的情况下,根据心跳中返回的最高价格及出价人与当前客户端显示的信息对比,进行修正。
客户端
- 时钟同步
早期的版本拍卖的倒计时是由客户端读取手机时间自己实现计时器去实现的。显而易见的问题就是每个用户读取自己的手机,在没有类似后端ntp时间服务器的机制下,会出现剩余时间的不一致。对于我们想拍卖捡漏的用户来说,特别喜欢在最后一两秒“偷塔”,因为时间和后端不一致而出价失败就会非常懊恼。
这种逻辑下,截拍时间差=收到开播消息接口的传输时间+手机时间和机器时间的误差,最大情况下我们发现差值达到1秒。
所以在提供了客户端心跳接口的基础上,接口即用来收集一些实时看播信息,同时也返回给客户端统一的机器时间,以此时间为准更新前端显示的时间达到各个端的一致。
总结
上述一顿三板斧下来总算让我们的服务看起来不那么卡了,但是整体来看在监控、日志上报和网络CDN层面我们还有很多优化可以很多的事情可以做。
当然现在各个大厂也在推出RTC超低时延直播等更加低延时的直播技术(当然一分价钱一分货需要更多的米),支持千万级并发场景下的毫秒级延迟直播能力。在互动直播场景中,用户送礼物、刷弹幕的动作更加实时反馈到主播端;在电商直播中,宝贝信息、代金券发放、限量商品抢购都可以更加实时地反馈给主播和买家双方。
这么一看,直播的优化还是星辰大海啊。