本篇文章谢绝转载,欢迎转发java
几年以前,曾蚍蜉撼树的想要写一个兼容RDBMS和NoSQL的数据库,结果仅实现了一个Raft
协议,写了一棵BTree
,就放弃了。使用Golang
写这个算是比较简单的了,但过程难以言诉,有点蚂蚁撼大树了。算法
而我的,因为工做的关系,也已经有四五年没有和SQL打交道了。最近重拾,感慨良多。sql
像MySQL
这种RDBMS
,天生是存在分布式缺陷的,在海量数据的今天,很容易就达到瓶颈。过去这么多年,一点长进都没有。因此常常的操做就是换存储引擎、分库分表、引入中间件,阉割功能。数据库
能让你不忍割舍的,一个就是MySQL协议,你用惯了;一个就是事务,你怕丢数据。编程
幸运的是,大多数互联网业务不须要强事务,甚至连MySQL协议都不须要。接下来咱们看一下要将一个传统的MySQL改形成分布式的存储,是有多么的困难。安全
CAP理论应该是人尽皆知的事情了,在此很少提。并发
单机上的任何数据都是不可信的,由于硬盘会坏,会断电,会被挖光缆。因此通常经过冗余多个副原本保证数据的安全。副本的另一个做用,就是提供额外的计算能力,好比某些请求,会落到副本上。副本越多,可用性越高。 框架
而当一类数据足够大(好比说某张表),在其上的操做已经很是耗时的状况下,就须要对此类数据进行切割,将其分布到多台机器上。这个切割过程就是Sharding,经过必定规则的分片来减小单次查询数据的规模,增长集群容量。分布式
当某些查询涉及到多个分片,这个过程就比较缓慢了。协调节点须要与每一个节点进行沟通,而后聚合查询的结果,分片数越多,时间越长。 高并发
通常,在一个维度上的分库都会遇到上述问题,可怕的是你可能会有多个维度的需求。对于一些NoSQL
来讲,每个维度,都须要冗余一份数据,这通常是膨胀性的。
集群的规划并非一成不变的,你的集群可能会加入新的节点;也可能有节点由于事故离线;也可能由于分片维度的问题,数据发生了倾斜。当这种状况发生,集群间的数据会发生迁移,以便达到平衡。这个过程有些是自动的,也有些是手动进行触发。这个过程也是最困难的:既要保证数据的增量迁移,又要保证集群的正确服务。
若是你想要事务(不少状况是你不懂技术的Leader决定),那就集中存储,不要分片。事务是不少性能场景和扩展场景的万恶之源,流量大了你会急着去掉它。
针对一个分片的数据,只能有一个写入的地方,这就是master
,其余副本都是从master
复制数据。
副本可以增长读操做的并行读,但会读到脏数据。若是你想要读到的数据是一致的,能够采用同步写副本的方式,好比KAFKA的ack=-1
,只有所有同步成功了,才认为本次提交成功。
但若是你的副本太多,这个过程会很是的慢。你可能想要经过分配写入和读取的副本个数来协调写入和读取的效率,Quorum
的R+W>N
就是一个权衡策略。
这个过程能够简单的用抽屉原理来解释。
上面的这个过程比较简单,因此须要有点复杂的压下轴。一个名门就是Paxos
,复杂的很,之前看了一个星期也没所有搞懂 -.-。 ZAB
协议是ZooKeeper
在Paxos
协议的基础上进行扩展而来的,说实话也没看懂,并且ZK的源代码也很是的... 惟一看得懂的就是Raft
协议,这个是Etcd
和Consul
的基础,是简化版的Paxos
,目前来看是高效且可靠的。
副本是用来作HA
的,因此master
死了,要有副本顶上来。这个过程就涉及到master
的选举。
像kafka
,借助zookeeper
来进行主分区的选举。而ES
是使用Bully
算法,经过选出ID最大的节点看成master。不管什么方式,都是要从一堆机器中,找到一个惟一的master节点,并且在选举的过程当中,都须要注意一个脑裂
问题(也就是不当心找到俩了)。master选举一般都是投票机制,因此最小组集群的台数通常都设置成n/2+1
。 这也是为何不少集群推荐奇数台的缘由!
cassandra采用了另一种协议来维护集群的状态,那就是gossip
,是最终一致性的典范。
副本机制在传统的DB上也工做的很好。好比MySQL经过binlog
完成副本的同步;Postgresql采用WAL
日志完成同步。但涉及到主从的切换,尤为是有多个从库的状况下,通常都不可以自动化执行。
分片就是对资料的切割,也就是一套主从已经装不下了。分片的逻辑能够放在客户端,好比驱动层的数据库中间件,Memcache等;也能够放在服务端,好比ES、Mongo等。
分片的信息组成了一组元数据,存放了切割的规则。这些信息能够借助外部的存储好比KAFKA;也有的直接同步在集群每一个节点的内存中,好比ES。比较流行的NoSQL主从信息
最小维度通常都是分片,一个节点上同时会有master分片和其余分片的副本。
分片的规则通常有下面几种:
Round-Robin 资料轮流落进不一样的机器,数据比较平均,适合弱相关性的数据存储。坏处是聚合查询可能会很是慢,扩容、缩容难。
Hash 使用某些信息的Hash进行寻路,客户端依照一样的规则能够方便的找到服务端数据。问题与轮询相似,数据过于分散且扩容、缩容难。Hash一样适合弱相关的数据,并可经过一致性哈希
来解决数据的迁移问题。
Range 根据范围来分片数据,好比日期范围。能够将一类数据归档到特定的节点,以增长查询速度。此类分片会遇到热点问题,会冷落不少机器。
自定义 自定义一些分片规则。好比经过用户的年龄,区域等进行切分。你须要维护大量的路由表,而后本身控制数据和访问的倾斜问题。
嵌套 属于自定义的一种,路由规则能够嵌套。好比首先使用Range
进行虚拟分片,而后再使用Hash
进行实际分片。在实际操做中,这颇有用,须要客户端和服务端的结合才能完成。
路由的元数据不能太多,不然它自己就是一个访问瓶颈;也不可以太复杂,不然数据的去向将成为谜底。分布式系统的数据验证和测试是困难的,缘由就在于此。
惋惜的是,使用用户的年龄,和使用用户的地域进行分片,数据的分布彻底不一样。增长了一个维度的查询速度,会减慢另外一个维度的性能,这是不可避免的。切分字段的选择很是重要,若是几个维度都很必要,解决的方式就是冗余---按照每一个切分维度,都写一份数据。
大部分互联网业务通常经过用户ID便可找到用户的全部相关信息,规划一个分层的路由结构即能知足需求。但数据统计类的需求就困难的多,你看到的不少年度报告,多是算了个把月才出来的。
数据写入简单,由于是按条写的。但数据的读取就复杂多了,由于可能涉及到大量分片,尤为是AGG查询业务。通常会引入中间节点负责数据的聚合,由于大量的计算会影响master的稳定,这是不能忍受的。
经过区分节点的职责,能够保证集群的稳定。根据不一样的须要,会有更多的协调节点被加入。
在作分布式以前,先要确保在单机场景可以最优。除了一些缓冲区优化,还有索引。但分布式是一直缺乏一个索引的,曾经想设计一种基于内存的分布式索引,但仍是赚钱养家要紧。
存储要有一个强大的查询语法引擎,目前来看非SQL引擎莫属。抽象成一棵巨大但语法树,而后在其上编程。像Redis这样简单的文本协议,是一个特定领域的特例。
ACID是强事务的单机RDBMS的特性。涉及到跨库,会有二阶段提交、三阶段提交之类的分布式事务处理。
数据库的分布式事务实现叫作XA
,也是一种2PC
,MySQL5.5版本开始已经支持这种协议。
2PC会严重影响性能,并非和高并发的场景,并且其实现复杂,牺牲了一部分可用性。
另外一种经常使用的方式就是TCC
(补偿事务)。TCC的本质是:对于每个操做,都须要一个与之对应的确认和撤销操做。但惋惜的是,在确认和撤销阶段,也有必定几率发生问题,须要TCC的TCC;不少业务根本没有相应的逆操做,好比删除某些数据,TCC就无法玩了。
TCC须要大量编码,适合在框架层统一处理。
还有一种思路是将分布式事务合并成本地事务来处理。也就是一个事务包含一条消息+一堆数据库操做,成功执行完毕后再设置消息的状态,失败后会重试。 此种方式将消息强制耦合到业务中,且消息系统自己的事务问题也是一个须要考虑的因素。
分布式事务除了要写多个分片的协调问题,还有并发读写某一个值的问题。
好比有不少请求同时在修改一个余额。经常使用的方法就是加锁,可是效率过低。咱们回忆一下java如何保证这种冲突。 对于读远大于小的操做,可使用CopyOnWrite这种方式优化;对于原子操做,可使用CompareAndSet的方式先比较再赋值。要想保证余额的安全,使用后者是颇有必要的。
MVCC
是行级别锁的一种妥协,他用来保证一个值在某个事务中是一致的,避免了脏读和幻读,但并不能保证数据的安全,这点必定要注意。
举个栗子:你的家庭资金共有500w,你私自借给好基友500万。使出了洪荒之力在年末讨回了借款,并追加了利息。在老婆查账的时候,原封不动的展现给她看。这就是最终一致性。
我习惯性这样描述:在可忍受的时间内,轻过程、重结果,达成一致便可。虽然回味起来心有余悸。
在这种状况下,不须要过多的使用分布式事务来控制。你只管写你的数据,不用管别人是否写成功。咱们经过其余的手段来保证数据的一致性。
一种方式是常见的定时任务,不断的扫描最近生成的数据,进行补齐。若是程序实在没法判断,则写入到异常表中人工介入。
另一种方式就是重放数据,将这个过程从新执行一遍,要求业务逻辑是可重入的(幂等)。若是依然有问题,仍是须要人工介入。
比较幸运的是,良好的设计下,这些异常情况产生的概率是比较小的,投入和产出会超出指望。采用了BASE的系统,选择的是弱一致性,高度依赖业务监控
组件来及时的发现问题。
这种思想已经被大多数研发所接受,除非你的老板可忍受时间很短!
哦,BASE的全称是: Basically Available(基本可用), Soft state(软状态), Eventually consistent(最终一致性)
做为研发人员,是不能对软件有好恶倾向的,只有合适与不合适的区别。没有精力去改进这些系统,只能经过不断的取舍,组合它们的优势。
Greenplum和ElasticSearch,在分布式DB领域,是两个典型实现,它们都以强大的分布式能力著称。
Greenplum表明了RDBMS是如何向分布式发展的,固然它是创建在强大的Postgresql基础上的。
ES是创建在Lucene上的全文检索搜索引擎,但好像你们也拿它当数据库使用。源码是java的,有不少值得推敲的地方。
缓慢的I/O设备,再也没法压榨单机的性能,注定了要走向分布式。但前路依然漫漫,看看五花八门的分布式数据库就知道了。
没有谁,能一统江湖。