《滴滴自研分布式 NoSQL 数据库 Fusion 的演进之路》

滴滴自研分布式 NoSQL 数据库 Fusion 的演进之路

阅读数:74342019 年 1 月 23 日 11:13

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

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

为了讲清楚滴滴自研存储 Fusion 的演进过程,本文将分成 3 个部分:

  • 诞生背景:滴滴业务发展简介
  • 演进过程:如何满足业务需求
    • 海量存储
    • 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 的集群就搭建好了,当然对外提供服务的时候,还有一层负载均衡。

RocksDB 项目最开始是在 Facebook 作为一个试验项目开发的高效的数据库软件,可以实现在服务器负载下快速存储(特别是闪存存储)的数据存储的全部潜力。它是一个 C++ 库,可以用于存储 KV,包括任意大小的字节流。它支持原子读写  

RocksDB 具有高度灵活的配置设置,可以调整为在各种生产环境(包括纯内存,闪存,硬盘或 HDFS)上运行。它支持各种压缩算法,并且有生产和调试环境的各种便利工具。

RocksDB 借用了来自开源 leveldb 项目的核心代码,以及来自 Apache HBase 的重要思想。初始代码是从开源 leveldb 1.5 fork 的。它还融入了 facebook 团队在开发 RocksDB 之前的若干代码及想法。

 

性能

RocksDB 的主要设计点是,它应该是快速存储和服务器工作负载的性能而设计。它应充分利用 Flash 或 RAM 提供的高速读/写速率的全部潜力。它应该支持高效的点查找以及范围扫描。它应该可配置为支持高随机读取工作负载,高更新工作负载或两者的组合。其架构应支持轻松调整参数,支持读取放大,写入放大和空间放大场景

 
   
   

下图右边是控制流部分,即我们在 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 虽然能做的事情很多,但不能做的事情更多,所以我们的目标是持续发展持续演进,把解决业务问题当做己任。未来我们将往分布式数据库方向前进,解决更多的业务问题。

作者介绍

余汶龙,滴滴出行技术专家,曾经就职于 VMware、淘宝核心系统、阿里云数据库组,长期从事分布式存储相关的研发。2016 年加入滴滴,从零开始构建滴滴自研的分布式 NoSQL 数据库 Fusion。

活动推荐

2019 年 7 月 12-13 日,ArchSummit 全球架构师峰会将在深圳举办。针对华南地区的技术氛围和技术人群的关注点,我们设置了金融技术、小程序、ToC 转 ToB 技术选型、AI 技术落地、数据平台建设等专题。戳此查看

原文地址:https://www.cnblogs.com/cx2016/p/12036412.html

时间: 2024-11-03 20:47:13

《滴滴自研分布式 NoSQL 数据库 Fusion 的演进之路》的相关文章

CI框架源码阅读笔记3 全局函数Common.php

从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap引导文件都会最先引入全局函数,以便于之后的处理工作). 打开Common.php中,第一行代码就非常诡异: if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 上一篇(CI框架源码阅读笔记2 一切的入口 index

IOS测试框架之:athrun的InstrumentDriver源码阅读笔记

athrun的InstrumentDriver源码阅读笔记 作者:唯一 athrun是淘宝的开源测试项目,InstrumentDriver是ios端的实现,之前在公司项目中用过这个框架,没有深入了解,现在回来记录下. 官方介绍:http://code.taobao.org/p/athrun/wiki/instrumentDriver/ 优点:这个框架是对UIAutomation的java实现,在代码提示.用例维护方面比UIAutomation强多了,借junit4的光,我们可以通过junit4的

Yii源码阅读笔记 - 日志组件

?使用 Yii框架为开发者提供两个静态方法进行日志记录: Yii::log($message, $level, $category);Yii::trace($message, $category); 两者的区别在于后者依赖于应用开启调试模式,即定义常量YII_DEBUG: defined('YII_DEBUG') or define('YII_DEBUG', true); Yii::log方法的调用需要指定message的level和category.category是格式为“xxx.yyy.z

源码阅读笔记 - 1 MSVC2015中的std::sort

大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格式化,去掉或者展开用于条件编译或者debug检查的宏,依重要程度重新排序函数,但是不会改变命名方式(虽然MSVC的STL命名实在是我不能接受的那种),对于代码块的解释会在代码块前(上面)用注释标明. template<class _RanIt, class _Diff, class _Pr> in

CI框架源码阅读笔记5 基准测试 BenchMark.php

上一篇博客(CI框架源码阅读笔记4 引导文件CodeIgniter.php)中,我们已经看到:CI中核心流程的核心功能都是由不同的组件来完成的.这些组件类似于一个一个单独的模块,不同的模块完成不同的功能,各模块之间可以相互调用,共同构成了CI的核心骨架. 从本篇开始,将进一步去分析各组件的实现细节,深入CI核心的黑盒内部(研究之后,其实就应该是白盒了,仅仅对于应用来说,它应该算是黑盒),从而更好的去认识.把握这个框架. 按照惯例,在开始之前,我们贴上CI中不完全的核心组件图: 由于BenchMa

CI框架源码阅读笔记2 一切的入口 index.php

上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里这次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中,我们并不会逐行进行解释,而只解释核心的功能和实现. 1.       设置应用程序环境 define('ENVIRONMENT', 'development'); 这里的development可以是任何你喜欢的环境名称(比如dev,再如test),相对应的,你要在下面的switch case代码块中

Apache Storm源码阅读笔记

欢迎转载,转载请注明出处. 楔子 自从建了Spark交流的QQ群之后,热情加入的同学不少,大家不仅对Spark很热衷对于Storm也是充满好奇.大家都提到一个问题就是有关storm内部实现机理的资料比较少,理解起来非常费劲. 尽管自己也陆续对storm的源码走读发表了一些博文,当时写的时候比较匆忙,有时候衔接的不是太好,此番做了一些整理,主要是针对TridentTopology部分,修改过的内容采用pdf格式发布,方便打印. 文章中有些内容的理解得益于徐明明和fxjwind两位的指点,非常感谢.

CI框架源码阅读笔记4 引导文件CodeIgniter.php

到了这里,终于进入CI框架的核心了.既然是"引导"文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.com/usr/reg 经过引导文件,实际上会交给Application中的UsrController控制器的reg方法去处理. 这之中,CodeIgniter.php做了哪些工作?我们一步步来看. 1.    导入预定义常量.框架环境初始化 之前的一篇博客(CI框架源码阅读笔记2 一切的入

jdk源码阅读笔记之java集合框架(二)(ArrayList)

关于ArrayList的分析,会从且仅从其添加(add)与删除(remove)方法入手. ArrayList类定义: p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Monaco } span.s1 { color: #931a68 } public class ArrayList<E> extends AbstractList<E> implements List<E> ArrayList基本属性: /** *

dubbo源码阅读笔记--服务调用时序

上接dubbo源码阅读笔记--暴露服务时序,继续梳理服务调用时序,下图右面红线流程. 整理了调用时序图 分为3步,connect,decode,invoke. 连接 AllChannelHandler.connected(Channel) line: 38 HeartbeatHandler.connected(Channel) line: 47 MultiMessageHandler(AbstractChannelHandlerDelegate).connected(Channel) line: