面试系列(八):分布式的一些问题

423 阅读38分钟

openfeign服务之间的调用怎么加密解密

Feign是客户端配置,@FeignClient注解有个configuation属性,可以配置我们自定义的配置类,在此类中注入微服务认证拦截器。

我们可以在这个拦截器里面进行加密和解密操作,但是要注意设置好加密方法。

 

微服务中我们经常说到的restfull是什么意思?

Restfull是一种设计风格,是一种架构约束条件和原则。

要理解restfull风格,就要理解在restfull风格中对资源的一种定义。为什么这么讲呢?其实在2000年的时候,Roy Fielding的博士论文中,它将restfull风格定义为是一种对资源的定义,获取,表述,状态的操作,也就是我们常说的表现层状态转移,也就是资源的表现格式的状态的转移。

那么什么是资源?

资源指的是任何事物,只要这个事物有被引用的必要,那么它就是一个资源。资源可以是实体,也可以是一个抽象概念,比如电话号码是一个资源,但是价值观也可以是资源。

那么有资源了,那么就需要让这个资源可以被识别,需要有一个唯一标识,在web中这个唯一标识就是URI,如果一个事物没有使用URI来形容,那就不能算是一个资源。所谓上网,其实就是互联网上一系列资源的互动而已,调用的就是资源专属的URI。

举个例子,https//baidu.com/picture/12645,这种就是一个符合restfull风格的URI。

接下来是资源的表现形式,资源的表现形式有很多种,一个文本,可以用json格式表现,也可以用xml格式表现,也可以用html格式表现。一张图片,可以用jpg格式表现,也可以用png格式表现。这种资源的格式,就是我们所说的资源的表现形式。一般来说,我们不应该在URI里面体现出现资源的表现形式,我们应该放在http的头信息中用Accept和Content-Type字段指定。

有了资源的表现形式,那么资源必然有状态的转化。但是在http协议中,是一个无状态协议,也就意味着我们的资源的状态,是存储在服务端的,如果我们要对一个资源进行状态上的改变,那么我们必须要通过某种手段,让服务端产生状态变化,而这种转换,就是建立在表现层的。但是在restfull风格里面,不能在URI里面添加关于资源的状态转化的动作的,比如说getOrder,deleteOrder这种风格的URI,都不符合restFull规范的。

Restfull将对资源的状态转化,放到了我们的请求类型上,restfull架构应该遵循统一的接口原则。因为https是我们当前唯一和restfull风格有关的实例,所以我们一般拿http来举例说明。

Get请求,这是在获取资源的表述。

Post请求,这是对资源的创建。

Put请求,这是对资源的修改。

Delete请求,这是对资源的删除。

HTTP协议使用这四个http动词,实现了资源的表现层状态转化。那么,restfull风格的软件架构设计,有什么好处呢?

第一个优点,是因为restfull的规范性,使我们的URL会有很强的可读性。

第二个优点,是restfull的轻量级优点,http+json可以解决一切请求。

第三个优点,就是它的无状态协议,如果客户端需要请求客户端,那它必须有着为这个请求所需要的所有信息,那么这样的话,如果我们的服务端进行扩展,那么对于resfull风格的架构来说,就是透明的,这极大增加了我们服务端的水平伸缩性。

 

什么是RPC?

Rpc是远程过程调用,是跨语言跨平台的服务调用。

RPC框架要做的是三件事情,第一件事情,服务端如何确定客户端需要调用的函数。服务端和客户端都通过维护一张【id->函数】的对应表,当客户端发送请求调用服务端的时候,会将这个id传过去,服务端通过这个id,判断客户端查询的是哪一个接口。

第二件事情,因为是服务之间的调用,那么必然是会入参和返参转换为字节流,在网络中传输,那就是涉及到序列化和反序列化了。

第三件事情就是,既然要进行网络传输,那肯定要确认使用的是哪种传输协议。大部分RPC框架使用的是TCP协议,因为没有消息头,信息发送更轻便,更高效。也有使用http的,这个要看具体的应用场景。

 

RPC和restfull的区别?

都是网络交互的协议规范。通常用于多个微服务之间的通信协议。

REST调用及测试都很方便,RPC就显得有点繁琐,但是RPC的效率是毋庸置疑的,所以建议在多系统之间的内部调用采用RPC。对外提供的服务,Rest更加合适。

RPC更多的面向过程的设计风格,而restfull是面向资源的。从这个维度上看,Restful风格的url在表述的精简性、可读性上都要更好。

 

分布式锁是怎么实现的?

分布式锁的实现方案主要包括以下四类,根据存储介质特性及适用场景划分:

一、基于数据库实现

  1. 悲观锁(行级锁)

    • 原理‌:使用 SELECT ... FOR UPDATE 锁定数据库记录,阻塞其他事务操作23。

    • 缺点‌:

      • 长事务导致连接池耗尽,性能差(低并发适用)29;
      • 死锁风险高,需数据库支持行锁810。
  2. 唯一索引约束

    • 实现‌:

      • 创建锁表,通过唯一键(如资源ID)插入记录作为加锁23;
      • 插入成功则获锁,失败则重试;删除记录释放锁79。
    • 优化‌:

      • 增加过期时间字段,定时清理死锁37。
  3. 乐观锁(版本号)

    • 原理‌:

      sqlCopy Code
      UPDATE lock_table SET version = version + 1 
      WHERE resource_id = 'res_001' AND version = 3;
      
      • 更新成功即获锁,失败则重试或放弃38。
    • 场景‌:冲突较少的低频写场景9。

