GO 调用C++ dll,报 is not a valid Win32 application.

Golangastaxie 回复了问题 • 2 人关注 • 1 个回复 • 634 次浏览 • 2016-10-11 15:26 • 来自相关话题

[QingCloud Insight 2016] How do we build TiDB

文章分享qiuyesuifeng 发表了文章 • 0 个评论 • 349 次浏览 • 2016-10-11 15:17 • 来自相关话题

首先我们聊聊 Database 的历史,在已经有这么多种数据库的背景下我们为什么要创建另外一个数据库;以及说一下现在方案遇到的困境,说一下 Google Spanner 和 F1,TiKV 和 TiDB,说一下架构的事情,在这里我们会重点聊一下 TiKV... 查看全部

首先我们聊聊 Database 的历史,在已经有这么多种数据库的背景下我们为什么要创建另外一个数据库;以及说一下现在方案遇到的困境,说一下 Google Spanner 和 F1,TiKV 和 TiDB,说一下架构的事情,在这里我们会重点聊一下 TiKV。因为我们产品的很多特性是 TiKV 提供的,比如说跨数据中心的复制,Transaction,auto-scale。


再聊一下为什么 TiKV 用 Raft 能实现所有这些重要的特性,以及 scale,MVCC 和事务模型。东西非常多,我今天不太可能把里面的技术细节都描述得特别细,因为几乎每一个话题都可以找到一篇或者是多篇论文。但讲完之后我还在这边,所以详细的技术问题大家可以单独来找我聊。


后面再说一下我们现在遇到的窘境,就是大家常规遇到的分布式方案有哪些问题,比如 MySQL Sharding。我们创建了无数 MySQL Proxy,比如官方的 MySQL proxy,Youtube 的 Vitess,淘宝的 Cobar、TDDL,以及基于 Cobar 的 MyCAT,金山的 Kingshard,360 的 Atlas,京东的 JProxy,我在豌豆荚也写了一个。可以说,随便一个大公司都会造一个MySQL Sharding的方案。


为什么我们要创建另外一个数据库?


昨天晚上我还跟一个同学聊到,基于 MySQL 的方案它的天花板在哪里,它的天花板特别明显。有一个思路是能不能通过 MySQL 的 server 把 InnoDB 变成一个分布式数据库,听起来这个方案很完美,但是很快就会遇到天花板。因为 MySQL 生成的执行计划是个单机的,它认为整个计划的 cost 也是单机的,我读取一行和读取下一行之间的开销是很小的,比如迭代 next row 可以立刻拿到下一行。实际上在一个分布式系统里面,这是不一定的。


另外,你把数据都拿回来计算这个太慢了,很多时候我们需要把我们的 expression 或者计算过程等等运算推下去,向上返回一个最终的计算结果,这个一定要用分布式的 plan,前面控制执行计划的节点,它必须要理解下面是分布式的东西,才能生成最好的 plan,这样才能实现最高的执行效率。


比如说你做一个 sum,你是一条条拿回来加,还是让一堆机器一起算,最后给我一个结果。 例如我有 100 亿条数据分布在 10 台机器上,并行在这 10 台 机器我可能只拿到 10 个结果,如果把所有的数据每一条都拿回来,这就太慢了,完全丧失了分布式的价值。聊到 MySQL 想实现分布式,另外一个实现分布式的方案是什么,就是 Proxy。但是 Proxy 本身的天花板在那里,就是它不支持分布式的 transaction,它不支持跨节点的 join,它无法理解复杂的 plan,一个复杂的 plan 打到 Proxy 上面,Proxy 就傻了,我到底应该往哪一个节点上转发呢,如果我涉及到 subquery sql 怎么办?所以这个天花板是瞬间会到,在传统模型下面的修改,很快会达不到我们的要求。


另外一个很重要的是,MySQL 支持的复制方式是半同步或者是异步,但是半同步可以降级成异步,也就是说任何时候数据出了问题你不敢切换,因为有可能是异步复制,有一部分数据还没有同步过来,这时候切换数据就不一致了。前一阵子出现过某公司突然不能支付了这种事件,今年有很多这种类似的 case,所以微博上大家都在说“说好的异地多活呢?”……


为什么传统的方案在这上面解决起来特别的困难,天花板马上到了,基本上不可能解决这个问题。另外是多数据中心的复制和数据中心的容灾,MySQL 在这上面是做不好的。
屏幕快照 2016-08-03 下午3.49.40.png-109.8kB
在前面三十年基本上是关系数据库的时代,那个时代创建了很多伟大的公司,比如说 IBM、Oracle、微软也有自己的数据库,早期还有一个公司叫 Sybase,有一部分特别老的程序员同学在当年的教程里面还可以找到这些东西,但是现在基本上看不到了。
另外是 NoSQL。NoSQL 也是一度非常火,像 Cassandra,MongoDB 等等,这些都属于在互联网快速发展的时候创建这些能够 scale 的方案,但 Redis scale 出来比较晚,所以很多时候大家把 Redis 当成一个 Cache,现在慢慢大家把它当成存储不那么重要的数据的数据库。因为它有了 scale 支持以后,大家会把更多的数据放在里面。
然后到了 2015,严格来讲是到 2014 年到 2015 年之间,Raft 论文发表以后,真正的 NewSQL 的理论基础终于完成了。我觉得 NewSQL 这个理论基础,最重要的划时代的几篇论文,一个是谷歌的 Spanner,是在 2013 年初发布的,再就是 Raft 是在 2014 年上半年发布的。这几篇相当于打下了分布式数据库 NewSQL 的理论基础,这个模型是非常重要的,如果没有模型在上面是堆不起来东西的。说到现在,大家可能对于模型还是可以理解的,但是对于它的实现难度很难想象。


前面我大概提到了我们为什么需要另外一个数据库,说到 Scalability 数据的伸缩,然后我们讲到需要 SQL,比如你给我一个纯粹的 key-velue 系统的 API,比如我要查找年龄在 10 岁到 20 岁之间的 email 要满足一个什么要求的。如果只有 KV 的 API 这是会写死人的,要写很多代码,但是实际上用 SQL 写一句话就可以了,而且 SQL 的优化器对整个数据的分布是知道的,它可以很快理解你这个 SQL,然后会得到一个最优的 plan,他得到这个最优的 plan 基本上等价于一个真正理解 KV 每一步操作的人写出来的程序。通常情况下,SQL 的优化器是为了更加了解或者做出更好的选择。


另外一个就是 ACID 的事务,这是传统数据库必须要提供的基础。以前你不提供 ACID 就不能叫数据库,但是近些年大家写一个内存的 map 也可以叫自己是数据库。大家写一个 append-only 文件,我们也可以叫只读数据库,数据库的概念比以前极大的泛化了。


另外就是高可用和自动恢复,他们的概念是什么呢?有些人会有一些误解,因为今天还有朋友在现场问到,出了故障,比如说一个机房挂掉以后我应该怎么做切换,怎么操作。这个实际上相当于还是上一代的概念,还需要人去干预,这种不算是高可用。


未来的高可用一定是系统出了问题马上可以自动恢复,马上可以变成可用。比如说一个机房挂掉了,十秒钟不能支付,十秒钟之后系统自动恢复了变得可以支付,即使这个数据中心再也不起来我整个系统仍然是可以支付的。Auto-Failover 的重要性就在这里。大家不希望在睡觉的时候被一个报警给拉起来,我相信大家以后具备这样一个能力,5 分钟以内的报警不用理会,挂掉一个机房,又挂掉一个机房,这种连续报警才会理。我们内部开玩笑说,希望大家都能睡个好觉,很重要的事情就是这个。


说完应用层的事情,现在很有很多业务,在应用层自己去分片,比如说我按照 user ID 在代码里面分片,还有一部分是更高级一点我会用到一致性哈希。问题在于它的复杂度,到一定程度之后我自动的分库,自动的分表,我觉得下一代数据库是不需要理解这些东西的,不需要了解什么叫做分库,不需要了解什么叫做分表,因为系统是全部自动搞定的。同时复杂度,如果一个应用不支持事务,那么在应用层去做,通常的做法是引入一个外部队列,引入大量的程序机制和状态转换,A 状态的时候允许转换到 B 状态,B 状态允许转换到 C 状态。


举一个简单的例子,比如说在京东上买东西,先下订单,支付状态之后这个商品才能出库,如果不是支付状态一定不能出库,每一步都有严格的流程。


Google Spanner / F1


说一下 Google 的 Spanner 和 F1,这是我非常喜欢的论文,也是我最近几年看过很多遍的论文。Google Spanner 已经强大到什么程度呢?Google Spanner 是全球分布的数据库,在国内目前普遍做法叫做同城两地三中心,它们的差别是什么呢?以 Google 的数据来讲,谷歌比较高的级别是他们有 7 个副本,通常是美国保存 3 个副本,再在另外 2 个国家可以保存 2 个副本,这样的好处是万一美国两个数据中心出了问题,那整个系统还能继续可用,这个概念就是比如美国 3 个副本全挂了,整个数据都还在,这个数据安全级别比很多国家的安全级别还要高,这是 Google 目前做到的,这是全球分布的好处。


现在国内主流的做法是两地三中心,但现在基本上都不能自动切换。大家可以看到很多号称实现了两地三中心或者异地多活,但是一出现问题都说不好意思这段时间我不能提供服务了。大家无数次的见到这种 case,我就不列举了。


Spanner 现在也提供一部分 SQL 特性。在以前,大部分 SQL 特性是在 F1 里面提供的,现在 Spanner 也在逐步丰富它的功能,Google 是全球第一个做到这个规模或者是做到这个级别的数据库。事务支持里面 Google 有点黑科技(其实也没有那么黑),就是它有 GPS 时钟和原子钟。大家知道在分布式系统里面,比如说数千台机器,两个事务启动先后顺序,这个顺序怎么界定(事务外部一致性)。这个时候 Google 内部使用了 GPS 时钟和原子钟,正常情况下它会使用一个 GPS 时钟的一个集群,就是说我拿的一个时间戳,并不是从一个 GPS 上来拿的时间戳,因为大家知道所有的硬件都会有误差。如果这时候我从一个上拿到的 GPS 本身有点问题,那么你拿到的这个时钟是不精确的。而 Google 它实际上是在一批 GPS 时钟上去拿了能够满足 majority 的精度,再用时间的算法,得到一个比较精确的时间。同时大家知道 GPS 也不太安全,因为它是美国军方的,对于 Google 来讲要实现比国家安全级别更高的数据库,而 GPS 是可能受到干扰的,因为 GPS 信号是可以调整的,这在军事用途上面很典型的,大家知道导弹的制导需要依赖 GPS,如果调整了 GPS 精度,那么导弹精度就废了。所以他们还用原子钟去校正 GPS,如果 GPS 突然跳跃了,原子钟上是可以检测到 GPS 跳跃的,这部分相对有一点黑科技,但是从原理上来讲还是比较简单,比较好理解的。


最开始它 Spanner 最大的用户就是 Google 的 Adwords,这是 Google 最赚钱的业务,Google 就是靠广告生存的,我们一直觉得 Google 是科技公司,但是他的钱是从广告那来的,所以一定程度来讲 Google 是一个广告公司。Google 内部的方向先有了 Big table ,然后有了 MegaStore ,MegaStore 的下一代是 Spanner ,F1 是在 Spanner 上面构建的。


TiDB and TiKV


TiKV 和 TiDB 基本上对应 Google Spanner 和 Google F1,用 Open Source 方式重建。目前这两个项目都开放在 GitHub 上面,两个项目都比较火爆,TiDB 是更早一点开源的, 目前 TiDB 在 GitHub 上 有 4300 多个 Star,每天都在增长。
另外,对于现在的社会来讲,我们觉得 Infrastructure 领域闭源的东西是没有任何生存机会的。没有任何一家公司,愿意把自己的身家性命压在一个闭源的项目上。举一个很典型的例子,在美国有一个数据库叫 FoundationDB,去年被苹果收购了。 FoundationDB 之前和用户签的合约都是一年的合约。比如说,我给你服务周期是一年,现在我被另外一个公司收购了,我今年服务到期之后,我是满足合约的。但是其他公司再也不能找它服务了,因为它现在不叫 FoundationDB 了,它叫 Apple了,你不能找 Apple 给你提供一个 enterprise service。
8.png-81.6kB
TiDB 和 TiKV 为什么是两个项目,因为它和 Google 的内部架构对比差不多是这样的:TiKV 对应的是 Spanner,TiDB 对应的是 F1 。F1 里面更强调上层的分布式的 SQL 层到底怎么做,分布式的 Plan 应该怎么做,分布式的 Plan 应该怎么去做优化。同时 TiDB 有一点做的比较好的是,它兼容了 MySQL 协议,当你出现了一个新型的数据库的时候,用户使用它是有成本的。大家都知道作为开发很讨厌的一个事情就是,我要每个语言都写一个 Driver,比如说你要支持 C++,你要支持 Java,你要支持 Go 等等,这个太累了,而且用户还得改他的程序,所以我们选择了一个更加好的东西兼容 MySQL 协议,让用户可以不用改。一会我会用一个视频来演示一下,为什么一行代码不改就可以用,用户就能体会到 TiDB 带来的所有的好处。
9.png-134kB
这个图实际上是整个协议栈或者是整个软件栈的实现。大家可以看到整个系统是高度分层的,从最底下开始是 RocksDB ,然后再上面用 Raft 构建一层可以被复制的 RocksDB,在这一层的时候它还没有 Transaction,但是整个系统现在的状态是所有写入的数据一定要保证它复制到了足够多的副本。也就是说只要我写进来的数据一定有足够多的副本去 cover 它,这样才比较安全,在一个比较安全的 Key-value store 上面, 再去构建它的多版本,再去构建它的分布式事务,然后在分布式事务构建完成之后,就可以轻松的加上 SQL 层,再轻松的加上 MySQL 协议的支持。然后,这两天我比较好奇,自己写了 MongoDB 协议的支持,然后我们可以用 MongoDB 的客户端来玩,就是说协议这一层是高度可插拔的。TiDB 上可以在上面构建一个 MongoDB 的协议,相当于这个是构建一个 SQL 的协议,可以构建一个 NoSQL 的协议。这一点主要是用来验证 TiKV 在模型上面的支持能力。
10.png-117.2kB
这是整个 TiKV 的架构图,从这个看来,整个集群里面有很多 Node,比如这里画了四个 Node,分别对应了四个机器。每一个 Node 上可以有多个 Store,每个 Store 里面又会有很多小的 Region,就是说一小片数据,就是一个 Region 。从全局来看所有的数据被划分成很多小片,每个小片默认配置是 64M,它已经足够小,可以很轻松的从一个节点移到另外一个节点,Region 1 有三个副本,它分别在 Node1、Node 2 和 Node4 上面, 类似的Region 2,Region 3 也是有三个副本。每个 Region 的所有副本组成一个 Raft Group, 整个系统可以看到很多这样的 Raft groups。


Raft 细节我不展开了,大家有兴趣可以找我私聊或者看一下相应的资料。


因为整个系统里面我们可以看到上一张图里面有很多 Raft group 给我们,不同 Raft group 之间的通讯都是有开销的。所以我们有一个类似于 MySQL 的 group commit 机制 ,你发消息的时候实际上可以 share 同一个 connection , 然后 pipeline + batch 发送, 很大程度上可以省掉大量 syscall 的开销。


另外,其实在一定程度上后面我们在支持压缩的时候,也有非常大的帮助,就是可以减少数据的传输。对于整个系统而言,可能有数百万的 Region,它的大小可以调整,比如说 64M、128M、256M,这个实际上依赖于整个系统里面当前的状况。


比如说我们曾经在有一个用户的机房里面做过测试,这个测试有一个香港机房和新加坡的机房。结果我们在做复制的时候,新加坡的机房大于 256M 就复制不过去,因为机房很不稳定,必须要保证数据切的足够小,这样才能复制过去。


如果一个 Region 太大以后我们会自动做 SPLIT,这是非常好玩的过程,有点像细胞的分裂。


然后 TiKV 的 Raft 实现,是从 etcd 里面 port 过来的,为什么要从 etcd 里面 port 过来呢?首先 TiKV 的 Raft 实现是用 Rust 写的。作为第一个做到生产级别的 Raft 实现,所以我们从 etcd 里面把它用 Go 语言写的 port 到这边。
12.png-315.9kB
这个是 Raft 官网上面列出来的 TiKV 在里面的状态,大家可以看到 TiKV 把所有 Raft 的 feature 都实现了。 比如说 Leader Election、Membership Changes,这个是非常重要的,整个系统的 scale 过程高度依赖 Membership Changes,后面我用一个图来讲这个过程。后面这个是 Log Compaction,这个用户不太关心。
13.png-695.5kB
这是很典型的细胞分裂的图,实际上 Region 的分裂过程和这个是类似的。


我们看一下扩容是怎么做的。
14.png-150.8kB
比如说以现在的系统假设,我们刚开始说只有三个节点,有 Region1 分别是在 1 、2、4,我用虚线连接起来代表它是 一个 Raft group ,大家可以看到整个系统里面有三个 Raft group,在每一个 Node 上面数据的分布是比较均匀的,在这个假设每一个 Region 是 64M ,相当于只有一个 Node 上面负载比其他的稍微大一点点。


