分享 | 滴滴分布式NoSQL数据库Fusion的演进之路

588 阅读15分钟

出品 | 滴滴技术

作者 | 余汶龙


前言:

Fusion 是滴滴自研的分布式 NoSQL 数据库,完全兼容 Redis 协议,支持超大规模数据持久化和高性能读写。在滴滴内部支撑了数百个业务,具有 PB 级别的数据存储量,是使用最广泛的主存储服务之一。在支持滴滴业务高速发展过程中,积累了很多分布式存储领域的经验,孵化了离线到在线的高速数据导入方案、NewSQL 方案、跨机房同步等,一路解决了 Redis 容量限制、 离线数据在线打通、数据库扩展性差、异地多活容灾等问题。

本文来自滴滴的技术专家、Fusion 的负责人余汶龙在 2018 年北京 ArchSummit 全球架构师峰会上的演讲内容,重点介绍了 Fusion 的核心设计以及架构演进过程。

内容框架

  • 诞生背景:滴滴业务发展简介

  • 演进过程:如何满足业务需求

    海量存储

    FastLoad

    NewSQL

    跨机房多活

  • 总结 & 展望

诞生背景

  • 业务 & 架构演进过程

滴滴出行成立于 2012 年,刚开始创业阶段技术主要靠外包解决,没太多技术沉淀;发展到了 2014 年,乘客司机和单量都有不错的增长,我们开始构建自己的系统架构,这个时候业务对于存储的需求很单纯,简单用用 MySQL 基本能解决我们的问题。

到了 2015 年前后,我们的业务线多了起来,专车快车等开始上线,这个时候我们一方面做了中台系统的重构,另一方面感受到了不小的存储压力,即业务数据量和请求量剧增;到了 2016 年,合并优步前后,日订单量逼近 2000 万,进一步挑战我们的存储系统,于是我们按照不同的业务,对存储进行拆分,因为不同的业务对于存储的需求是不一样的,不同的业务对于吞吐、延迟、容量、数据请求大小等都有不同的需求,分库分表也只是缓兵之计。

如何有效应对这些个性化需求呢?于是在这个时候,我们就开始孵化滴滴自己的 NoSQL 数据库 Fusion 了,用它来丰富我们滴滴的存储生态,为业务提供更多的存储选择。


  • Fusion 是什么?

前面我们不断提到 Fusion 的关键字,那么是时候正式介绍下 Fusion。Fusion 是一个兼容 Redis 协议的分布式 NoSQL 数据库。定位介于 Redis 与 MySQL 之间的主存储数据库。怎么理解这个定位呢?也就是性能方面我们向 Redis 看齐,即低延迟;持久化能力方面我们向 MySQL 看齐,即 MySQL 具备的多副本、高可用、ACID 事务,我们都是支持的,同时定位为服务打车订单这样的主流程在线业务。


它如何实现的呢?大家都知道 Redis 的数据是存放于内存中,虽然性能很好,但是容量极小,且每 GB 存储成本很高(大概是我们 Fusion 的 10 倍以上)。于是我们就基于 SSD 磁盘实现了一套分布式的存储系统,在 SSD 磁盘上实现了 Redis 的数据结构,对外通过 proxy 屏蔽内部细节,让用户像访问 Redis 一样访问 Fusion。当前我们已经支持 String\Hash\Bitmap\Set\Sorted Set\List 这些主流的 Redis 数据结构。

演进过程

我们 Fusion 的发展总共经历了 4 个阶段,分别解决了 4 类业务问题,我们接下来重点看下具体过程。

  • 海量存储

首先来看如何解决海量存储的问题。


Redis 是一款非常优秀的内存数据库,但它也有这样一些已知问题存在:容量受限于内存、扩容迁移和大 key 过期、删除过程是阻塞的、宕机恢复慢等问题。我们 Fusion 设计之初,就避免了这些问题。具体是如何实现的呢?我们从需求分析出发。

 需求分析

Fusion 诞生初期,主要解决 2 个业务需求:

一是滴滴的历史订单,按照前面提到的每日千万级别订单量,很快就能达到几百亿的订单,这么庞大的数据量,存 MySQL 显然是不够灵活的,修改字段、修改索引都比较困难,存 Redis 就更加不可能,因此他们有新型存储的需求;

二是地图团队的司机行程轨迹,每产生一条打车订单就会产生一条司机行程轨迹,每一条行程轨迹由多个点组成,行程越长轨迹数据越大,这是一个比历史订单的数据量还要大的业务,存储的困难可想而知。


因此,我们对上述两个业务的需求做了提炼和优先级排定:

  1. 刚需是海量存储。

  2. 具备基本的在线故障处理能力。

  3. 稳定性很重要!

  4. 性能要足够好,以司机行程轨迹为例,每天 300 亿级别写入,他们对性能的追求当然是越高越好。

  5. 接入要求简单,这里我们选择了 Redis 协议。

  6. 打通其他存储系统。

满足了这些需求后,就诞生了存储系统 Fusion 的雏形。

  • 架构设计

· 软件结构

下图左边是数据流部分,从下往上看,即 Fusion 是构建在 SSD 磁盘上的存储服务,我们引用优秀的存储引擎 RocksDB 来做磁盘 IO 操作,然后在磁盘之上,我们增加一层 cache 来提升性能,然后封装一层网络框架并支持 Redis RPC,就实现了单机版本的 Fusion 存储节点,然后在单机的基础上加上我们的集群路由管理,Fusion 的集群就搭建好了,当然对外提供服务的时候,还有一层负载均衡。

下图右边是控制流部分,即我们在 SaltStack 平台基础上,构建了用户系统、运维系统、统计、监控、计费等系统,方便用户以及运维人员使用。


· 集群架构

集群架构上,我们采用 hash 分片的方式来做数据 sharding。从上往下看,用户通过 Redis 协议的客户端(jedis、redigo、hiredis 等)就可以访问 Fusion,首先会经过 VIP 做负载均衡,然后转发到具体 proxy,再由 proxy 转发数据到后端 Fusion 的数据节点。

proxy 到后端数据节点的转发,是根据请求的 key 计算 hash 值,然后对 slot 分片数取余,得到一个固定的 slotid,每个 slotid 会固定的映射到一个存储节点,以此解决数据路由问题。

此外,我们还做了存储生态的打通。支持 Hadoop、MySQL、Redis 的数据同步到 Fusion,也支持 Fusion 数据同步到 MQ,供下游消费。


小结

接下来就对 Fusion 做个小结,拿 Redis 来做个简单对比。


FastLoad

我们演进过程中,解决的第二个问题是,离线数据到在线系统的快速打通。因此我们做了一个叫 FastLoad 的系统。


  • 需求分析

首先,FastLoad 诞生初期主要支持两个业务:标签平台和特征平台。标签平台是指对每个乘客和司机,都打上 N 个标签,然后后续的打车流程会依赖这部分标签,比如优惠券的发放;然后特征平台呢,会收集创建各类特征,对每个对象用某个特征库做一次判断,即可确定某种行为。接下来我们对需求进行提取。

  1. 高性能。由于这部分数据是在离线计算平台 Hadoop 上加工完成的,业务很容易想到就近存放在 Hive 上,但 Hive 的查询性能实在不能满足在线查询的高吞吐、低延迟要求。因此对于新的存储系统,他们第一个要求就是性能!

  2. 定时更新。像特征数据,一般只需要小时级别甚至天级别的更新,所以业务需要有快捷的定时更新功能。

  3. 快速更新。特征数据还有一个特点,就是数据量特别大,以乘客特征为例,动辄上亿条数据,约 TB 级别数据量。这么大的数据量通过 SDK 写入肯定是不行的。刚开始业务方也确实是这么玩的,直接通过 Hadoop 任务调用 Redis SDK,然后一条条的写入 Fusion,一般是每天凌晨开始写数据,等到早高峰 8 点时大量读取。但是这种方法实践下来,经常导致 Fusion 各类超时,在早高峰打车已经来临

    时还在写凌晨的数据,非常影响稳定性。因此第 3 个需求是必须快速更新。

  4. 稳定性。这个是毋容置疑的。

  5. 多表隔离。有些业务有很多类特征数据,他们有隔离存储的需求,也有分类更新、分类查找的需求,因此需要多表来支持逻辑到物理的隔离。


  • 架构设计

