抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

分布式(distributed)是为了解决单个物理服务器容量和性能瓶颈问题而采用的优化手段,将一个业务拆分成不同的子业务,分布在不同的机器上执行。服务之间通过远程调用协同工作,对外提供服务。

0、分布式和微服务的区别

分布式(Distributed System) 和 微服务(Microservices) 不是对立的概念,而是不同维度、不同层次上的概念。你可以把它们理解为 “目标” 和 “实现方式” 的关系。

背景:我们要开发一个电商系统。

  • **单体架构 (Monolithic)**:
    • 我们把用户服务、商品服务、订单服务、支付服务等所有功能都打包成一个巨大的 ecommerce.jar 文件,部署到一台服务器上。
    • 问题:难维护、技术栈固化、部署效率低、无法局部扩缩容。
  • **分布式架构 (Distributed)**:
    • 为了解决单体架构的性能瓶颈,我们把这个巨大的 ecommerce.jar 复制多份,分别部署到多台服务器上,前面用负载均衡器(如Nginx)把用户请求分发到不同的服务器上。
    • 这就是分布式(更具体地说,这叫集群)。它解决了性能和高可用问题,但代码本身还是单体,没有解决开发和维护的问题
  • **微服务架构 (Microservices)**:
    • 我们从代码层面就把这个巨大的电商应用拆开了。拆成独立的用户微服务商品微服务订单微服务支付微服务
    • 每个服务都可以用不同的技术栈(如用Go写用户服务,用Java写订单服务),由不同的团队独立开发和维护。
    • 每个服务都可以独立部署和扩展(比如订单服务访问量大,就只给订单服务多部署几台机器)。
    • 这就是微服务。它必须依赖分布式技术(服务发现、配置中心、API网关)才能让这些分散的服务协同工作。所以,微服务系统一定是分布式的
维度 分布式 (Distributed System) 微服务 (Microservices)
概念性质 一种技术架构理念/风格。描述的是一组独立的计算机,通过网络相互连接、协调工作,对用户来说就像一个统一的系统 一种具体的应用架构风格。是实现分布式系统的一种具体方式最佳实践
关注点 解决系统级问题:如何将一个大型任务拆分成小块,分配到不同的机器上并行计算,从而提高整体计算能力和可靠性。核心是“任务拆分”和“并行计算” 解决应用开发问题:如何将一个庞大的单体应用拆分成一系列小型、自治、松耦合的服务核心是“业务拆分”和“服务化”
对立面 集中式系统 (Centralized System),即所有功能都部署在一台大型机上的系统。 单体架构 (Monolithic Architecture),即所有功能都打包在一个部署单元里的应用。
粒度 可粗可细。可以是一整个应用,也可以是一个服务,甚至可以是一个函数。 较细。特指一个独立的、围绕业务能力构建的服务
目标 提升性能、容量、可靠性。利用更多的机器,干更大的事,并且不让单点故障影响全局。 提升开发效率、可维护性、部署灵活性。让不同的团队可以独立开发、测试、部署和扩展各自的服务。
关系 微服务架构是分布式架构的一种具体实现形式。所有微服务系统都是分布式的,但并非所有分布式系统都是微服务。 分布式架构是微服务架构的底层基石。微服务依赖于分布式技术(服务发现、配置中心等)才能正常工作。

你可以这样回答(总分总结构):

“分布式和微服务是不同维度的概念,它们不是对立的,而是相辅相成的。

分布式是一种技术架构理念,它的核心目标是利用多台机器协同工作来解决性能、可用性和扩展性问题。只要是多台机器组成的系统,就可以叫分布式系统,比如一个简单的Web应用集群。

微服务则是一种具体的应用架构风格,是实现分布式的一种最佳实践。它的核心目标是通过业务拆分来解决大型应用的开发复杂性和部署灵活性问题,把一个大应用拆成多个小型、自治的服务。

它们的关系是:微服务架构必然是分布式的,因为它需要将不同的服务部署到不同的节点上。但分布式系统不一定采用微服务架构,比如一个做了集群的单体应用,它是分布式的,但不是微服务。

简单来说,分布式是‘筋骨’,关注机器层面的协作;而微服务是‘血肉’,关注应用本身的拆分和组织方式。 我们通常是在分布式技术的支撑下,来构建微服务架构。”