这是一个在线的视频。默认的时候,我们都是推荐 3 个副本或者 5 个副本的配置。Raft 本身有一个特点,如果一个 leader down 掉之后,其它的节点会选一个新的 leader,那么这个新的 leader 会把它还没有 commit 但已经 reply 过去的 log 做一个 commit ,然后会再做 apply,这个有点偏 Raft 协议,细节我不讲了。


复制数据的小的 Region,它实际上是跨多个数据中心做的复制。这里面最重要的一点是永远不丢失数据,无论如何我保证我的复制一定是复制到 majority,任何时候我只要对外提供服务,允许外面写入数据一定要复制到 majority。很重要的一点就是恢复的过程一定要是自动化的,我前面已经强调过,如果不能自动化恢复,那么中间的宕机时间或者对外不可服务的时间,便不是由整个系统决定的,这是相对回到了几十年前的状态。


MVCC


MVCC 我稍微仔细讲一下这一块。MVCC 的好处,它很好支持 Lock-free 的 snapshot read ,一会儿我有一个图会展示 MVCC 是怎么做的。isolation level 就不讲了,MySQL 里面的级别是可以调的,我们的 TiKV 有 SI,还有 SI+lock,默认是支持 SI 的这种隔离级别,然后你写一个 select for update 语句,这个会自动的调整到 SI 加上 lock 这个隔离级别。这个隔离级别基本上和 SSI 是一致的。还有一个就是 GC 的问题,如果你的系统里面的数据产生了很多版本,你需要把这个比较老的数据给 GC 掉,比如说正常情况下我们是不删除数据的, 你写入一行,然后再写入一行,不断去 update 同一行的时候,每一次 update 会产生新的版本,新的版本就会在系统里存在,所以我们需要一个 GC 的模块把比较老的数据给 GC 掉,实际上这个 GC 不是 Go 里面的GC,不是 Java 的 GC,而是数据的 GC。
19.png-138.5kB
这是一个数据版本,大家可以看到我们的数据分成两块,一个是 meta,一个是 data。meta 相对于描述我的数据当前有多少个版本。大家可以看到绿色的部分,比如说我们的 meta key 是 A,keyA 有三个版本,是 A1、A2、A3,我们把 key 自己和 version 拼到一起。那我们用 A1、A2、A3 分别描述 A 的三个版本,那么就是 version 1/2/3。meta 里面描述,就是我的整个 key 相对应哪个版本,我想找到那个版本。比如说我现在要读取 key A 的版本 10,但显然现在版本 10 是没有的,那么小于版本 10 最大的版本是 3,所以这时我就能读取到 3,这是它的隔离级别决定的。关于 data,我刚才已经讲过了。


分布式事务模型


接下来是分布式事务模型,其实是基于 Google Percolator,这是 Google 在 2006 发表的一篇论文,是 Google 在做内部增量处理的时候发现了这个方法,本质上还是二阶段提交的。这使用的是一个乐观锁,比如说我提供一个 transaction ,我去改一个东西,改的时候是发布在本地的,并没有马上 commit 到数据存储那一端,这个模型就是说,我修改的东西我马上去 Lock 住,这个基本就是一个悲观锁。但如果到最后一刻我才提交出去,那么锁住的这一小段的时间,这个时候实现的是乐观锁。乐观锁的好处就是当你冲突很小的时候可以得到非常好的性能,因为冲突特别小,所以我本地修改通常都是有效的,所以我不需要去 Lock ,不需要去 roll back 。本质上分布式事务就是 2PC 或者是 2+xPC,基本上没有 1PC,除非你在别人的级别上做弱化。比如说我允许你读到当前最新的版本,也允许你读到前面的版本,书里面把这个叫做幻读。如果你调到这个程度是比较容易做 1PC 的,这个实际上还是依赖用户设定的隔离级别的,如果用户需要更高的隔离级别,这个 1PC 就不太好做了。
这是一个路由,正常来讲,大家可能会好奇一个 SQL 语句怎么最后会落到存储层,然后能很好的运行,最后怎么能映射到 KV 上面,又怎么能路由到正确的节点,因为整个系统可能有上千个节点,你怎么能正确路由到那一个的节点。我们在 TiDB 有一个 TiKV driver , 另外 TiKV 对外使用的是 Google Protocol Buffer 来作为通讯的编码格式。


Placement Driver


来说一下 Placement Driver 。Placement Driver 是什么呢?整个系统里面有一个节点,它会时刻知道现在整个系统的状态。比如说每个机器的负载,每个机器的容量,是否有新加的机器,新加机器的容量到底是怎么样的,是不是可以把一部分数据挪过去,是不是也是一样下线, 如果一个节点在十分钟之内无法被其他节点探测到,我认为它已经挂了,不管它实际上是不是真的挂了,但是我也认为它挂了。因为这个时候是有风险的,如果这个机器万一真的挂了,意味着你现在机器的副本数只有两个,有一部分数据的副本数只有两个。那么现在你必须马上要在系统里面重新选一台机器出来,它上面有足够的空间,让我现在只有两个副本的数据重新再做一份新的复制,系统始终维持在三个副本。整个系统里面如果机器挂掉了,副本数少了,这个时候应该会被自动发现,马上补充新的副本,这样会维持整个系统的副本数。这是很重要的 ,为了避免数据丢失,必须维持足够的副本数,因为副本数每少一个,你的风险就会再增加。这就是 Placement Driver 做的事情。
同时,Placement Driver 还会根据性能负载,不断去 move 这个 data 。比如说你这边负载已经很高了,一个磁盘假设有 100G,现在已经用了 80G,另外一个机器上也是 100G,但是他只用了 20G,所以这上面还可以有几十 G 的数据,比如 40G 的数据,你可以 move 过去,这样可以保证系统有很好的负载,不会出现一个磁盘巨忙无比,数据已经多的装不下了,另外一个上面还没有东西,这是 Placement Driver 要做的东西。


Raft 协议还提供一个很高级的特性叫 leader transfer。leader transfer 就是说在我不移动数据的时候,我把我的 leadership 给你,相当于从这个角度来讲,我把流量分给你,因为我是 leader,所以数据会到我这来,但我现在把 leader 给你,我让你来当 leader,原来打给我的请求会被打给你,这样我的负载就降下来。这就可以很好的动态调整整个系统的负载,同时又不搬移数据。不搬移数据的好处就是,不会形成一个抖动。


MySQL Sharding


MySQL Sharding 我前面已经提到了它的各种天花板,MySQL Sharding 的方案很典型的就是解决基本问题以后,业务稍微复杂一点,你在 sharding 这一层根本搞不定。它永远需要一个 sharding key,你必须要告诉我的 proxy,我的数据要到哪里找,对用户来说是极不友好的,比如我现在是一个单机的,现在我要切入到一个分布式的环境,这时我必须要改我的代码,我必须要知道我这个 key ,我的 row 应该往哪里 Sharding。如果是用 ORM ,这个基本上就没法做这个事情了。有很多 ORM 它本身假设我后面只有一个 MySQL。但 TiDB 就可以很好的支持,因为我所有的角色都是对的,我不需要关注 Sharding、分库、分表这类的事情。


这里面有一个很重要的问题没有提,我怎么做 DDL。如果这个表非常大的话,比如说我们有一百亿吧,横跨了四台机器,这个时候你要给它做一个新的 Index,就是我要添加一个新的索引,这个时候你必须要不影响任何现有的业务,实际上这是多阶段提交的算法,这个是 Google 和 F1 一起发出来那篇论文。


简单来讲是这样的,先把状态标记成 delete only ,delete only 是什么意思呢?因为在分布式系统里面,所有的系统对于 schema 的视野不是一致的,比如说我现在改了一个值,有一部分人发现这个值被改了,但是还有一部分人还没有开始访问这个,所以根本不知道它被改了。然后在一个分布系统里,你也不可能实时通知到所有人在同一时刻发现它改变了。比如说从有索引到没有索引,你不能一步切过去,因为有的人认为它有索引,所以他给它建了一个索引,但是另外一个机器他认为它没有索引,所以他就把数据给删了,索引就留在里面了。这样遇到一个问题,我通过索引找的时候告诉我有, 实际数据却没有了,这个时候一致性出了问题。比如说我 count 一个 email 等于多少的,我通过 email 建了一个索引,我认为它是在,但是 UID 再转过去的时候可能已经不存在了。


比如说我先标记成 delete only,我删除它的时候不管它现在有没有索引,我都会尝试删除索引,所以我的数据是干净的。如果我删除掉的话,我不管结果是什么样的,我尝试去删一下,可能这个索引还没 build 出来,但是我仍然删除,如果数据没有了,索引一定没有了,所以这可以很好的保持它的一致性。后面再类似于前面,先标记成 write only 这种方式, 连续再迭代这个状态,就可以迭代到一个最终可以对外公开的状态。比如说当我迭代到一定程度的时候,我可以从后台 build index ,比如说我一百亿,正在操作的 index 会马上 build,但是还有很多没有 build index ,这个时候后台不断的跑 map-reduce 去 build index ,直到整个都 build 完成之后,再对外 public ,就是说我这个索引已经可用了,你可以直接拿索引来找,这个是非常经典的。在这个 Online, Asynchronous Schema Change in F1 paper 之前,大家都不知道这事该怎么做。


Proxy Sharding 的方案不支持分布式事务,更不用说跨数据中心的一致性事务了。 TiKV 很好的支持 transaction,刚才提到的 Raft 除了增加副本之外,还有 leader transfer,这是一个传统的方案都无法提供的特性。以及它带来的好处,当我瞬间平衡整个系统负载的时候,对外是透明的, 做 leader transfer 的时候并不需要移动数据, 只是个简单的 leader transfer 消息。


然后说一下如果大家想参与我们项目的话是怎样的过程,因为整个系统是完全开源的,如果大家想参与其中任何一部分都可以,比如说我想参与到分布式 KV,可以直接贡献到 TiKV。TiKV 需要写 Rust,如果大家对这块特别有激情可以体验写 Rust 的感觉 。


TiDB 是用 Go 写的,Go 在中国的群众基础是非常多的,目前也有很多人在贡献。整个 TiDB 和TiKV 是高度协作的项目,因为 TiDB 目前还用到了 etcd,我们在和 CoreOS 在密切的合作,也特别感谢 CoreOS 帮我们做了很多的支持,我们也为 CoreOS 的 etcd 提了一些 patch。同时,TiKV 使用 RocksDB ,所以我们也为 RocksDB 提了一些 patch 和 test,我们也非常感谢 Facebook RocksDB team 对我们项目的支持。


另外一个是 PD,就是我们前面提的 Placement Driver,它负责监控整个系统。这部分的算法比较好玩,大家如果有兴趣的话,可以去自己控制整个集群的调度,它和 k8s 或者是 Mesos 的调度算法是不一样的,因为它调度的维度实际上比那个要更多。比如说磁盘的容量,你的 leader 的数量,你的网络当前的使用情况,你的 IO 的负载和 CPU 的负载都可以放进去。同时你还可以让它调度不要跨一个机房里面建多个副本。


原文链接

[CDAS 2016] 分布式数据库模式与反模式

文章分享qiuyesuifeng 发表了文章 • 0 个评论 • 350 次浏览 • 2016-10-11 15:12 • 来自相关话题

TiDB 是一个 OLTP 的数据库,我们主要 focus 在大数据的关系型数据库的存储和可扩展性,还有关系的模型,以及在线交易型数据库上的应用。所以,今天整个数据库的模式和反模式,我都会围绕着如何在一个海量的并发,海量的数据存... 查看全部


TiDB 是一个 OLTP 的数据库,我们主要 focus 在大数据的关系型数据库的存储和可扩展性,还有关系的模型,以及在线交易型数据库上的应用。所以,今天整个数据库的模式和反模式,我都会围绕着如何在一个海量的并发,海量的数据存储的容量上,去做在线实时的数据库业务的一些模式来讲。并从数据库的开发者角度,来为大家分享怎样写出更加适合数据库的一些程序。



基础软件的发展趋势


先简单介绍一下,现在我认为的一些基础软件上的发展趋势。


开源


第一点,开源是一个非常大的趋势。大家可以看到一些比较著名的基础软件,基本都是开源的,比如 Docker,比如 k8s。甚至在互联网公司里面用的非常多的软件,像 MySQL、Hadoop 等这种新一代的大数据处理的数据库等基础软件,也大多是开源的。其实这背后的逻辑非常简单:在未来其实你很难去将你所有的技术软件都用闭源, 因为开源会慢慢组成一个生态,而并不是被某一个公司绑定住。比如国家经常说去 IOE,为什么?很大的原因就是基本上你的业务是被基础软件绑死的,这个其实是不太好的一个事情。而且现在跟过去二十年前不一样,无论是开源软件的质量,还是社区的迭代速度,都已经是今非昔比,所以基本上开源再也不是低质低量的代名词,在互联网公司已经被验证很多次了。


分布式


第二,分布式会渐渐成为主流的趋势。这是为什么?这个其实也很好理解,因为随着数据量越来越大,大家可以看到,随着现在的硬件发展,我感觉摩尔定律有渐渐失效的趋势。所以单个节点的计算资源或者计算能力,它的增长速度是远比数据的增长速度要慢的。在这种情况下,你要完成业务,存储数据,要应对这么大的并发,只有一种办法就是横向的扩展。横向的扩展,分布式基本是唯一的出路。scale-up 和 scale-out 这两个选择其实我是坚定的站在 scale-out 这边。当然传统的关系数据库都会说我现在用的 Oracle,IBM DB2,他们现在还是在走 scale-up 的路线,但是未来我觉得 scale-out 的方向会渐渐成为主流。


碎片化


第三,就是整个基础软件碎片化。现在看上去会越来越严重。但是回想在十年前、二十年前,大家在写程序的时候,我上面一层业务,下面一层数据库。但是现在你会发现,随着可以给你选择的东西越来越多,可以给你在开源社区里面能用到的组件越来越多,业务越来越复杂,你会发现,像缓存有一个单独的软件,比如 redis,队列又有很多可以选择的,比如说 zeromq,rabbitmq,celery 各种各样的队列;数据库有 NoSQL、HBase,关系型数据库有 MySQL、PG 等各种各样的基础软件都可以选。但是就没有一个非常好东西能够完全解决自己的问题。所以这是一个碎片化的现状。


微服务


第四,是微服务的模式兴起。其实这个也是最近两年在软件架构领域非常火的一个概念。这个概念的背后思想,其实也是跟当年的 SOA 是一脉相承的。就是说一个大的软件项目,其实是非常难去 handle 复杂度的,当你业务变得越来越大以后,维护成本和开发成本会随着项目的代码量呈指数级别上升的。所以现在比较流行的就是,把各个业务之间拆的非常细,然后互相之间尽量做到无状态,整个系统的复杂度可以控制,是由很多比较简单的小的组件组合在一起,来对外提供服务的。
这个服务看上去非常美妙,一会儿会说有什么问题。最典型的问题就是,当你的上层业务都拆成无状态的小服务以后,你会发现原有的逻辑需要有状态的存储服务的时候你是没法拆的。我所有的业务都分成一小块,每一小块都是自己的数据库或者数据存储。比如说一个简单的 case,我每一个小部分都需要依赖同一个用户信息服务,这个信息服务会变成整个系统的一个状态集中的点,如果这个点没有办法做弹性扩展或者容量扩展的话,就会变成整个系统很致命的单点。


所以现在整个基础软件的现状,特别在互联网行业是非常典型的几个大的趋势。我觉得大概传统行业跟互联网行业整合,应该在三到五年,这么一个时间。所以互联网行业遇到的今天,可能就是传统行业,或者其他的行业会遇到的明天。所以,通过现在整个互联网里面,在数据存储、数据架构方面的一些比较新的思想,我们就能知道如何去做这个程序的设计,应对明天数据的量级。


现有存储系统的痛点


其实今天主要的内容是讲存储系统,存储系统现在有哪些痛点?其实我觉得在座的各位应该也都能切身的体会到。


弹性扩展


首先,大数据量级下你如何实现弹性扩展?因为我们今天主要讨论的是 OLTP ,是在线的存储服务,并不是离线分析的服务。所以在线的存储服务,它其实要做到的可用性、一致性,是要比离线的分析业务强得多的。但是在这种情况下,你们怎样做到业务无感知的弹性扩展,你的数据怎么很好的满足现有的高并发、大吞吐,还有数据容量的方案。


可用性


第二,在分布式的存储系统下,你的应用的可用性到底是如何去定义,如何去保证?其实这个也很好理解,因为在大规模的分布式系统里面,任何一个节点,任何一个数据中心或者支架都有可能出现硬件的故障,软件的故障,各种各样的故障,但这个时候你很多业务是并没有办法停止,或者并没有办法去容忍 down time 的。所以在一个新的环境之下,你如何对你系统的可用性做定义和保证,这是一个新的课题。一会儿我会讲到最新的研究方向和成果。


可维护性


第三,对于大规模的分布式数据库来说它的可维护性,这个怎么办?可维护性跟单机的系统是明显不同的,因为单机的数据库,或者传统的单点的数据库,它其实做到主从,甚至做到一主多从,我去维护 master ,别让它挂掉,这个维护性主要就是维护单点。在一个大规模的分布式系统上,你去做这个事情是非常麻烦的。可以简单说一个案例,就是 Google 的 Spanner。Spanner 是 Google 内部的一个大规模分布式系统,整个谷歌内部只部署了一套,在生产环节中只部署了一套。这一套系统上有上万甚至上数十万的物理节点。但是整个数据库的维护团队,其实只有很小的一组人。想像一下,上十万台的物理节点,如果你要真正换一块盘、做一次数据恢复或者人工运维的话,这是根本不可能做到的事情。但是对于一个分布式系统来说,它的可维护性或者说它的维护应该是转嫁给数据库自己。


