分布式中的一致性与可用性之复制

概述

为了保证分布式存储系统的高可靠和高可用,数据在系统中-般存储多个副本。当某个副本所在的存储节点出现故障时,分布式存储系统能够自动将服务切换到其他的副本,从而实现自动容错。分布式存储系统通过复制协议将数据同步到多个存储节点,并确保多个副本之间的数据-致性。

复制协议分为两种,强同步复制以及异步复制二者的区别在于用户的写请求是否需要同步到备副本才可以返回成功。假如备份副本不止一个,复制协议还会要求写请求至少需要同步到几个备副本。

一致性和可用性是矛盾的,强同步复制协议可以保证主备副本之间的一-致性,但是当备副本出现故障时,也可能阻塞存储系统的正常写服务,系统的整体可用性受到影响;异步复制协议的可用性相对较好,但是一致性得不到保障,主副本出现故障时还有数据丟失的可能。下图就是一个强同步复制协议:
在这里插入图片描述
反之,如果是异步复制协议,就是**主副本完成就通知客户端已经完成。**在还没有同步备副本之前,主副本如果崩了,那就会丢失数据。

强同步复制和异步复制都是将主副本的数据以某种形式发送到其他副本,这种复制协议称为基于主副本的复制协议(Primary-basedprotocol)。这种方法要求在任何时刻只能有一个副本为主副本,由它来确定写操作之间的顺序。如果主副本出现故障,需要选举一个备副本成为新的主副本,这步操作称为选举,经典的选举协议为Paxos协议:见:分布式协议

主备副本之间的复制-般通过操作日志来实现。操作日志的原理很简单:为了利用好磁盘的顺序读写特性,将客户端的写操作先顺序写人到磁盘中,然后应用到内存中,由于内存是随机读写设备,可以很容易通过各种数据结构,比如B+树将数据有效地组织起来。当服务器宕机重启时,只需要回放操作日志就可以恢复内存状态。为了提高系统的并发能力,系统会积攒- -定的操作日志再批量写人到磁盘中,这种技术一般称为成组提交

再者就是会有一个称为检查点的东西,用来避免操作日志过于庞大。一段一段分开就好。比如Redis 的偏移量等,也是为了提高性能啦。

除了基于主副本的复制协议,分布式存储系统中还可能使用基于写多个存储节点的复制协议( Replicated-write protocol )。比如Dynamo系统中的NWR复制协议,比较少用,不讨论!!!

关于一致性与可用性的探讨见:CAP理论。记得粘链接!!

接下来,我们来看看三种DB是如何实现主备复制的。(都基于主副本的复制协议)

1. MYSQL(mysql提供了两种复制的方式:基于行的复制与基于语句的复制)

实现模型就是上面提到的:基于主副本的异步复制协议(二进制文件操作日志),所以就会导致同一时间点上主备库数据不一致。加上SQL执行影响很多行数据,就会使得备库延迟无法估计。

  • 基于语句的复制:操作日志文件是执行过的SQL命令。(存在环境等等条件不一致,导致无法执行的情况)按照事务执行顺序写
  • 基于行的复制:操作日志文件是实际数据。

基本的工作流程如下:
在这里插入图片描述
如何确定主备是否一致?:pt-table-checksum 工具

突然想到一个问题:一个事务的完成意味着什么?相对于分布式 Mysql 而言。答:意味着主库写入事务操作日志文件成功。

2. Redis

说明:该部分是基于 Redis2.8 版本的学习 ,最新版本请查看官网文档。

执行SLAVEOF ip:port,自身成为从(slave)服务器,被复制的服务器成为主(master)服务器。

这里为了减少啰嗦,我就直接从2.8以上版本开始说起。总体而言,Redis的主从复制分为两个功能:

  • 同步:将从服务器更新到主服务器状态。
  • 命令传播:主服务器的数据被修改,使主从保持一致的状态。

同步

旧版本的实现前提:客户端执行SLAVEOF要求从服务器同步,从服务器向主服务器发送"SYNC"。

RDB文件是一个经过压缩的二进制文件。RDB保存的是变化的数据,而不是执行的命令,AOF保存的是DB上执行过的命令。具体说到可用性时再讲。

在这里插入图片描述
在这里插入图片描述
那么这样子的方式有什么问题呐?来看这张图:
在这里插入图片描述

新版的复制功能的实现 “PSYNC”:

其将同步分为了两种:完整重同步和部分重同步

  • 完整重同步:用于初次复制的情况。和上边一样。
  • 部分重同步:只要为了解决断线重连的问题。实现如下图:
    在这里插入图片描述
    很明显,看到这张图,第一个想到的就应该是master是如何保存写命令,以及如何断点续传的
部分重同步的实现

由三部分完成:复制偏移量,复制积压缓冲区,运行ID

  • 复制偏移量:很简单,就是主从分别维护一个偏移量,如果不相同就不是同步的。
    在这里插入图片描述
  • 复制积压缓冲区:主服务器维护的一个固定长度的队列,用来写入最近传播的写命令。默认1MB。那就很明显喽,如果偏移量范围在缓冲区里面就部分重同步,如果超出就完整重同步喽!!!

在这里插入图片描述

  • 运行ID:每个运行的服务器都会有一个ID。40个16进制数字组成。就是在从服务器初次复制时,保存主服务器ID,然后重新连接后判断是否相同,相同肯定就有可能执行部分重同步,不同就一定会是完整重同步。

命令传播:就是将主服务器执行过的命令下发到从服务器执行一遍。

关于复制的整个流程记录:

  1. 设置主服务器的地址和端口
  2. 建立socket 连接
  3. ping
  4. 身份验证
  5. 发送端口信息
  6. 同步
  7. 命令传播

3. OceanBase (分布式关系型DB)

不知道该怎么写,先占坑。。。。

参考文章:
https://mp.weixin.qq.com/s?__biz=MzI5OTUxMDk1Mw==&mid=2247484037&idx=1&sn=e0d7f531dd5c698e9aeb860f007c97cd&chksm=ec943f43dbe3b6553dac060988e44eab09d744141573a78f1b9912e6163f9d7e0ba548a07263&mpshare=1&scene=23&srcid=0804n3JwZaPMDOt4kGsFpuDf&sharer_sharetime=1564910544639&sharer_shareid=7081cb93b545d317430d14bbeac639d9#rd

在腾讯涉及到分布式时,遇到的一些真实案例:

在这里插入图片描述

  • 如何编写一个简单事务:
class Object
{
    Object(const char * hostname,const char * db_name,......){

    }
    int ExecSql(string sql)
    {
        执行sql语句
    }
    ~Object(){
        if(is_commit)
            return  ;
        else {
            rollback;
        }
    }
    private:
        bool  is_commit;
};
int  main()
{
    Object  ob("xxx","xxx");
    int ret = ob.ExecSql(sql1);
    if(ret != 0)
    {
        DEBUG_LOG();
        return ret;
    }
    ret = ob.ExecSql(sql2);
    if(ret != 0)
    {
        DEBUG_LOG();
        return ret;
    }
    ret = ob.commit();
       {
        DEBUG_LOG();
        return ret;
    }
}

使用的是两阶段提交协议。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值