以下文章来源于百度Geek说 ,作者UNDB
关注我们,带你了解更多百度技术干货。
自2016年起,百度进入『搜索+信息流』双引擎驱动构建内容生态的“信息分发2.0时代”,搜索、推荐的内容也不再局限于网页,而是引入越来越多的视频、图片、音频等多媒体资源。KV存储作为在搜索和推荐中台中被广泛使用的在线存储服务,更是受到了存储规模和访问流量的双重考验。
至2018年初,我们投入各类KV存储服务的服务器数量便超过万台,数据规模超过百PB,承接了每天近千亿次的各类访问请求。集群规模的增长除了资源成本的提升,还加剧了运维管理的难度。作为有状态服务,集群的故障机处理、服务器升级、资源扩缩容都需要专人跟进,运维人力随集群规模呈正比增长。彼时又逢推荐业务完成了微服务化改造,业务资源交付和上线都能当天完成,存储资源动辄周级的交付能力也成了业务上线效率的瓶颈。
这些都促使我们对原来的系统架构进行彻底升级,通过提升单机引擎性能和云原生化有效降低资源成本和运维人力成本。同时我们还要满足业务对服务的敏捷性要求,通过云基础设施提供的资源编排能力,使系统具备小时级服务交付能力。
单机引擎性能是KV系统的关键指标,一般我们通过读写性能(OPS、延时(latency))和空间利用率来衡量引擎的性能,由于当前引擎架构和存储设备硬件性能的制约,我们在引擎中往往只能选择读性能、写性能和空间利用率中某个方向进行重点优化,比如牺牲空间利用率提升写吞吐,或者牺牲写吞吐、提升空间利用率和读吞吐。
这类优化对于业务单一的场景比较有效,之前我们的系统就针对大业务场景进行逐一调参优化,针对其他业务则使用读写性能和空间利用率均衡的『均衡型』引擎接入。
但是百度的信息流和搜索业务规模都非常庞大、业务场景极其复杂,我们接入的数千个业务中有每天更新PB级数据的业务,也有读写比超过100:1的业务,还有要求强一致性、存储规模数十PB的业务,不少业务还会体现出潮汐特性,全天流量都集中在某个特定时段。
因此我们通过引擎优化,既要解决如何在降低读写放大的同时,尽可能平衡空间放大的问题;又要在引擎内实现自适应机制,解决业务充分混布场景下,吞吐模式多变的问题。
云原生架构的主要价值在于效率的提升,包括资源利用效率和研发效率两个方面。
百度信息流和搜索业务对其业务模块制定了统一的云原生化标准:
KV服务在对齐上述标准的过程中,主要难点在于容器化改造和动态管理两个方面。
容器化改造方面,单机时代的KV服务以用满整机资源为目标,对内存资源和存储介质IO的使用往往不加任何限制。引擎的容器化改造,要求我们精细化控制对上述资源的使用:
动态管理则要求业务具有一定的容错能力和弹性伸缩能力,由于KV是典型的有状态服务,兼具了数据持久化、多分片、多副本等特点,我们在动态管理中需要关注:
之前也提到百度的信息流和搜索业务规模都非常庞大,一些大业务根据自身场景已经做了单机引擎的特化,有些特化还涉及修改linux内核,通用引擎在性能上无法超越这些特化引擎,这就要求能从业务中提取共性,同时允许其保留特性,使业务团队能在资源利用效率和研发效率两个方面都获得收益。
为此我们提出了UNDB - NoSQL联合存储系统(United NoSQL Database)的概念,兼顾统一通用能力与保持业务特性:
引擎是KV系统的核心组件,鉴于RocksDB在开源社区和工业界的广泛应用,我们一开始便直接使用RocksDB作为单机引擎。基于LSM-Tree实现,RocksDB在HDD介质上有良好的性能,但在SSD介质上的性能表现却并不出众,主要问题在于:
我们业务场景中的value一般在KB ~ 百KB级别,为了降低LSM-Tree的写放大,我们在RocksDB基础上实现了Key-Value分离的单机存储引擎,如下图左侧引擎结构所示:
图1:普通引擎与基于OpenChannel SSD的软硬协同引擎的架构对比
为了进一部提升引擎的I/O效率,我们又对Compaction策略和压缩方式进行了优化:
此外,我们在引擎中为同步框架封装了Log View,实现数据同步与引擎复用,WAL降低了数据同步造成的写放大。
通过上述优化,在软件层面,我们在空间放大 <1.6x的情况下,将写放大控制到了 < 1.5x。在业务持续以30MB/s更新数据的场景下,单盘寿命由之前的半年内提升至3年左右。
但是,SSD的写放大并不限于软件层,物理特性决定其不支持覆盖写,只能先擦除旧数据再写入新数据,大部分SSD按4KB(Page)写入、按256KB ~ 4MB(Block)擦除数据。SSD在擦除一个Block时,需要迁移Block中仍然有效的Page,这个迁移动作导致了SSD硬件层的写放大,硬件层写放大又与SSD剩余空间密切相关,一般情况下,当SSD容量使用达90%时,写放大会超过3.5x。
细心的同学或许会发现,我们之前在计算SSD寿命时并没有提到这部分放大,这里其实包含了SSD厂商的优化:SSD介质的实际容量单位是GiB(Gibibyte),1GiB = 230bit,提供给用户的指标则是GB(Gigabyte),1GB = 109 bit,前者比后者多了7.374%的空间,被厂商用作了内部操作空间。加之我们在实际使用时,也会保持磁盘用量不超过80%,因此可以控制写放大。但是这些策略其实牺牲了SSD的容量。
为了进一步发掘设备潜能,我们和百度的基础架构部门合作,基于Open Channel SSD实现了一款软硬协同设计的引擎,如上图右侧引擎结构所示与传统用法相比:
软硬协同引擎在性能上超过软件引擎 > 30%,软硬整体放大率 < 1.1x。
图2:引擎性能对比,依次为:数据加载性能、读写吞吐(读写1:1)、99分位写延时、99分位读延时
上图是我们实现的KV分离引擎(Quantum)、软硬协同引擎(kvnvme)和开源Rocksdb在数据加载、随机读写场景下的性能对比。
测试中我们选用:NVME 1TB SSD(硬件指标:4KB随机写7万IOPS,随机读46.5万IOPS)。数据集选用:1KB、4KB、16KB和32KB共4组,每组数据集都随机预构建320GB初始数据,再采用齐夫分布(Zipf)进行读写测试,读写测试时保持读写比为1:1。
Value Size | key数量 | 原始数据集 |
1KB | 3.2亿 | 320GB |
4KB | 8千万 | 320GB |
16KB | 2千万 | 320GB |
32KB | 4千万 | 320GB |
从测试结果可以发现:
上节中我们通过引擎优化重构解决了业务混布性能和容器化问题,这节将介绍一下我们是如何解决动态管理问题。
UNDB服务整体框架如下图所示:
图3:UNDB系统框架
架构上我们将服务分成了Operator(数据调度)、控制面和数据面三部分:
我们在系统设计、实现中主要考虑如何实现数据全局调度能力和海量存储实例的动态管理能力。
3.1 数据全局调度能力
数据全局调度指:
这种能力的意义在于:
图4:Table在UNDB集群间迁移示意
如上图(图4)所示,全局调度由Operator发起,通过控制面协调数据面中的存储实例完成操作,其中:
基于上述能力,我们除了支持即时集群容量均衡和即时业务容量调整,还实现了周级机房建设、搬迁。
3.2 海量存储实例的动态管理能力
之前提到我们在动态管理中需要关注服务可用性、数据可靠性、管理效率和部署效率,上节中我们通过引擎优化实现了小时级完成TB数据的迁移、恢复。这里我们将关注如何在动态管理中确保服务的可用性、数据可靠性和管理效率。
业务访问KV服务的过程可以简单概括为:
我们在数据面中:
由于数据中心间的元信息不尽相同,尤其是拓扑信息完全不同,且拓扑信息具有极强的时效性,冷备效果并不好,因此对于控制面我们采用了利用数据面,集群控制服务多级兜底的思路,如下图(图5)所示:
另外我们还通过独立的路由服务向业务屏蔽了元信息服务,业务间通过多组路由服务进行物理隔离。并通过接入管理服务管理业务和路由服务间的映射关系,这样可以有效防止由于某个业务的异常访问(比如:连接泄露)影响其他业务的路由访问。
在提升管理效率方面,我们主要采用了元信息和集群控制分离的设计,由于元信息服务需要确保节点间数据一致性,我们的数据修改操作只能在Leader节点上进行,如果不采用分离设计,所有控制操作只有Leader节点才能进行,当集群规模和数量增加后,无法通过水平扩容节点增加控制面算力,因此我们选用了两种模块分离的方法,使控制面具备水平伸缩控制算力的能力以应对超大规模集群。
NoSQL概念发展至今,业界已经出现了数百种不同的开源和商业NoSQL数据库,当业务发展到一定程度,对标准数据库进行改造,使其更适合业务模型的需求也变得越来越普遍。因此我们在整合KV系统时,提出了整合通用功能保留业务特性的设计思路。在这个思路指导下,我们统一了控制面,用于实现统一的运维管理,数据面则分成了3个功能模块、6个分层向业务开放了对服务接口、数据模型、存储模式、以及对同步框架的定制化能力,如下图所示:
图6:UNDB多模型存储架构
UNDB系统自落地以来,已经覆盖了百度信息流和搜索主要业务场景,涉及集群规模数十万实例,每天承接了超过万亿次的各类访问流量,成本相较原先降低近50%。同时系统还保持着月级频率的全集群业务无感更新。在运维方面则实现了高度自动化,集群无专职运维,全部由研发团队自主管理。目前团队还在打造多模型数据库、单机性能优化、存储架构优化等方向持续努力,力求使UNDB具备更完善的业务生态。
技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。