开发复杂度


还有,就是对于一个分布式数据库来说,它在开发业务的时候复杂度是怎么样的。大家其实可能接触的比较多的,像 Hbase、Cassandra、Bigtable 等这种开源的实现,像 NoSQL 数据库它其实并没有一个很好的 cross-row transaction 的 support。另外,对于很多的 NoSQL 数据库并没有一个很好的 SQL 的 interface,这会让你写程序变得非常麻烦。比如说对于一些很普通的业务,一个表,我需要去 select from table,然后有一个 fliter 比如一个条件大于 10,小于 100,这么简单的逻辑,如果在 HBase 上去做的话,你要写十行、二十行、三十行;如果你在一个关系的数据库,或者支持 SQL 的数据库,其实一行就搞定了。其实这个对于很多互联网公司来说,在过去的几年之内基本上已经完成了这种从 RDBMS 到 NoSQL 的改造,但是这个改造的成本和代价是非常非常高的。比如我原来的业务可能在很早以前是用 MySQL 已经写的稳定运行好久了,但是随着并发、容量、可扩展性的要求,我需要迁移 Bigtable、Hbase、Cassandra、MongoDB 这种 NoSQL 数据库上,这时基本上就要面临代码的完整重写。这个要放在互联网公司还可以,因为它们有这样的技术能力去保证迁移的过程。反正我花这么多钱,招这么牛的工程师,你要帮我搞定这个事情。但是对于传统的行业,或者传统的机构来说,这个基本上是不可能的事情。你不可能让他把原来用 Oracle 用SQL 的代码改成 NoSQL 的 code。
因为 NoSQL 很少有跨行事务,首先你要做一个转账,你如果不是一个很强的工程师,你这个程序基本写不对,这是一个很大的问题。这也是为什么一直以来像这种 NoSQL 的东西并没有很好的在传统行业中去使用的一个最核心的原因,就是代价实在太大。


存储系统的扩展模型


所以其实在去讲这些具体到底该怎么解决,或者未来数据库会是什么样的之前,我想简单讲一下扩展的模型。对于一个关系型数据库也好,对于存储的系统本身也好,它的扩展模型有哪些。


Sharding 模式


第一种模式是 Sharding 模式。如果在座的各位有运维过线上的 MySQL 的话,对这个模型会非常熟悉。最简单的就是分库、分表加中间件,就是说我不同的业务可能用不同的库,不同的表。当一个单表太大的时候,我通过一些 Cobar、Mycat 等这样的数据库中间件来去把它分发到具体的数据库的实例上。这种模型是目前用的最普遍的模型,它其实也解决了很大部分的问题。为什么这十年在关系型数据库上并没有很好的扩展方案,但是大家看上去这种业务还没有出现死掉的情况,就是因为后面有各种各样 Sharding 的中间件或者分库分表这种策略在硬扛着。像这种中间件 Sharding 第一个优势就是实现非常简单。你并不需要对数据库内做任何的改造,你也并不需要去比如说从你原来的 SQL 代码转到 NoSQL 的代码。
但是它也有自己的缺点。首先,对你的业务层有很强的侵入性。这是没有办法的,比如你想用一个中间件,你就需要给它指定一个 Sharding key。另外,原来比如你的业务有一些 join ,有一些跨表跨行的事务,像这种事务你必须得改掉,因为很多中间件并没有办法支持这个跨 shard 的分布式 join。
第二个比较大的缺陷是它的分片基本是固定的,自动化程度、扩展性都非常差,你必须得有一个专职的 DBA 团队给你的 MySQL 或者 PG 的 Sharding 的集群去做运维。我之前在豌豆荚做过一段时间 MySQL cluster 的分片的维护工作。当时我记得是一个 16 个节点的 MySQL 的集群,我们需要扩展到 32 个节点的规模,整整提前演练了一个月,最后上线了一个礼拜。上线那个礼拜,晚上基本上没有办法睡觉,所以非常痛苦。
再说一个 Google 的事情,Google 在刚才我说的 Spanner 和 F1 这两个数据库没有上线之前,Google 的广告系统的业务是由 100 多个节点的 MySQL 的集群对外提供服务的。如 Google 这么牛的公司,在维护一百多个节点的 MySQL Sharding 的数据库的时候,都已经非常痛苦,宁可重新去写一个数据库,也不想去维护这个 datebase cluster。其实大家可以看到,像这种 Sharding 的方案,它比较大的问题就是它的维护代价或者维护集群的复杂度,并不是随着节点数呈线性增长,而是随着节点的增加非线性的增长上去。比如你维护 2 个节点的还好,维护 4 个节点的也还可以,但是你维护 16 个、64 个、128 个基本就是不可能的事情。
第三就是一些复杂的查询优化,并没有办法在中间件这一层,去帮你产生一个足够优化的执行计划,因此,对于一些复杂查询来说,Sharding 的方案是没法做的。所以对你的业务层有很高的要求。


Region Base 模型


第二种扩展模型是 Region Base。这张图是我项目里面扒出来的图。
分布式数据库模式与反模式 图1.png-296.9kB
它整个思路有点像 Bigtable,它相当于把底下的存储层分开,数据在最底层存储上已经没有表、行这样结构的划分,每一块数据都由一个固定的 size 比如 64 M、128 M 连续的 Key-value pairs 组成。其实这个模型背后最早的系统应该是谷歌在 06 年发表的 Bigtable 这篇论文里面去描述的。这个模型有什么好处呢?一是它能真正实现这种弹性的扩展。第二个,它是一个真正高度去中心化。去中心化这个事情,对于一个大的 Cluster 来说是一个非常重要的特性。
还有一个优势,在 KV 层实现真正具有一定的自动 Failover 的能力。 Failover指的是什么呢?比如说在一个集群比较大的情况下,或者你是一个 cluster,你任何一个节点,任何一个数据损坏,如果能做到业务端的透明,你就真正实现了 Auto-Failover 的能力。其实在一些对一致性要求不那么高的业务里面,Auto-Failover 就是指, 比如在最简单的一个 MySQL 组从的模型里,当你的组挂掉了以后,我监控的程序自动把 slave 提上来,这也是一种 Failover 的方式。但是这个一致性或者说数据的正确性并不能做到很好的保证。你怎么做到一致性的 Auto-Failover,其实背后需要做非常非常多的工作。
这是 Region 模型的一些优势。但是它的劣势也同样明显,这种模型的实现非常复杂。我一会儿会说到背后的关键技术和理论,但是它比起写中间件真的复杂太多了。你要写一个能用的 MySQL 或者 PG 的中间件,可能只需要一两个工程师,花一两周的时间就能写出一个能用的数据库中间件;但是你如果按照这个模型做一个弹性扩展的数据库的话,你的工作量就会是数量级的增加。
第二个劣势就是它业务层的兼容性。像 Region Base 的模型,最典型的分布式存储系统就是 HBase。HBase 它对外的编程接口和 SQL 是千差万别,因为它是一个 Key Value 的数据库。你的业务层的代码兼容性都得改,这个对于一些没有这么强开发能力的用户来说,是很难去使用的,或者它说没有 SQL 对于用户端这么友好。


可用性级别


我一会儿会讲一下,刚才我们由 Region Base 这个模型往上去思考的一些东西,在此之前先说一些可用性。高可用。其实说到高可用这个词,大多数的架构师都对它非常熟悉。我的系统是高可用的,任何一个节点故障业务层都不受影响,但是真的不受影响吗?我经过很多的思考得到的一个经验就是主从的模型是不可能保证同时满足强一致性和高可用性的。可能这一点很多人觉得,我主从,我主挂了,从再提起来就好,为什么不能保护这个一致性呢?就是因为在一个集群的环境下,有一种故障叫脑裂。脑裂是什么情况?整个集群是全网络联通的,但是出现一种情况,就是我只是在集群内部分成了两个互不联通的一个子集。这两个子集又可以对外提供服务,其实这个并不是非常少见的状况,经常会发生。像这种情况,你贸然把 slave 提起来,相当于原来的 master 并没有完全的被 shutdown,这个时候两边可能都会有读写的情况,造成数据非常严重的不一致,当然这个比较极端了。所以你会发现阿里或者说淘宝,年年都在说我们有异地多活。但是去年甚至前几个月,杭州阿里的数据中心光纤被挖断,支付宝并没有直接切到重复层,而是宁可停止服务,完全不动,也不敢把 slave 数据中心提起来。所以其实任何基于主从模型的异地多活方案都是不行的。这个问题有没有办法解决呢?其实也是有的。
还是说到 Google,我认为它才是全世界最大的数据库公司,因为它有全世界最大的数据量。你从来没有听说过 Google 哪一个业务因为哪一个数据中心光纤挖断,哪一个磁盘坏了而对外终止服务的,几乎完全没有。因为 Google 的存储系统大多完全抛弃了基于主从的一致性模型。它的所有数据都不是通过主从做复制的,而是通过类似 Raft 或者 Paxos 这种分布式选举的算法做数据的同步。这个算法的细节不展开了,总体来说是一个解决在数据的一致性跟自动的数据恢复方面的一个算法。同时,它的 latency 会比多节点强同步的主从平均表现要好的一个分布式选举的算法。
在 Google 内部其实一直用的 Paxos,它最新的 Spanner 数据库是用 Paxos 做的 replication 。在社区里面,跟 Paxos 等价的一个算法就是 Raft。Raft 这个算法的性能以及可靠性都是跟 Paxos 等价的实现。这个算法就不展开了。我认为这才是新一代的强一致的数据库应该使用的数据库复制模型。


分布式事务


说到事务。对于一个数据库来说,我要做传统的关系型数据库业务,事务在一个分布式环境下,并不像单机的数据库有这么多的方法,这么多的优化。其实在分布式事务这个领域只有一种方法,并且这么多年了从分布式事务开始到现在,在这个方法上并没有什么突破,基本只有一条出路就是两阶段提交。其实可以看一下 Google 的系统。对于我们做分布式系统的公司来说,Google 就是给大家带路的角色。Google 最新的数据库系统上它使用的分布式事务的方法仍然是两阶段提交。其实还有没有什么优化的路呢?其实也是有的。两阶段提交最大的问题是什么呢?一个是延迟。因为第一阶段先要把数据发过去,第二阶段要收到所有参与的节点的 response 之后你才能去 commit 。这个过程,相当于你走了很多次网络的 roundtrip,latency 也会变得非常高。所以其实优化的方向也是有的,但是你的 latency 没法优化,只能通过吞吐做优化,就是 throughput 。比如说我在一万个并发的情况下,每个用户的 latency 是 100 毫秒,但是一百万并发,一千万并发的时候,我每个用户的 latency 还可以是 100 毫秒,这在传统的单点关系型数据库上,是没有办法实现的。第二就是去中心化的事务管理器。另外没有什么东西是银弹,是包治百病的,你要根据你的业务的特性去选择合适的一致性算法。


NewSQL


其实刚刚这些 pattern 会发展出一个新的类别,我们能不能把关系数据库上的一些 SQL、Transaction 跟 NoSQL 跟刚才我说到的 Region Base 的可扩展的模型融合起来。这个思想应该是在 2013 年左右的时候,学术界提出来比较多的东西,NewSQL。NewSQL 首先要解决 Scalability 的问题, 刚给我们说过 scalability 是一个未来的数据库必须要有的功能,第二个就是 SQL,SQL 对于业务开发者来说是很好的编程的接口。第三,ACID Transaction,我希望我的数据库实现转帐和存钱这种强一致性级别的业务。第四,就是整个 cluster 可以支持无穷大的数据规模,同时任何数据节点的宕机、损坏都需要集群自己去做监控,不需要 DBA 的介入。


案例:Google Spanner / F1


有没有这样的系统?其实有的。刚才一直提到 Google 的 Spanner 系统。Spanner 系统是在 2012 年底于 OSDI 的会议上发布了论文; F1 这篇论文在 2013 年的 VLDB 发布的,去描述了整个 Google 内部的分布式关系型数据库的实现。首先,根据 Spanner 的论文 Spanner 和 F1 在生产环境只有一个部署,上万物理节点遍布在全球各种数据中心内,通过 Paxos 进行日志复制。第二,整个架构是无状态的 SQL 层架在一个 NoSQL 的基础之上。第三,它对外提供的是一个跨行事务的语义,这个跨行事务是透明的跨行事务,我不需要对我的业务层做修改,它是通过 一个硬件支持的 Truetime API,GPS 时钟和原子钟去实现事务的隔离级别。这个系统是最早用来支撑 Google 的在线广告业务,在线广告业务大家知道,其实对于扣费、广告计费、点击记录,广告活动等一致性的级别要求非常高。首先这个钱不能多扣,也不能少扣,实时性要求非常高。而且业务逻辑相当复杂,这个时候需要一个 SQL 数据库,需要一个支持事务的数据库,需要一个支持 Auto-Failover 的数据库,所以就有了 Google Spanner/F1 这个系统。


典型业务场景


最典型的业务场景是什么呢?对于这种高吞吐、大容量的数据量级对于 NoSQL 系统来说,典型的应用的 Pattern 就是高吞吐大容量,还有就是 Workload 相对比较分散的情况。比较典型的反例是秒杀,秒杀这个场景其实非常不适合这种 NewSQL。另外就是一致性、可用性和 latency 之间怎么做取舍。在这种分布式数据库上面,第一个要丢弃的就是延迟,在这么大规模的量级上做强一致性,延迟是首先是不能保证的。你去看谷歌的 F1 和 Spanner 的论文里面,它提到它们的 latency 基本都是 10 毫秒、20 毫秒、甚至50 毫秒、100 毫秒的量级,但是它并不会太关心 latency 。因为它必须保证通过 Paxos 去做跨机房、多机房的复制,光速你肯定没法超越,所以这个时间是省不了的,它整个系统是面向吞吐设计的,而不是面向低延迟设计的,但是它要求非常强的一致性。


MySQL Sharding


一个典型的场景就是替换 MySQL Sharding 的业务。它的业务典型的特点,就是高吞吐,海量并发的小事务,并不是特别大的 transection,模型也相对简单,没有复杂的 join 的程序。但是痛点刚才提到了非常明显,首先就是对于 MySQL Sharding 方案 scale 能力很差,对于表结构变更的方案并没有太多;再者,cross shard transaction,目前 Sharding 的方案并没有办法很好支持,但 NewSQL 面向的场景和 MySQL Sharding 面向的场景是非常的像的。


Cross datacenter HA


第二种场景是 Cross datacenter HA(跨数据中心多活),这种场景简直是数据开发者追求的圣杯。因为像 Oracle、DB2文,它在最关键,最核心的业务上,比如说像银行这种业务,它必须要求实现这种跨数据中心的高可用。但是在一个分布式的数据库上,或者说是 Open-source solution 里,有没有人有办法实现这个 Cross datacenter HA 呢?完全没有。目前来说并没有任何一个数据库能去解决这个问题。因为刚才提到了主从的模型根本是不可靠的。像这种业务的数据极端的重要,任何一点数据的丢失都不能容忍。另外即使整个数据中心宕机也不能影响线上的业务,很典型的像支付宝,这种涉及钱相关的,甚至有些比如你的银行卡,你肯定不能容忍说你吃完饭,告诉你不好意思数据中心挂了,你的钱我刷不了。但是并没有开源的数据库能解决这个问题,如果你真的自己做同步复制的方案的话,特别两地三中心的情况,你请求的延迟是取决于离你最远的数据中心,所以大部分业务来说延迟过大。 另外就是这些系统重度依赖人工运维。我认为一旦任何系统有人工的介入一定是会出错的。因为人是最难自动化的一个因素。


反模式


滥用传统关系模型


对于这种大规模的分布式 NewSQL 来说,比较大的反模式就是,大量的使用存储过程 foreign key,视图等操作。因为首先存储过程是没有办法 scale 的。另外,大表与大表的 JOIN ,在线上的 OLTP 数据库上最大的开销并不是优化器的开销,而是网络通信的开销。比如两个表去做一个笛卡儿积,算起来可能并不是这么慢,但是要把数据一条一条在网络中传输代价是非常大的,这种情况下用 OLAP 的数据库是比较适合。


没有利用好并发


刚才我一直强调对于新型的 OLTP 或者分布式关系型数据库来说,并发或者说吞吐才是应该追求的优化点。在架构设计时,不应该把对延迟非常敏感的业务去使用分布式数据库来实现。所以其实在延迟跟吞吐之间,数据库永远去选择吞吐。所以写程序会遇到的一个很典型的问题,就是我的查询是一行一行,上下之间有这种强依赖关系的模式。其实在这种模式下,对于分布式关系型数据库来说是非常不合适的。所以你应该用并发的思想去做,比如说我的请求,我可以把吞吐打到整个集群上,我的 Database,我的 cluster 会自动 balance 一些 workload,如果一行行查询之间是有依赖的话,那么每一条查询之间的 latency 是会叠加起来。所以这个其实并不是一个太好的 pattern 。


不均匀的设计


