如何打造一个高性能高可用直播系统架构

目前斗鱼直播系统每天有 20 亿+的请求量,并且在今年 3 月份,PDD 入驻斗鱼的时候还曾达到 40 万+的瞬时 QPS,同时我们的服务增长到了上千实例的规模,在全国不同地区和不同机房进行了部署,提供给上百个内部业务方使用。总结下来,斗鱼直播系统有三个特点——流量大、服务多、架构复杂。针对这些问题,要确保整个系统的高性能和高可用,对于我们的技术开发团队来说也是一个比较大的挑战。

高性能业务架构演进中的挑战与解决方案
三大挑战
早在 5 年前,我们和其他公司一样处于单体应用时期,主要使用“Nginx+PHP+Memcache+MySQL”,当时遇到最大的一个问题,是如果一个用户进入到直播间访问Memcache的时候,如果刚好 Memcache 里面缓存数据失效了,那么请求就会穿透到MySQL,会对服务造成很大的压力。

所以从 2016 年开始,我们将 Memcache 换成了 Redis,将全量的直播间的数据缓存到Redis的内存缓存,解除服务对 MySQL 的直接依赖,同时还做了一些业务隔离:将业务进行了垂直拆分。保证了那个时期的服务稳定。

但是随着斗鱼体量的日益增长,请求量越来越大,我们没想到 Redis 也会成为一个新的瓶颈。所以从去年开始斗鱼着手对系统做了一些改造,将PHP换成了 Golang,然后在我们Golang里面做了一些内存缓存池和 Redis 连接池优化,经过这一系列的改造,目前斗鱼的直播系统无论是在性能上还是可用性上都有很显著的一个提升。

首先给大家介绍一下我们斗鱼的 Memcache 时期。在理想的情况下,当一位用户进入到我们直播间页面,请求会先到 Memcache,如果 Memcache 里面有缓存数据,那么就会直接将数据返给用户。但是通常情况下并不总是理想状况,我们现在举一个具体案例进行介绍。

假设有一位大主播开播了,他就会发开播信息给他的粉丝们,这些粉丝会在短时间内进入到该主播的直播间,这样就带来第一个问题——瞬时流量。 紧接着大量请求会并发到 Memcache 里,由于 Memcache 采用是一致性 hash 的算法,所以同一个直播间的缓存 key 会落在同一个 Memcache 节点上,这就会造成某个 Memcache 节点在短时间内负载过高,导致第二个问题——热点房间问题。同时又由于直播间刚开播,之前没有人访问过这个页面,Memcache 里是没有这个直播间信息,那么又会有大量的请求穿透到 MySQL,对 MySQL 造成性能影响,所以就造成了第三个问题——缓存穿透问题。

解决之道
针对上述三大挑战,我们做了一些优化。

首先要解决的是缓存穿透问题。斗鱼直播系统里写操作都是主播进行触发,而大量读服务接口则是用户进行触发,所以我们在业务层面上做了一个读写分离,规定:只有主播接口才可以对 MySQL 和 Redis 做写入操作;并且将直播间基础数据全量且不过期的缓存到 Redis 里。这样就去除了用户请求对 MySQL 的依赖。也就是说用户请求无论如何都不会穿透到 MySQL。

针对于第二个问题——瞬时流量,我们主要在 Nginx 的配置上面做了一些优化。首先,将直播系统单独分配一个 nginx 的 proxy cache,做一些隔离,避免非核心业务的 cache 占用了核心业务 cache 的空间;其次,对突发流量,在服务返回 502,504 状态码(服务容量不够了)时,返回上一次缓存的数据,避免直播间白屏,保证其可用性;最后,做了 proxy lock on 的配置,确保瞬时同样的请求,只会有一个请求到后端,降低后端和数据源的负载。

最后针对热点房间问题,我们将一致性 hash 的 Memcache 换成了主从结构的 Redis。多从库的 Redis,每个里面都有大主播的数据信息,可以有效的分担大主播流量,从而使得后端数据源的负载均衡,不会出现单节点的性能问题。

新的问题
优化之后,大家都觉得还挺不错的,但是,我们发现把 Memcache 换成 Redis 之后又带来了新问题:

带宽过高
首先我们遇到的是内网带宽过高的问题。以前 Memcache 把响应数据给客户端的时候,它会将这些数据做一个压缩。我们将 Memcache 迁移到 Redis 后,由于 Redis 没有这个能力,内网带宽翻了 4 到 5 倍,引起了网卡负载较高,出现一些丢包和性能问题。

使用不当
随着我们斗鱼的体量越来越大,我们的请求量越来越大,Redis 逐渐成为了我们的性能瓶颈,大量的业务共用同一组 Redis,如果某个业务方出现 Redis 用法不当,阻塞了单线程的 Redis,会影响其他业务。同时由于 Redis 没有限流功能,很容易 Redis 被打垮,导致所有业务全部瘫痪。

时延过高
因为业务需求的升级,我们想带给用户更好的体验,将许多曾经的缓存接口变成了实时接口。如何降低我们请求的时延,考验着我们开发的能力。

微服务化
为了解决这三个问题,我们步入了一个新的时期——微服务。首先是在架构上做了一些改变:PHP 换成了 Golang、 将 Redis 主从结构变成 Redis 分片的主从结构。其次我们针对业务做了一些区分:大主播和小主播采用不同的存储策略,小主播的信息我们是通过 hash 算法放到一个节点里,这样是为了降低 Redis 内存开销;大主播的信息在所有分片均会存放,保证大主播在突发流量时有很多节点进行分担。

架构改变说完后,我们再聊下,我们斗鱼对 Redis 的一些理解和经验。

最佳实践
刚才也说了,我们之前业务上共用 Redis 造成了一些问题,为了解决这些问题,首先要做的是对 Redis 做好隔离;其次针对带宽过高的问题,一方面将 Redis 里的 key 做了精简,降低流量和存储大小;另一方面从业务上规范了房间字段的按需索取。最后从代码层次规范了 Redis 的使用方法:我们根据线上全链路压测的压测结果,调整了 Redis 连接池空闲连接数,这样可以解决线上的突发流量问题;并且规范了获取 Redis 数据的方式,严格限定了获取 Redis 的每次包尽量不要超过 1KB,并且尽量使用 pipeline,减少网络 io。

再快一点
刚才说过我们后来有许多接口变成了实时性接口,怎么让服务更快一点呢。我们就把一些热门主播通过任务提前算好,服务通过异步获取热门主播名单缓存这些直播间数据,用户再看这些热门主播的时候速度可以更快。同时对于获取批量直播间数据,通过计算 redis 从库个数,并发去拉取直播间数据,来降低业务方的时延。

实验数据
为了让大家有个直观对优化有个直观的理解,下面第一个图,是我们斗鱼在对批量直播间数据做的对比试验。大家可以看到精简 key 和按需索取,其性能要好很多。下面第二个图,是官方做的 Redis 包大小的对比图。当获取 Redis 数据包大小在 1KB 以下,性能比较平稳,但超过 1KB,性能急剧下降。所以我们业务场景在使用 Redis 的时候,一定要注意些这些细节。才能使得我们系统性能更好。

为了让大家知道高可用的必要性,我们先来看下斗鱼的技术架构图。这是一个多机房架构的部署图。我们先来看一个服务区域,首先必不可少的是我们的应用(客户端和服务端),服务端和客户端靠 etcd 来发现彼此。不同的服务区域则是通过我们自己写的 sider 来相互通信和感知。然后 prometheus 和日志都会把数据采集到微服务管理平台,一个服务从开始部署上线、到运行监控、故障定位及修复操作都可以在微服务管理平台进行。

高可用
从刚才介绍大家应该可以知道目前斗鱼的架构和部署的复杂性。如果没有好的高可用的解决方案,那么势必会造成系统的不可靠,导致严重的线上问题。