二、基于 Redis 实现

  1. 单节点锁(SETNX + EXPIRE)

    • 命令‌:

      shCopy Code
      SET lock_key $uuid NX EX 30  # 原子操作设置键值及过期时间:ml-citation{ref="6,7" data="citationList"}
      
    • 释放锁‌:

      • 校验值(UUID)匹配后删除,避免误删(需Lua脚本保证原子性)712。
    • 缺陷‌:

      • 主从故障切换可能导致锁失效(异步复制延迟)56。
  2. Redlock 算法

    • 流程‌:

      1. 向 ‌多个独立 Redis 节点‌ 依次申请锁;
      2. 多数节点(N/2+1)获取成功才算获锁;
      3. 锁持有时间需小于多数节点设置的超时时间56。
    • 适用‌:金融等高一致性场景5。

三、基于 ZooKeeper 实现

  1. 临时有序节点

    • 步骤‌:

      1. 创建临时有序节点(如 /lock/res_001/node_0001);
      2. 检查是否为最小节点,是则获锁;
      3. 否则监听前序节点的删除事件(回调通知)48。
    • 特性‌:

      • 节点断开连接自动删除,避免死锁48;
      • 顺序公平锁,无饥饿问题12。
  2. 局限‌:

    • 性能较低(万级QPS),强一致性导致延迟高58。

四、基于 etcd 实现

  1. 租约(Lease) + 事务(TXN)

    • 过程‌:

      1. 创建租约并绑定临时键值对;
      2. 事务比较键版本号,原子提交更新5。
    • 优势‌:

      • Raft协议保证强一致性,比ZooKeeper更轻量5;
      • 内置续约机制避免锁过期5。

方案对比与选型建议

方案性能可靠性适用场景代表应用
Redis 单节点⭐⭐⭐⭐⭐⭐高并发、允许偶发锁失效(如缓存)67秒杀库存锁定
Redis Redlock⭐⭐⭐⭐⭐⭐⭐强一致性金融交易56支付订单处理
ZooKeeper⭐⭐⭐⭐⭐⭐⭐CP系统(配置管理、选主)812Kafka 控制器选举
etcd⭐⭐⭐⭐⭐⭐⭐⭐K8s等云原生系统协调5Kubernetes 资源锁
数据库乐观锁⭐⭐⭐低频写、无高性能要求910后台任务调度

工业级实践‌:

  • 京东方案:通过内存值比较+回滚机制优化锁抢占(专利 CN120112894A)1;
  • 避免锁过期:Redis 用 Redisson 看门狗自动续期,ZK/etcd 依赖会话保活45。

根据业务需求选择:‌高性能选 Redis(需容错则用 Redlock),强一致选 ZK/etcd,轻量级低频场景可考虑数据库乐观锁‌58。

redLock分布式锁可能的问题

RedLock 分布式锁虽设计为解决 Redis 单点锁的缺陷,但在实际应用中仍存在以下关键问题及解决方案:

一、核心架构缺陷

  1. 宕机重启导致锁失效

    • 问题‌:节点宕机后未持久化锁数据,重启后其他客户端可重新获取同一把锁(如客户端1在节点A、B、C获锁,C宕机重启后客户端2从C、D、E获锁)。

    • 解法‌:

      • 开启 AOF持久化 + fsync=always(性能急剧下降);
      • 节点故障后延迟重启(需人工介入且无法完全避免)。
  2. 时钟跳跃引发锁异常

    • 问题‌:节点间系统时钟不同步,导致:

      • 锁提前过期:客户端误判锁已释放;
      • 延迟重启策略失效(时间戳错乱)。
    • 解法‌:

      • 强制使用 NTP 同步时钟;
      • 增加时钟冗余余量(降低锁有效时长利用率)。

二、并发与性能瓶颈

  1. 脑裂导致锁竞争失败

    • 问题‌:高并发下多个客户端同时获部分节点锁(如A获3节点、B获2节点),均未达到多数派(N/2+1),全部失败。

    • 解法‌:

      • 客户端并发请求所有节点(缩短锁获取时间);
      • 失败后随机退避重试(降低冲突概率)。
  2. 高延迟与低吞吐

    • 问题‌:需串行访问多个独立节点(通常5个),网络延迟叠加导致锁获取耗时增加(相比单节点锁性能下降3-5倍)。
    • 适用场景‌:仅限强一致性要求的低频操作(如金融交易)。

三、安全性与可靠性争议

  1. 锁超时与业务执行冲突

    • 隐患‌:业务执行时间超过锁TTL,触发自动释放(其他客户端可抢占锁)。

    • 解法‌:

      • 看门狗机制自动续期(如Redisson);
      • 合理预估TTL并预留缓冲时间。
  2. 多数派机制不保证绝对安全

    • 争议点‌:Martin Kleppmann指出,节点宕机+网络分区组合场景下仍可能产生双锁(需依赖持久化+时钟同步,代价高昂)。
    • Redis作者回应‌:RedLock 提供‌足够好‌(good enough)的安全性,而非理论完美。

四、工业级替代方案