数据库其实经常会被大家滥用。比如说我看到很多传统行业数据库使用的方法,其实真的是非常痛苦。无论什么业务我全都直接上 Oracle,不管这个业务是否真的需要 Database 来去支持;比如有人用 MySQL 做计数器,有些有 MySQL 做队列或者做秒杀,这个并不是太 scale 的东西。特别像秒杀的业务,特别高的并发打到同一个 key 上,workload 没办法去分拆到好多个节点来帮你去处理,所以整个分布式的集群就会退化成一个单点,这是非常不合理的。第二,当你的索引设计的不太好的时候,涉及到的过多全表扫描是非常不划算的。刚刚也说过,在一个分布式集群里面,最大的开销是网络的开销,你做全表扫描的时候并不是在一台机器上,而是在好多机器上做全表扫描,同时经过很大的网络传输,当你的索引设计不合理的时候会出现这样的问题。还有是过多的无效索引,会导致整个系统在写入的时候有延迟也好或者吞吐也好,会变得更慢。主要注意的就是不要存在任何可以把你的系统退化成单点的机会。


错误的一致性模型


还有一种最容易犯的问题,就是在没有好好思考你的业务适合的场景情况下,就去使用很强的一致性模型。因为当你要求一个非常高的强一致性的时候,你的分布式事务的 latency 一定比不这么强的一致性的业务要高得多的。其实,很多业务并不需要那么强的一致性,我的数据库虽然给你了这个能力,但如果你去滥用的话,你会说这个数据库好慢,或者为什么我这个请求 100 毫秒才给我返回。但其实很多场景下,你并不是这么强的依赖强一致性 。这个跟数据库本身提不提供这个机制是没有关系的。作为一个 Database 开发者来说,我还是需要给你能实现这个功能的机制,但是不要滥用。
另外根据你业务的冲突的频繁程度选择不同的锁策略。你知道这个业务就是一个很高的冲突的场景,比如说可能像类似发工资,一个集团的账号发到成千上万的小账号。如果你是一个乐观事务的话,可能涉及到的冲突就会很大。因为所有的事务的发起者都要去修改这个公共账号,这种情况下一般可以使用悲观事务,因为你已经预知到你业务的 conflict 级别会很高。但当我知道我的业务的冲突很小,我要追求整个系统的吞吐,我可能会选用乐观的事务的模型。


Q&A


问:对于秒杀这个场景,什么样的数据库设计是合适的?



黄东旭:其实秒杀这个业务是一个比较系统的工程,并不是一个简单的我用什么数据库就可以做的东西。我之前给一些朋友做秒杀的架构设计的时候,从最上层,甚至你可能从 JS 这边就要去做排队,一层一层的排队。比如在缓存这边我可以用 redis,我可以用一些缓存的数据库再去进行二级的排队,因为每一次排队你都会丢掉很多,最后到底下的队列,在数据库这一层,比如库存就十个,你只要在前面放十个进来就行了。就是说,你在上层把流量用排队的方式做更好。最典型的例子就是大家看 12306 卖火车票的时候经常会有排队。基本没有什么数据库能去应对,像淘宝,像京东这种秒杀的业务,它从上到下是整个系统的过程。谢谢。



问:分布式的 OLTP 数据库,历史数据怎么清理,如果后续还需要分析数据的话。



黄东旭:其实在谷歌 Spanner 包括像 TiDB 、TiKV 这个系统之上,是通过一种 MVCC 的技术,每一条数据都会有一个版本号。比如说你不停的 update,它可能会产生好多版本。我背后会有一个 GC 的逻辑,去选定一个 safe point,在这个 safe point 之前的所有数据全都删掉也好,还是挪到冷的存储上也好,我们是通过这种方式实现的。另外随着数据量的膨胀,整个系统设计的时候,我们是通过 Raft 算法,比如说这个数据慢慢长大,我会把它切成两块,再慢慢 Auto-balance 到其它的节点上,然后新的那一块会再长大......这相当于一个细胞一样,一个细胞分裂成两个细胞,成千上万的细胞会均匀的分布在整个集群里面。如果你要删数据可以直接删,如果你不想删的话,你的数据可以全都留下,你自己随便往集群里增加新的节点。这个集群很大,它会自动帮你把数据均匀的分到这儿。所有的历史数据是通过 GC 的模块把历史的版本拿出来,丢到冷的存储上。
在 Google 几乎是完全不删数据的,因为存储的成本是很低的,但是数据未来说不定什么时候就用了。大概是这样,我需要有一个办法能把它存下来。



问:当前做的交易要使用历史的参考数据,可能量不是很大,有这样的例子吗?



黄东旭:当然有。像 MVCC 在访问历史,它其实提供了一个 Lock-free 的 Snapshot Read, 它在读历史版本的时候是不会读线上的正在进行的读写事务,所以它是有一个无所读的机制,这也是为什么它要去采用每一个数据都要有版本号的机制去实现。它是有这个需求,而且在实现的时候也非常漂亮,不会阻塞其他的请求。



问:TiDB 是怎么考虑用 MySQL 或者 PG 的?当时是怎么考虑的?



黄东旭:首先我们没有用 MySQL 或者 PG,刚才说的 TiDB 这个模型是谷歌的 Spanner 跟 F1 的模型。用传统的单机数据库做改造的话,有一个比较大的误区,整个 SQL 的查询优化器跟存储的数据结构,并不知道你底层是分布式的存储,它还是假设你生成 SQL 的方案都是单机的。比如最简单的例子,我需要去 count ,如果是一个单机的 MySQL 优化器,没有建索引的话会一行一行把数据拉过来计一个数,这样算下去;但是如果对一个分布式系统来说,我只要把 count 这个逻辑推到所有存储表的数据节点上,算法再 reduce 回来就可以,更像一个 Mapreduce 这种分布式框架的 SQL 优化器。如果你没有从底到上完整的去实现 Database 的话,你很难对分布式的场景或者分布式数据存储的这些性质来去对数据库做优化。没有办法,这是一条很难的道路,但是我们也得去下走。



原文链接

云时代数据库的核心特点

文章分享qiuyesuifeng 发表了文章 • 0 个评论 • 297 次浏览 • 2016-10-11 15:06 • 来自相关话题

引言

最近几年,随着云计算相关技术的发展,各种不同类型的云层出不穷,服务越来越多不同类型的企业业务,传统企业也渐渐开始探索上云的道路。在云上,作为业务最核心的数据库,相比之前的传统方案会有哪些变化呢?在正式... 查看全部

引言


最近几年,随着云计算相关技术的发展,各种不同类型的云层出不穷,服务越来越多不同类型的企业业务,传统企业也渐渐开始探索上云的道路。在云上,作为业务最核心的数据库,相比之前的传统方案会有哪些变化呢?在正式聊云时代的数据库特点之前,我们需要了解一下目前云时代架构发生的变化。


畅想一下,未来的服务都跑在云端,任何的服务资源都可以像水电煤一样按需选购。从 IaaS 层的容器/虚拟机,到 PaaS 层的数据库,缓存和计算单元,再到 SaaS 层的不同类型的应用,我们只需要根据自身业务特点进行资源选配,再也不用担心应用服务支撑不住高速的业务增长,因为在云上一切都是弹性伸缩的。有了可靠的基础软件架构,我们就可以把更多精力放到新业务的探索,新模式的创新,就有可能产生更多不一样的新场景,从而催生更强大能力的云端服务,这是一件多么 cool 的事情。


当然,理想要一步一步实现,未来的基础软件栈到底会怎样呢?社区在这方面正在进行积极地探索,其中最有代表性的就是基于容器(以 Docker 为代表)的虚拟化技术和微服务(Microservice)。


在云时代,一切都应该是可伸缩的,使用 k8s(Kubernetes)在保证资源平衡的前提下,通过 Docker 部署我们依托于容器的微服务模块,我们不用关心服务到底跑在哪里,只需要关心我们需要多少服务资源。Docker 提供了极大的便利性,一次构建,到处运行,我们可以很好地解决开发、测试和上线的环境一致性问题。(如果不能很好地保证测试和实际上线环境的一致性,则很有可能需要花费远超过开发的时间去发现和修复问题。)k8s 更是在 Docker 构建的基础上增加了更多的云特性,包括 Docker 的升级,高可用和弹性伸缩等等。 关于 Docker/k8s 相关的讨论已经很多了,因为时间关系,关于具体的细节就不再展开。我们只需要了解,有了它,可以很轻松地解决服务的安装和部署。


下面再聊聊微服务,微服务将一个服务拆分成相对独立的更小的子服务单元,不同的子服务单元之间通过统一的接口(HTTP/RPC 等)进行数据交互。


相比于传统的解决方案,这种架构有很多的优点。



  • 更好的开发效率和可维护性。微服务将一个单独的服务进行更细力度的拆分,每一个子服务单元专注于更小的功能模块,可以更好地根据业务建立对应的数据模型,降低复杂度,使得开发变得更轻松,维护和部署变得更加友好.

  • 更好的可扩展性。每个不同的子服务单元相互独立,彼此之间没有任何依赖,所以可以根据业务的具体需要,灵活地部署多个子服务单元进行水平扩展。

  • 更强的容错性。当其中一个子服务出现故障的时候,可以通过辅助的负载均衡工具,自动路由到其他的子服务,不会影响整体服务的可用性.


当然,微服务也不是一个银弹,相对来说,这种方案会使整体系统的设计更加复杂,同时也加大了网络的延迟,对整个系统测试的复杂度也会更高。


Docker 提供的隔离型和可移植性,与微服务是一种天然的契合,微服务将整个软件进行拆分和解耦,而通过 Docker/k8s 可以很自然地做到独立的部署,高可用和容错性,似乎一切都可以完美地运转起来。但是真的是这样么?我们是不是忽略了什么?


是的,我们在讨论前面的问题的时候忽略了一个很重要的东西:状态。


从整个技术发展的角度来看,微服务是一个非常有意义的探索。每个人都期望着每个微服务的子服务都是无状态的,这样我可以自由地启停和伸缩,没有任何的心智负担,但是现实的业务情况是什么样的呢?比如一个电商网站,用户正在下单购买一件商品,此时平台是通过订单子服务的 A 应用来提供服务的,突然,因为机器故障,订单子服务的 A 应用不可用了,改由订单子服务的 B 应用提供服务,那么它是必须要知道刚才用户的订单信息的,否则正在访问自己订单页面的用户会发现自己的订单信息突然不见了。虽然我们尽量想把子服务设计成无状态的,但是很多时候状态都是不可避免的,我们不得不通过存储层保存状态,业界最主要的还是各种数据库,包括 RDBMS 和 NoSQL,比如使用 MySQL、MongoDB、HBase、Cassandra 等,特别是有些场景还要考虑数据一致性问题的时候,更加重了对存储层的依赖。


由此可见,云计算时代系统的架构发生了巨大的变化,这一方面为用户提供了更优秀的特性,另一方面也对云计算的组件提出了更高的要求。数据库作为云计算最基础的组件之一,也需要适应这种架构的变化。(这里我们主要关注 SQL 数据库,云时代的数据库以下简称云数据库。)


那么云数据库主要有一些什么样的特点呢?我认为主要有以下几点。


弹性伸缩


传统的数据库方案,常见的会选用 Oracle,MySQL,PostgreSQL。在云时代,数据量的规模有爆发性的增长,传统的数据库很容易遇到单机的存储瓶颈,不得不选用一些集群方案,常见的比如 Oracle RAC、 MySQL Sharding 等,而这些集群方案或多或少都有一些不令人满意的地方。


比如说,Oracle RAC 通过共享存储的硬件方案解决集群问题,这种方式基本上只能通过停机换用更大的共享内存硬件来解决扩容问题,RAC 节点过多会带来更多的并发问题,同样也会带来更高的成本。


以 MySQL Sharding 为代表的数据分片方案,很多时候不得不提前对数据量进行规划,把扩容作为很重要的一个计划来做,从 DBA 到运维到测试到开发人员,很早之前就要做相关的准备工作,真正扩容的时候,为了保证数据安全,经常会选择停服务来保证没有新的数据写入,新的分片数据同步后还要做数据的一致性校验。当然业界大公司有足够雄厚的技术实力,可以采用更复杂的方案,将扩容停机时间尽量缩短(但是很难缩减到 0),但是对于大部分中小互联网公司和传统企业,依然无法避免较长时间的停服务。


在云时代,理想中所有的资源都是根据用户业务需求按需分配的,服务器资源,应用容器资源,当然也包括数据库资源。添加或者减少新的数据库资源,完全就像日常吃饭那样稀疏平常,甚至用户基本感知不到。比如作为一个电商用户,在双 11 促销活动之前,可以通过增加数据库节点的方式,扩大更多的资源池,用来部署相应的容器服务,当活动结束之后,再将多余的资源移除去支持其他的服务,这样可以极大地提高资源的利用率,同样可以弹性地支撑各种峰值业务。


高可用


传统的 MySQL 方案,数据复制的时候默认采用异步的方式,对于一个写入的请求,主库写入成功后就会返回成功信息给客户端,但是这个时候数据可能还没有同步给从库,一旦主库这个时候挂掉了,启动从库的时候就会有丢失数据的风险。当然,也有人会选择半同步的复制方式,这种方式在正常情况下是同步的,但是在遇到数据压力比较大的时候,依然会退化为异步的方式,所以本质上来说,同样有丢失数据的风险。其他也有一些多主的同步方案,比如在应用层做数据同步,但是这种方式一是需要应用层的配合,二是在对网络超时的处理非常复杂,增加心智负担。


在云时代,因为所有的数据库资源都是分布式存储的,每个数据库节点出现问题都是很正常的事情,所以就必须有一种可以实现数据一致性的数据复制方式来保证服务的高可用,业界给出的答案就是:Paxos/Raft(关于 Paxos 和 Raft 的实现细节我们不在这里展开)。PingCAP 在做的 TiDB 就是选择了 Raft 协议,Raft 协议看起来更像是一个多副本的自适应的主从复制协议,对于每次写请求,Raft 都会保证大多数写成功才会返回客户端,即使 Raft Group的Leader 挂掉了,在一个有限的时间范围内,会很快地选出一个新的 Leader 出来,继续提供服务。同样,对于一个 3 副本的 Raft Group,只要 2 个写入成功,就可以保证成功,而大多数情况下,最先写入成功的往往是与 Leader 网络情况最好的那个副本,所以这种 Majority 写的方式,可以很自然地选择速度最快的副本进行数据同步复制。另外,Raft 协议本身支持 Config Change,增加一个新的节点,可以很容易地做副本数据分布的变更,而不需要停止任何服务。


同样,在云时代,数据库的 DDL 操作也会是一个非常有趣的事情。以一个常见的 Add Column 操作为例,在表规模已经很大的情况下,在传统的实现方案中,比较有参考意义的是,通过一些工具,创建类似表级别的触发器,将原表的数据同步到一个新的临时表中,当数据追平的时候,再进行一个锁表操作,将临时表命名为原表,这样一个 Add Column 操作就完成了。但是在云时代,分布式的数据存储方式决定了这种方案很难实现,因为每个数据库节点很难保证 Schema 状态变更的一致性,而且当数据规模增长到几十亿,几百亿甚至更多的时候,很短的阻塞时间都有可能会导致很大的负载压力变化,所以 DDL 操作必须是保证无阻塞的在线操作。值得欣慰的是,Google 的 F1 给我们提供了很好的实现参考,TiDB 即是根据 F1 的启发进行的研发,感兴趣的同学可以看下相关的内容。


易用透明

我们可以将云数据库想象成一个提供无限大容量的数据库,传统数据库遇到单机数据存储瓶颈的问题将不复存在。已有的程序基本上不怎么需要修改已有的代码,就可以很自然地接入到云数据库中来获得无限 Scale 的能力。增减数据库节点,或者节点的故障恢复,对于应用层来说完全透明。另外,云数据库的监控、运维、部署、备份等等操作都可以在云端通过高效的自动化工具来自动完成,极大地降低了运维成本。


多租户

云数据库本身应该是可以弹性伸缩的,所以很自然的,从资源利用率的角度来考虑,多个不同用户的数据库服务底层会跑在一个共享的云数据库中。因此多租户技术会成为云数据库的标配。但是这里面就有一个不得不面对的问题,如何做到不同用户的隔离性?用户数据隔离是相对比较容易的,比如还是以电商用户(这里说的是电商企业,不是顾客客户)为例,每个用户都有一个唯一的 ID,这样在云数据库的底层存储中,可以保证每个用户数据都带有自己 ID 前缀,用户登陆进来的时候可以根据这个前缀规则,获取他对应的数据,同时他看不到其他用户的数据。


在一个真实的多租户环境下面,纯粹的数据隔离往往是不够的,你还需要做到资源公平性的隔离。比如有的用户写一个 SQL,这个 SQL 没有做优化,主要做的事情是一个全表描扫,这个表的数据量特别特别大,这样他会吃掉很多的 CPU、Memory、IO 等资源,导致其他用户很轻量级的 SQL 操作都可能会变得很慢,影响到其他用户实际的体验。那么针对这种情况怎么做隔离?与此类似的还有,网络带宽怎么做隔离?大家都是跑在一个云数据库上面的,如果一个用户存放的数据特别大,他把带宽都吃掉了,别人就显得非常慢了。


还有一种情况,如果我本身作为一个租户,内部又怎么做隔离,大家知道 MySQL 可以建很多 Database,不同的 Database 给不同的团队来用,那么他们之间内部隔离又怎么做,这个问题就进一步更加复杂了。


目前来讲没有特别好的方法,在一个分布式的环境下面去做很好的隔离,有两个方向可以考虑:


第一种是最简单也是有效的方法,制定一些规则,把某些用户特别大的数据库表迁移到独享的服务器节点上面,这样就不会影响其他用户的服务,但是这里面就涉及到定制化的事情了,本身理念其实与云数据库并不相符。


第二种就是依靠统计信息,做资源隔离和调度,但是这里面对技术的要求就比较高了。因为云数据库是分布式的,所以一般的统计都要横跨很多的机器,因为网络原因,不可能做到完全准确的统计,所有统计都是有延迟的。比如说对于某个用户,现在统计到的流量是 1 个 G,他可能突然就有一次峰值的网络访问,可能下一次统计消耗的流量是 5 个 G(这里面只是举例说明问题),如果你给他流量限制是 1 个 G,中间统计的间隔是多少比较合适,如果间隔比较小,那么这个对整个系统的压力就比较大,可能影响正常的用户 SQL 访问,另外本身这个流量限制的系统也是很复杂的系统。


调度算法一直是整个分布式系统领域很困难的一个问题,如何做到隔离性和公平调度也是未来云数据库非常有挑战的一个事情。


低成本

低成本应该是云时代基础设施最明显的特点。首先,云数据库的高可用和容错能力,使得我们不再需要昂贵的硬件设备,只需要普通的 X86 服务器就可以提供服务。然后,受益于 Docker 的虚拟化技术,使得不同类型的应用容器可以跑在同一个物理机上,这样可以极大地提高资源的利用率。其次,多租户的支持,使得不同的用户可以共用一套底层的数据库存储系统,在数据库层面再一次提高了资源的利用效率。再次,云数据库的自动化运维工具,降低了整个核心数据库的运维成本。最后,云数据库资源是按需分配的,用户完全可以根据自身的业务特点,选购合适的服务资源。


高吞吐


云数据库虽然可以做到弹性扩容,但是本身是分布式存储的,虽然可以通过 Batch Write、Pipeline 和 Router Cache 等方式加快访问 SQL 请求的数据,但是相对传统单机的数据库来说,在数据访问链路上至少也要多走一次网络,所以大部分并发量不大的小数据量请求,都会比单机延迟要高一些。也就是说,当没有足够高的并发 SQL 访问的话,其实不能完全体现云数据库的性能优势,所以这也是我们在选用云数据库的时候需要认识到的问题,云数据库更多的是追求高吞吐,而不是低延迟。当并发大到一定规模,云数据库高吞吐特性就显现出来了,即使在很高的并发下,依然可以维持相当稳定的延迟,而不会像单机数据库那样,延迟线性增长。当然,延迟的问题,在合理的架构设计方案下,可以通过缓存的方式得到极大的缓解。


数据安全

云数据库的物理服务器分布在多个机房,这就为跨数据库中心的数据安全提供了最基础的硬件支持。谈到金融业务,大家耳熟能详的可能就是两地三中心,比如北京有两个机房,上海有一个。未来一切服务都跑在云上,金融类的业务当然也不例外。相比其他业务,金融类业务对数据安全要求就要高得多。当然,每个公司内部都有核心的业务,所以如果上云的话,也会有同样的强烈需要。这样,对云数据库来说,数据的一致性、分布式事务、跨数据中心的数据安全等更高端的需求有可能会日益强烈。常见的数据备份也有可能会被其他新的模式所取代或者弱化,比如基于 Paxos/Raft 的多副本方案,本身就保证了会有多份备份。


自动负载平衡

对于云数据库来说,负载平衡是一个很重要的问题,它直接决定了整个云数据库系统性能的好坏,如果一个数据库节点的数据访问过热的话,就需要考虑把数据迁移到其他的数据库节点来分担负载,不然就很容易出现性能瓶颈。整个负载平衡是一个动态的过程,调度算法需要保证资源配比的最大平衡,还有保证数据迁移的过程对系统整体的负载影响最小。这在未来也是云数据库需要解决的一个核心问题。


小结

从目前已有的 SQL 数据库实现方案来看,NewSQL 应该是最贴近于云数据库理念的实现。NewSQL 本身具有 SQL、ACID 和 Scale 的能力,天然就具备了云数据库的一些特点。但是,从 NewSQL 到云数据库,依然有很多需要挑战的难题,比如多租户、性能等。


上面提到的一些云数据库的特点,也是 PingCAP 目前在着力实现的部分,TiDB 作为国内第一个 NewSQL 的开源项目,在与社区的共同努力下,我们在上月底刚刚发布了 Beta 版本,欢迎各位上 GitHub 了解我们。


随着整个社区技术水平的发展和云时代新的业务需求的驱动,除了 PingCAP 的 TiDB,相信会有更多的团队在这方面进行探索, 期待早日看到云数据库成熟的那一天。


Q&A

问:由于客户数据环境复杂多样,在迁移到云端的时候怎么怎么做规划,以便后期统一运维管理?或者说,怎么把用户 SQL Server 或者 MongoDB 逐渐迁移到 TiDB 之类的分布式数据库?

崔秋:因为每个业务场景都不太相同,所以在选用云端服务的时候,首先要了解自身业务和云服务具体的优缺点。
如果你的业务本身比较简单,比如你之前用的 MongoDB,现在很多云服务厂商都会提供云端的 MongoDB 服务。这个时候你就要根据业务特点来做判断,如果 MongoDB 本身容量不大,远期的业务数据不会增长过快的话,这个时候其实你可以直接使用 MongoDB 的服务的。但是如果你本身的数据量比较大,或者数据增长比较快的话,就可能要考虑数据的扩容问题,MongoDB 在这方面做的不是太好。
你可以考虑 SQL 数据库的集群方案。比如 TiDB,它本身是支持弹性扩容,高并发高吞吐和跨数据库中心数据安全的,另外有一点明显的好处是 TiDB 兼容 MySQL 协议,所以如果你的应用程序是使用 MySQL,就基本上可以无缝地迁移到 TiDB,这方面是非常方便的。后续我们会提供常用的数据库迁移工具,帮用户把数据从 MongoDB/SQL Server 等平滑迁移到 TiDB 上面。
还是那个原则,不要为了上云而上云,一定要了解清楚自己的业务特点,云数据库会帮助你提供很多特性,如果真的很适用你的业务的话,就可以考虑。


问:但从产品的角度来看,云厂商提供的 RDS 产品是 Copy 客户数据库的思路,或者说是为了支持不同的数据库而支持。请问这种局面以后会有什么改变吗?

崔秋:现在确实蛮多云数据库服务其实就是在传统的 RDS 上面包了一层自动化的监控,运维和部署工具,就卖给用户提供服务了,但是实际上本身解决的仅仅是自动化管控的问题,云服务提供的数据库特性还是单机的 RDS,或者 RDS Sharing 的特性。如果本身底层的数据库满足不了你的需求的话,云服务也是满足不了的。
如果你需要不停服务的弹性扩容,单机的 RDS 服务肯定是搞不定的,RDS Sharing 也很难帮助你做到,这就对底层的数据库有了更高的要求,当然这方面是 TiDB 的强项了。
现在很多云上的 RDS 产品还远远没有达到理想中的云数据库的要求,不过随着社区的发展和业务需求的推动,我个人觉得,这方面最近几年会有更多的变化。如果对于这方面感兴趣的话,可以关注下 TiDB。


问:从 Oracle 分流数据到 TiDB、Oracle 增量修改、Update 的记录,如何同步到 TiDB?有没有工具推荐,比如类似 Ogg?


崔秋:目前 TiDB 还没有相应的工具。如果真的需要在线从 Oracle 这边分流的话,可以考虑使用 Oracle 的触发器,将数据的变化记录下来,然后转化为 SQL,同步到 TiDB,当然这需要一定开发的工作量。


原文链接

基于 Raft 构建弹性伸缩的存储系统的一些实践

文章分享qiuyesuifeng 发表了文章 • 0 个评论 • 374 次浏览 • 2016-10-11 15:00 • 来自相关话题

最近几年来,越来越多的文章介绍了 Raft 或者 Paxos 这样的分布式一致性算法,且主要集中在算法细节和日志同步方面的应用。但是呢,这些算法的潜力并不仅限于此,基于这样的分布式一致性算法构建一个完整的可弹性伸缩的高可用的大规模存储系统,是一个很新的课... 查看全部

最近几年来,越来越多的文章介绍了 Raft 或者 Paxos 这样的分布式一致性算法,且主要集中在算法细节和日志同步方面的应用。但是呢,这些算法的潜力并不仅限于此,基于这样的分布式一致性算法构建一个完整的可弹性伸缩的高可用的大规模存储系统,是一个很新的课题,我结合我们这一年多以来在 TiKV 这样一个大规模分布式数据库上的实践,谈谈其中的一些设计和挑战。


本次分享的主要内容是如何使用 Raft 来构建一个可以「弹性伸缩」存储。其实最近这两年也有很多的文章开始关注类似 Paxos 或者 Raft 这类的分布式一致性算法,但是主要内容还是在介绍算法本身和日志复制,但是对于如何基于这样的分布式一致性算法构建一个大规模的存储系统介绍得并不多,我们目前在以 Raft 为基础去构建一个大规模的分布式数据库 TiKV ,在这方面积累了一些第一手的经验,今天和大家聊聊类似系统的设计,本次分享的内容不会涉及很多 Raft 算法的细节,大家有个 Paxos 或者 Raft 的概念,知道它们是干什么的就好。


先聊聊 Scale


其实一个分布式存储的核心无非两点,一个是 Sharding 策略,一个是元信息存储,如何在 Sharding 的过程中保持业务的透明及一致性是一个拥有「弹性伸缩」能力的存储系统的关键。如果一个存储系统,只有静态的数据 Sharding 策略是很难进行业务透明的弹性扩展的,比如各种 MySQL 的静态路由中间件(如 Cobar)或者 Twemproxy 这样的 Redis 中间件等,这些系统都很难无缝地进行 Scale。


Sharding 的几种策略


在集群中的每一个物理节点都存储若干个 Sharding 单元,数据移动和均衡的单位都是 Sharding 单元。策略主要分两种,一种是 Range 另外一种是 Hash。针对不同类型的系统可以选择不同的策略,比如 HDFS 的Datanode 的数据分布就是一个很典型的例子:


WechatIMG12.png-50.3kB


首先是 Range


Range 的想法比较简单粗暴,首先假设整个数据库系统的 key 都是可排序的,这点其实还是蛮普遍的,比如 HBase 中 key 是按照字节序排序,MySQL 可以按照自增 ID 排序,其实对于一些存储引擎来说,排序其实是天然的,比如 LSM-Tree 或者 BTree 都是天然有序的。Range 的策略就是一段连续的 key 作为一个 Sharding 单元:


屏幕快照 2016-10-13 下午4.40.22.png-180.9kB


例如上图中,整个 key 的空间被划分成 (minKey, maxKey),每一个 Sharding 单元(Chunk)是一段连续的 key。按照 Range 的 Sharding 策略的好处是临近的数据大概率在一起(例如共同前缀),可以很好的支持 range scan 这样的操作,比如 HBase 的 Region 就是典型的 Range 策略。


但是这种策略对于压力比较大的顺序写是不太友好的,比如日志类型的写入 load,写入热点永远在于最后一个 Region,因为一般来说日志的 key 基本都和时间戳有关,而时间显然是单调递增的。但是对于关系型数据库来说,经常性的需要表扫描(或者索引扫描),基本上都会选用 Range 的 Sharding 策略。


另外一种策略是 Hash


与 Range 相对的,Sharding 的策略是将 key 经过一个 Hash 函数,用得到的值来决定 Sharding ID,这样的好处是,每一个 key 的分布几乎是随机的,所以分布是均匀的分布,所以对于写压力比较大、同时读基本上是随机读的系统来说更加友好,因为写的压力可以均匀的分散到集群中,但是显然的,对于 range scan 这样的操作几乎没法做。


屏幕快照 2016-10-19 下午6.14.37.png-135.4kB


比较典型的 Hash Sharding 策略的系统如:Cassandra 的一致性 Hash,Redis Cluster 和 Codis 的 Pre-sharding 策略,Twemproxy 有采用一致性 Hash 的配置。


当然这两种策略并不是孤立的,可以灵活组合,比如可以建立多级的 Sharding 策略,最上层用 Hash ,每一个 Hash Sharding 中,数据有序的存储。


在做动态扩展的时候,对于 Range 模型的系统会稍微好做一些,简单来说是采用分裂,比如原本我有一个 [1, 100) 的 Range Region,现在我要分裂,逻辑上我只需要简单的将这个 region 选取某个分裂点,如分裂成 [1,50), [50, 100) 即可,然后将这两个 Region 移动到不同的机器上,负载就可以均摊开。


但是对于 Hash 的方案来说,做一次 re-hash 的代价是挺高的,原因也是显而易见,比如现在的系统有三个节点,现在我添加一个新的物理节点,此时我的 hash 模的 n 就会从 3 变成 4,对于已有系统的抖动是很大,尽管可以通过 ketama hash 这样的一致性 hash 算法尽量的降低对已有系统的抖动,但是很难彻底的避免。


Sharding 与高可用方案结合


选择好了 sharding 的策略,那剩下的就是和高可用方案结合,不同的复制方案达到的可用性及一致性级别是不同的。很多中间件只是简单的做了 sharding 的策略,但是并没有规定每个分片上的数据的复制方案,比如 redis 中间件 twemproxy 和 codis,MySQL 中间件 cobar 等,只是在中间层进行路由,并未假设底层各个存储节点上的复制方案。但是,在一个大规模存储系统上,这是一个很重要的事情,由于支持弹性伸缩的系统一般来说整个系统的分片数量,数据分片的具体分布都是不固定的,系统会根据负载和容量进行自动均衡和扩展,人工手动维护主从关系,数据故障恢复等操作在数据量及分片数量巨大的情况下几乎是不可能完成的任务。选择一个高度自动化的高可用方案是非常重要的。


在 TiKV 中,我们选择了按 range 的 sharding 策略,每一个 range 分片我们称之为 region,因为我们需要对 scan 的支持,而且存储的数据基本是有关系表结构的,我们希望同一个表的数据尽量的在一起。另外在 TiKV 中每一个 region 采用 Raft 算法在多个物理节点上保证数据的一致性和高可用。


图4.png-46kB


从社区的多个 Raft 实现来看,比如 Etcd / LogCabin / Consul 基本都是单一 raft group 的实现,并不能用于存储海量的数据,所以他们主要的应用场景是配置管理,很难直接用来存储大量的数据,毕竟单个 raft group 的参与节点越多,性能越差,但是如果不能横向的添加物理节点的话,整个系统没有办法 scale。


scale 的办法说来也很简单,采用多 raft group,这就很自然的和上面所说的 sharding 策略结合起来了,也就是每一个分片作为一个 raft group,这是 TiKV 能够存储海量数据的基础。但是管理动态分裂的多 raft group 的复杂程度比单 group 要复杂得多,目前 TiKV 是我已知的开源项目中实现 multiple raft group 的仅有的两个项目之一。


正如之前提到过的我们采用的是按照 key range 划分的 region,当某一个 region 变得过大的时候(目前是 64M),这个 region 就会分裂成两个新的 region,这里的分裂会发生在这个 region 所处的所有物理节点上,新产生的 region 会组成新的 raft group。


总结


构建一个健壮的分布式系统是一个很复杂的工程,上面提到了在 TiKV 在实践中的一些关键的设计和思想,希望能抛砖引玉。因为 TiKV 也是一个开源的实现,作为 TiDB 的核心存储组件,最近也刚发布了 Beta 版本,代码面前没有秘密,有兴趣深入了解的同学也可以直接阅读源码和我们的文档,谢谢大家。


Q&A


Q1:如何在这个 region 的各个副本上保证分裂这个操作安全的被执行?


其实这个问题比较简单,就是将 split region 这个操作作为一个 raft log,走一遍 raft 状态机,当这个 log 成功 apply 的时候,即可以认为这个操作被安全的复制了(因为 raft 算法干得就是这个事情)。确保 split log 操作被 accept 后,对新的 region 在走一次 raft 的选举流程(也可以沿用原来的 leader,新 region 的其他节点直接发心跳)。split 的过程是加上网络隔离,可能会产生很复杂的 case,比如一个复杂的例子:


a, b 两个节点,a 是 leader, 发起一个分裂 region 1 [a, d) -> region 1 [a, b) + region 2 [b, d), region 2的 heartbeart 先发到 b,但这时候 region 2 分裂成了 region 2 [b, c) + region 3 [c, d),给 b 发送的 snapshot 是最新的 region 2 的 snapshot [b, c),region 1的 split log 到了 b,b 的老 region 1 也分裂成了 region 1 [a, b) + region 2 [b,d), 这之后 a 给 b 发送的最新的 region 2 的 snapshot [b, c) 到了,region 2 被 apply 之后,b 节点的 region 2 必须没有 [c, d) 区间的数据。


Q2:如何做到透明?


在这方面,raft 做得比 paxos 好,raft 很清晰的提供了 configuration change 的流程,configuration change 流程用于应对 raft gourp 安全的动态添加节点和移除节点,有了这个算法,在数据库中 rebalance 的流程其实能很好的总结为:


对一个 region: add replica / transfer leadership / remove local replica


这三个流程都是标准的 raft 的 configuration change 的流程,TiKV 的实现和 raft 的 paper 的实现有点不一样的是:


config change 的 log 被 apply 后,才会发起 config change 操作,一次一个 group 只能处理一个 config change 操作,避免 disjoint majority,不过这点在 diego 的论文里提到过。


主要是出于正确性没问题的情况下,工程实现比较简单的考虑。
另外这几个过程要做到业务层透明,也需要客户端及元信息管理模块的配合。毕竟当一个 region 的 leader 被转移走后,客户端对这个 region 的读写请求要发到新的 leader 节点上。


客户端这里指的是 TiKV 的 client sdk,下面简称 client , client 对数据的读写流程是这样的:首先 client 会本地缓存一份数据的路由表,这个路由表形如:


{startKey1, endKey1}   ->     {Region1, NodeA}
{startKey2, endKey2} -> {Region2, NodeB}
{startKey3, endKey3} -> {Region3, NodeC}

client 根据用户访问的 key,查到这个 key 属于哪个区间,这个区间是哪个 region,leader 现在在哪个物理节点上,然后客户端查到后直接将这个请求发到这个具体的 node 上,刚才说过了,此时 leader 可能已经被 transfer 到了其他节点,此时客户端会收到一个 region stale 的错误,客户端会向元信息管理服务请求然后更新自己的路由表缓存。


这里可以看到,路由表是一个很重要的模块,它需要存储所有的 region 分布的信息,同时还必须准确,另外这个模块需要高可用。另一方面,刚才提到的数据 rebalance 工作,需要有一个拥有全局视角的调度器,这个调度器需要知道哪个 node 容量不够了,哪个 node 的压力比较大,哪个 node region leader 比较多?以动态的调整 regions 在各个 node 中的分布,因为每个 node 是几乎无状态的,它们无法自主的完成数据迁移工作,需要依靠这个调度器发起数据迁移的操作(raft config change)。


大家应该也注意到了,这个调度器的角色很自然的能和路由表融合成一个模块,在 Google Spanner 的论文中,这个模块的名字叫 Placement Driver, 我们在 TiKV 中沿用了这个名称,简称 pd,pd 主要的工作就是上面提到的两项:1. 路由表 2. 调度器。
Spanner 的论文中并没有过多的介绍 pd 的设计,但是设计一个大规模的分布式存储系统的一个核心思想是一定要假设任何模块都是会 crash 的,模块之间互相持有状态是一件很危险的事情,因为一旦 crash,standby 要立刻启动起来,但是这个新实例状态不一定和之前 crash 的实例一致,这时候就要小心会不会引发问题.
比如一个简单的 case :因为 pd 的路由表是存储在 etcd 上的,但是 region 的分裂是由 node 自行决定的 ( node 才能第一时间知道自己的某个 region 大小是不是超过阈值),这个 split 事件如果主动的从 node push 到 pd ,如果 pd 接收到这个事件,但是在持久化到 etcd 前宕机,新启动的 pd 并不知道这个 event 的存在,路由表的信息就可能错误。


我们的做法是将 pd 设计成彻底无状态的,只有彻底无状态才能避免各种因为无法持久化状态引发的问题。


每个 node 会定期的将自己机器上的 region 信息通过心跳发送给 pd, pd 通过各个 node 通过心跳传上来的 region 信息建立一个全局的路由表。这样即使 pd 挂掉,新的 pd 启动起来后,只需要等待几个心跳时间,就又可以拥有全局的路由信息,另外 etcd 可以作为缓存加速这一过程,也就是新的 pd 启动后,先从 etcd 上拉取一遍路由信息,然后等待几个心跳,就可以对外提供服务。


但是这里有一个问题,细心的朋友也可能注意到了,如果集群出现局部分区,可能某些 node 的信息是错误的,比如一些 region 在分区之后重新发起了选举和分裂,但是被隔离的另外一批 node 还将老的信息通过心跳传递给 pd,可能对于某个 region 两个 node 都说自己是 leader 到底该信谁的?


在这里,TiKV 使用了一个 epoch 的机制,用两个逻辑时钟来标记,一个是 raft 的 config change version,另一个是 region version,每次 config change 都会自增 config version,每次 region change(比如split、merge)都会更新 region version. pd 比较的 epoch 的策略是取这两个的最大值,先比较 region version, 如果 region version 相等则比较 config version 拥有更大 version 的节点,一定拥有更新的信息。


原文链接

开源数据库的现状

文章分享qiuyesuifeng 发表了文章 • 0 个评论 • 454 次浏览 • 2016-10-11 14:57 • 来自相关话题

数据库作为业务的核心,是整个基础软件栈非常重要的一环。近几年的开源社区,新的思想和方案层出不穷,我将总结一下近几年一些主流的开源数据库方案,及其背后的设计思想以及适用场景。本人才疏学浅如有遗漏或者错误请见谅。本次分享聚焦于数据库即结构化数据存储 OLTP... 查看全部

数据库作为业务的核心,是整个基础软件栈非常重要的一环。近几年的开源社区,新的思想和方案层出不穷,我将总结一下近几年一些主流的开源数据库方案,及其背后的设计思想以及适用场景。本人才疏学浅如有遗漏或者错误请见谅。本次分享聚焦于数据库即结构化数据存储 OLTP 及 NoSQL 领域,不会涉及 OLAP、对象存储以及分布式文件系统。


开源 RDBMS 与互联网的崛起
很长时间以来,关系型数据库一直是大公司的专利,市场被 Oracle / DB2 等企业数据库牢牢把持。但是随着互联网的崛起和开源社区的发展,上世纪九十年代 MySQL 1.0 的发布,标志着在关系型数据库的领域,社区终于有了可选择的方案。


✦MySQL


第一个介绍的单机 RDBMS 就是 MySQL。相信大多数朋友都已经对 MySQL 非常熟悉,基本上 MySQL 的成长史就是互联网的成长史。我接触的第一个 MySQL 版本是 MySQL 4.0,后来的 MySQL 5.5 更是经典——基本上所有的互联网公司都在使用。MySQL 也普及了「可插拔」引擎这一概念,即针对不同的业务场景选用不同的存储引擎,这也是 MySQL tuning 的一个重要方式。比如对于有事务需求的场景使用 InnoDB;对于并发读取的场景 MyISAM 可能比较合适;但是现在我推荐绝大多数情况还是使用 InnoDB,毕竟 MySQL 5.6 后它已经成为了官方的默认引擎。MySQL 适用于几乎所有需要持久化结构化数据的场景, 大多数朋友应该都知道,我就不赘述了。


另外值得一提的是 MySQL 5.6 中引入了多线程复制和 GTID,使得故障恢复和主从的运维变得比较方便。另外,MySQL 5.7(目前处于 GA 版本) 发布了一个重大更新,主要是在读写性能和复制性能上有了长足的进步:在 5.6 版本中实现了 SCHEMA 级别的并行复制。不过意义不大,倒是 MariaDB 的多线程并行复制大放异彩,有不少人因为这个特性选择 MariaDB。另外,MySQL 5.7 MTS 支持两种模式,一种是和 5.6 一样,另一种则是基于 binlog group commit 实现的多线程复制,也就是 MASTER 上同时提交的 binlog 在 SLAVE 端也可以同时被 apply,实现并行复制。如果有单机数据库技术选型的朋友,基本上只需要考虑 MySQL 5.7 或者 MariaDB 就好了,而且 MySQL 5.6 和 5.7 由 Oracle 接手后,性能和稳定性都有了明显的提升。


✦PostgreSQL


PostgreSQL 的历史也非常悠久,其前身是 UCB 的 Ingres,主持这个项目的 Michael Stronebraker 于 2015 年获得图灵奖。后来该项目更名为 Post-Ingres,基于 BSD license 下开源。 1995 年几个 UCB 的学生为 Post-Ingres 开发了 SQL 的接口,正式发布了 PostgreSQL95,随后一步步在开源社区中成长起来。和 MySQL 一样,PostgreSQL 也是一个单机的关系型数据库,但是与 MySQL 方便用户过度扩展的 SQL 文法不一样的是,PostgreSQL 的 SQL 支持非常强大,不管是内置类型、JSON 支持、GIS 类型以及对于复杂查询的支持,PL/SQL 等都比 MySQL 强大得多,而且从代码质量上来看,PostgreSQL 的代码质量是优于 MySQL 的。另外,相对于 MySQL 5.7 以前的版本, PostgreSQL 的 SQL 优化器比 MySQL 强大很多,几乎所有稍微复杂的查询 PostgreSQL 的表现都优于 MySQL。


从近几年的趋势上来看,PostgreSQL 的势头也很强劲,我认为 PostgreSQL 的不足之处在于没有 MySQL 那样强大的社区和群众基础。MySQL 经过那么多年的发展,积累了很多的运维工具和最佳实践,但是 PostgreSQL 作为后起之秀,拥有更优秀的设计和更丰富的功能。PostgreSQL 9 以后的版本也足够稳定,在做新项目技术选型的时候,是一个很好的选择。另外也有很多新的数据库项目是基于 PostgreSQL 源码的基础上进行二次开发,比如 Greenplum 等。


我认为,单机数据库的时代很快就会过去。摩尔定律带来的硬件红利总是有上限的,现代业务的数据规模、流量以及现代的数据科学对于数据库的要求,单机已经很难满足。比如,网卡磁盘 IO 和 CPU 总有瓶颈,线上敏感的业务系统可能还得承担 SPOF(单点故障) 的风险,主从复制模型在主挂掉时到底切还是不切?切了以后数据如何恢复?如果只是出现主从机器网络分区问题呢?甚至是监控环境出现网络分区问题呢?这些都是单机数据库面临的巨大挑战。所以我的观点是,无论单机性能多棒(很多令人乍舌的评测数据都是针对特定场景的优化,另外甚至有些都是本机不走网络,而大多数情况数据库出现的第一个瓶颈其实是网卡和并发连接……),随着互联网的蓬勃发展和移动互联网的出现,数据库系统迎来了第一次分布式的洗礼。


分布式时代:NoSQL 的复兴和模型简化的力量
在介绍 NoSQL 之前,我想提两个公司,一个是 Google,另一个是 Amazon。


✦Google


Google 应该是第一个将分布式存储技术应用到大规模生产环境的公司,同时也是在分布式系统上积累最深的公司,可以说目前工业界的分布式系统的工程实践及思想大都来源于 Google。比如 2003 年的 GFS 开创了分布式文件系统,2006 年的 Bigtable 论文开创了分布式键值系统,直接催生的就是 Hadoop 的生态;至于 2012 年发表论文的 Spanner 和 F1 更是一个指明未来关系型数据库发展方向的里程碑式的项目,这个我们后续会说。


✦Amazon


另一个公司是 Amazon。2007 年发表的 Dynamo 的论文 尝试引入了最终一致性的概念, WRN 的模型及向量时钟的应用,同时将一致性 HASH、merkle tree 等当时一些很新潮的技术整合起来,正式标志着 NoSQL 的诞生。NoSQL——对后来业界的影响非常也是很大,包括后来的 Cassandra、RiakDB、Voldemort 等数据库都是基于 Dynamo 的设计发展起来的。


✦新思潮


另外这个时期(2006 年前后持续至今)一个比较重要的思潮就是数据库(持久化)和缓存开始有明确的分离——我觉得这个趋势是从 memcached 开始的。随着业务的并发越来越高,对于低延迟的要求也越来越高;另外一个原因是随着内存越来越便宜,基于内存的存储方案渐渐开始普及。当然内存缓存方案也经历了一个从单机到分布式的过程,但是这个过程相比关系型数据库的进化要快得多。这是因为 NoSQL 的另外一个重要的标志——数据模型的变化——大多 NoSQL 都抛弃了关系模型,选择更简单的键值或者文档类型进行存储。数据结构和查询接口都相对简单,没有了 SQL 的包袱,实现的难度会降低很多。另外 NoSQL 的设计几乎都选择牺牲掉复杂 SQL 的支持及 ACID 事务换取弹性扩展能力,也是从当时互联网的实际情况出发:业务模型简单、爆发性增长带来的海量并发及数据总量爆炸、历史包袱小、工程师强悍,等等。其中最重要的还是业务模型相对简单。


✦嵌入式存储引擎


在开始介绍具体的开源的完整方案前,我想介绍一下嵌入式存储引擎们。


随着 NoSQL 的发展,不仅仅缓存和持久化存储开始细分,再往后的存储引擎也开始分化并走上前台。之前很难想象一个存储引擎独立于数据库直接对外提供服务,就像你不会直接拿着 InnoDB 或者 MyISAM 甚至一个 B-tree 出来用一样(当然,bdb 这样鼎鼎大名的除外)。人们基于这些开源的存储引擎进行进一步的封装,比如加上网络协议层、加上复制机制等等,一步步构建出完整的风格各异的 NoSQL 产品。


这里我挑选几个比较著名的存储引擎介绍一下。


✦TC


我最早接触的是 Tokyo Cabinet(TC)。TC 相信很多人也都听说过,TC 是由日本最大的社交网站 Mixi 开发并开源的一个混合 Key-Value 存储引擎,其中包括 HASH Table 和 B+ Tree 的实现。但是这个引擎的一个缺陷是随着数据量的膨胀,性能的下降会非常明显,而且现在也基本不怎么维护了,所以入坑请慎重。与于 TC 配合使用的 Tokyo Tyrant(TT) 是一个网络库,为 TC 提供网络的接口使其变成一个数据库服务,TT + TC 应该是比较早的 NoSQL 的一个尝试。


✦LevelDB


在 2011 年,Google 开源了 Bigtable 的底层存储引擎: LevelDB。LevelDB 是一个使用 C++ 开发的嵌入式的 Key-Value 存储引擎,数据结构采用了 LSM-Tree,具体 LSM-Tree 的算法分析可以很容易在网上搜索到,我就不赘述了。其特点是,对于写入极其友好,LSM 的设计避免了大量的随机写入;对于特定的读也能达到不错的性能(热数据在内存中);另外 LSM-Tree 和 B-tree 一样是支持有序 Scan 的;而且 LevelDB 是出自 Jeff Dean 之手,他的事迹做分布式系统的朋友一定都知道,不知道的可以去 Google 搜一下。


LevelDB 拥有极好的写性能,线程安全,BatcTCh Write 和 Snapshot 等特性,使其很容易的在上层构建 MVCC 系统或者事务模型,这对于数据库来说非常重要。另外值得一说的是,Facebook 维护了一个活跃的 LevelDB 的分支,名为 RocksDB。RocksDB 在 LevelDB 上做了很多的改进,比如多线程 Compactor、分层自定义压缩、多 MemTable 等。另外 RocksDB 对外暴露了很多 ConfigurationConfigration ,可以根据不同业务的形态进行调优;同时 Facebook 在内部正在用 RocksDB 来实现一个全新的 MySQL 存储引擎:MyRocks,值得关注。RocksDB 的社区响应速度很快也很友好,实际上 PingCAP 也是 RocksDB 的社区贡献者。我建议新的项目如果在 LevelDB 和 RocksDB 之间纠结的话,请果断选择 RocksDB。


✦B-tree 家族


当然,除了 LSM-Tree 外,B-tree 的家族也还是有很多不错的引擎。首先大多数传统的单机数据库的存储引擎都选择了 B+Tree,B+Tree 对磁盘的读比较友好,第三方存储引擎比较著名的纯 B+Tree 实现是 LMDB。首先 LMDB 选择在内存映像文件 (mmap) 实现 B+Tree,而且同时使用了 Copy-On-Write 实现了 MVCC 实现并发事务无锁读的能力,对于高并发读的场景比较友好;同时因为使用的是 mmap 所以拥有跨进程读取的能力。不过因为我并没有在生产环境中使用过 LMDB ,所以并不能给出 LMDB 的一些缺陷,见谅。


✦混合引擎


还有一部分的存储引擎选择了多种引擎混合,比如最著名的应该是 WiredTiger,大概是 2014 年去年被 MongoDB 收购,现在成为了 MongoDB 的默认存储引擎。WiredTiger 内部有 LSM-Tree 和 B-tree 两种实现,对外提供相同的一套接口,根据业务的情况可自由选择。另外一些特殊数据结构的存储引擎在某些特殊场合下非常抢眼,比如极高压缩比 TokuDB,采用了名为分形树的数据结构,在维持一个可接受的读写压力的情况下,能拥有 10 倍以上的压缩率。


✦NoSQL


说完了几个比较著名的存储引擎,我们来讲讲比较著名的 NoSQL。在我的定义中,NoSQL 是 Not Only SQL 的缩写,所以可能包含的范围有内存数据库,持久化数据库等。总之就是和单机的关系型数据库不一样的结构化数据存储系统。


我们先从缓存开始。


✦memcached


前面提到了 memcached 应该是第一个大规模在业界使用的缓存数据库,memcached 的实现极其简单,相当于将内存用作大的 HASH Table,只能在上面进行 get/set/ 计数器等操作,在此之上用 libevent 封装了一层网络层和文本协议(也有简单的二进制协议),虽然支持一些 CAS 的操作,但是总体上来看,还是非常简单的。但是 memcached 的内存利用率并不太高,这是这个因为 memcached 为了避免频繁申请内存导致的内存碎片的问题,采用了自己实现的 slab allocator 的方式。即内存的分配都是一块一块的,最终存储在固定长度的 chunk 上,内存最小的分配单元是 chunk,另外 libevent 的性能也并没有优化到极致。但是这些缺点并不妨碍 memcached 成为当时的开源缓存事实标准。(另外,八卦一下,memcached 的作者 Brad Fitzpatrick 现在在 Google,大家如果用 Golang 的话,Go 的官方 HTTP 包就是这哥们写的,是个很高产的工程师)。


