直播道具高可用建设
本期作者
刘江涛
哔哩哔哩资深开发工程师
根据2022年第四季度的财报数据显示,B站在跨年晚会期间的直播人气峰值达到了3.3亿。直播业务对于B站来说是一个重要的增长点,而道具投喂(赠送礼物,后面统称为道具投喂,礼物统称为道具)在直播业务中扮演着重要的角色。在本文中,我们将介绍如何确保直播道具相关系统的高可用性,以实现99.99%的稳定性目标。文章将分为三个部分,分别是道具面板,道具投喂和多活。
一、道具面板
(进入直播间后点击右下角礼物显示)
背景:道具面板负责展示直播间所有道具,为了保证用户进入直播间后体验较好(不会出现loading),设计了预加载机制:用户进入房间时就拉取道具面板数据,从而在用户点击道具面板时不需要loading。
挑战:面板的第一个tab显示的道具根据不同的直播间有所不同,这里通过直播间维度的缓存,可以实现很高的并发。然而特权和定制tab会根据直播间+用户显示不同内容,没办法根据直播间维护做缓存,在进房流量大的时候就会面临以下2个问题:
1:道具面板依赖了几个不同用户维度的相关接口,根据不同的用户展示不同道具,但这些接口的稳定性可能存在一定问题,出现问题时会影响到道具面板的用户体验
2:当直播间面临大型赛事时,进房的用户流量会突增,可能会超过接口的tps
方案:上面2个问题除了推动提升服务的性能之外,目前采取的方案主要是熔断+降级,对依赖的外部系统接口都不信任,均改造成弱依赖,具体方案如下(针对以上两个问题):
1:假设某个用户的特权道具在相关接口超过50ms内没有返回,系统会默认降级,认为返回的数据为空。如果失败率超过50%,则会自动熔断一段时间。熔断/降级发生时会导致用户看不到特权道具,刷新后又恢复正常,虽然可能会影响用户的使用体验,但是至少保证了道具面板可以用。
2:当流量增加时,网关层会自动识别热点房间,降级直接从内存中获取房间的缓存数据返回给前端,以确保接口的可用性。这种方式会让所有用户看到的道具面板都是相同的,可能会影响部分用户的体验,不过保证了所有用户都能看到道具面板。
经过熔断和降级之后,热点赛事/晚会期间的系统稳定性提高了很多。虽然熔断和降级方案能够以少部分用户体验来换取全部用户的高可用性,但最好的解决方案仍然是进一步提升接口的性能(粉丝勋章正在性能优化)。
二、道具投喂(送道具给主播)
道具面板提供了各式各样的道具和礼物,包括盲盒、宝盒、小电视飞船等重要道具,同时还支持连击等操作。为了更好地理解相关系统的结构,可以看下面简要版的系统结构图:
订单、商品以及清分/结算系统是我们的营收中台,这些系统不仅服务于道具系统,也为其他营收业务(比如大航海,人气红包等)提供服务。房间信息是整个直播系统的基础,因此它的可用性要求更高。这里为了保证4个9的可用性,我们所面临的一些主要挑战有:
1、数据库不稳定:比如创建订单db超时突然增多,导致大量下单失败
2、流量突增可能超出订单处理能力
3、订单超时,但实际上却已经完成
4、消息队列出现问题
1、数据库不稳定
数据库的不稳定性通常可以归结为两种情况:设计不合理和硬件问题。设计不合理可能导致性能瓶颈、查询效率低下和数据冗余等问题。针对这一点,查了chatgpt:
我们采用的主要方式:拆!
1、分集群:一个集群出现问题(包括硬件),还有另外的集群。按uid分集群,一个集群出问题时只影响部分用户
2、分库分表:订单系统采用uid取模分10歌库,然后每个库按照月的维度再分表,保证表的数据量可控
3、日常监控治理:每天运维会通过发送慢sql的邮件,我们都会记录下来作为待办的事项,对于可能会对重要流程有影响的及时处理,不重要的择期处理。还有观察报警日志,对于比较慢的接口和慢sql一样根据优先级进行处理,这样通过日常治理减少出现事故的概率。如果是硬件问题,比如msyql宕机,运维有一套自己的解决方案进行主从切换,这里就不多说了(了解的也不够多 😄)
2、流量暴增
针对一些大型赛事/晚会都会有保障专项,根据预估的流量提前进行全链路压测,提前优化性能以及调配硬件资源。我们探讨一下未预料的流量暴增导致的问题(这里不仅仅指送道具的场景)
1、服务的机器资源不够(比如内存)
我们采用了K8S+ HPA自动扩容机器的解决方案。如果您有兴趣,可以查看这篇文章了解详情:https://dbaplus.cn/news-134-4991-1.html
2、redis热点key问题
基架提供了redis热点key探测功能,直接改redis到内存,通过牺牲实时性换取可用性(根据不同场景设置可接受内存的缓存时间),然后在内存失效时可以考虑通过sync.singleflight去获取redis,比如100个pod,并发可以控制在100。
3、其他:db,redis等超时多
其他情况目前是通过限流大法解决,分布式限流:quota-server,有兴趣的可以看看毛老师的分享:B站高可用架构实践 - 腾讯云开发者社区
3、订单超时
在道具投喂的链路中,订单是相对比较重要的一环,但同时也存在较高的风险:
1、跨进程超时控制:在毛老师的一篇高可用架构实践分享中,提到了跨进程超时控制,目前B站的请求链路中都默认使用250ms的跨进程超时控制。
由于订单需要涉及到一些数据库操作,因此性能相对较低,很难满足整个链路的250ms需求。即使调大超时时间,仍然可能出现问题。因此,目前的解决方案是将跨进程超时控制detach掉,以避免误超时。
2、下单成功但超时:虽然没有跨进程超时的问题,但是在实际过程中,由于网络或其他原因,道具系统调用订单时仍有可能超时,即使订单实际上已经支付成功。
回查:如果下单超时,道具系统延迟2秒后通过流水回查订单状态,如果支付成功则道具购买成功。如果订单未查询到则返回失败,返回失败。
对账:如果回查失败,但实际过了一段时间成功,这个时候如果不做任何处理,会出现钱扣了但道具没购买成功,这种也是不能接受的。我们通过订单系统主动对账的方式,发现一段时间后道具系统里仍然没有对应的道具流水,就会报警,通过人工确认方式看是否退款。
4、mq出现问题
mq出现问题会有三种情况:数据延迟、数据重复和数据丢失。
数据延迟:可以通过预警方式进行处理。例如,在订单对账和清分/结算一段时间没有数据时也会预警
数据重复:采用幂等的方式处理,例如通过数据库唯一索引或分布式锁等
数据丢失:当前使用的手动commit方式一般不会出现,如果出现,可能是由于生产者数据丢失或MQ本身丢失数据。这种情况通常需要通过对账来解决,例如订单系统和清分/结算系统以及道具系统之间都需要进行对账
注:手动commit可能会引起mq堵塞,超过最大重试次数后应塞入死信队列。
除了系统本身的高可用,多活也是密切相关的。下一步,我们将讨论与道具相关的多活现状和规划。
三、多活
多活是确保业务高可用性的必要手段之一。即使发生灾难性的机房级别故障,也可以通过其他机房提供的服务保持业务的正常运行。
目前线上的多活:同城多活,架构如下
(图来自于B站SRE Jaxon)
道具相关系统完成了第一阶段的升级:服务部署在同城多个机房,通过跨机房调用来访问 Redis、数据库、以及MQ。现在正在进行第二阶段的多活改造:优先使用本地机房的 Redis、数据库和MQ等资源。多活的最大问题就是确保数据一致性,接下来我们将详细讨论这个问题。
Redis
常见的redis多活方案
1、主从复制:在每个数据中心中都运行一个Redis主节点和多个Redis从节点,所有写操作都发送到主节点,然后通过主从复制机制将数据同步到从节点,读操作可以从任意节点中获取数据。当一个数据中心发生故障时,其他数据中心中的Redis节点可以继续服务,确保系统的高可用性和容灾能力。
2、读写分离:在多个数据中心中分别运行Redis实例,并将读请求和写请求分别发送到不同的数据中心,以降低读写操作的网络延迟和负载。这种方式需要应用程序支持读写分离,并需要额外的代理或路由器来处理请求的路由和负载均衡。
3、Active-Active模式:在多个数据中心中同时运行多个Redis实例,并让这些实例互相同步数据。这种方式可以使得所有Redis实例都能够同时接受读写请求,并且每个数据中心中的Redis实例都可以独立地处理请求。但是,这种方式需要解决数据一致性和冲突的问题,并且需要更高的网络带宽和更复杂的应用程序设计。
我们的方案:把redis当缓存用,机房之间数据不同步。彻底不考虑同步问题
问题:对一些常见的redis用法不支持:1、分布式锁 2、秒杀库存控制 3、布隆过滤器 4、持久化kv,5、mq等。
思路:mq 和 布隆过滤器在道具链路的系统中暂无使用redis的,暂时不考虑。剩余的功能都可以使用公司自研的taishan系统(也可以考虑etcd)
taishan:B站自研的一套高性能持久化KV存储,支持redis各种数据结构,基于raft协议,支持最终一致和强一致性两种策略。
redis分布式锁
常用的替代redis分布式锁的方案包括etcd,zookeeper等,个人认为etcd也是一个很好的选择(强一致性)
图片来自于:基于Etcd的分布式锁实现原理及方案
数据库
方案:一主多从,跨机房主从同步。写操作需要跨机房写入主库所在机房,而读操作根据不同场景的一致性要求来决定。
弱一致性:对于可接受短暂数据同步延迟的业务,可以读取本机房的从库,这样同城多活的耗时很小(通常在2毫秒以内)。例如不通过redis直接查询db的数据,显示给前端,可接受短暂的延迟。
强一致性:对于一些业务,如支付和订单等,需要在写操作成功后立即强一致性地读取数据。这时需要跨机房读取主库。但是,由于跨机房读取会增加延时,部分业务可能无法接受,因此需要在实现强一致性的同时,尽可能减少延时。
如果主库所在的机房出现问题,运维保障主库自动切换到可用机房的从库,dbproxy自动连到新的主库。
MQ
方案
同机房生产消费:需要确保生产者所有在机房,对应的消费者都存在
跨机房消费:适用于消费者未部署多机房场景,生产的数据通过kafka mirror(基于kafka) 方式同步数据到不同的机房。消息打上机房的标,如果消费者同机房都存在则选择只消费本机房的消息,过滤其他机房
注意事项:
跨机房消息乱序(比如:订单的创建与撤销请求打到了两个机房)
热备冷备,单机房挂了是否需要立即恢复
服务
通过公司自研的discovery 注册发现实现:优先调用本机房的相关服务。discovery:https://github.com/bilibili/discovery/blob/master/doc/intro.md
还有关于Taishan的多活方案,笔者这里暂时了解的比较有限,就不多说了。总结一下,为了确保道具相关系统的高可用性达到四个九(99.99%),采用了多种方案,包括熔断、限流、多级缓存、K8s+HPA(水平自动扩展)、分库分表、主从隔离、多活和注册发现等。
以上是今天的分享内容,如果你有什么想法或疑问,欢迎大家在留言区与我们互动,如果喜欢本期内容的话,欢迎点个“在看”吧!
往期精彩指路