分布式中间件总结

354 阅读7分钟

分布式 CAP架构

CAP原则 C:一致性(consistency) A:可用性(Availablity) P:分区容错性(Partition tolerance)

中间件: MQ Zookeeper Nacos Redis ....

1、leader选举(raft、ZAB)

2、数据同步(主从复制)

 2.1 鉴定从节点数据是否最新
  
     zk : ZXID 事务id  越大数据越新  
     redis: SLAVE_RANK,表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新

redis

1、leader选举(广播方式给别的master发消息,只有master响应,master发FAILOVER_AUTH_ACK,收到半数则当选成功)

  1.      slave发现自己的master变为FAIL
    
  2.      将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息
    
  3.      其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack
    
  4.      尝试failover的slave收集master返回的FAILOVER_AUTH_ACK
    
  5.      slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点是不能选举成功的)
    
  6.      slave广播Pong消息通知其他集群节点。
    

    2、数据同步(异步复制:主节点写入成功便返回成功,另起一个进程去同步) 2.1 主从原理 1.从结点发送同步命令 2.主节点进行数据持久化,生成收到同步命令时的数据快照rdb 后续命令做一个缓存 3.将数据快照发给从节点 4.从节点清空老数据,加载主节点的快照文件 5.主节点发送缓存数据 6.从节点把缓存命令写入内存 2.2 主从复制风暴(多个从节点同时复制主节点导致主节点压力过大) 解决:让部分从节点与从节点(与主节点同步)同步数据

    3、数据存储(持久化)

    方式: RDB:复制整个数据到rdb文件 AOF:保存指令

    操作: save:同步,阻塞客户端命令 bgsabe:异步,需要fork子进程,消耗内存,借助操作系统提供的写时复制技术(Copy-On-Write, COW),在生成快照的同时,依然可以正常处理写命令,

    4、高性能原因 : 零拷贝 NIO 数据都在内存 单线程减少线程切换性能损耗

            单线程(网络事件处理器 - 文件事件处理器(file event handler):Redis在处理客户端的请求时,包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理,这就是所谓的“单线程”
    
            多线程(6.0以后):网络连接数量太大了,单线程(线程池)顶不住了,再搞一个(线程池)处理,一个主线程池处理连接请求,另一个负责读取、发送,但是写操作还是使用单线程来处理(对内存操作)
    

    5、高可用方案

    cluster: 特点:将所有数据划分为 16384 个 slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。

      槽位定位算法:HASH_SLOT = CRC16(key) mod 16384
    
      跳转重定位:当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向 客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。客户端收到指令后除了跳转到正确的节点上去操作,还会同步更新纠正本地的槽位映射表缓存,后续所有 key 将使用新的槽位映射表。
      
      节点通信方式:  gossip 一传十,十传百
    
      leader选举:(广播方式给别的master发消息,只有master响应,master发FAILOVER_AUTH_ACK,收到半数则当选成功)
    
    
        
    

MQ

1、结构

  broker(一个kafka服务端,一个broker)
  topic
  Tag(RocketMQ特色)
  Queue(RocketMQ)/Partition(kafka)
  offset(偏移量,消息ID)
  Zookeeper(选controller)/NameServer

2、消息如何存到磁盘上的?(刷盘机制)

     同步刷盘:在返回写成功状态时,消息已经被写入磁盘。具体流程是,消息写入内存的PAGECACHE后,立刻通知刷盘线程刷盘, 然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写 成功的状态。

     异步刷盘:在返回写成功状态时,消息可能只是被写入了内存的PAGECACHE,写操作的返回快,吞吐量大;当内存里的消息量积累到一定程度时,统一触发写磁盘动作,快速写入

3、消息数据如何同步? (主从复制)

     RocketMQ:
     Dledger主从架构(基于raft协议)  两阶段同步
        1、Leader Broker上的Dledger收到一条数据后,会标记为uncommitted状态,然后他通过自己的DledgerServer组件把这个uncommitted数据发给Follower Broker的DledgerServer组件。

        2、Follower Broker的DledgerServer收到uncommitted消息之后,必须返回一个ack给Leader Broker的Dledger。然后如果Leader Broker收到超过半数的Follower Broker返回的ack之后,就会把消息标记为committed状态。

        3、Leader Broker上的DledgerServer就会发送committed消息给Follower Broker上的DledgerServer,让他们把消息也标记为committed状态。

2、高性能原因

  零拷贝、sendfile技术 、在磁盘末尾添加数据  

3、问题及解决方案

  消息堆积 : 加consumer
 
  消息有序(局部有序):存到同一个MessageQueue,MessageQueue的FIFO设计天生就可以保证这一组消息的有序。

  消息不丢失
        1、生产者使用事务消息机制。
        2、Broker配置同步刷盘+Dledger主从架构
        3、消费者不要使用异步消费。
        4、整个MQ挂了之后准备降级方案
  NameServer挂了如何保证消息不丢失?(服务降级,redis顶上)  

4、特点 支持事务(half消息,探测mq服务状态)

Zookeeper

  ZXID :在 ZAB 协议的事务编号 ZXID 设计中,ZXID 是一个 64 位的数字,其中低 32 位可以看作是一个简单的递增的计数器,针对客户端的每一个事务请求,Leader 都会产生一个新的事务 Proposal 并对该计数器进行 + 1 操作。
 
  Myid:节点编号

1、ZAB协议

  消息广播:ZAB 协议的消息广播过程使用的是一个原子广播协议,类似一个两阶段提交过程。对于客户端发送的写请求,全部由 Leader 接收,Leader 将请求封装成一个事务 Proposal,将其发送给所有 Follwer ,然后,根据所有 Follwer 的反馈,如果超过半数(含leader自己)成功响应,则执行 commit 操作。

  崩溃恢复:如果让 Leader 选举算法能够保证新选举出来的 Leader 服务器拥有集群中所有机器 ZXID 

最大的事务,那么就能够保证这个新选举出来的 Leader 一定具有所有已经提交的提案

     两个原则:1、ZAB 协议确保丢弃那些只在 Leader 提出/复制,但没有提交的事务。
              2、ZAB 协议确保那些已经在 Leader 提交的事务最终会被所有服务器提交。

2、节点类型 znode

  •      PERSISTENT-持久化目录节点
    
  •      PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
    
  •      EPHEMERAL-临时目录节点
    
  •      EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
    
  •      Container 节点(3.5.3版本新增,如果Container节点下面没有子节点,则Container节点在未来会被Zookeeper自动清除,定时任务默认60s 检查一次)
    
  •      TTL 节点( 默认禁用,只能通过系统配置 zookeeper.extendedTypesEnabled=true 开启,不稳定) 
    

    3、分布式锁实现

    1、创建一个目录mylock;
    2、线程A想获取锁就在mylock目录下创建临时顺序节点;
    3、获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
    4、线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
    4、线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。