分布式架构常见的问题与解决方案

布式架构分常见的问题与解决方案

Posted by caotc on March 20, 2021

分布式与集群

现在随处可见分布式集群这个词,由于分布式和集群这两个词经常被放在一起使用,所以两个词似乎就是连在一起使用的,其实并非如此.

个人认为,集群指的是多台机器节点部署的是同样的应用,提供的是同样的服务.

而分布式指的是一个操作,需要在不同的服务节点完成.

所以分布式和集群本身是两个概念,要解决的也是不同的问题.

集群要解决的问题是高并发请求下单节点服务器存在性能上限和节点宕机导致不可用的问题.

分布式要解决的是单体架构无法进行细粒度的维护、开发、管理、隔离、优化、分配资源的问题.

所以分布式和集群并不是必须绑在一起的,只是现实中大部分情况下是一起使用的.

如果使用的是单体式集群架构,就不需要面对分布式架构需要面对的问题.

单节点应用到分布式应用常见的问题与解决方案

我们试着梳理一下,单节点应用到分布式应用常见的问题与解决方案.

单节点应用

单节点应用在不采用极端优化方案时,是不存在一致性问题的,只有性能问题和可用性问题.

性能优化方案

NIO(Non-blocking IO)

原理

将网络和文件的IO操作从BIO(Blocking I/O)改为NIO能减少代价昂贵的线程切换,增加CPU利用率.

常见案例
  • Netty是作为异步事件驱动的网络应用程序框架,默认NIO.所有使用netty的项目均使用了NIO方案,包括Apache Flink/Apache Spark/Elastic Search等项目.

  • Jetty,tomcat等容器也可以配置使用NIO方式

请求AIO(Asynchronous I/O)

原理

将操作变成异步的,内存写操作成功后,磁盘写入只写入将请求参数,就返回成功响应. 后续操作由服务器静默完成,该方案由于降低了响应时间,也能增加tps承受力.

常见案例
  • redis的持久化可以有多种配置方式,如果希望保证不丢失数据,那么必须开启AOF(append-only file),并且配置AOF模式为appendfsync always.即每次有数据修改发生时都会写入AOF文件,再返回成功响应.
  • RocketMQ 的刷盘机制分为同步和异步模式,RocketMQ的同步刷盘模式.
  • kafka同样可以配置异步刷盘机制参数,指定为og.flush.interval.messages=1,即1条消息刷盘1次.

磁盘AIO(Asynchronous I/O)

原理

在上面请求AIO方案的基础上,将磁盘写入的机制也变成异步的,内存写操作成功后,就返回成功响应. 后续操作由服务器静默完成,能够进一步降低了响应时间,也能增加tps承受力.

代价
  • 不能保证数据持久性:即使客户端收到了写操作成功的响应,也可能在服务器宕机的情况下丢失改写操作的修改.
常见案例
  • redis的持久化的RDB(快照持久化)和appendfsync always以外的AOF模式.
  • RocketMQ 的刷盘机制中的异步刷盘机制.
  • kafka配置异步刷盘机制参数og.flush.interval.messages=1以外的参数.

内存AIO(Asynchronous I/O)

原理

在上面磁盘AIO方案的基础上,将内存写入的机制也变成异步的.不等待内存写操作成功,直接将请求参数记录到内存即返回成功响应. 后续操作由服务器静默完成,能够进一步降低了响应时间,也能增加tps承受力.

代价
  • 不能保证数据持久性:即使客户端收到了写操作成功的响应,也可能在服务器宕机的情况下丢失改写操作的修改.
  • 不能保证读操作的数据一致性:即使客户端收到了写操作成功的响应,由于内存写操作还未实际完成,读操作获取的值可能为旧值.

宕机问题

单节点应用无法解决任何宕机问题,只需要节点宕机则服务必然不可用.

节点宕机的解决方案只有多节点部署.

磁盘物理损坏问题

单节点应用也无法解决磁盘物理损坏问题,只要磁盘物理损坏,必然丢失持久化数据.

磁盘物理损坏问题的解决方案只有多份数据备份.

分布式应用

分布式应用采用多节点部署,多份数据备份的方式解决了可用性问题,但同时也带来了一致性与可用性的矛盾.

分布式一致性问题

这里所指的分布式应用指的是需要管理资源的持久化的,有状态的应用.

对于无状态的应用,其实并没有我们常说的那些分布式一致性等分布式问题.

分布式一致性问题从概念上讲可以分为两种:

  1. 分布式事务:一个写操作请求,对应多个分布式应用的写操作,多应用之间写操作的一致性问题.

  2. 分布式数据一致性:一个写操作请求,对应同一个分布式应用的多个节点之间的写操作的一致性问题.

由于分布式事务问题通常用分布式事务这个专用名词来指代,因此我们通常所说和这里所讨论的分布式一致性问题仅指第二种.

如果采用所有数据备份都写入完成才响应成功的方案,那么只要有一个节点处于不可用状态,就会导致整个服务集群处于不可用状态,这与单节点不可用时仍然让服务整体保持可用的目标所矛盾.

但是如果不采取这种方案,则必然损失数据一致性.

所以分布式一致性问题无法在不牺牲可用性的前提下解决,只能在可用性与一致性之间取舍,这就是CAP理论.

常见一致性级别

强一致性

全局的读写操作保证强一致性,任何时刻,任何用户或节点都可以读到最近一次成功更新的副本数据.

原理

所有写操作成功,才返回成功响应.

说明

全局悲观锁机制