因此我们提出了高可用的一些解决方案。我们将高可用分为两个层次。第一层是自动挡,比如像负载均衡、故障转移、超时传递、弹性扩容、限流熔断等,这些都是可以通过代码层面或者运维自动化工具,不需要人工干预,做到系统的自愈。第二层是手动档,这些主要就是监控报警、全链路压测、混沌工程、sop 等,我们能通过一些手段提前预知可能存在的问题,或者线上出现问题无法自愈,我们怎么快速发现,快速解决。

因为高可用的内容比较多,限于篇幅有限。我们今天就主要讲两个内容负载均衡和监控报警。

负载均衡
通常情况微服务都会采用 roundrobin 的负载均衡算法,它实现起来比较简单,但是它的问题也不小。在这里给大家看一个负载均衡 roundrobin 的案例。为了方便大家理解这个算法,我们对模型做了一些简化。大家可以看这个图,有个定时任务,每秒都会先调用一个消耗低 cpu 的单个房间信息接口,然后再调用一个消耗高 cpu 的批量房间信息接口。如果刚好服务端就两台实例,根据 roundrobin 算法,这个请求的调用方式就是:奇数次单个房间信息接口都调用到 a 节点,偶数次批量房间信息接口都调用到 b 节点。显然易见的就是 a 节点负载会远远低于 b 节点。

为了解决以上问题,我们可不可以根据系统的负载动态的调度呢。于是我们会在客户端用 gRPC 调用服务端的时候,让服务端不仅返回业务数据,同时在 header 头里会将服务端 cpu 的 load 返给客户端。客户端根据服务端的 cpu load 和请求的耗时,异步算出下次客户端需要请求的服务端实例。这个调度算法看似挺美好,但是他会带来“马太效应”。假设服务端某个节点此时负载比较小,客户端可能会因为算法一窝蜂去请求他,使它负载变高。这样服务端节点 cpu 会变得忽低忽高,这也不是我们想要的结果。

所以这个时候,我们通过调研采用了业界流行的 p2c 算法。我们在客户端在做调度的时候做了一个随机数。 如图左边所示。可以看到加了随机数后,可以避免这种“马太效应”的调度问题,负载较低的多调度点,负载较高的少调度点。当然如果服务端有问题,是直接剔除的。

通过优化负载均衡的算法,我们平滑了服务端的 cpu 负载,同时可以快速剔除有问题的服务节点。当然还有个最关键的成效,就是有了这个算法,我们才可以把多机房的 etcd 注册数据进行同步,做多数据中心,然后客户端根据算法自动选择不同机房的节点,实现同城双活。

这个图就是我们通过 wrr 算法切到 p2c 算法后的对照图,可以看到在这个图的前半部分,每个实例之间的负载差距都比较大,并且同一个实例的负载波动也比较大。换成 p2c 算法后,趋势图看起来就比较稳定了。

shxw2024.robot-china.com、dashuisk.robot-china.com、
tianzhi.robot-china.com、bolasiman.robot-china.com
datangzijin.robot-china.com、xiaowu003.robot-china.com
shenlan0791.robot-china.com、ansdkn.robot-china.com
aidier.robot-china.com、sxjsdjqr.robot-china.com
88289171.robot-china.com、dayou269.robot-china.com
qixing.robot-china.com、bsd0574.robot-china.com
kmxpjqr.robot-china.com、shnduw.robot-china.com
quanfeng.robot-china.com、cshxnt.robot-china.com
kudou333.robot-china.com、jiaowyzz.robot-china.com
zhaodan.robot-china.com、chinanews.robot-china.com
szsne669.robot-china.com、suofang.robot-china.com
st0754.robot-china.com、gdsgsxj.robot-china.com
jdzs600.robot-china.com、chuanyu.robot-china.com
chenxk.robot-china.com、maoming0668.robot-china.com
gdnn.robot-china.com、woyezhineng.robot-china.com
mzjx.robot-china.com、yinyun.robot-china.com
yihao129.robot-china.com、shuming.robot-china.com
w2024.robot-china.com、czswam.robot-china.com
xinyoukai.robot-china.com、gdjyzh.robot-china.com
tulijuan.robot-china.com、xunteng.robot-china.com
zuxna.robot-china.com、wuhai785.robot-china.com
rusyanz.robot-china.com、czqweaz.robot-china.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值