总结一下关键句:

  • 分布式是“筋骨”(基础设施)。
  • 微服务是“血肉”(业务实现)。
  • 微服务架构是分布式架构的一种具体实现形式

一、分布式理论

1、CAP原则

在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)这 3 个基本需求,最多只能同时满足其中的 2 个。

  • **C - Consistency (一致性)**:所有节点在同一时间看到的数据是完全相同的。换句话说,对某个数据的写操作成功后,后续任何节点上的读操作都必须读到这个最新的值。
  • A - Availability (可用性)非故障的节点必须在合理的时间内给出非错的响应(不能是超时或拒绝连接)。即系统一直可用,不会出现操作失败的情况。
  • P - Partition Tolerance (分区容错性):当分布式系统中的节点之间因为网络故障(如光缆被挖断、交换机故障)导致无法通信,即发生网络分区时,系统仍然能够继续对外提供服务。

什么是网络分区?

分布式系统中,多个节点之间的网络本来是连通的,但是因为某些故障(比如部分节点网络出了问题)导致节点之间无法通信的情况, 整个网络就分成了几块区域,这就叫 网络分区

网络分区

常见误区与深入理解

  1. 不是“3选1”,而是“3选2”:系统总是在 C 和 A 之间进行权衡,并且必须保证 P。
  2. 不是整个系统只能有一种选择:一个大型系统可以由不同的子系统组成,这些子系统可以采取不同的策略。例如,一个电商系统:
    • 用户购物车服务可以采用 AP,保证用户永远可以添加商品。
    • 库存扣减支付系统必须采用 CP,保证数据强一致性,不能超卖。
  3. “BASE”理论是 AP 的延伸:为了解决完全放弃一致性带来的问题,提出了 BASE 理论:
    • **Basically Available (基本可用)**:系统在出现故障时,允许损失部分可用性(如响应时间变长、功能降级)。
    • **Soft State (软状态)**:允许系统在不同节点的数据副本之间存在中间状态,并且认为该状态不存在不一致问题。
    • **Eventually Consistent (最终一致性)**:经过一段时间后,所有数据副本最终会达到一致的状态。
    • BASE 是对 AP 系统中一致性弱化后的一个补充描述。

你可以这样回答(总分总结构):

“CAP理论是分布式系统设计的基础原则,它指出 Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性)这三个特性无法在分布式系统中同时满足,最多只能实现其中的两项。

由于网络分区(P)是客观存在的,无法避免,所以分布式系统必须选择P。因此,实际的设计就是在C和A之间做权衡:

  • 选择CP:就是要保证强一致性。当网络发生分区时,系统会拒绝写入或部分读取,牺牲可用性。像ZooKeeper、Etcd这类协调组件就是CP系统。
  • 选择AP:就是要保证高可用性。当网络发生分区时,系统仍然提供服务,但返回的数据可能不是最新的(最终一致性)。像Eureka这类注册中心就是AP系统。

在实际应用中,我们需要根据业务场景来权衡选择CP还是AP。比如,支付系统必须选CP保证数据强一致,而商品详情页可以选AP保证高可用。现在很多系统也通过柔性事务和最终一致性(BASE理论)来弥补AP系统在一致性上的不足。”

2、为什么CAP不能同时满足

为什么只能三选二?

  • 为什么不能放弃 P (分区容错性)?
    网络分区(P)是客观存在的,无法避免。只要使用网络,就有可能发生故障。因此,一个分布式系统必须能够处理网络分区问题。放弃 P 意味着你的系统不是一个真正的分布式系统(比如是单机系统),或者它无法容忍网络故障,这在现实中是不可接受的。因此,对于分布式系统,P 是必须选择的
  • 所以,实际的选择只有两种:
    1. **CP (一致性 + 分区容错性)**:当网络分区(P)发生时,系统为了保证一致性(C),必须拒绝一些客户端的请求,从而牺牲了可用性(A)。
    2. **AP (可用性 + 分区容错性)**:当网络分区(P)发生时,系统为了保证可用性(A),必须返回可能不是最新的数据,从而牺牲了一致性(C)。

另外,需要补充说明的一点是:如果网络分区正常的话(系统在绝大部分时候所处的状态),也就说不需要保证 P 的时候,C 和 A 能够同时保证。