问题类型RedLock方案更优替代
节点宕机锁失效延迟重启(不彻底)ZooKeeper/etcd 临时节点自动释放
高并发锁竞争随机退避重试分桶锁(如库存分段)
锁续期管理看门狗线程基于租约的自动续期(etcd)
强一致性需求多数派投票Raft共识算法(天然强一致)

选型建议‌:

  • 接受 ‌99.99%安全性‌ → RedLock(需5节点+时钟同步);
  • 追求 ‌绝对安全‌ → 改用 ZooKeeper/etcd(牺牲性能);
  • 高并发场景‌ → 结合业务降级(如本地锁+Redis锁分层)。  

分布式全局id是怎么实现的?

实现分布式全局id的方式有很多种,主要的类型有三种,mysql的方式,redis的方式,或者雪花算法的方式。

首先来讲述一下通过mysql来生成全局id的方式。为了保证这个全局id的获取是高可用的,那么我们先假设我们的mysql是做了集群的,集群的节点是3个,那么我们会设置说第一个节点获取1000个自增主键,第二个节点获取1000-2000的主键id,第三个节点获取2000-3000的主键id。然后每个节点,都是获取一次性获取1000个主键id,放到我们的redis中,等到下次来获取的时候,会将主键id加2000。这种模式叫做号段模式,数据库的表设计主要是版本号乐观锁来更新数据库。

根据数据库的模式来生成全局id还是比较简单的,而且我们的号段模式是批量获取了全局id存储到redis内存中,这样的话可以减少数据库的请求访问的压力,而且做了集群部署,实现了高可用的特性,也是基本上符合了我们的要求的。

当然这种方案依然有缺点,数据库是存储在硬盘上的,一旦真的出现高并发的情况,还是扛不住的。

也因此,还有通过redis的方式实现生成全局id的方案。

Redis本身自带incr命令,我们可以先设置一个key的值为1,然后通过incr命令操作这个key,然后就会将这个key自增1,并且返回这个key对应的值给我们。

由于redis本身的特性,是可以抗住高并发的,而且也可以通过搭建集群的方式实现高可用,但是使用redis来实现全局id的生成,要注意的是,我们要自己实现这个生成全局id的工具类,而且redis有一个缺陷,它的持久化方式AOF和RDB,都有可能在redis宕机后导致数据丢失,如果重启redis,redis可能会给到我们重复的值,也因此在写工具类的时候,我们要注意规避这一点。

除了mysql和redis,现在市面上比较流行的生成全局id的方式是通过雪花算法来实现。雪花算法生成的主键是一个Long类型的主键,Long类型的数据有8个字节,就有64个bit。雪花算法规定了这64个bit的值的类型。

第一个bit,是来标记这个值是正数还是负数的,我们的id一般都是正数,所以这个bit的值是0。

接着的41个bit,是时间戳,单位是毫秒,也就是我们的当前时间戳减去我们设置的时间戳,这个我们设置的时间戳,一般就是这个我们的id生成器开始运行的时间。这个41位的时间戳可以使用69年。

接下来的10个bit,是我们的工作机器id,也就是workId,这个也是我们自己设置的。

再接下来还有12个bit,这12个bit是自增的值,意味着每一毫秒,每一个服务器节点可以生成2的12次方个主键id,也就是说可以生成4096个Id。

当然,因为雪花算法只是一种算法思想,具体的实现要我们自己去实现。我们也可以对这个雪花算法做一定的优化,比如说将时间戳的单位设置为s,而不是ms,这样雪花算法的世界戳就可以用上69000年了,但是在1s内只能生成4096个id了,比之前的1ms可以生成4096个id的性能相比,那还是差的很远的。这个就看我们自己的业务场景来修改了。

 

分布式事务怎么实现?

分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。

简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

但是在使用分布式事务之前,还需要确定一下,是否真的需要分布式事务,很多时候,我们都是团队过度设计,有太多的微服务,其实可以将一些相关的服务合并,这样就可以避免一些分布式数据不一致的情况出现。

当然如果要实现分布式事务,主要有以下几种。

第一种是XA二阶段提交方案。

这种事务方案一般都是在数据库上得到实现的。首先,事务管理器会将有事务的请求进行预提交,在这个时候,数据库的资源开始被锁定,参与者将undo日志和redo日志写入事务日志。

到了第二阶段,每一个参与者开始向事务管理器,也就是我们说的协调者进行通知,只要有一个参与者说自己失败了,那么我们的事务管理器会通过undo日志进行回滚操作。如果所有的参与者都说自己成功了,那么我们的事务管理器就会提交事务。

这个方案有一定的问题,第一整个事务过程是阻塞的,而且同步阻塞的时间很长。而且如果事务管理器也就是我们的协调者宕机后,在协调者宕机前收到了事务提交命令的参与者会提交事务,而在协调者宕机后,因为一直没有收到协调者的命令,一些参与者的事务就一直没有办法提交了,会导致我们的数据不一致发生。

也因此,后续出现了三阶段提交方案。

三阶段方案,在二阶段的基础上,优化了两点,第一是当参与者超时的情况下,协调者会判断事务失败,直接事务回滚。第二点是当协调者超时后,参与者收不到协调者的命令的时候,也会进行事务回滚。但是这种方案也没能解决数据不一致的问题。