✦Redis


如果我没记错的话,在 2009 年前后,一位意大利的工程师 Antirez ,开源了 Redis。从此彻底颠覆了缓存的市场,到现在大多数缓存的业务都已用上 Redis,memcached 基本退出了历史舞台。Redis 最大的特点是拥有丰富的数据结构支持,不仅仅是简单的 Key-Value,还包括队列、集合、Sorted Set 等等,提供了非常丰富的表达力,而且 Redis 还提供 sub/pub 等超出数据库范畴的便捷功能,使得几乎一夜之间大家纷纷投入 Redis 的怀抱。


✦Twemproxy


但是随着 Redis 渐渐的普及,而且越用越狠,另外内存也越来越便宜,人们开始寻求扩展单机 Redis 的方案,最早的尝试是 twitter 开源的 twemproxy。twemproxy 是一个 Redis 中间件,基本只有最简单的数据路由功能,并没有动态的伸缩能力,但是还是受到了很多公司的追捧,因为确实没其他替代方案。随后的 Redis Cluster 也是难产了好久,时隔好几年,中间出了 7 个 RC 版本,最后才发布;2014 年底,我们开源了 Codis,解决了 Redis 中间件的数据弹性伸缩问题,目前广泛应用于国内各大互联网公司中,这个在网上也有很多文章介绍,我也就不展开了。所以在缓存上面,开源社区现在倒是非常统一,就是 Redis 及其极其周边的扩展方案。


✦MongoDB


在 NoSQL 的大家庭中,MongoDB 其实是一个异类,大多 NoSQL 舍弃掉 SQL 是为了追求更极致的性能和可扩展能力,而 MongoDB 主动选择了文档作为对外的接口,非常像 JSON 的格式。Schema-less 的特性对于很多轻量级业务和快速变更的了互联网业务意义很大,而且 MongoDB 的易用性很好,基本做到了开箱即用,开发者不需要费心研究数据的表结构,只需要往里存就好了,这确实笼络了一大批开发者。


尽管 MongoDB 早期的版本各种不稳定,性能也不太好(早期的 Mongo 并没有存储引擎,直接使用了 mmap 文件),集群模式还全是问题(比如至今还未解决的 Cluster 同步带宽占用过多的问题),但是因为确实太方便了,在早期的项目快速迭代中,Mongo 是一个不错的选择。但是这也正是它的问题,我不止一次听到当项目变得庞大或者「严肃」的时候,团队最后还是回归了关系型数据库。Anyway,在 2014 年底 MongoDB 收购了 WiredTiger 后,在 2.8 版本中正式亮相,同时 3.0 版本后更是作为默认存储引擎提供,性能和稳定性有了非常大的提升。


但是,从另一方面讲,Schema-less 到底对软件工程是好事还是坏事这个问题还是有待商榷。我个人是站在 Schema 这边的,不过在一些小项目或者需要快速开发的项目中使用 Mongo 确实能提升很多的开发效率,这是毋庸置疑的。


✦HBase


说到 NoSQL 不得不提的是 HBase,HBase 作为 Hadoop 旗下的重要产品,Google Bigtable 的正统开源实现,是不是有一种钦定的感觉 :)。提到 HBase 就不得不提一下 Bigtable, Bigtable 是 Google 内部广泛使用的分布式数据库,接口也不是简单的 Key-Value,按照论文的说法叫:multi-dimensional sorted map,也就是 Value 是按照列划分的。Bigtable 构建在 GFS 之上,弥补了分布式文件系统对于海量、小的、结构化数据的插入、更新以及、随机读请求的缺陷。


HBase 就是这么一个系统的实现,底层依赖 HDFS。HBase 本身并不实际存储数据,持久化的日志和 SST file (HBase 也是 LSM-Tree 的结构) 直接存储在 HDFS 上,Region Server (RS) 维护了 MemTable 以提供快速的查询,写入都是写日志,后台进行 Compact,避免了直接随机读写 HDFS。数据通过 Region 在逻辑上进行分割,负载均衡通过调节各个 Region Server 负责的 Region 区间实现。当某 Region 太大时,这个 Region 会分裂,后续可能由不同的 RS 负责,但是前面提到了,HBase 本身并不存储数据,这里的 Region 仅是逻辑上的,数据还是以文件的形式存储在 HDFS 上,所以 HBase 并不关心 Replication 、水平扩展和数据的分布,统统交给 HDFS 解决。


和 Bigtable 一样,HBase 提供行级的一致性,严格来说在 CAP 理论中它是一个 CP 的系统,但遗憾的是并没有更进一步提供 ACID 的跨行事务。HBase 的好处就不用说了,显而易见,通过扩展 RS 可以几乎线性提升系统的吞吐,及 HDFS 本身就具有的水平扩展能力。


但是缺点仍然是有的。首先,Hadoop 的软件栈是 Java,JVM 的 GC Tuning 是一个非常烦人的事情,即使已经调得很好了,平均延迟也得几十毫秒;另外在架构设计上,HBase 本身并不存储数据,所以可能造成客户端请求的 RS 并不知道数据到底存在哪台 HDFS DataNode 上,凭空多了一次 RPC;第三,HBase 和 Bigtable 一样,并不支持跨行事务,在 Google 内部不停的有团队基于 Bigtable 来做分布式事务的支持,比如 MegaStore、Percolator。后来 Jeff Dean 有次接受采访也提到非常后悔没有在 Bigtable 中加入跨行事务,不过还好这个遗憾在 Spanner 中得到了弥补,这个一会儿说。总体来说,HBase 还是一个非常健壮且久经考验的系统,但是需要你有对于 Java 和 Hadoop 比较深入的了解后,才能玩转,这也是 Hadoop 生态的一个问题,易用性真是不是太好,而且社区演进速度相对缓慢,也是因为历史包袱过重的缘故吧。


✦Cassandra


提到 Cassandra (C),虽然也是 Dynamo 的开源实现,但就没有这种钦定的感觉了。C 确实命途多舛,最早 2008 由 Facebook 开发并开源,早期的 C* 几乎全是 bug,Facebook 后来索性也不再维护转过头搞 HBase 去了,一个烂摊子直接丢给社区。还好 DataStax 把这个项目捡起来商业化,搞了两年,终于渐渐开始流行起来。


C 不能简单的归纳为读快写慢,或者读慢写快,因为采用了 qourm 的模型,调整复制的副本数以及读的数量,可以达到不同的效果,对于一致性不是特别高的场景,可以选择只从一个节点读取数据,达到最高的读性能。另外 C 并不依赖分布式文件系统,数据直接存储在磁盘上,各个存储节点之间自己维护复制关系,减少了一层 RPC 调用,延迟上对比 HBase 还是有一定优势的。


不过即使使用 qourm 的模型也并不代表 C 是一个强一致的系统。C 并不帮你解决冲突,即使你 W(写的副本数) + R(读请求的副本数) > N(节点总数),C 也没办法帮你决定哪些副本拥有更新的版本,因为每个数据的版本是一个 NTP 的时间戳或者客户端自行提供,每台机器可能都有误差,所以有可能并不准确,这也就是为什么 C 是一个 AP 的系统。不过 C* 一个比较友好的地方是提供了 CQL,一个简单的 SQL 方言,比起 HBase 在易用性上有明显优势。


即使作为一个 AP 系统,C 已经挺快了,但是人们追求更高性能的脚步还是不会停止。应该是今年年初,ScyllaDB 的发布就是典型的证明,ScyllaDB 是一个兼容 C 的 NoSQL 数据库,不一样的是,ScyllaDB 完全用 C++ 开发,同时使用了类似 DPDK 这样的黑科技,具体我就不展开了,有兴趣可以到 Scylla 的官网去看看。BTW, 国内的蘑菇街第一时间使用了 ScyllaDB,同时在 Scylla 的官网上 share 了他们的方案,性能还是很不错的。


中间件与分库分表
NoSQL 就先介绍到这里,接下来我想说的是一些在基于单机关系型数据库之上的中间件和分库分表方案。


这些技术确实历史悠久,而且也是没有办法的选择。关系型数据库不比 Redis,并不是简单的写一个类似 Twemproxy 的中间件就搞定了。数据库的中间件需要考虑很多,比如解析 SQL,解析出 sharding key,然后根据 sharding key 分发请求,再合并;另外数据库有事务,在中间件这层还需要维护 Session 及事务状态,而且大多数方案并没有办法支持跨 shard 的事务。这就不可避免的导致了业务使用起来会比较麻烦,需要重写代码,而且会增加逻辑的复杂度,更别提动态的扩容缩容和自动的故障恢复了。在集群规模越来越大的情况下,运维和 DDL 的复杂度是指数级上升的。


✦中间件项目盘点


数据库中间件最早的项目大概是 MySQL Proxy,用于实现读写分离。后来国人在这个领域有过很多著名项目,比如阿里的 Cobar 和 TDDL(并未完全开源);后来社区基于 Cobar 改进的 MyCAT、360 开源的 Atlas 等,都属于这一类中间件产品;在中间件这个方案上基本走到头的开源项目应该是 Youtube 的 Vitess。Vitess 基本上是一个集大成的中间件产品,内置了热数据缓存、水平动态分片、读写分离等等,但是代价也是整个项目非常复杂,另外文档也不太好。大概 1 年多以前,我们尝试搭建起完整的 Vitess 集群,但是并未成功,可见其复杂度。


另外一个值得一提的是 Postgres-XC 这个项目,Postgres-XC 的野心还是很大的,整体的架构有点像早期版本的 OceanBase,由一个中央节点来处理协调分布式事务 / 解决冲突,数据分散在各个存储节点上,应该是目前 PostgreSQL 社区最好的分布式扩展方案。其他的就不提了。


未来在哪里?NewSQL!
一句话,NewSQL 就是未来。


2012 年 Google 在 OSDI 上发表了 Spanner 的论文,2013 年在 SIGMOD 发表了 F1 的论文。这两篇论文让业界第一次看到了关系模型和 NoSQL 的扩展性在超庞大集群规模上融合的可能性。在此之前,大家普遍认为这个是不可能的,即使是 Google 也经历了 Megastore 这样的失败。


✦Spanner 综述


但是 Spanner 的创新之处在于通过硬件(GPS 时钟 + 原子钟)来解决时钟同步的问题。在分布式系统里,时钟是最让人头痛的问题,刚才提到了 C* 为什么不是一个强 C 的系统,正是因为时钟的问题。而 Spanner 的厉害之处在于即使两个数据中心隔得非常远,不需要有通信(因为通信的代价太大,最快也就是光速)就能保证 TrueTime API 的时钟误差在一个很小的范围内(10ms)。另外 Spanner 沿用了很多 Bigtable 的设计,比如 Tablet / Directory 等,同时在 Replica 这层使用 Paxos 复制,并未完全依赖底层的分布式文件系统。但是 Spanner 的设计底层仍然沿用了 Colossus,不过论文里也说是可以未来改进的点。


Google 的内部的数据库存储业务,大多是 3~5 副本,重要一点的 7 副本,遍布全球各大洲的数据中心,由于普遍使用了 Paxos,延迟是可以缩短到一个可以接受的范围(Google 的风格一向是追求吞吐的水平扩展而不是低延迟,从悲观锁的选择也能看得出来,因为跨数据中心复制是必选的,延迟不可能低,对于低延迟的场景,业务层自己解决或者依赖缓存)。另外由 Paxos 带来的 Auto-Failover 能力,更是能让整个集群即使数据中心瘫痪,业务层都是透明无感知的。另外 F1 构建在 Spanner 之上,对外提供了更丰富的 SQL 语法支持,F1 更像一个分布式 MPP SQL——F1 本身并不存储数据,而是将客户端的 SQL 翻译成类似 MapReduce 的任务,调用 Spanner 来完成请求。


其实 Spanner 和 F1 除了 TrueTime 整个系统并没有用什么全新的算法,其意义在于这是近些年来第一个 NewSQL 在生产环境中提供服务的分布式系统技术。


Spanner 和 F1 有以下几个重点:



  1. 完整的 SQL 支持,ACID 事务;

  2. 弹性伸缩能力;

  3. 自动的故障转移和故障恢复,多机房异地灾备。


NewSQL 特性确实非常诱人,在 Google 内部,大量的业务已经从原来的 Bigtable 切换到 Spanner 之上。我相信未来几年,整个业界的趋势也是如此,就像当年的 Hadoop 一样,Google 的基础软件的技术趋势是走在社区前面的。


✦社区反应


Spanner 的论文发表之后,当然也有社区的追随者开始实现(比如我们 :D ),第一个团队是在纽约的 CockroachDB。CockroachDB 的团队的组成还是非常豪华的,早期团队由是 Google 的分布式文件系统 Colossus 团队的成员组成;技术上来说,Cockroach 的设计和 Spanner 很像,不一样的地方是没有选择 TrueTime 而是 HLC (Hybrid logical clock),也就是 NTP + 逻辑时钟来代替 TrueTime 时间戳;另外 Cockroach 选用了 Raft 代替 Paxos 实现复制和自动容灾,底层存储依赖 RocksDB 实现,整个项目使用 Go 语言开发,对外接口选用 PostgreSQL 的 SQL 子集。


✦TiDB


目前从全球范围来看,另一个朝着 Spanner / F1 的开源实现这个目标上走的产品是 TiDB(终于谈到我们的产品了)。TiDB 本质上是一个更加正统的 Spanner 和 F1 实现,并不像 CockroachDB 那样选择将 SQL 和 Key-Value 融合,而是像 Spanner 和 F1 一样选择分离,这样分层的思想也是贯穿整个 TiDB 项目始终的。对于测试、滚动升级以及各层的复杂度控制会比较有优势;另外 TiDB 选择了 MySQL 协议和语法的兼容,MySQL 社区的 ORM 框架和运维工具,直接可以应用在 TiDB 上。


和 F1 一样,TiDB 是一个无状态的 MPP SQL Layer,整个系统的底层是依赖 TiKV 来提供分布式存储和分布式事务的支持。TiKV 的分布式事务模型采用的是 Google Percolator 的模型,但是在此之上做了很多优化。Percolator 的优点是去中心化程度非常高,整个集群不需要一个独立的事务管理模块,事务提交状态这些信息其实是均匀分散在系统的各个 Key 的 meta 中,整个模型唯一依赖的是一个授时服务器。在我们的系统上,极限情况这个授时服务器每秒能分配 400w 以上个单调递增的时间戳,大多数情况基本够用了(毕竟有 Google 量级的场景并不多见);同时在 TiKV 中,这个授时服务本身是高可用的,也不存在单点故障的问题。


TiKV 和 CockroachDB 一样也是选择了 Raft 作为整个数据库的基础;不一样的是,TiKV 整体采用 Rust 语言开发,作为一个没有 GC 和 Runtime 的语言,在性能上可以挖掘的潜力会更大。


✦关于未来


我觉得未来的数据库会有几个趋势,也是 TiDB 项目追求的目标:


●数据库会随着业务云化,未来一切的业务都会跑在云端,不管是私有云、公有云还是混合云,运维团队接触的可能再也不是真实的物理机,而是一个个隔离的容器或者「计算资源」。这对数据库也是一个挑战,因为数据库天生就是有状态的,数据总是要存储在物理的磁盘上,而移动数据的代价比移动容器的代价可能大很多。


●多租户技术会成为标配,一个大数据库承载一切的业务,数据在底层打通,上层通过权限,容器等技术进行隔离;但是数据的打通和扩展会变得异常简单,结合第一点提到的云化,业务层可以再也不用关心物理机的容量和拓扑,只需要认为底层是一个无穷大的数据库平台即可,不用再担心单机容量和负载均衡等问题。


●OLAP 和 OLTP 会进一步细分,底层存储也许会共享一套,但是 SQL 优化器这层的实现一定是千差万别的。对于用户而言,如果能使用同一套标准的语法和规则来进行数据的读写和分析,会有更好的体验。


●在未来分布式数据库系统上,主从日志同步这样落后的备份方式会被 Multi-Paxos / Raft 这样更强的分布式一致性算法替代,人工的数据库运维在管理大规模数据库集群时是不可能的,所有的故障恢复和高可用都会是高度自动化的。


Q
&
A
问:HANA 等内存数据库怎么保证系统掉电而处理结果不丢?传统数据库也用缓存,可是 HANA 用的内存太大。


黄东旭:没用过 HANA,但是直观感觉这类内存数据库的可用性可能通过集中方式保证:
●写入会先写 WAL;




  • 写入可能会通过主从或者 paxos 之类的算法做同步和冗余复制还有 HANA 本身就是内存数据库,会尽可能把数据放到内存里,这样查询才能快呀。



问:对于传统创业公司如何弥补 NoSQL 的技术短板?快速的引入 NoSQL 提高效率?


黄东旭:选用 NoSQL 主要注意两点:



  1. 做好业务的调研,估计并发量,数据量,数据的结构看看适不适合;

  2. 对各种 NoSQL 擅长和不擅长的地方都尽可能了解。
    不要盲目相信关系型数据库,也不要盲目相信 NoSQL,没有银弹的。


问:有多个条件 比如年龄 20 到 30 或年龄 35 到 40 并且加入购物车或下单 这种数据怎么存储?