Spring Cloud在CAP法则上主要满足的是AP法则,Dubbo和Zookeeper在CAP法则主要满足的是CP法则 ,Nacos 不仅支持 CP 也支持 AP

3、BASE理论

BASE 是 Basically Available(基本可用)Soft-state(软状态) 和 Eventually Consistent(最终一致性) 三个短语的缩写。

其核心思想时:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。 也就是牺牲数据的一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需要保持系统整体“主要可用”。

  • Basically Available(基本可用):什么是基本可用呢?假设系统出现了不可预知的故障,但还是能用,只是相比较正常的系统而言,可能会有响应时间上的损失,或者功能上的降级(正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的部分非核心功能无法使用)。

  • Soft-state(软状态):什么是硬状态呢?要求多个节点的数据副本都是一致的,这是一种“硬状态”。

    软状态也称为弱状态,相比较硬状态而言,允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。

  • Eventually Consistent(最终一致性):最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。


二、分布式锁

1.为什么需要分布式锁?

在单机单进程环境中,我们可以用编程语言自带的锁(如 Java 的 synchronized 或 ReentrantLock)来保证线程安全。但在分布式系统或微服务架构中,应用会部署在多台机器上,如下图所示:

此时,单机锁只能控制本JVM内的线程互斥,无法跨JVM、跨机器控制其他节点的操作。因此,我们需要一个所有分布式节点都能访问到的外部中间件,来作为锁的公共存储器,从而实现全局的、分布式的互斥控制。

2.分布式锁要具有哪些条件?

一个最基本的分布式锁需要满足:

  • 互斥:任意一个时刻,锁只能被一个线程持有。
  • 高可用:锁服务是高可用的,当一个锁服务出现问题,能够自动切换到另外一个锁服务。并且,即使客户端的释放锁的代码逻辑出现问题,锁最终一定还是会被释放,不会影响其他线程对共享资源的访问。这一般是通过超时机制实现的。
  • 可重入:一个节点获取了锁之后,还可以再次获取锁。

除了上面这三个基本条件之外,一个好的分布式锁还需要满足下面这些条件:

  • 高性能:获取和释放锁的操作应该快速完成,并且不应该对整个系统的性能造成过大影响。
  • 非阻塞:如果获取不到锁,不能无限期等待,避免对系统正常运行造成影响。

3.MySQL实现分布式锁

实现方式

  • 乐观锁:在数据库表中增加一个 version 字段。更新数据时,必须同时验证并更新版本号(UPDATE table SET value=new_value, version=current_version+1 WHERE id=xxx AND version=current_version)。
  • 悲观锁/唯一索引:创建一张锁表,通过唯一键约束(如 method_name)来插入一条记录代表获取锁,删除记录代表释放锁。或者使用 SELECT ... FOR UPDATE 行锁。

优点:实现简单,利用现有数据库,无需引入新组件。
缺点性能差,对数据库压力大;可靠性依赖数据库,数据库挂了锁就失效;FOR UPDATE 可能引发死锁;锁的失效时间难以控制。

结论不推荐用于生产环境高并发场景,可作为简单场景的备选方案。

3.Redis实现

实现一个健壮的 Redis 分布式锁的核心问题

问题 描述 Redis 解决方案
1. 互斥性 这是最基本的功能,同一时间只能有一个客户端持有锁。 使用 SET ... NX ... 命令。
2. 防止死锁 持有锁的客户端崩溃后,锁必须能被自动释放,否则其他客户端将永远无法获取锁。 使用 SET ... PX ... 命令为锁设置一个过期时间
3. 谁加的锁谁释放 只能由锁的持有者来释放锁,不能误删别人的锁。 设置一个唯一的值(如 UUID + 线程ID)作为 value。释放锁时,先 GET 判断 value 是否与自己设置的一致,一致才 DEL(使用 Lua 脚本保证原子性)。
4. 原子性 获取锁和设置过期时间、判断和释放锁必须是原子操作。 使用一条命令SET key random_value NX PX 30000)来加锁。使用 Lua 脚本来释放锁。
5. 可重入性 同一个线程可以多次获取同一把锁。 在客户端维护一个计数器(如 ThreadLocal),并在 Redis value 中记录重入次数。实现较为复杂。
6. 高可用/脑裂 Redis 主从架构中,主节点宕机可能导致锁丢失。 使用 Redlock 算法(同时向多个独立的 Redis 实例申请锁,过半数成功才算获取锁),或使用 CP 模式的 Redis 哨兵/集群。

加锁:

SET key value NX PX milliseconds:一条原子命令,同时完成设置值和设置过期时间的操作。这是目前最推荐的单节点实现方式

  • NX:等效于 SETNX
  • PX:设置过期时间(毫秒)。

解锁:通过Lua脚本,确保判断和释放锁必须是原子操作。

1
2
3
4
5
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end

KEYS[1] 是锁的key,ARGV[1] 是客户端生成的唯一value)

以上就是一个简易的Redis分布式锁了,如果还想实现可重入和高可用

可重入的实现:

可重入分布式锁的实现核心思路是线程在获取锁的时候判断是否为自己的锁,如果是的话,就不用再重新获取了。为此,我们可以为每个锁关联一个可重入计数器和一个占有它的线程。 当可重入计数器大于 0 时,则锁被占有,需要判断占有该锁的线程和请求获取锁的线程是否为同一个。

高可用:

为了避免单点故障,生产环境下的 Redis 服务通常是集群化部署的。

Redis 集群下,上面介绍到的分布式锁的实现会存在一些问题。由于 Redis 集群数据同步到各个节点时是异步的, 如果在 Redis 主节点获取到锁后,在没有同步到其他节点时,Redis 主节点宕机了, 此时新的 Redis 主节点依然可以获取锁,所以多个应用服务就可以同时获取到锁。

Redlock 算法的思想是让客户端向 Redis 集群中的多个独立的 Redis 实例依次请求申请加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败。

即使部分 Redis 节点出现问题,只要保证 Redis 集群中有半数以上的 Redis 节点可用,分布式锁服务就是正常的。Redlock 是直接操作 Redis 节点的,并不是通过 Redis 集群操作的,这样才可以避免 Redis 集群主从切换导致的锁丢失问题.

4.zookeeper实现(TODO)

5.Redisson的分布式锁原理

Redisson 是一个开源的 Java 语言 Redis 客户端,提供了很多开箱即用的功能,不仅仅包括多种分布式锁的实现。

而redisson不仅实现了上述功能,还更为强大,有以下功能:

  1. 自动续期
  2. 可重入
  3. 读写锁
  4. 公平锁

下面介绍redisson原理

key是锁的名称,value是个map,map的key是线程id,value是锁的重入次数,然后设置锁过期时间

加锁成功

  • 如果加锁成功,锁的重入次数加一,这就实现了重入锁的功能;
    • 加锁成功后,就会执行一个看门狗的机制。看门狗机制是为了防止业务还没执行完,但锁到期了的问题。看门狗就是一个定时任务,只要当前线程任务没有挂掉,且没有主动释放锁,就会隔一段时间给锁续期。默认情况下,每过 10 秒,看门狗就会执行续期操作,将锁的超时时间设置为 30 秒。
  • 如果失败,返回锁的过期时间
    • 加锁失败后,会进入一个循环中,此线程会被semaphore阻塞,当之前的线程释放锁后,会通过semaphore来唤醒此线程,然后获得锁后跳出此循环
  • 释放锁时,如果该锁的线程id不是自己的,就无权释放;如果是,就将重入次数减一,如果减后的重入次数还是>0,就不能释放,更新锁到期时间,否则就释放锁,然后发送锁释放消息,唤醒被阻塞的线程

无论加锁成功或失败,都会有一个future结果器来接收加锁结果


三、分布式ID


1.为什么需要分布式ID?

在单机系统中,我们通常使用数据库的自增主键(Auto Increment)来生成唯一ID,简单又高效。但在分布式系统(如微服务架构)中,这种方案会遇到瓶颈:

  1. 性能瓶颈:所有ID生成都依赖单个数据库,数据库容易成为性能和可用性的单点瓶颈。
  2. 扩展性问题:数据库分库分表后,多个数据库实例无法保证自增ID的全局唯一性。
  3. 安全问题:连续的自增ID会暴露业务数据量(如订单数、用户数),存在安全风险。

因此,我们需要一个能在分布式环境下生成全局唯一高性能高可用的ID方案。

2.分布式ID要具备哪些条件?