还有TCC的事务方案。这种方案是为了解决事务运行过程中粗粒度资源被锁定的问题。这种方案是基于业务层面的事务定义,资源锁的粒度完全由业务自己控制,它本质上是一种补偿的思路,在事务失败后通过业务代码对我们的数据进行修复的一个过程。

TCC主要分为三个过程,第一步是try,完成所有的业务检查,并且预留必须的业务资源。第二步是confirm,真正的执行业务。第三步cancel,当第二步出现问题后,第三步会将第二步造成的数据问题,进行修复回滚的操作。

但是TCC模式的问题很多,第一点是confirm接口和cancel接口都需要保证幂等性,然后如果canfirm接口和cancel接口调用失败了,我们必须要将这些失败信息记录到数据库中,间隔一段时间后要继续去调用这个接口,完成数据的最终一致性。然后try,confirm,cancel等操作,都需要我们去修改我们的业务代码,其实开发量上来说是非常重的,使用这个事物模式,要比较慎重。

除了TCC模式以外,还有一种事物叫SAGA事物,这是我们经常讲的seata事务的前身。

SAGA事务的核心思想,就是一个长事务拆分为多个短事务,由SAGA事务协调器协调,如果一个短事务正常完成了,那就没事。但是如果一个短事务失败了,则会调用这个短事务对应的一个补偿操作,完成事务补偿,保持数据的最终一致性。

比如说一个事务,需要执行A,B,C操作,那么如果在执行的过程中,一旦C操作调用失败了,那么我们会调用A操作和B操作的对应的补偿操作,进行补偿,保证数据的一致性。

可以看出来,和TCC模式不同,SAGA操作是没有try操作的,这会导致一个问题,我们知道TCC模式的try操作是为了锁定资源,保证一定的事务隔离性,而SAGA操作等于是没有事务隔离性的,那么如果在C操作调用失败之前,A操作执行后的数据已经被其他事务修改了,那么就会造成数据的极大不一致问题。

解决方案,可以从业务层面出发,使用分布式锁的方式锁定资源,保证事务的正常执行和补偿操作是串行化就可以了。

请讲一下分布式session

分布式会话(Distributed Session)是指在分布式计算环境中,允许多个服务器间共享用户会话信息,以便在不同服务器间进行负载均衡、容错备份等操作时,用户可以始终保持相同的会话状态。

在传统的单机环境下,用户的会话信息通常存储在服务器的内存或磁盘中。当用户请求被分发到不同的服务器上时,会话信息会丢失,导致用户需要重新登录或重新填写表单等操作,给用户带来不便。

在分布式环境下,解决这个问题的一种方法是使用分布式会话。分布式会话通常通过以下步骤实现:

  1. 用户访问网站时,网站将用户的请求分发到某个服务器上。
  2. 如果该服务器上没有用户的会话信息,则向一个共享的存储系统(如数据库或缓存)请求获取会话信息。
  3. 如果共享存储系统中不存在该用户的会话信息,则创建一个新的会话,并在共享存储系统中保存该会话信息。
  4. 服务器返回响应并将会话信息存储在本地内存或磁盘中。
  5. 当用户的请求被分发到另一个服务器时,该服务器从共享存储系统中获取用户的会话信息,并将其加载到本地内存或磁盘中,以便继续处理该请求。

常见的分布式会话解决方案包括:

  1. 基于数据库的分布式会话:将会话信息存储在数据库中,多个服务器共享数据库。这种方案的优点是可靠性高,缺点是性能较差。
  2. 基于缓存的分布式会话:将会话信息存储在缓存中,多个服务器共享缓存。这种方案的优点是性能高,缺点是可靠性较低。
  3. 基于消息队列的分布式会话:将会话信息存储在消息队列中,多个服务器共享消息队列。这种方案的优点是可靠性高,性能较好,但实现起来较为复杂。

总之,分布式会话是在分布式计算环境中保持用户会话状态的重要手段,可以提高系统的可用性和用户体验。

什么是 CAP 理论?在实际应用中,如何选择一致性、可用性和分区容忍性的平衡?

CAP 理论是分布式系统设计的核心原则,指出‌一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance)三者不可兼得‌,设计时需根据场景权衡取舍25。以下是其核心要点及实际应用中的平衡策略:

一、CAP 三要素定义

  1. 一致性 (Consistency)

    • 所有节点访问同一份最新数据,写入后所有读操作立即看到更新37。
    • 例如:银行转账后,所有分行查询余额必须一致4。
  2. 可用性 (Availability)

    • 每次请求均获得非错误响应(不保证数据最新)18。
    • 例如:社交平台故障时仍可浏览旧动态,但可能延迟显示新内容7。
  3. 分区容错性 (Partition Tolerance)

    • 网络分区(节点间通信中断)时系统仍能运行36。
    • 例如:跨数据中心部署的数据库,即使部分机房断网仍提供服务10。

关键矛盾‌:网络分区是物理世界必然现象(如光纤断裂),因此 ‌P 是分布式系统的必选项‌,实际取舍在 C 和 A 之间29。

二、实际应用中的权衡策略

1. CP 系统(牺牲可用性,保证一致性)

  • 场景‌:强一致性优先的业务,如金融交易、库存管理24。

  • 实现方式‌:

    • 网络分区时,拒绝写入或返回错误(如 ZooKeeper、HBase)2。
    • 通过算法(如 Raft、Paxos)确保数据同步后才响应14。
  • 代价‌:故障期间服务不可用10。

