故事还是得从单机开始,没有单机哪儿来的分布式?
在IT世界,二进制的数据是我们最宝贵的资产,必须要把它保存在断电也不怕的硬盘上。
但是只用一块硬盘很危险,万一坏了数据就彻底没了。于是人们就想了个办法,把两块硬盘组织了起来,互为备份。
这种方式有个专门的术语,叫RAID ,就是冗余磁盘阵列的意思。
上图中两个磁盘互为备份,是RAID 1 , 数据会被同时写到两块硬盘中,安全性大大提高。
需要提醒的是,虽然这里有两块硬盘,但是从用户角度,只能看到一个逻辑的硬盘,操作系统已经把底层的两块硬盘给隐藏了。
RAID 1 的不爽之处是浪费了一个硬盘的容量, 改进方案可以用RAID 5:
RAID5 并没有像RAID 1那样对数据做备份, 它引入了一个叫做奇偶校验的东西(上图的黄色部分), 这样当一个磁盘坏的时候,就可以利用其它磁盘的数据进行恢复。
具体的计算如下(略繁琐,可以跳过)。
奇偶校验是用异或(xor)来实现的,简单的说,相同为0,不同为1
比如, 磁盘1保存 的数值是7(二进制 0111), 磁盘2 保存的是数值是8(二进制1000), 做一个xor操作
0111 xor 1000 => 1111
那磁盘3保存的值就是二进制1111。
现在假设磁盘2坏掉了,换了个新磁盘,那根据磁盘1 中的 0111 和磁盘3中的1111,就可以计算出磁盘2的值是:
0111 xor 1111 => 1000
于是磁盘2的值就可以恢复了!
但是, 如果在恢复的时候,又有一个盘坏掉了,那就悲催了。
使用RAID 5,写入时需要做计算,性能受到一定影响。
聪明的你肯定可以想到,除了RAID 1, 5, 还有其他方式, 例如RAID 0, 2,3,6,7, 5E 等,八仙过海,各显神通。
主从备份
但是在互联网应用中,RAID用的越来越少了,更常用的是跨越机器的备份。
这种方式不会把数据分散地写到各个机器的硬盘中,而是整体地写到一个机器中,然后整体地复制到其他机器。
由于跨越了网络,这种备份通常是异步的。
副本多了,还带来了一个好处,可以把读操作发到副本机器上去读,主库只负责写操作, 压力就降低了。
这就是常见的MySQL主从复制,读写分离。
可是,由于时间差的关系, 副本的数据可能会落后于主库, 产生读写不一致的问题,需要程序员想办法来解决。
(注:具体的复制细节,请移步《MySQL:硬盘在24 * 7工作中罢工了,我该怎么办?》)
分区
备份问题解决了, 如果你的数据越来越多,马上就会面对另外一个问题:一个机器无论如何也放不下了,怎么办?
解决办法就是分区, 把逻辑上是整体的数据,分别存储到不同的物理机器上去。
(不同的系统称呼不同,Elasticsearch 把它称为Shard,即分片, HBase把他称为Region)
可以这么分:
每个机器保存一段数据, Redis的Cluster就采用了类似的方式,每个服务器负责一段Hash槽。
还可以这么分:
这种方式称为Round-Robin ,也是Kafka默认的分区策略
Elasticsearch 则采用了另外一种办法:余数算法
余数算法简单明了, 非常容易确定一个数据保存在哪个机器上, 存取都非常地方便。
但是弱点也非常明显:如果数据更多了,想增加分区怎么办?
对不起,增加不了! 因为一旦增加分区,余数就会发生变化,数据就找不到了。
分区+备份
仅仅把数据做了分区还不够,如果分区所在的节点如果坏了,数据就丢了。
为了可靠性,数据必须得有备份,像这样:
每个机器上都有一个分区的主副本,每个主副本都有两个从副本(保存在不同的机器上),数据安全多了。
这是个非常常见的复制方式,Kafka和Elastic search 都在使用,需要注意的是:复制的最小单位是分区,而不是节点。
比如机器1挂掉了, 分区1的主副本丢掉了, 不用怕, 分区1还有两个从副本,分别保存在机器2和机器3上, 可以从他们那里去读数据。
问题是现在有两个从副本,他们都想当主副本,承担起主副本的职责,到底谁来当?
这是个非常麻烦的问题,把问题抽象一下:这相当于在一个分布式环境下(通常有着不可靠的网络),各个节点要达成共识:同意一个节点当老大。
解决这个问题,必须要利用一些共识算法(Raft, Paxos 等), 这些算法都很复杂,可以自己去实现,但是最好还是利用现成的组件,例如Zookeeper,让这个稳定可靠的“黑盒子”来帮忙吧。