要求 说明 重要性
全局唯一 整个分布式系统中绝对不重复 ⭐⭐⭐⭐⭐
高性能 高并发下仍能快速生成ID ⭐⭐⭐⭐
高可用 ID生成服务需要99.99%可用 ⭐⭐⭐⭐
有序性 ID最好趋势递增(利于数据库索引) ⭐⭐⭐
可扩展 能够适应业务增长 ⭐⭐⭐
信息安全 避免被猜出业务量等敏感信息 ⭐⭐

3.分布式ID有哪些实现方案?

1.数据库

主要有两种实现方式:数据库自增ID号段模式

数据库自增ID

  1. 创建一个数据库表。
  2. 通过 replace into 来插入数据。

插入数据这里,我们没有使用 insert into 而是使用 replace into 来插入数据,具体步骤是这样的:

  • 第一步:尝试把数据插入到表中。
  • 第二步:如果主键或唯一索引字段出现重复数据错误而插入失败时,先从表中删除含有重复关键字值的冲突行,然后再次尝试把数据插入到表中。

号段模式(Segment Mode)

一次从数据库获取一个号段(比如1000个ID),缓存在内存中,用完后再获取下一个号段。

数据库表设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE TABLE segment_id_generator (
biz_tag VARCHAR(64) NOT NULL COMMENT '业务标识', -- 如: order, user, product
max_id BIGINT NOT NULL DEFAULT 0 COMMENT '当前最大ID',
step INT NOT NULL DEFAULT 1000 COMMENT '号段长度',
version BIGINT NOT NULL DEFAULT 0 COMMENT '版本号(乐观锁)',
description VARCHAR(256) COMMENT '描述',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (biz_tag)
) ENGINE=InnoDB COMMENT='号段ID生成表';

-- 初始化数据
INSERT INTO segment_id_generator (biz_tag, max_id, step, version, description)
VALUES
('order', 0, 1000, 0, '订单ID生成器'),
('user', 0, 2000, 0, '用户ID生成器');

执行流程

2.UUID

其生成规则包括 MAC 地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,计算机基于这些规则生成的 UUID 是肯定不会重复的。

总共32位,8-4-4-4-12

1
2
3
// 生成UUID
String id = UUID.randomUUID().toString();
// 结果:f47ac10b-58cc-4372-a567-0e02b2c3d479
  • 优点:生成速度通常比较快、简单易用
  • 缺点:无序、字符串存储空间大、索引效率低 、没有具体业务含义

比如使用 UUID 作为 MySQL 数据库主键的时候就非常不合适:

  • 数据库主键要尽量越短越好,而 UUID 的消耗的存储空间比较大(32 个字符串,128 位)。
  • UUID 是无顺序的,InnoDB 引擎下,数据库主键的无序性会严重影响数据库性能。

3.雪花算法

一个 64 位的 ID,包含以下部分: 结构时间戳 + 工作机器ID + 序列号

  • 第一位符号位(标识正负),始终为 0,代表生成的 ID 为正数。
  • 41 位用于时间戳(从某个自定义的纪元开始的毫秒数)。
  • 10 位用于机器 ID(数据中心或节点 ID)。一般来说,前 5 位表示机房 ID,后 5 位表示机器 ID(实际项目中可以根据实际情况调整)。这样就可以区分不同集群/机房的节点。
  • 12 位用于序列号(确保同一毫秒内生成的多个 ID 是唯一的)。

优点:生成速度比较快、生成的 ID 有序递增、比较灵活(可以对 Snowflake 算法进行简单的改造比如加入业务 ID)

缺点:需要解决重复 ID 问题(ID 生成依赖时间,在获取时间的时候,可能会出现时间回拨的问题,也就是服务器上的时间突然倒退到之前的时间,进而导致会产生重复 ID。强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。)、依赖机器 ID 对分布式环境不友好(当需要自动启停或增减机器时,固定的机器 ID 可能不够灵活)。

4.美团Leaf——TODO

四、分布式事务

分布式事务,就是指不是在单个服务或单个数据库架构下,产生的事务,例如:

  • 跨数据源的分布式事务
  • 跨服务的分布式事务
  • 综合情况

我们之前解决分布式事务问题是直接使用Seata框架的AT模式,但是解决分布式事务问题的方案远不止这一种。

为了解决分布式事务问题,业界提出了多种方案,可以分为强一致性最终一致性两大流派。

方案一:强一致性方案