2. AP 系统(牺牲一致性,保证可用性)

  • 场景‌:高可用优先的业务,如社交平台、电商商品页57。

  • 实现方式‌:

    • 网络分区时,允许节点返回旧数据(如 Cassandra、DynamoDB)2。
    • 采用最终一致性(BASE 理论),异步同步数据1012。
  • 代价‌:用户可能读到过期数据(如购物车短暂显示缺货)7。

3. CA 系统(放弃分区容错性)

  • 本质‌:单机或同机房集群,无网络分区风险(如单机 MySQL)29。
  • 局限‌:无法扩展为真正的分布式系统13。
mermaidCopy Code
graph LR
    A[网络分区P必然存在] --> B[选择CP ?]
    A --> C[选择AP ?]
    B --> D[强一致性场景: 金融/库存]
    C --> E[高可用场景: 社交/电商]

三、平衡实践建议

  1. 识别业务容忍度

    • 金融系统:可容忍短暂不可用(选 ‌CP‌),但必须数据准确4。
    • 内容平台:可容忍数据延迟(选 ‌AP‌),但需持续服务712。
  2. 柔性一致性替代强一致

    • 使用 ‌最终一致性‌(如消息队列异步同步)10。
    • 例如:用户发表朋友圈后,好友稍后可见(AP 系统)5。
  3. 分层设计混合策略

    • 核心交易用 CP(如支付),非核心用 AP(如商品详情)12。
    • 例如:电商平台库存扣减用 CP,用户评论用 AP7。

总结:CAP 决策表

策略适用场景典型案例代价
CP金融、库存、分布式锁ZooKeeper, HBase故障时服务不可用
AP社交、内容、实时推荐Cassandra, Redis可能读到旧数据
CA单机/同机房系统(非分布式)MySQL, Redis 单机无法扩展,无容灾能力

核心原则‌:分布式系统必须保障 ‌P‌,实际选择需基于业务对数据延迟与服务中断的容忍度29。现代系统常通过 ‌BASE 理论‌(Basically Available, Soft-state, Eventually consistent)在 AP 基础上实现柔性一致性510。

解释一下Raft、Paxos这两种共识算法,并且举例说明哪些中间件使用了这两个算法

以下是 ‌Raft‌ 和 ‌Paxos‌ 两种共识算法的核心原理、对比及典型中间件应用举例:

一、Raft 算法:可理解性优先的共识协议

核心设计目标

解决 Paxos 晦涩难懂的问题,通过‌角色分解‌和‌强领导机制‌提升可理解性。

工作原理

  1. 角色划分‌:

    • Leader(领导者) ‌:处理所有客户端请求,管理日志复制。
    • Follower(跟随者) ‌:被动响应 Leader 的指令。
    • Candidate(候选者) ‌:选举期间临时角色。
  2. 关键过程‌:

    • Leader 选举‌(超时触发):

      • Follower 超时未收到心跳 → 成为 Candidate,发起投票。
      • 获得‌多数节点票数‌ → 晋升为 Leader。
    • 日志复制‌:

      • Leader 接收写请求 → 追加日志 → 同步日志到 Followers。
      • 多数节点持久化日志‌ → 提交日志 → 通知 Followers 应用更改。
    • 安全性保证‌:

      • 选举限制:只有包含最新日志的节点可成为 Leader(避免数据回滚)。

典型中间件应用

  1. Etcd‌(Kubernetes 的键值存储):

    • 使用 Raft 保证配置一致性,实现分布式锁和服务发现。
  2. Consul‌(服务网格):

    • 通过 Raft 管理服务注册信息,确保多数据中心一致性。
  3. TiDB‌(分布式数据库):

    • 存储层 TiKV 使用 Raft 复制数据分片(Region)。

mysql,kafka,redis,es使用的也是Raft


二、Paxos 算法:分布式共识的理论基础

核心设计目标

解决‌异步网络环境下分布式系统达成一致‌的问题(拜占庭将军问题简化版)。

工作原理(经典 Paxos)

  1. 两阶段协议‌:

    • Prepare 阶段‌:

      • Proposer 发送提案编号 N 给 Acceptors。
      • Acceptor 承诺‌不再接受编号小于 N 的提案‌,并返回已接受的最大编号提案。
    • Accept 阶段‌:

      • Proposer 选择‌最高编号提案的值‌(或自定值),发送 Accept(N, Value)
      • Acceptor 接受提案(除非已承诺更高编号)。
      • 多数 Acceptor 接受 → 值被选定(Chosen) ‌。
  2. 活锁问题‌:

    • 多个 Proposer 竞争提交导致无限重试(通过随机退避缓解)。

工程优化:Multi-Paxos

  • 选举稳定 Leader‌:避免频繁 Prepare 阶段,提升性能。
  • 一次 Prepare 多次 Accept → 降低消息开销。

三、Raft vs Paxos 核心对比

特性RaftPaxos
可理解性✅ 角色清晰、流程直观❌ 理论抽象,工程实现复杂
领导机制✅ 强 Leader,所有请求由 Leader 处理⚠️ Multi-Paxos 隐含 Leader 但不强制
日志连续性✅ 强制日志顺序提交⚠️ 允许日志空洞(需额外修复)
选举效率✅ 随机超时选举,快速收敛❌ 活锁风险需额外控制
工业应用广泛(Etcd, Consul, TiDB)底层优化后应用(Chubby, Spanner)

