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