这类方案追求数据的强一致性,通常性能开销较大。

1. 两阶段提交 (2PC - Two-Phase Commit)

  • 角色:一个协调者(Coordinator) 和多个参与者(Participant)
  • 阶段一:准备阶段
    • 协调者向所有参与者发送准备请求,并携带事务内容。
    • 参与者执行事务(但不提交),写入Undo/Redo日志,然后回复“Yes”或“No”。
  • 阶段二:提交/回滚阶段
    • 如果所有参与者都回复“Yes”:协调者向所有参与者发送提交请求,参与者正式提交事务。
    • 如果任何一个参与者回复“No”或超时:协调者向所有参与者发送回滚请求,参与者利用Undo日志回滚事务。
    • 如果当前阶段失败(提交/回滚),会不断重试
  • 优点:强一致性,保证了原子性。
  • 缺点
    • 同步阻塞:所有参与者在等待协调者指令时处于阻塞状态,性能差。
    • 单点问题:协调者宕机,整个事务将停滞。
    • 数据不一致:在阶段二,如果协调者或网络发生故障,部分参与者可能收不到指令,导致数据不一致。
  • 适用场景
    • 适用于对数据一致性要求高、并发度低的场景,如金融系统转账业务。

2. 三阶段提交 (3PC - Three-Phase Commit)

  • 2PC的改进版,引入了超时机制和预提交阶段来减少阻塞。
  • 三个阶段:准备阶段, 预提交阶段, 提交阶段。
  • 优点:降低了阻塞范围,解决了单点故障问题。
  • 缺点:实现更复杂,数据不一致问题依然存在。

无论是 2PC 还是 3PC 都不能保证分布式系统中的数据 100%一致

方案二:最终一致性方案(推荐)

这类方案基于BASE理论,是目前主流的选择,性能高,可用性好。

1. TCC(Try-Confirm-Cancel)

2PC 和 3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务

  • 核心思想:将业务逻辑分为三个阶段,由业务代码实现。
  • Try阶段尝试执行。完成所有业务的检查,并预留必要的资源(如冻结金额、库存预占)。
  • Confirm阶段确认执行。真正执行业务操作,使用Try阶段预留的资源。此操作要求幂等
  • Cancel阶段取消执行。释放Try阶段预留的资源。此操作要求幂等
  • 优点:性能好,数据最终一致,避免了数据库层的事务锁。
  • 缺点:对代码侵入性强,需要为每个业务逻辑编写Try/Confirm/Cancel三个接口,实现复杂。

2. 本地消息表(异步确保)

  • 核心思想:利用消息队列和本地事务的原子性来保证最终一致性。
  • 流程
    1. 事务发起方在执行本地事务的同时,将一条消息存入本地数据库的消息表(与业务数据在同一个数据库事务中)。
    2. 有一个后台任务轮询消息表,将消息发送到消息队列(如RocketMQ, Kafka)。
    3. 消息消费者消费消息,执行另一个本地事务。如果执行成功,则结束;如果失败,则重试(需要保证接口幂等)。
  • 优点:简单实用,避免了复杂的分布式事务协议。
  • 缺点:消息表会耦合在业务数据库中;性能也有一定的损耗。消费者需要保证幂等性。

3.消息事务

RocketMQ 就很好的支持了消息事务,让我们来看一下如何通过消息实现事务。

第一步先给 Broker 发送事务消息即半消息,半消息不是说一半消息,而是这个消息对消费者来说不可见,然后发送成功后发送方再执行本地事务

再根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令

并且 RocketMQ 的发送方会提供一个反查事务状态接口,如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令。

如果是 Commit 那么订阅方就能收到这条消息,然后再做对应的操作,做完了之后再消费这条消息即可。

如果是 RollBack 那么订阅方收不到这条消息,等于事务就没执行过。

可以看到通过 RocketMQ 还是比较容易实现的,RocketMQ 提供了事务消息的功能,我们只需要定义好事务反查接口即可。

适用于对数据一致性要求为最终一致性、系统耦合度低的场景,如电商订单支付、库存扣减。

3. 最大努力通知

  • 核心思想:事务发起方尽最大努力将结果通知给接收方,但不保证一定能通知到。接收方如果没有收到通知,需要主动调用查询接口进行核对。
  • 适用场景:对一致性要求不高的场景,如支付结果通知。