四、Paxos 的典型中间件应用

  1. Google Chubby‌(分布式锁服务):

    • 基于 Multi-Paxos 实现强一致性的锁和元数据存储(Zookeeper 的灵感来源)。
  2. Google Spanner‌(全球分布式数据库):

    • 用 Paxos 跨数据中心同步数据,保障全球一致性。
  3. Amazon DynamoDB‌(键值存储):

    • 虽主打 AP,但一致性模式底层使用 Paxos 变种(如 AutoAdmin 组件)。

五、场景选择建议

  • 首选 Raft‌:
    需要快速开发且强一致性场景(如微服务协调、数据库复制)。
    举例‌:Kubernetes 用 Etcd(Raft)存储集群状态。
  • 选择 Paxos 变种‌:
    超大规模跨地域系统(如全球数据库),需极致优化吞吐。
    举例‌:Google Spanner 用 Paxos 同步洲际数据中心。

总结‌:Raft 以可维护性成为主流选择(占 80%+ 新系统),Paxos 在超大规模系统中仍有理论价值。两者均通过 ‌多数派(Quorum)机制‌ 在容忍少数节点故障下达成共识。

在分布式系统中,如何确保服务之间的正确通信?

  1. 通信框架与协议

    • RPC框架(如gRPC) ‌:提供结构化通信机制,通过序列化、连接池管理和超时控制确保基础通信可靠性。
    • 消息队列(如Kafka/RabbitMQ) ‌:异步解耦服务,支持持久化存储,防止消息丢失;结合ACK机制(消费者确认处理成功)和重试策略保证消息必达。
    • 网络协议优化‌:采用TCP/IP保证传输层可靠性,结合数据压缩、流水线处理提升效率。
  2. 容错与一致性保障

    • 共识算法(如Raft/Paxos) ‌:通过Leader选举和日志复制,确保多节点间状态一致,避免脑裂问题。
    • 分布式锁(基于Redis/ZooKeeper) ‌:控制共享资源访问,防止并发冲突。
    • 幂等性设计‌:通过唯一ID或版本号,确保消息重复处理时结果一致。
  3. 监控与恢复

    • 死信队列(DLQ) ‌:捕获处理失败的消息,便于人工介入或自动修复。
    • 消息追踪‌:记录全链路生命周期,快速定位故障点。

在多数据中心的分布式系统中,如何确保数据的同步和一致性?

在多数据中心分布式系统中,确保数据同步和一致性需结合网络延迟容忍、分区容错及业务场景权衡设计,核心方案如下:

一、数据同步机制

  1. 同步复制(强一致性)

    • 两阶段提交(2PC) ‌:协调者先收集各中心节点预提交结果,全部通过后再提交,保障原子性但存在阻塞风险 。

    • 共识算法(Paxos/Raft) ‌:

      • Paxos‌:通过提议者(Proposer)、接受者(Acceptor)交互达成多数派共识,适用于跨中心强一致场景 。
      • Raft‌:领导者(Leader)统一处理写请求,日志复制需多数节点确认,延迟较低但跨中心性能受网络影响 。
    • 适用场景‌:金融交易等强一致性需求,需牺牲部分可用性(CP系统)。

  2. 异步复制(最终一致性)

    • 主从异步复制‌:主数据中心接收写请求后异步同步到从中心,可能产生暂时不一致 。
    • 多主复制(Multi-Master) ‌:各中心均可处理写请求,通过冲突检测解决数据分歧(如版本向量)。
    • 适用场景‌:电商订单状态等容忍短暂不一致的业务(AP系统)。

二、一致性保障策略

  1. 冲突检测与解决

    • 版本向量(Vector Clocks) ‌:为数据标记逻辑时间戳,合并时识别冲突版本并解决(如"最后写入获胜"或业务自定义规则)。
    • CRDT(冲突无关数据类型) ‌:数据结构设计保证并发操作可交换(如计数器、集合),天然支持最终一致 。
  2. 分布式事务优化

    • Saga模式‌:长事务拆分为本地子事务,失败时触发补偿操作(如订单创建→支付→库存扣减)7。
    • TCC(Try-Confirm-Cancel) ‌:业务层实现预留资源(Try)、提交(Confirm)、回滚(Cancel)三阶段,避免长期锁竞争 。
  3. 读写策略优化

    • Quorum机制‌:设定读写最小成功节点数(如 W + R > N),确保读写重叠节点包含最新数据 。
    • 写主读本地‌:写操作路由到主中心,读操作优先本地副本,降低延迟但需同步等待主中心确认 。

三、多数据中心架构设计

  1. 拓扑优化

    • 星型拓扑‌:指定一个中心为全局协调者,简化同步逻辑但存在单点风险 。
    • 网状拓扑‌:中心间直接同步,延迟更低但冲突概率增加(需结合Gossip协议扩散更新)。
  2. 容灾与流量调度

    • 数据分片(Sharding) ‌:按业务维度(如用户ID哈希)分配数据到不同中心,减少跨中心交互 12。
    • 故障切换‌:通过ZooKeeper/Etcd监控节点状态,主中心故障时自动选举新主并同步数据 。

什么是分布式系统中的一致性哈希?它如何应用在负载均衡和缓存中?

一致性哈希(Consistent Hashing)是一种特殊的哈希算法,专为分布式系统设计,用于解决节点动态增减时数据迁移成本过高的问题。其核心思想是将数据和节点映射到同一个哈希环上,通过环形拓扑实现高效的数据定位与负载均衡。以下是其原理及在负载均衡和缓存中的应用详解:

一、一致性哈希的核心原理

  1. 哈希环构建

    • 将哈希值空间(如 0 ~ 2^32-1)构成一个首尾相接的环形结构 。
    • 节点映射‌:对节点标识(如 IP 地址)哈希计算,映射到环上的位置 。
    • 数据映射‌:对数据键(Key)哈希计算,同样映射到环上 。
  2. 数据定位规则

    • 数据存储/访问时,从其在环上的位置‌顺时针查找第一个节点‌,该节点即为目标节点 。
      示例
    mermaidCopy Code
    graph LR
    A[数据Key] -- 哈希计算 --> B(数据位置)
    B -- 顺时针查找 --> C[节点B]
    
  3. 虚拟节点优化负载均衡

    • 问题‌:物理节点分布不均可能导致数据倾斜 。
    • 方案‌:每个物理节点关联多个虚拟节点(如 NodeA-1NodeA-2),均匀分布在环上 。
    • 效果‌:数据分布更均匀,避免热点问题 。
  4. 节点动态变化的优势

    • 新增节点‌:仅影响新节点逆时针方向相邻节点的部分数据,需迁移数据量 ≈ 数据总量/节点数 。
    • 移除节点‌:仅该节点数据迁移至下一节点,影响范围极小 。

对比传统哈希‌:

  • 传统哈希取模(hash(key) % n):节点数 n 变化时,几乎所有数据需重新映射 。
  • 一致性哈希:仅需迁移 K/n 个数据(K 为数据总量)。

二、在负载均衡中的应用

  1. 解决传统负载均衡痛点

    • 传统轮询/哈希取模在节点扩容或故障时,会导致大量请求重定向,引发服务雪崩 。
    • 一致性哈希确保节点变化时,仅少量请求受影响 。
  2. 请求路由流程

    • 步骤1‌:客户端请求的 Key(如用户ID)哈希映射到环上。
    • 步骤2‌:顺时针定位到目标节点,转发请求 。
    • 动态调整‌:节点增减时,仅相邻节点需处理重定向请求 。
  3. 实践案例

    • Nginx‌:通过 ngx_http_upstream_consistent_hash 模块实现一致性哈希负载均衡 。
    • LVS‌:支持一致性哈希调度算法,避免后端服务器变化时的会话丢失 。

三、在缓存系统中的应用

  1. 数据分片与定位

    • 缓存键(如 user:123)哈希映射到环上,定位存储节点 。
    • 读写请求直接路由到目标节点,减少集群扫描开销 。
  2. 高效扩容与故障恢复

    • 扩容‌:添加新缓存节点后,仅需从相邻节点迁移少量数据 。
    • 故障‌:节点宕机时,其数据自动由下一节点接管,避免缓存穿透 。
  3. 工业级实践

    • Redis Cluster‌:采用哈希槽分片,但一致性哈希用于代理中间件(如 Twemproxy)。
    • Memcached‌:客户端库(如 libmemcached)支持一致性哈希分片 。

四、一致性哈希 vs 传统哈希方案

场景传统哈希取模一致性哈希
节点扩容/缩容数据迁移量:≈100%数据迁移量:≈ 1/n(n 为节点数)9
负载均衡易倾斜,依赖权重配置虚拟节点实现天然均衡 412
故障影响大量请求重定向,可能引发雪崩仅故障节点的数据受影响 1113
适用系统节点固定的场景动态伸缩的分布式系统(云原生)28

总结

一致性哈希通过‌环形拓扑‌和‌虚拟节点‌技术,在分布式系统中实现了:

  1. 动态伸缩友好‌:节点变化时数据迁移成本极低 。

  2. 负载均衡优化‌:虚拟节点避免数据倾斜,提升资源利用率。

  3. 高可用保障‌:节点故障仅影响局部数据,系统整体保持稳定。
    其设计哲学是‌以空间换稳定性‌,广泛应用于缓存(Redis/Memcached)、负载均衡(Nginx/LVS)及分布式存储(Cassandra)等场景 。

强一致性和最终一致性的差别是什么?

强一致性与最终一致性是分布式系统中的两种核心数据一致性模型,其差异及实现方法如下:

维度强一致性最终一致性
数据可见性写入后‌立即全局可见‌,所有节点数据实时一致 写入后允许‌短暂不一致‌,经过一段时间后达成一致 
性能影响高延迟(需同步阻塞等待所有节点确认)低延迟(异步复制,无阻塞等待)
可用性牺牲可用性(节点故障时无法写入)高可用性(容忍节点故障或网络分区)
适用场景金融转账、库存扣减等零容忍不一致场景 社交动态、评论更新等容忍延迟场景 
理论基础优先满足 CAP 中的 ‌C(一致性) ‌ 优先满足 CAP 中的 ‌A(可用性) ‌ 