满足上述需求后,就诞生了我们的 FastLoad 系统。接下来就来看下我们的架构是如何设计的。我们给用户提供两种接入方式:控制台和 OpenAPI。用户通过任一一种方式提交 FastLoad 任务时,都会在我们的 FastLoad 服务器上,创建一个 DTS 任务,该任务会在 Hadoop 配置中心注册一个调度任务(周期性或一次性,由用户决定),然后 FastLoad 服务器根据用户上传的数据存储路径或 Hive 表(我们支持的数据源有:HDFS 上的 JSON 文件和 Hive 结构的数据),按照用户提交的拼 key 方式,我们启动 map/reduce 任务直接构造 Fusion 底层存储在文件系统上的文件 SST,并把它们构造好相互之间的排序,避免重复,构造好后通知 Fusion 存储节点,下载 SST 文件,然后 load 到 Fusion 数据库中。此后,用户就可以通过 Redis-Client 访问我们帮它加载的数据了。


  • 小结

总结一下我们的 FastLoad 一站式 DTS 平台,有如下优势:

  1. 减少 N 次网络交互。相比调用 Redis SDK 的方式写入,我们减少非常多的网络交互,传输的是压缩格式文件,节省了网络带宽。

  2. 对用户请求 0 影响。我们利用 map/reduce 的计算能力,做了 SST 的全局排序,让 SST 进入 Fusion 的时候,不经由 L0,直接到达最终 level,避免了 LSM 的 compact 影响,因此对用户可以说没有影响。

  3. 接入简单,用户 0 感知细节。用户既不需要关心 Hadoop 使用、任务调度,也不需要自己写 Redis SDK 的代码,只需要告诉我们,在什么时间点需要什么样的数据即可!

  4. 提供了 OpenAPI,方便用户的自动化流程打通。

  5. 提供全量覆盖和增量导入两种方式。

NewSQL

在演进过程的第 3 个阶段,我们主要是针对 MySQL 的。大家都知道 MySQL 的扩展性比较差,面对百亿级存储,有几个问题,一个是数据存不下,一个是扩展不灵活,比如修改字段、修改索引等。接着就来讨论下,我们是如何解决这类问题的。


  • 需求分析

同样的,我们先来分析下业务的需求是什么?简单理解下,我们认为有 3 点刚需:

  1. 轻松改字段。即需要足够的扩展性。

  2. 存储不限量。即需要一个容量尽可能大的存储。

  3. 省成本。既然需要存 MySQL 都存不下的数据,那么成本一定要考虑清楚。

至于事务、稳定性、高性能、二级索引,我们认为都是基本需求。


  • 背景问题

· 如何实现 shema 到 key/value 的转换?

前面的介绍我们知道,Fusion 是支持 Redis 协议的,那么 schema 转换成 key/value,就可以用 Redis 的 hash 结构来实现,下图我们就以 student 表为例,转换了 2 行数据。


· 如何做主键查询呢?

下面的图片给出了一个例子,即查询 ID 为 1 的学生的全部信息或年龄。


· 如何实现二级索引呢?

我们还是以 student 表为例,分别构建如下 age\sex 索引,其编码规则如下可见。


· 如何做非主键查询和范围查询呢?

在上图构建好索引后,就很容易实现下面的两个例子,即查询年龄在某个范围的学生,和查询某种性别的所有学生。


· 背景问题

架构设计上分成接入层和数据存储层,在接入层(DISE)我们提供控制台来管理用户的字段,用户可以在这里定义自己的 schema、字段、索引,并做相应的修改。然后用户通过我们提供的类 SQL 的 SDK 接入,由我们的 SchemaServer 做 schema 转换,接着插入数据到存储层。然后数据存储层吐出 binlog,由 IndexServer 异步消费 binlog 并构建索引。查询时候,用户的请求经由 SDK 到达 SchemaServer,SchemaServer 先查询索引服务器,拿到对应的主键信息,然后根据命中的主键查询详细信息,最后返回给用户。


  • 小结

NewSQL 解决的问题是针对 MySQL 的特殊场景的,我们就拿 MySQL 来跟 Fusion 做个对比,可以看到 Fusion 只是在部分场景上解决了 MySQL 的容量限制以及扩展问题,但还是有很多场景并不能支持的。


跨机房多活建设

最后一个演进我们讲的是如何支持业务的跨机房容灾问题。


  • 背景介绍

滴滴多活的业务架构如下图,可以看到用户层接入层和业务层都是无状态的,因此如图中的白色虚线所描述的,他们的请求可以在两个机房间来回路由,而不影响业务请求的正确性。那么是如何做到这一点的呢?必然得有一个地方维护着状态的一致性,才能让业务自由切换。因此跨机房多活容灾最复杂的部分就在底层数据同步层,这里的数据同步涉及到很多中间件,我们这里只关心 Fusion 的跨机房多活。


  • 架构设计

下图是 Fusion 的跨机房同步架构,不依赖任何外部中间件,也不依赖内部 proxy。当用户数据通过 A 机房写入时,落地到某个存储节点上,该存储节点会 cache 一份对端节点的路由表,并异步的将刚才写入的数据转发到对端集群。

我们这里的转发采用了两个异步特性:1. 跟用户写入主流程完全异步,即不影响用户正常请求;2. A 机房到 B 机房的数据同步,采用了异步、批量、应答的方式高效同步。既保证了用户请求主机房的吞吐和延迟,也大幅降低了备机房数据读取的延迟。


  • 小结

到此总结下我们的多活方案:

  1. 异步数据复制。在追求性能的同时,放弃了一段时间的不一致。如果在数据未达成一致的时候,主机房宕机,备机房的数据将缺失,但这个缺失不会是永久,等到主机房恢复后,我们会把这部分数据自动补齐到备机房,这个过程我们会根据时间戳去重。

  2. 自适应感知集群状态变更,比如切主、扩容等。在运行过程中,两个机房的集群难免会发生各类路由变化,我们在设计时考虑到了这一点,针对路由变化,我们会及时更新路由表,以把数据同步到正确的节点上。

  3. 数据可靠同步。我们的数据同步是依赖滑动窗口的应答机制,因此实现了一种可靠的数据同步。

  4. 支持双写,解决秒级冲突。由第一点提到,在某些场景是存在双写的,如果双写的是不同 key,自然不需要解决冲突,如果双写的是针对同一个 key,那么我们会根据时间戳做冲突检测。

  5. 自动数据补偿。也就是在发生主机房宕机后,写入备机房的增量数据,可以自动的补偿到主机房;原先滞留在主机房的数据,在主机房恢复后,也可以补偿到备机房。即可以达到最终一致性。


总结与展望

  • 总结

在伴随滴滴业务发展的过程中,Fusion 经历了 4 个发展阶段,我们坚持”好东西是用出来“,因此在每个阶段,都尽量避免”过度设计“,只解决特定的业务问题。这给了我们很多认真打磨产品的时间和精力,让我们赢得了用户口碑。


  • 展望

通过前面的分享我们知道,Fusion 虽然能做的事情很多,但不能做的事情更多,所以我们的目标是持续发展持续演进,把解决业务问题当做己任。未来我们将往分布式数据库方向前进,解决更多的业务问题。


  • 余文泷,滴滴出行技术专家,2016年加入滴滴,从零开始构建滴滴自研的分布式 NoSQL 数据库 Fusion。