三、架构设计
接下来为大家介绍的是B站对于不同数据量的场景的架构设计理念。
1、大型直播活动
整体概括起来有以下四种类型:
1)高并发写入
高并发写入考验的是主库的写入能力和从库的复制能力。
2)高并发查询
高并发查询一般都会引入缓存的能力,缓存主要涉及以下几种:
- 分布式缓存主要解决容量的问题;
- Local Cache在应用层提供能力,在应用本地可以缓存部分数据,但是数据可能存在不及时的问题;
- 多级缓存能够缓解爆炸性增长的流量带来的压力。
3)实时排序
实时排序最直观的场景就是观众在直播间刷礼物的时候展示出来的名次,为保证时效性以及顺序,我们一般会采用Redis有序集合。
4)预期外突发流量
预期外突发流量对于我们而言考验的主要是应用层的快速扩容以及如何对流量进行削峰,同时保证数据库比较平稳地写入,也就是异步写入的场景。今年最明显的预期外突发流量场景是佩洛西事件,比我们平常的流量大了将近5倍。
2、电商大促
整体上归纳下来有以下几个特点:
1)秒杀场景
秒杀场景主要涉及合适的选型和请求最简化。基于公司的基建进行定制,才可以实现最好的性能和体验。
2)订单
订单有很明显的冷热数据特征。一般情况下,我们的订单会被进行一年前、两年前以及实时订单的不同拆分。这块对于数据库而言考验的是数据的归档及查询能力。
3)库存
库存与秒杀场景存在一定关联,但并不是完全相关。秒杀场景会涉及到库存,但是库存在平常也会一直使用,因此两者不能进行强挂钩。
库存场景主要在于保证减库存的准确性,以及减少用户端在访问时可能存在的冲突,另外是一致性的问题,也就是在秒杀和减库存时不能出现超卖的情况,避免对商家造成亏本。
4)流量削峰
流量削峰与大型直播赛事遇到的突发流量是不同的,因为这一部分流量是我们已知的,已经预估好会有多少流量,因此我们一般会进行队列处理以及做分层。
前面介绍了大型直播赛事和电商大促两个典型场景,我们做了一部分数据库架构设计以及与应用端的联动。下面介绍我们真正进行数据库架构设计时,需要考虑哪些关键点。
3、数据
首先需要考虑数据,按照我们数据类型的使用场景,我们可以将数据分为以下三种:
1)配置型
配置型类似于我们的数据字典以及一些权限配置,特点包括:
- 量小
- 几乎无事务依赖
- 读多写少
如果需要对配置型数据进行高并发访问,只需要加缓存即可,不需要做过多处理。
2)日志型
日志型数据包括交易流水、订单状态等,我认为日志型数据也可以称为流水型数据,特点包括:
- 量大:无法避免,因为我们需要记录中间各部状态;
- 无事务依赖:我们后续进行的更多是查询而很少更改;
- 写多读少:读的比例一般是写的几十分之一。
3)状态型
- 数据量:与业务有关,状态型数据可以理解为我们的订单,以及直播场景里刷礼物的扣减情况;
- 事务强依赖:必须保证用户下单成功之后的库存扣减,以及用户给主播打赏之后平台的扣减和主播收到的礼物;
- 读多写多:与用户的进程挂钩,写和读的场景都比较多。
综上所述,对于数据一般通过数据量、事务和读写请求三个维度进行判断,从而对数据进行规整和梳理,对比上述我列出的三种数据类型,可以得出数据的特定类型。有了数据类型之后,我们就可以考虑进行下一个阶段,即业务对数据库的要求。
4、业务
业务对数据库选型的要求相对而言比较多,包括事务、性能、扩缩容、高可用、迁移。
1)事务
对事务的要求需要基于数据类型进行判断。
2)性能
一些业务对耗时比较敏感,也就是性能要求比较高,要求必须在多少毫秒以内将数据结果反馈回来。那么在进行数据库选型时,我们需要考虑该数据库能否承载这么高的性能反应。
3)扩缩容
如果业务要上一个新业务,要考虑满足一年至两年的增长的需求,因此数据库的扩缩容能力非常重要。如果之前申请的数据量比较大,但是业务发展没有达到预期,那么数据库需要缩容,所以这一方面对于数据库选型也是有要求的。
4)高可用
高可用需要进行取舍,如果要保证数据的强一致性,以及性能的稳定性,必须舍弃一部分东西,具体要与业务沟通和协调,从而保证实际效果符合业务要求。
5)迁移
迁移不仅是业务代码的改造,从A类数据库迁到B类数据库还需要考虑数据库的迁移成本,以及能否支撑同构和异构。对于业务而言,业务更多考虑的是迁移带来的业务改造成本,一般业务会比较喜欢协议无变更、基础操作语法不变的平滑迁移。
5、数据库
数据库我们要考虑的关键点有:
1)事务
如果你想要强事务依赖,可以用传统型数据库,以及现有的NewSQL,比如TiDB、OceanBase等。如果不考虑事务,数据库选择会更多,比如Redis、MongoDB,主要取决于具体的使用场景和数据库要承担的能力。
2)性能
每一种数据库的性能不同,以关系型数据库和非关系型数据库为例,MongoDB和MySQL两者的性能差别是很大的,依然取决于数据库要承担的能力。
3)扩缩容、高可用
扩缩容和高可用不需要进行过多的解释,因为高可用是DBA选择数据库的硬性要求。
4)迁移
这一部分的迁移与业务的迁移存在差异,业务的迁移主要考虑业务改造成本,数据库的迁移需要考虑以下三点:
- 数据是否一致
- 数据迁移时是否有增量
- 数据迁移会对业务产生什么影响
如果业务允许直接一刀切,那么方案则比较简单;如果业务要求无损,那么如何评估方案也是需要大家进行考量的。
5)备份/还原
如果可能出现数据需要恢复的场景,则必须考虑备份/还原的能力。我们一般会更倾向于做物理备份,因为物理备份还原比较快,但是一些数据库没有提供物理备份的能力,如MongoDB。Redis我们也不会做持续化的备份,因为会导致性能的严重下降。
6)容灾
容灾是第一部分B站数据库架构演进我们提到的两地三中心和同城多活需要具备的一个能力。
7)稳定性
数据库的要求是能够平稳地对外提供服务,因此稳定性非常关键。
8)成本
我们不可能为了保证性能无限地往数据库里加机器,因为成本会很高。同时需要考虑开源数据库和商业数据库的选择,在一定程度上商业数据库的性能比同等规格的开源数据库更好,但是需要考虑维护成本和二次定制化能力的成本。
9)定制化
商业数据库有时不会让我们做更多的定制化开发,但是这会给我们的上下游依赖带来一个问题,因为大部分场景我们会依赖于类似MySQL的binlog,下游的刷缓存能力以及大数据的实时数仓能力都需要依靠binlog去往下游,也就是CDC能力。那么这一方面也是数据库选型需要进行评估的重要能力。
6、策略
1)多维度综合考虑
数据库架构选型并不是从一个维度考虑的,每个数据库有自身的使用场景和特点,因此数据库架构选型需要从多个维度综合考虑,包括数据的维度、业务的真实诉求、DBA团队能提供的数据库能力,以及公司对于数据库的支撑能力,主要是公司其他团队如开发和平台类支撑。
2)满足未来三到五年需求
数据库架构例如扩缩容能力,必须满足未来3~5年的需求,而不是频繁地迭代和更新,否则对业务而言是有损的。
3)稳定为主
数据库需要平稳运行,而不是天天宕机,因此数据库架构选型需要以稳定为主。
上图的右侧是目前B站的数据库团队使用的数据库占比,可以看出:
- Redis、MySQL分别占比第一和第二;
- 占比第三的是MC,因MC无高可用,这一方面需要从业务层进行设计,如MC异常后的回源能力;
- 其他数据库相对数量较少。
总体来说,B站的数据库特点是Redis和MySQL为主,其它数据库主要是基于我们的使用场景进行选择和提供。
四、稳定性
今天主要是想向大家介绍B站万亿级数据库选型与架构设计实践,所以需要考虑数据库如何提供稳定性能力。
1、高可用
在提供稳定性方面,主要是如何保证数据库高可用。BRM是我们基于B站的业务特点自研的MySQL高可用组件,在该架构上我们提供了两个功能节点Leader和Follower,能够对集群内的所有节点进行管理和探活。不管在哪个节点进行注册,我们都可以将其注册到整个集群。因为内部有一个网关会把所有请求转发到主节点,同时再分发到剩下的Follower节点上。
Leader和Follower都参与投票决策,用以规避因网络抖动问题导致BRM误判数据库不可用,然后由Leader节点根据投票结果判断该节点到底是否宕机。
整体概括起来,我们自研的BRM会有以下六个核心功能:
- 多节点部署:解决MHA单点风险;
- 支持跨机房:跨机房部署解决因网络异常引起的误切风险;
- 支持权重:B站的数据库有单机房、同城多活和异地多活,如何保证切到想要的节点上。通过对不同节点设置权重,实现类似MongoDB一样的基于权重的选主能力;
- 多节点投票决策:通过多个BRM节点对同一个实例探测,满足多数节点一致才判定实例不可用;
- 专线抖动误切预防:通过多机房多节点部署我们可以预防因专线抖动导致的主节点误切,也可以避免跨机房专线异常造成的误判;
- 熔断机制:如果出现机房宕机的情况,我们可以先切一部分,查看故障发生的原因,确认没有问题之后再把熔断机制放开。
2、预警
保证系统的平稳运行,也涉及到预警的能力。对于数据库的预警,真正比较具有可预测性和可观察性的是慢查询。数据库的CPU和IO之类的也可以作为参考,但是会存在一定的误判,所以我们的方案是针对慢查询,并且做了一套慢查询预警体系。
首先对于DB层的慢查询,我们做了流式的采集上报和实时分析。在实时分析之后,可能会存在误报的情况,因为如果集群在常态情况下,每天固定某个时刻都会出现比如100条慢查询,那么此时是否该报,其实这本身是一个业务某个时间点的特定行为,不会影响整体行为,所以需要将其屏蔽。针对这一方面,我们引入多次线性回归,通过多次线性回归实现了对偶发性的抖动的过滤,不同业务级别环比倍数、持续性增长(未到阈值倍数,但持续增长或存在)慢查询的预警,并且基于规则引擎实现自定义处理。
3、Proxy
通过对Proxy的大量使用,我们可以实现针对某个数据库、某个服务、某类SQL指纹进行拦截、限流、熔断,以阻止某些异常流量打崩数据的场景,也可以做比较轻松状态下的读写分离。
我们也可以做多机房路由,将机动架构下的数据流量转发到主库,同时能够动态发现拓扑结构的变化,新增或删除从库以及节点的变化都比较易于发现。
同时我们可以去做更精细化的Sidecar模式,从而减少业务技术与能力,通过Sidecar模式使用Proxy,可以满足大家在大量场景下的能力。
4、多活
多活是为了保证在一个机房挂掉之后,我们可以有另外的机房支撑这一方面的能力,我前面讲到的Proxy、BRM以及DTS等都是用于满足多活的诉求。通过多活我们可以保证最大能力的冗灾,同时对用户的影响达到最小,当一个机房挂掉之后,影响的用户可能只有一部分,快速将用户全部导流到另外一个机房可以为用户提供平稳的使用体验。