07_2、【Java面试-分布式篇】(下)

77 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情

4. Gossip 协议

要求

  • 掌握 Gossip 协议

Gossip 协议

与 Paxos 和 Raft 目标是强一致性不同,Gossip 达到的是最终一致性

  • A gossip protocol is a procedure or process of computer peer-to-peer communication that is based on the way epidemics spread.

它可以快速地将信息散播给集群中每个成员,散播速度为 𝑙𝑜𝑔𝑓(𝑁)𝑙𝑜𝑔_*𝑓 (𝑁) ,其中 f 术语称为 fanout,代表每次随机传播的成员数,而 N 代表总共成员数。例如:*

  • 𝑙𝑜𝑔4(40)2.66𝑙𝑜𝑔_*4 (40)≈2.66 ,也就是大约三轮传播,就可以让集群达到一致*

  • 实际传播次数可能会高于此结果,因为随机时会随到一些重复的成员

Gossip 协议工作流程

  1. 例如,图中红色节点有其它节点不知道的信息,它的传播方式如下

image-20210902161814969.png

  1. 在红色节点能连通的节点中随机挑选 fanout 个(粗线所示)

image-20210902161917080.png

image-20210902162015054.png

  1. 把信息传播给它们(感染)

image-20210902162053851.png

  1. 在这些已被【感染】的节点中,重复 2. 3. 两步,直至全部感染,即达到最终一致性

image-20210902162400932.png

Gossip 协议优点

  • 扩展性高,传播次数不会受集群成员增长而增长过快

  * 例如: 𝑙𝑜𝑔4(80)3.16𝑙𝑜𝑔_*4 (80)≈3.16 ,集群实际成员翻了一倍,但传播次数几乎不变*

  • 容错性好,即使某些节点间发生了故障无法通信,也不会影响最终的一致性

  * 例如:A 与 B 之间发生故障无法通信,但只要 A 与其他能连通 B 的节点通信,那么信息就一定会散播到 B 

  • Robust(鲁棒性),即皮实,集群中的节点是对等的,即便一些节点挂了,一些节点新添加进来,也不会影响其它节点的信息传播

****** **参考资料 ****

5. 分布式通用设计

要求

  • 掌握检测节点活着

  • 掌握实现高可用

  • 掌握全局 ID 生成

  • 掌握负载均衡策略

  • 掌握数据分片策略

  • 掌握分布式事务

****** **提示 ****

  • 这里介绍以思想为主,因为实现和涉及的框架实在是太多了

如何检测节点活着

答案:通过心跳

  • 向节点周期性发送心跳请求,如果能收到心跳回应,表示该节点还活着

  • 但如果收不到心跳回应,却不能证明该节点死了,可能由于网络抖动、回应延时等原因没能及时收到回应。有如下解决思路:

  1. 如 Redis 哨兵模式中,如果 sentinel 向 master 发送 PING 而没有收到 PONG,只能判定主观下线,必须采纳其它 sentinel 的意见,达到多数派后才能判定客观下线,进入主备切换流程

  2. 将周期心跳检测升级为累计心跳检测机制,即记录统计该节点的历史响应时间,如果超过警戒,则发起有限次的重试作为进一步判定

如何实现高可用

建议阅读《大型网站技术架构 – 核心原理与案例分析》一书,李智慧著,优点是条理清晰,不像另一些东拼西凑的文章

节录、概要如下:

  • 应用层高可用

  * 关键是做到:无状态,即所有节点地位平等,去 session 化。利用负载均衡将请求发送到任意一台节点进行处理,如果有某个节点宕机,把该节点从服务列表中移除,不会影响业务运行

  • 服务层高可用

  * 同样要做到:无状态,此外还应当考虑:

    ① 核心服务和非核心服务隔离部署,分级管理,方便非核心服务降级

    ② 对于即时性没有要求的服务可以考虑采用异步调用优化

    ③ 合理设置超时时间,在超时后应当有相应的处理策略,如:重试、转移、降级等

  • 数据层高可用

  * 需要有数据备份机制与故障转移机制

  * 缓存服务是否需要高可用,两种观点:

    ① 缓存服务不可用会让数据库失去保护,因此需要保证缓存服务高可用

    ② 缓存服务不是数据存储服务,缓存宕机应当通过其他手段解决,如扩大缓存规模,一个缓存服务器的宕机只会影响局部

全局 ID 生成

  1. 数据库 id 表

   * Oracle 数据库,直接使用序列作为 id

   * MySQL 数据库,采用自增主键作为 id,如果想避免单点故障,用多台 MySQL 使用不同的起始值和步长来设置 auto_increment

   * 缺点:数据库并发不高,属于集中式的解决方案

  1. Redis 

   * 使用 incr 生成 id,由于 redis 的单线程特性,能保证它不会重复

   * 缺点:仍然属于集中式的解决方案,有网络消耗

  1. UUID

   * UUID 有多种实现,典型的 UUID 实现会包含时间信息、MAC 地址信息、随机数

   * 优点:属于本地解决方案,无网络消耗

   * 缺点:MAC 地址提供了唯一性的保证,但也带来安全风险,最糟的是它是字符串形式,占用空间大,查询性能低,无法保证趋势递增

  1. Snowflake

   * 通常的实现是 41 位时间信息、精确到毫秒,10 位的机器标识、12 位 的序列号,还有 1 位没有使用,共 8 个字节

   * 理解思想后,可以根据自己实际情况对原有算法做调整

   * 优点:本地解决方案,无网络消耗。长整型避免了字符串的缺点,并能保证趋势递增