黄东旭:购物车这种场景是典型的 OLTP 的场景,可以选用关系型数据库 MySQL PostgreSQL 什么的,如果对于扩展性的数据跨机房有要求的话,可以调研一下 NewSQL,比如我们的 TiDB。


问:多纬度查询应该选择哪种数据库?


黄东旭:多纬度查询可以说是一个 OLAP 的场景,可以选用 Greenplum 或者 Vertica 之类的分析性数据库。


问:想知道为什么需要这些开源的数据库,既然已经有了 MySQL、DB2、Oracle 这些成熟的数据库,成本考虑,还是传统数据库满足不了需求?


黄东旭:对,传统数据库的扩展性是有问题的,在海量并发和数据量的场景下很难支持业务。所以可以看到比较大的互联网公司基本都有自己的分布式数据库方案。


问:未来可能不再需要数据仓库吗?


黄东旭:大家可以想想数据仓库的定义,如果是还需要离线的从线上库倒腾数据到数据仓库上,这样很难做到实时查询,而且空间的利用率也低,我认为是目前并没有太好的方案的情况下的折衷……
如果有一个更好的数据库能解决数据仓库的场景,为什么还需要一个独立的数据仓库?


原文链接

分布式数据库 TiDB 过去现在和未来

文章分享qiuyesuifeng 发表了文章 • 0 个评论 • 2250 次浏览 • 2016-10-11 14:50 • 来自相关话题

近年来,随着移动互联网、物联网、人工智能等技术的兴起,我们已经进入了一个信息爆炸的大数据时代,需要处理和分析的数据越来越多,与此同时,PC Server 的造价不断降低,促使 Hadoop、Spark 等分布式分析计算框架得到广泛的使用,可以肯定的是,在... 查看全部

近年来,随着移动互联网、物联网、人工智能等技术的兴起,我们已经进入了一个信息爆炸的大数据时代,需要处理和分析的数据越来越多,与此同时,PC Server 的造价不断降低,促使 Hadoop、Spark 等分布式分析计算框架得到广泛的使用,可以肯定的是,在未来,分布式一定是主流的数据处理技术。TiDB 是 PingCAP 团队开发的一款开源的 NewSQL 数据库,整个设计参考 Google Spanner 和 F1,目标是构建一个面向高并发、高吞吐的在线海量数据存储的关系型数据库,提供透明的跨行事务及兼容 MySQL 的 SQL 语法支持,支持无缝的水平伸缩以及跨数据中心多活等特性。目前项目社区相当活跃(5000+ Stars),已经成为在国际上有影响力的顶级开源项目。


为什么要做这么一个数据库?现在的方案有什么问题吗?其实一直以来,在关系型数据库这一层几乎没有一种优雅的分布式方案。但是作为存储架构的核心,关系型数据库的水平扩展是不可避免的,目前大多数方案基本都需要对业务层有很强的侵入性,比如采用 NoSQL 替换原有关系型数据库,但是这样就需要涉及大规模的业务重构,相当于将数据库层的复杂度转嫁给业务层;另外一种方案是通过中间件,或者分库分表,但是这种方案仍然很难做到透明和数据的均匀分布,也无法支持一致性的跨节点事务和 JOIN 操作,而且随着集群规模的增大,维护和扩展的复杂度呈指数级上升。另外经常被人忽略的是,两种方案都没有很好的解决高可用的问题,跨机房多活、故障恢复、扩容经常都需要繁重的人工介入。直到 Google 发布 Spanner 和 F1 ,这才出现了一个真正在 Google 这样的业务规模上验证过的分布式关系型数据库。作为 Google Spanner 和 F1 的开源实现,TiDB 和 TiKV 完美地解决了现有的这些问题。下面简单介绍一下我们是如何解决这些问题的。


TiDB 包含三个子项目:一个是 TiDB,一个是 TiKV,还有 PD (placement driver)。TiDB 是一个无状态的 SQL 层,客户端可以任意连接到一个 TiDB Server 实例上,看到的存储层是完全一致的。TiDB 负责解析用户的 SQL 请求,生成查询计划,交给底层的 TiKV 去执行,TiDB 和 TiKV 通过 Protobuf RPC 进行通信;而 TiKV 负责实际的数据存储,支持全局分布式事务,提供对上层透明的水平扩展。另外一个组件 PD 负责存储元信息,如 Region 具体分布在哪台物理节点上,每个 TiKV Node 每隔一定的时间会将自己的状态信息和本机的 Region 分布情况上报给 PD,PD 会根据需要进行 Region 数据的移动和发起添加副本指令等操作,可以说是集群的大脑,整个 TiDB 项目的架构大致就是如此。
水平扩展
我们先来看看 TiKV 是如何实现无缝的水平扩展的。


从以上 TiKV 的架构图中可以看出,TiKV 将数据逻辑上分为很多小的 Region,每一个 Region 是一系列连续按照字节排序的 Key-value 对。和很多传统的中间件方案不同的是,TiKV 并不关心上层的 Schema 结构,也不知道上层每个 Column 的类型,就是一个很纯粹的 Key-value 数据库。 数据移动的单位是 Region,每个 Region 都会在不同的机器上维护副本(默认 3 副本)。当一个 Region 的数据增长到一定程度的时候,这个 Region 就会像细胞分裂一样会分裂成两个新的 Region,然后新的 Region 可能会被移动到不同的机器上,用这种方式来进行负载均衡以及容量的扩展。


在 TiKV 中,每个 Region 的不同副本之间通过 Raft 协议进行强一致保证,我们在 TiKV 的上层实现了两阶段提交(2PC)来支持跨 Region 的透明分布式事务。整个模型正是 Raft 的作者 Diego Ongaro 博士于 2014 年其博士论文中描述的理想的 Large-scale storagesystem 的模型:


高可用与跨数据中心多活
其实如果抛开数据库层面来谈高可用并不是太复杂,因为业务逻辑服务是比较容易设计成无状态的(一般来说,状态都会持久化到存储层)。现在很多比较前卫的互联网公司,已经在业务的最上层通过动态 DNS + LVS 、F5、HAProxy 等负载均衡工具配合 Docker 以及调度器(K8S 或者 Mesos)实现按需的动态伸缩。如果不考虑存储层,即使整个数据中心宕机,业务层也能做到透明的 Failover,也就是多起几个容器的事情,难点无非是控制好瞬时流量等问题,这从整个模型看是比较完善的。


但是到数据库这层就不那么美好了,传统的关系型数据库的容灾主要依赖主从(master-slave)模型,比如 MySQL 的半同步复制,Oracle 的 DataGuard 都属于主从复制的范畴。但是主从模型有两个很大的问题,即数据的一致性和故障的自动切换。在主从模型中,即使是热备,在极端情况下也并不能保证 Failover 时数据的一致性。网络环境错综复杂,比如出现集群脑裂的状态(网络部分隔离),监控程序自动的将副本提升 master,但是原来的 master 可能仍然在处理部分客户端的请求,这种情况下,数据就可能发生不一致。这也就是为什么很多一致性敏感的业务即使主库挂掉了,宁可暂停服务也不敢自动切换热备,这样业务的中断时间就不可控了。再者,如果面对一个非常庞大的集群,可能上百个物理节点甚至更多,机器故障是常态,如果每一次故障都需要人工介入,整个系统的维护代价是非常惊人的。


那有没有办法能在强一致的前提下做到安全的故障恢复和故障转移呢?答案是有的。Spanner 的做法是利用 Multi-Paxos 进行同步,这类分布式选举算法拥有很强的容错能力,即使发生节点宕机,网络隔离,整个系统都不会发生数据丢失或者不一致的状态。而且在保证安全的同时,所有的 Failover 工作都是自动的。自动化是一个能弹性水平扩展的系统的重要条件,因为一切需要人工介入的工作都是无法弹性水平扩展的。但是长期以来,由于 Paxos 的实现极其复杂,社区并没有可以在生产环境中使用的实现。


在 TiKV 中我们采用了 Raft 算法来作为和 Spanner 中的 Multi-Paxos。Raft 是 Diego Ongaro 在 2014 年发布的一个全新的先进的分布式一致性算法,在正确性和性能上和 Multi-Paxos 等价,但是对工程实现的复杂度控制很好。目前 TiKV 的 Raft 实现是和 CoreOS 一起合作开发的。CoreOS 是知名的 Raft 项目 etcd 背后的公司。抛弃传统的主从模型,而选用 Raft 这样的分布式一致性算法为 TiDB 在稳定性和健壮性带来了质的飞跃。可以说,整个 TiDB 系统都是构建在 Raft 这个基石之上的。


另外,得益于 Raft 算法的特性,TiDB 可以在保证延迟处于可接受范围内的情况下,能够真正支持跨数据中心多活,这也是 TiDB 的一个重要特点。现在很多对于强一致性要求极端严格的业务,比如金融、支付、计费等,两地三中心的部署渐渐成为共识,但是在多数据中心容灾这个事情上很难找到一个很好的方案。在传统的方案中,如果要做到强一致的热备,需要所有数据中心的副本同步写成功,一个写入的延迟取决于所有数据中心尤其是最远的数据中心的延迟。而在 TiDB 上,由于 Raft 协议只需要同步复制到「大多数」的数据中心即可保证数据的强一致性,所以写入的延迟取决于「大多数」数据中心的延迟。一般来说,对于两地三中心的部署,一定会有一个同城双机房,这时候的延迟较于异地机房的延迟就会小很多了。
完整的面向分布式存储的 SQL Layer
TiDB 实现了完整的 SQL 解析器和优化器。传统的中间件方案只做简单的路由和转发到底层的数据库上,并不能很好地支持 JOIN 或者透明的事务,也就很难完整地兼容原有的业务,对业务层有比较大的侵入性。另外传统的单机数据库的查询优化器并没有考虑到底层存储可能是一个分布式存储,很多优化技巧和手段都难以利用分布式的计算能力和优势。出于以上几点原因,TiDB 选择了重新实现完整的 SQL 解析和 MPP SQL 优化器等组件以支持客户端透明的复杂查询,以及分布式事务。
另外 TiDB 支持完整的 MySQL 语法和网络协议兼容。这样,TiDB 可以直接使用 MySQL 社区的海量测试用例,大量的已有 DBA 工具,更使得那些使用 MySQL 的客户的迁移和兼容的工作成本降到最低。这很好的替代了传统的分库分表和中间件等过渡方案。而且 TiDB 还能完美兼容 MySQL 的 binlog, 可以和现有的 MySQL 互为备份,进一步降低了早期的测试成本。
产品规划
分布式执行引擎


TiDB 目前内置一个分布式执行框架,采用和 HBase Coprocessor 类似的方案。这套框架可以利用多台 TiKV 并行处理数据,再将结果汇集到单台 TiDB 来做最终的处理。在处理 OLTP 请求以及中小规模的 OLAP 请求时,这套框架用起来得心应手。但是当处理大型的 OLAP 请求(例如超大表之间的 Join)时,就显得力不从心了。后续我们计划引入一套新的分布式执行引擎,来专门处理复杂 Query。


Spark 集成


Spark 是当前最火的大数据分析工具,在业界得到广泛的应用。TiDB 可以通过 JDBC 接口和 Spark 对接,原有的基于 MySQL 的 Spark 作业可以无缝迁移到 TiDB 上。这样用户可以同时享有 TiDB 海量存储的能力以及 Spark 强大的通用计算能力,从数据中挖掘出更大的价值。这也让 TiDB 同时支持了 OLTP 和 OLAP 两种生态。


Kubernetes


另外一个很重要的事情就是将 TiDB 的整个部署与 Kubernetes 整合。我们认为未来的数据库一定会和云深度结合以提供无缝的扩展及部署体验,而第一步就是和容器调度器的深度整合,我们在内部的测试版本中已经成功的将 TiDB 的部署和扩展在 Kubernetes 上实现,相信很快就会在社区中亮相。


社区情况 / 如何进行贡献


目前 TiDB 项目的社区贡献者达近百人,其中包括京东和华为的大量代码及 Use case 贡献;作为一个国际化开源项目,CoreOS 及 Facebook 等公司也加入进来一起合作开发,因此也非常感谢 Facebook 的 RocksDB team 和 CoreOS 的 etcd team 的大力支持。同时,TiKV 作为 Rust 社区的顶级项目,得到了 Rust 语言官方的大力支持,项目被收录到 Rust Weekly 作为常驻栏目向 Rust 社区通报每周进展。TiDB 作为一个开放的开源项目,欢迎一切感兴趣的开发者的加入,在 Github 的 TiDB 项目首页有 How to contribute 的文档,Welcome aboard and happy hacking!


原文链接

[校招][实习] PingCAP 招前端开发工程师

回复

招聘应聘qiuyesuifeng 发起了问题 • 1 人关注 • 0 个回复 • 835 次浏览 • 2016-10-11 14:30 • 来自相关话题

[校招][实习] PingCAP 招后端开发工程师

回复

招聘应聘qiuyesuifeng 发起了问题 • 1 人关注 • 0 个回复 • 902 次浏览 • 2016-10-11 14:27 • 来自相关话题

Hello from PingCAP/TiDB

Go开源项目qiuyesuifeng 发表了文章 • 0 个评论 • 297 次浏览 • 2016-10-11 14:17 • 来自相关话题

Hello Gophers,

We're PingCAP, the team behind 查看全部

Hello Gophers,


We're PingCAP, the team behind TiDB. Welcome to follow us on Github!

beego中的重定向

有问必答angelina 回复了问题 • 2 人关注 • 2 个回复 • 1360 次浏览 • 2016-10-11 11:19 • 来自相关话题

golang适合做web开发吗?

有问必答zituocn 回复了问题 • 2 人关注 • 2 个回复 • 884 次浏览 • 2016-10-12 11:28 • 来自相关话题

提问的智慧

文章分享astaxie 发表了文章 • 1 个评论 • 337 次浏览 • 2016-10-11 10:28 • 来自相关话题

提问的智慧

艾瑞克.史蒂文.雷蒙德(Eric Steven Raymond)

查看全部

提问的智慧


艾瑞克.史蒂文.雷蒙德(Eric Steven Raymond)


https://github.com/ruby-china/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md


这是一篇长文,看完需要十几分钟的时间。如果之前没有认真看过并且思考过,这十几分钟会改变你的职业生涯。这文章可能会出现一些让人不适的词语或者过时的例子,但我认为这不会影响它要表达的内容,而你需要好好琢磨作者的思想。


《提问的智慧》是一个敲门砖,它把黑客间的礼仪和准则明白地写下来,让我们了解到一个事实,为什么那些看起来很牛的人几乎从不提问。其实他们也有问题,但是通常在提问之前就自己解决了。不是因为他们本来就牛,而是解决问题的经历让他们成为牛人。最终,你只会看到网络上多了一篇文章:关于解决XXX问题的方案。


要不要花十几分钟改变自己的人生,决定权在自己。

10.11 每日早报

文章分享astaxie 发表了文章 • 0 个评论 • 361 次浏览 • 2016-10-11 09:52 • 来自相关话题

10.11 每日早报

新闻:

1.哔哩哔哩正式上线VIP会员制度“大会员”,单月价格25元

2.Mozilla为Firefox Sync引入设备管理功能,可远程取消设备同步

3.京东发起公益活动,在... 查看全部

10.11 每日早报


新闻:


1.哔哩哔哩正式上线VIP会员制度“大会员”,单月价格25元


2.Mozilla为Firefox Sync引入设备管理功能,可远程取消设备同步


3.京东发起公益活动,在北京和天津推出快递小哥上门收旧衣服活动


4.花椒直播获3亿元A轮融资,360投资6000万元


5.福田汽车牵手百度,聚焦无人驾驶超级卡车和车联网


6.ofo共享单车完成1.3亿美元C轮融资,宣布向非校园的城市用户开放


7.电子合同签署云平台契约锁获得900万元天使轮融资,上海泛微网络领投


资源:


2016上半年度中国快递市场研究报告
http://www.bigdata-research.cn/content/201610/354.html


注:上述内容来源于互联网,由EGO整理

一个支持负载均衡,健康检查的 TcpProxy

开源程序astaxie 发表了文章 • 1 个评论 • 598 次浏览 • 2016-10-11 07:31 • 来自相关话题

goTcpProxy


GitHub:https://github.com/zheng-ji/goTcpProxy


Go Report Card


一个支持负载均衡,健康检查的 TcpProxy


smailltcp


Description


English



  • A tcp proxy service

  • Supprot multi backend severs

  • Consistent Hash Load Balance

  • Auto detect down server, and remove it.

  • Monitor backend health status


中文



  • TCP 代理服务

  • 后端支持多个服务器

  • 支持一致性哈希的负载均衡

  • 自动检测失败的后端服务器,并移除

  • 后端服务的健康检查接口


How To Compile


cd $GOPATH;
git clone http://github.com/zheng-ji/goTcpProxy;
make

How To Use


配置文件详解


bind: 0.0.0.0:9999      // 代理服务监听端口
wait_queue_len: 100 // 等待队列长度
max_conn: 10000 // 并发最大连接
timeout: 5 // 请求超时时间
failover: 3 // 后端服务允许失败次数
stats: 0.0.0.0:19999 // 健康检查接口
backend: // 后端服务列表
- 127.0.0.1:80
- 127.0.0.1:81
log:
level: "info"
path: "/Users/zj/proxy.log"

// 运行服务
./goTcpProxy -c=etc/conf.yaml

gotcp


License


Copyright (c) 2015 released under a MIT style license.