常见案例
  • kafka的replica副本同步机制中,只有所有副本都同步完写操作,消费者才能进行对该消息的消费.如果生产者设置request.required.acks=-1,即生产者等到所有副本同步完才返回成功,则kafka此时为强一致性.
全局单调一致性

任何时刻,任何用户一旦读到某个数据在某次更新后的值,任何用户不会再读到比这个值更旧的值。

原理

所有写操作成功,写结果才可见.

说明

写操作和读操作之间无法保证数据一致性.即使已经返回了写操作成功,但是由于有部分节点数据未写入完成,那么读操作依然看不到之前写操作的成功结果.

但是写结果可见就代表所有节点写操作成功,所以能够保证全局的单调一致性.

常见案例
  • kafka的replica副本同步机制中,只有所有副本都同步完写操作,消费者才能进行对该消息的消费.如果生产者设置request.required.acks不为-1,即生产者并未等到所有副本同步完才返回成功,则kafka此时为全局单调一致性.
用户单调一致性

任何时刻,任何用户一旦读到某个数据在某次更新后的值,该用户不会再读到比这个值更旧的值。

原理

服务端返回写操作成功响应,写结果即可见.

但是用户与服务端都存储各自的全局事务id,服务端应用节点只能处理全局事务id小于自己的用户.

说明

用户读操作可能看见旧值也可能看见新值,取决于处理请求的服务端应用节点事务id,但是用户看见新值后就无法再看见旧值,所以能够保证用户级别的单调一致性.

常见案例
会话单调一致性

任何用户在某一次会话内一旦读到某个数据在某次更新后的值,这个用户在这次会话过程中不会读到比这个值更旧的值。

原理

服务端返回写操作成功响应,写结果即可见.

但是用户与服务端应用节点保持会话关系,在会话失效前,用户请求由同一个服务端处理.

说明

用户会话内读操作可能看见旧值也可能看见新值,取决于处理请求的服务端应用节点事务id,但是用户在会话内看见新值后就无法再看见旧值,所以能够保证会话级别的单调一致性.

但是如果会话失效时,不保证该用户的新会话所匹配的服务器的事务id新旧.

常见案例
  • rocketmq新版本使用基于raft算法协议的Dledger库来保证一致性.
最终一致性

一旦数据更新成功,各个副本上的数据最终达到完全一致的状态,但达到完全一致状态所需要的时间不能保障.

原理

服务端返回写操作成功响应,写结果即可见.

但是用户请求处理的服务端应用节点不固定.

说明

用户读操作可能看见旧值也可能看见新值,完全取决于处理当前请求的服务端应用节点事务id.

常见案例
  • 暂无

所有写成功

所有数据备份写入成功,才返回写成功.

各项特性

  • 可用性:任何节点不可用将导致整个集群不可用
  • 持久性:除非所有备份磁盘损坏,否则保证持久性
  • 一致性:强一致性

常见案例

  • kafka生产者设置request.required.acks=-1时,即自 Leader 和所有 Follower 的 ACK 确认之后,才算消息发送成功.
  • rocketmq旧版本如果配置主从复制模式为同步复制,即主从写入消息都成功后才算消息发送成功.

过半写成功

一半数据备份写入成功,即返回写成功.

这是paxos分布式一致性算法协议提出的写入成功标准,包括其变种zab,raft等协议都采取这一标准.

根据数学原理,这样的标准能够保证在节点不可用情况(包括磁盘损坏和宕机在内)数目不超过一半的情况下保持服务的可用和数据持久性.

各项特性

  • 可用性:除非半数及以上节点宕机,否则保证可用性.
  • 持久性:除非半数及以上备份磁盘损坏,否则保证持久性.
  • 一致性:一致性可以根据方案取舍,在不同级别的一致性之间选择.
    1. 全局单调一致性
    2. 用户单调一致性
    3. 会话单调一致性
    4. 最终一致性

单写成功(主从模式)

单节点写操作成功,即返回成功.

主从不能自动切换
  • 可用性:从节点宕机可以保证可用性,主节点宕机则服务不可用
  • 持久性:主从任一节点磁盘保留最新数据即可保证持久性.
  • 一致性:一致性级别可选范围与过半写入时一样.一致性可以根据方案取舍,在不同级别的一致性之间选择.
    1. 全局单调一致性
    2. 用户单调一致性
    3. 会话单调一致性
    4. 最终一致性
常见案例
  • mysql主从复制
  • redis主从
  • rocketmq旧版本配置主从复制模式为异步复制
主从自动切换
  • 可用性:任一节点可用即可保证可用性.
  • 持久性:发生主从切换的情况下无法保证未同步写操作持久性,可能会由于磁盘损坏或者丢弃而损失持久性.
  • 一致性:最终一致性.返回成功响应的写操作可能会被丢弃,因此无法保证任何级别的单调一致性,只能保证最终一致性.
常见案例
  • kafka生产者设置request.required.acks = 1时,即Producer只等待来自 Leader 的 ACK 确认.
  • redis哨兵/redis cluster

单节点容量上限问题

无论是什么配置的机器,服务器的磁盘容量始终是有上限的,如果数据量不断增大,那么应用需要持久化的数据最后会超过服务器的磁盘容量上限. 即使可以采用一些压缩算法,但是也只能起到缓解作用,无法作为解决方案.

解决方案

进行分片存储管理,一般根据数据全局唯一键进行hash分片

常见案例
  • mysql分库分表
  • redis cluster使用hash槽进行数据分片管理