负载均衡策略

负载均衡:即使用多台服务器共同分担计算任务,把网络请求和计算按某种算法均摊到各个服务器上

  • 可以使用硬件实现(如 F5),也可以使用软件实现(如 Nginx、Dubbo、 Ribbon 等诸多软件均有自己的负载均衡实现)

  • 常见负载均衡算法有:

  * 轮询,轮流来(Nginx、Ribbon)

  * 加权轮询,在轮询的基础上考虑权重,权重高的,分到请求的机会更多(Nginx 、Dubbo)

  * 最少连接,指谁的活跃连接数少,就把请求分发给谁,因为活跃多意味着响应慢(Nginx、Dubbo)

  * 最少响应时间,指谁的响应快,且活跃连接数少,就把请求发给谁(Nginx、Ribbon)

  * 随机,随便发给谁(Nginx、Dubbo、Ribbon)

  * hash,例如根据 ip 的hash 值分配请求,ip 相同的请求总会由同一台服务器处理(Nginx)

  * 一致性 hash,比 hash 好处在于添加、移除节点对请求分发影响较小(Dubbo)

****** **参考文档 ****

> * dubbo.apache.org/zh/docsv2.7…

数据分片策略

所谓分片就是指数据量较大时,对数据进行水平切分,让数据分布在多个节点上。

  1. Hash

   * 按照 key 的 hash 值将数据映射到不同的节点上

   * 优点:实现简洁、数据分布均匀

   * 缺点1:如果直接 hash 与节点数取模,节点变动时就会造成数据大规模迁移,可以使用一致性 hash 改进

   * 缺点2:查询某一类热点数据时,由于它们是用 hash 分散到了不同节点上,造成查询效率不高

  1. Range

   * 可以将 key 按照进行 range 划分,让某一范围的数据都存放在同一节点上

   * 优点1:按 range 查询,性能更高

   * 优点2:如果配合动态 range 分片,可以将较小的分片合并、将热点数据分散,有很多有用的功能

  1. 静态调度与动态调度

   * 静态意味着数据分片后分布固定,即使移动也需要人工介入

   * 动态意味着通过管理器基于调度算法在各节点之间自由移动数据

分布式事务 - 方案1:2PC 两阶段提交

image-20210902163714878.png

  • 在准备阶段所有参与者都返回 yes,则提交阶段通知参与者提交事务

  • 在准备阶段有一个参与者返回 no,或是返回响应超时,则提交阶段通知所有参与者回滚事务

存在问题

  • 阻塞型协议:所有参与者在等待接到下一步操作前,都处于阻塞,占用的资源也一直被锁定

  • 过于保守:任一个节点失败都将导致事务回滚

  • 数据不一致:在阶段二,如果只有部分参与者收到了提交请求,则会造成数据不一致

  • 协调者单点问题:如果协调者故障在阶段二出现问题,会导致所有参与者(不会超时)始终处于阻塞状态,资源也被锁定得不到释放

分布式事务 - 方案2:TCC 事务补偿

image-20210902163905501.png

  • Try:对数据校验、资源预留

  • Confirm:执行业务确认

  • Cancel:实现与 try 相反的操作

要点

  • 本质上还是两阶段提交,不过无需借助数据库驱动,在应用层完成,业务侵入比较深

  • 需要每个节点上配置 TCC 框架,记录操作日志和状态,以便在宕机时恢复

  • TCC 操作必须要考虑幂等

分布式事务 - 方案3:基于可靠性消息的最终一致性方案

要点

  • 2PC 和 TCC 都属于同步方案,实际开发中更多采用的是异步方案

  * 例如:下单后的支付、扣减库存、增加积分等操作对实时性要求并不高。此时将下单成功的消息写入消息中间件,利用消息中间件实现最终一致性

  • 问题转换成保证本地事务与消息投递的原子性

  * 例如:RocketMQ 的解决方案如下

    ① 发送消息到 broker ,只是此时消息称为半消息,无法消费

    ② 执行本地事务,如果成功,则半消息转换为正式消息,允许被消费;如果失败,删除 broker 上的半消息

    ③ 对于 broker 这端,如果迟迟不能收到半消息的 commit 或 rollback 信息,则会回查本地事务是否完成,根据状态确定如何处理

6. 一致性 Hash(补充)

前面讲负载均衡和数据分片时,都提到了一致性 Hash,它是为了解决在服务器增、删时普通 hash 算法造成数据大量迁移问题的

普通 hash 算法

  • 假设有 3 台服务器,10 个 key 在服务器上的分布如下图所示

image-20210902164609023.png

  • 添加一台服务器后,数据分布变成下图,可以看到除了一个 key(上下颜色相同的)以外,其它 key 都得迁移

image-20210902164843883.png

一致性 hash 算法

  • 假设有 3 台服务器,10 个 key 在服务器上的分布如下图所示

image-20210902165058027.png

  • 添加一台服务器后,数据分布变成下图,发现仅有 3 个key 需要迁移(上下颜色不同的)

image-20210902165158024.png