关键差异示例‌:

  • 强一致性:银行转账后,所有 ATM 立即显示新余额 。
  • 最终一致性:朋友圈发布后,部分用户可能短暂看不到更新 。

实现强一致性的方法

  1. 共识算法

    • Paxos‌:通过提案投票达成多数派共识,确保数据原子提交(如 Google Spanner)。
    • Raft‌:选举 Leader 统一处理写请求,日志复制需多数节点确认(如 Etcd)。
  2. 分布式事务协议

    • 两阶段提交(2PC) ‌:

      pythonCopy Code
      # 第一阶段:预提交(Prepare)
      coordinator.send_prepare()  # 询问各节点是否可提交
      if all_nodes_agree():        # 所有节点返回 "YES"
          # 第二阶段:提交(Commit)
          coordinator.send_commit() # 通知提交
      else:
          coordinator.send_rollback() # 回滚
      

      缺点:协调者单点故障可能导致阻塞 。

    • 三阶段提交(3PC) ‌:引入超时机制减少阻塞,但无法完全避免脑裂问题 。

  3. 读写强同步

    • Quorum 机制‌:配置读写最小成功节点数(如 W + R > N),确保每次读写重叠节点包含最新数据 。

实现最终一致性的方法

  1. 异步复制

    • 主节点写入成功后,‌异步推送更新‌到从节点(如 MySQL 主从复制)。
  2. 冲突解决机制

    • 版本向量(Vector Clocks) ‌:为数据标记逻辑时间戳,合并时解决冲突(如 Cassandra)。
    • CRDT(无冲突复制数据类型) ‌:设计可交换的数据结构(如计数器、集合),天然规避冲突(如 Riak)。
  3. 消息队列补偿

    • 通过消息队列(如 Kafka)异步传递事件,结合 ‌Saga 模式‌:

      mermaidCopy Code
      graph LR
      A[订单服务] -->|1. 创建订单| B(消息队列)
      B -->|2. 扣减库存| C[库存服务]
      C -->|失败| D[触发补偿操作]
      D -->|3. 取消订单| A
      

      确保最终一致性与可回滚 。

如何保证事务的幂等性

在分布式系统中,保证事务的幂等性是防止重复操作导致数据异常的核心机制。以下是结合业务场景的实现方案,分为‌防重控制策略‌、‌状态机设计‌与‌分布式协同‌三类:

一、防重控制策略

  1. Token 机制(请求级防重)

    • 流程‌:

      1. 客户端先申请唯一 Token(服务端生成并存入 Redis,设置 TTL)13。

      2. 业务请求携带该 Token,服务端校验 Redis 是否存在:

        • 存在 → 删除 Token 并执行业务。
        • 不存在 → 拒绝请求(判定为重复提交)14。
    • 局限‌:

      • 增加一次网络交互,需客户端配合流程;
      • Redis 故障时系统不可用13。
  2. 唯一索引(数据层防重)

    • 适用场景‌:新增数据(如订单创建)。

    • 实现‌:

      • 对业务唯一键(如订单号+用户ID)建立数据库唯一索引;
      • 重复插入时触发唯一约束异常,拦截重复请求34。
  3. 悲观锁(并发控制)

    • 操作‌:SELECT ... FOR UPDATE 锁定数据行,阻止并发修改3。
    • 缺点‌:长事务导致性能瓶颈,死锁风险高4。

二、状态机驱动(业务逻辑幂等)

  1. 乐观锁(版本号控制)

    • 实现‌:

      • 数据表增加 version 字段,更新时校验版本号:

        sqlCopy Code
        UPDATE table SET status = 'paid', version = version + 1 
        WHERE order_id = '123' AND version = 3; -- 版本不匹配则更新失败
        
    • 优势‌:无锁竞争,适合高并发场景47。

  2. 状态流转约束

    • 规则‌:限定业务状态单向流转(如 未支付→已支付,禁止逆向)。
    • 示例‌:订单支付成功后,再次支付请求因状态不匹配被拒绝714。

三、分布式环境幂等保障

  1. 分布式锁

    • 流程‌:

      • 以业务唯一 ID 为 Key(如订单号),获取 Redis/ZooKeeper 分布式锁;
      • 执行业务逻辑后释放锁(需设置锁超时防止死锁)38。
    • 关键‌:锁需覆盖‌整个事务周期‌,确保原子性13。

  2. 消息队列消费幂等

    • 方案‌:

      • 生产者‌:为消息附加唯一 ID(如雪花算法 ID)111;

      • 消费者‌:

        • 通过 Redis Set 记录已处理 ID,重复 ID 直接跳过11;
        • 或结合数据库唯一索引拦截重复消费7。
  3. 补偿事务(Saga 模式)

    • 逻辑‌:

      • 每个子事务设计逆向补偿操作(如扣款失败则退款);
      • 重试时通过事务 ID 保证补偿操作只执行一次1113。

四、方案选型参考

场景推荐方案案例
短时高频请求(如支付)Token 机制 + Redis 防重电商支付接口 14
数据库写入(新增数据)唯一索引订单创建 37
更新操作(如库存扣减)乐观锁版本控制秒杀系统 414
异步消息处理消息 ID + 幂等消费表RocketMQ/Kafka 消费者 111
跨服务长事务Saga 模式 + 事务 ID 追踪分布式订单流程 1113