4. Saga模式

  • 核心思想:将一个长事务拆分为多个本地子事务,每个子事务都有对应的补偿操作(Compensating Transaction)。
  • 流程:按顺序执行子事务T1, T2, T3…。如果其中一步失败,则按反向顺序执行补偿操作C3, C2, C1…。
  • 优点:适合长流程业务,性能好。
  • 缺点:补偿操作的设计和实现有较高难度。

TODO阿里的Seata支持哪些模式的分布式事务

五、分布式一致性算法

六、微服务

1.微服务有哪些组件?

1.服务注册与发现
在微服务架构中,服务注册于发现的组件有很多,如Consul、Zookeeper、Nacos和Eureka等,不过Netflix官方之前宣布不再维护Eureka2.x。可以帮助服务消费者自动发现和调用服务提供者。

2.网关
现在主流的网关是Spring Cloud官方提供的Spring Cloud Gateway,它用于构建基于Spring Boot的微服务网关,使用了Spring Webflux框架和Reactor库实现了异步非阻塞的处理模型,极大的提高了网关的性能。它提供了路由、负载均衡、请求过滤和鉴权等功能。

3.配置中心
分布式配置管理组件,主流的实现有Spring Cloud Config、Consul、NacosApollo,这些配置中心都提供了统一的管理配置信息的方式,可以帮助开发者集中管理应用程序的配置,实现配置的动态刷新、版本控制、环境隔离等功能。根据实际需求和场景选择合适的配置中心对于构建弹性、可扩展的分布式系统非常重要。

4.服务限流、降级与熔断
限流、降级和熔断的解决方案在微服务架构中非常重要,可以帮助系统处理异常情况、保证系统的稳定性和可靠性。根据实际需求和场景选择合适的解决方案,可以有效提高系统的抗压能力和容错性。目前常用的熔断、降级方案有Resilience4J和Sentinel

5.负载均衡
目前常用的负载均衡组件是Spring Cloud官方提供的LoadBalancer,它可以帮助客户端在多个服务提供者之间进行负载均衡。

6.服务之间通信
目前服务之间互相调用的组件有Dubbo(RPC)Open Feign以及gRPC,这些组件可以根据不同的需求和场景选择合适的组件来实现服务之间的通信,帮助构建弹性、可靠的分布式系统。Feign 基于 HTTP 协议,Dubbo 基于 RPC 协议

7.链路追踪
链路追踪可以帮助开发者跟踪分布式系统中请求的调用链路,识别潜在的性能瓶颈和故障点,从而优化系统性能和可靠性。根据实际需求和偏好选择合适的链路追踪组件,以实现更好的监控和分析效果。目前常用的链路追踪组件有:Spring Cloud Sleuth、Zipkin和SkyWalking。

8.分布式事务
保证跨多个微服务的一致性和原子性操作。常见的实现包括: Seata

2.nacos配置中心怎么实现无感刷新配置的?

一般来说客户端和服务端的交互分为两种:推(Push)拉(Pull),Nacos 在Pull的基础上,采用了长轮询来进行配置的动态刷新。

在长轮询模式下,客户端定时向服务端发起请求,检查配置信息是否发生变更。如果没有变更,服务端会”hold”住这个请求,即暂时不返回结果,直到配置发生变化或达到一定的超时时间。

具体的实现过程如下:

Nacos长轮询Nacos长轮询

  1. 客户端发起 Pull 请求,服务端检查配置是否有变更。如果没有变更,则设置一个定时任务,在一段时间后执行,并将当前的客户端连接加入到等待队列中。
  2. 在等待期间,如果配置发生变更,服务端会立即返回结果给客户端,完成一次”推送”操作。
  3. 如果在等待期间没有配置变更,等待时间达到预设的超时时间后,服务端会自动返回结果给客户端,即使配置没有变更。
  4. 如果在等待期间,通过 Nacos Dashboard 或 API 对配置进行了修改,会触发一个事件机制,服务端会遍历等待队列,找到发生变更的配置项对应的客户端连接,并将变更的数据通过连接返回,完成一次”推送”操作。

通过长轮询的方式,Nacos 客户端能够实时感知配置的变化,并及时获取最新的配置信息。同时,这种方式也降低了服务端的压力,避免了大量的长连接占用内存资源。

评论