分布式理论基础
1、分布式架构有哪些特点,优势和缺陷
特点:
微服务架构的优点 | 微服务架构的缺陷 |
---|---|
自由使用不同技术 | 增加故障排除挑战 |
每一个微服务都侧重于单一功能 | 由于远程调用增加延迟 |
支持单个可部署单元 | 增加了配置与其他操作的工作量 |
允许经常发布软件 | 难以保持交易安全 |
确保每项服务的安全性 | 难以跨越各种边界追踪数据 |
多个服务时并行开发和部署的 | 难以在微服务间进行编码 |
2、分布式系统如何进行无状态化改造
状态概念:如果一个数据需要被多个服务共享,才能完成交易,那么这个数据被称为状态,反之则称为无状态。
阻碍单体架构变为分布式架构的关键点在于状态的处理,对于任何状态,需要考虑它的分发、处理、存储。
对于数据的存储,主要包含这几类数据:
- 会话数据,主要保存在内存中
- 结构化数据,主要是业务逻辑相关
- 文件图片数据,比较大,往往通过CDN下发
- 非结构化数据,例如文本,评论等
如果这些数据保存到本地,和业务逻辑耦合在一起,就需要在数据分发时,将同一个用户的所有数据分到同一个进程,这样就会影响架构的横向扩展。
解决方案:
问题点 | 处理方案 |
---|---|
会话数据 | 使用统一的外部缓存 如 spring session + redis |
结构化数据 | 分布式数据库 + 读写分离 |
文件数据 | 分布式共享存储 如 HDFS + CDN 预加载 |
非结构化数据 | 使用统一搜索引擎 如 ES |
3、CAP理论下的注册中心选择
常见的注册中心:zookeeper、eureka、nacos、consul
zookeeper | eureka | nacos | consul | |
---|---|---|---|---|
一致性 | CP | AP | CP+AP | CP |
健康检查 | Keep Alive | 心跳 | TCP/HTTP/MYSQL/Client Beat | TCP/HTTP/gRPC/Cmd |
雪崩保护 | 无 | 有 | 有 | 无 |
访问协议 | TCP | HTTP | HTTP/DNS | HTTP/DNS |
springcloud集成 | 支持 | 支持 | 支持 | 支持 |
Zookeeper:CP设计,保证一致性,在集群环境下,某个节点失效,则会选举新的leader,或者半数以上节点不可用,则无法提供服务,因此无法满足可用性
Eureka:AP设计,不区分主从节点,一个节点挂了,自动切换到其它可用节点,去中心化,保证可用性
结论:
如果要求一致性,则选择zookeeper/Nacos,如金融行业 CP
如果要求可用性,则Eureka/Nacos,如电商系统 AP
微服务和分布式
分布式系统(Distributed Systems)
本质:通过网络连接的多台计算机协同工作,表现为单一系统的技术体系
核心目标:解决单机性能瓶颈,实现高可用、可扩展和容错
微服务架构
本质:将单体应用拆分未多个独立部署的小型服务,每个服务实现特定的业务功能
核心特征:
- 单一职责原则
- 独立部署运行
- 轻量级通信
- 去中心化治理
两者关系
- 微服务是分布式系统的实现范式:所有微服务架构都是分布式系统
- 分布式系统是更广泛的概念:包含但不限于微服务(如集群计算、网格计算)
- 关键差异
维度 | 分布式系统 | 微服务 |
---|---|---|
关注点 | 基础设施与资源整合 | 业务解耦与敏捷交付 |
粒度 | 可变(进程/机器/数据中心) | 业务功能级细粒度 |
技术一致性 | 通常同质化 | 允许技术异构 |
数据管理 | 可能共享存储 | 每个服务独立数据库 |
幂等性
幂等性是指同一操作的多次执行和单次执行产生相同的效果。
产生幂等性问题的原因主要有:
1.网络请求重试
:网络波动或超时,客户端可能会重复发送相同的请求。
2.用户界面重复提交
:用户在用户界面上可能会不小心重复点击按钮,导致相同的请求被发送多次。
3.消息队列重试机制
:使用消息队列(如Kafka、RabbitMQ)时,消息可能会被重复消费。
4.数据库并发操作
:数据库的插入、更新和删除操作多个事务同时修改同一条记录,而没有使用适当的锁机制或事务隔离级别。
5.外部系统API接口重试
:对外提供的API接口可能由于调用方的重试逻辑,导致数据库操作被重复调用。
幂等性核心实现方案
唯一标识(Token机制):
数据库唯一约束
表结构设计唯一约束业务字段
乐观锁机制(版本号控制)
// 更新库存示例 public boolean deductStock(Long productId, Integer quantity, Integer version) { String sql = "UPDATE product SET stock = stock - ?, version = version + 1 " + "WHERE id = ? AND version = ?"; int rows = jdbcTemplate.update(sql, quantity, productId, version); return rows > 0; }
分布式锁
在分布式系统中,使用分布式锁来保证同一时间只有一个实例处理特定消息或请求
状态机
使用状态机是判断业务流程,确保操作只执行一次。
状态机设计:
- 订单创建:订单初始化,状态为
PENDING
(待支付)。 - 支付操作:当订单状态为
PENDING
时,允许执行支付操作,支付成功后状态变为PAID
(已支付)。 - 重复支付检查:如果再次尝试支付一个已经是
PAID
状态的订单,状态机将拒绝该操作,保持订单状态不变。
- 订单创建:订单初始化,状态为
幂等性架构设计
关键组件:
- 网关层幂等拦截器:基于请求ID过滤重复请求
- 分布式幂等服务:集中管理幂等Token和状态
- 业务层幂等处理器:封装各种幂等策略
- 持久层幂等设计:数据库唯一约束+乐观锁
常见问题解决方案
分库分表下的全局唯一约束
- 使用分布式ID生成器(Snowflake/TinyID)
- 单独创建全局索引表
- 通过Redis原子操作实现分布式锁
幂等与并发冲突
// Redis Lua脚本保证原子性 String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " + " return redis.call('del', KEYS[1]) " + "else " + " return 0 " + "end"; Long result = redisTemplate.execute( new DefaultRedisScript<>(luaScript, Long.class), Collections.singletonList(key), token);
长事务中的幂等
- 分段提交:将大事务拆分为多个幂等子任务
- 状态快照:保存中间状态支持断点续执
- 补偿事务:为每个操作设计幂等补偿
分布式事务
1、本地事务
传统的事务是通过关系型数据库来控制事务,这是利用数据库本身的使用特性来实现的,基于关系型数据库的事务被称为本地事务。
事务的的四大特性:ACID
事务特性 | 说明 |
---|---|
Atomic 原子性 | 构成事务的所有操作要么都执行,要么都不执行 |
Consistency 一致性 | 事务执行前后,数据库的整体状态时不变的 |
Isolation 隔离性 | 多个事务的执行互不干扰,一个事务不能看到其它事务未提交的中间状态 |
Duration 持久性 | 事务一旦提交,数据就会持久化到磁盘,不可回滚 |
2、分布式事务
在分布式系统环境下,一个事务操作会由多个不同网络节点上的的服务参与,通过网络远程协作来完成一个大的事务,保证多个服务节点的数据一致性,这就是分布式事务。
3、数据一致性
- 严格一致性:分布式系统中的某个数据一旦被更新成功后,后续无论从哪个服务节点访问,都能读取到最新的数据值。在数据库集群中,其中某个节点的数据发生了变更,那么需要等待集群内所有节点进行数据同步,同步完成后,才能正常对外提供服务。
- 顺序一致性:分布式系统节点变动的数据,与实际操作顺序一致,它不像严格一致性那样,要求每次更新都要同步,它只要求数据同步的顺序一致即可。
- 最终一致性:分布式系统中所有节点的数据,最终会变得一致,不要求任意时刻任意节点上的数据都是相同的。
4、CAP定理
C 一致性(Consistency):更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,不能存在中间状态。
A 可用性(Availibility):系统提供的服务必须一直处于可用状态,对于用户的每一个操作请求总能在有限的时间内返回结果。
P 分区容错性(Partition tolerance):即使分布式系统中的某个功能组件不可用,操作依然可以完成。
在分布式系统里面,一致性、可用性、分区容错性这三个指标,不可能同时做到,最多只能满足两个,而分区容错性时最基本的要求。在实际应用中,经常会在保证分区容错性的情况下,然后牺牲部分一致性,不要求强一致性,也就是不一定要等到所有节点的数据状态都是一样时才能对外提供服务。可以在程序设计实现里面加一些控制处理,即使出现短暂的数据不一致,也不影响系统的正常使用,这样就保证了三者基本可以同时满足。
5、BASE定理
基本可用:允许系统的部分功能不可用或响应时间延长。
软状态:允许分布式系统中部分节点存在中间状态数据,不影响系统的可用性,不要求数据的强一致性。
最终一致性:允许不同节点数据在业务允许的时间范围内不同,但最终所有节点数据必须保持一致。
6、两阶段(2PC)和三阶段(3PC)提交协议
两阶段提交
两阶段提交协议:准备阶段(Prepare Phase)和提交阶段(Commit Phase)。涉及一个协调者(Coordinator)和多个参与者(Participants)。
阶段一:准备阶段
- 协调者向所有参与者发送事务内容,询问是否可以提交事务,然后等待所有参与者的响应。
- 参与者执行事务操作(但不提交),将Undo和Redo信息记录到事务日志。
- 参与者向协调者返回实务操作的执行结果 Yes或No。
阶段二:提交阶段
协调者根据所有参与者的反馈决定是否提交事务:
情况1:所有参与者都返回 Yes
- 协调者向所有的参与者发送 Commit 指令
- 参与者收到指令后执行提交操作,并释放资源
- 参与者向协调者发送Ack消息
- 协调者收到所有参与者的Ack后,完成事务
情况2:有任意一个参与者返回No或超时
- 协调者向所有参与者发送Rollback指令
- 参与者收到指令后通过Undo日志执行回滚操作,并释放事务资源
- 参与者向协调者发送回滚完成消息
- 协调者收到所有参与者的Ack后,中断事务
优缺点
优点:
- 原理简单,实现方便
- 大多数情况下能保证事务的原子性
缺点:
- 同步阻塞问题:在准备阶段后,所有的参与者都会阻塞等待协调者的执行,期间占用资源无法释放。
- 单点故障问题:如果协调者故障,参与者将一直处于阻塞状态,等待提交/回滚指令
- 数据不一致问题:在阶段二,协调者只发送了部分Commit指令就故障,那么部分参与者提交事务,部分未收到指令未提交事务,导致数据不一致
- 保守机制:任意一个参与者失败就会导致整个事务回滚,没有容错机制
三阶段提交
三阶段提交是对两阶段提交的改进,将两阶段的准备阶段一分为二,形成三个阶段:询问阶段(CanCommit)、预提交阶段(PreCommit)和提交阶段(DoCommit)。同时引入超时机制来解决2PC中的阻塞问题。
阶段一:询问阶段
- 协调者向所有参与者发送一个包含事务内容的CanCommit请求,询问是否可以执行事务,然后等待响应。
- 参与者根据自身情况(如是否可以锁定资源)反馈Yes或No
阶段二:预提交阶段
协调者根据阶段1的反馈决定是否继续:
**情况1:**所有参与者反馈Yes
- 协调者向所有参与者发送PreCommit请求
- 参与者收到PreCommit后,执行事务操作(写Undo/Redo日志),但不提交
- 参与者反馈Ack(表示已经准备好)
**情况2:**有参与者反馈No或超时
- 协调者向所有参与者发送中止请求
- 参与者(未收到PreCommit的参与者)中断事务
阶段三:提交阶段
协调者根据阶段2的反馈结果决定是否提交
**情况1:**协调者收到所有参与者的Ack
- 协调者向所有参与者发送DoCommit请求
- 参与者收到后执行提交,释放资源,并反馈Ack
- 协调者收到所有Ack后完成事务
**情况2:**有参与者未返回Ack或协调者超时
- 协调者向所有参与者发送中止请求
- 参与者使用Undo日志回滚事务,释放资源
改进点
- 引入超时机制解决阻塞问题
- 在询问阶段超时,参与者会中止事务
- 在预提交和提交阶段,如果参与者超时,则会自动提交事务
- 将2阶段的准备阶段拆分为两个阶段,询问阶段只做检查,不锁定资源,减少资源锁定时间
优缺点
优点:
- 降低了阻塞范围:在等待超时后,参与者会自动提交或回滚,不会一直阻塞
- 解决了协调者单点故障问题:在提交阶段,若协调者故障,参与者在超时后会自动提交
缺点:
- 在提交阶段,如果因为网络分区导致部分参与者未收到PreCommit请求,超时自动提交了,而部分参与者收到了终止请求,进行了回滚,会导致数据不一致(但概率比2PC低)
- 交互次数增多,实现更复杂
2PC VS 3PC
维度 | 2PC | 3PC | 优劣分析 |
---|---|---|---|
阶段数量 | 2阶段 | 3阶段 | 3PC增加网络交互 |
阻塞时间 | 长(含资源锁定) | 短(仅DoCommit锁定) | 3PC减少30%阻塞时间 |
协调者故障 | 事务永久阻塞 | 超时后自动提交/回滚 | 3PC解决单点故障 |
数据一致性 | 强一致 | 最终一致 | 2PC更适合金融场景 |
网络分区容忍 | 弱 | 较强 | 3PC更适合跨地域部署 |
实现复杂度 | ★★☆ | ★★★ | 2PC更易实现 |
适用场景 | 银行转账 | 电商订单 | 3PC更适合互联网应用 |
最佳实践
协议选择原则:
- 金融系统:2PC + 协调者集群 + 重试机制
- 互联网应用:3PC + 异步补偿 + 幂等设计
性能优化关键:
// 批量处理优化 public void batchCommit(List<Transaction> transactions) { // 合并网络请求 batchPrepare(transactions); batchCommit(transactions); }
故障处理四板斧:
- 超时中断:设置合理超时阈值
- 状态可查:实现事务状态查询接口
- 人工干预:提供管理台操作入口
- 自动补偿:基于日志的自动恢复
监控指标体系:
指标 预警阈值 监控手段 事务成功率 <99.9% Prometheus 平均延迟 >200ms Grafana 资源锁定时间 >1s 日志分析 协调者切换次数 >5次/小时 Zabbix
Seata
Seata核心概念
角色 | 作用 |
---|---|
TC (Transaction Coordinator) | 事务协调器(独立部署),负责全局事务的提交/回滚决策,维护全局事务状态。 |
TM (Transaction Manager) | 事务管理器(集成在业务服务中),负责开启/提交/回滚全局事务。 |
RM (Resource Manager) | 资源管理器(集成在业务服务中),负责管理分支事务(如本地数据库操作)。 |
Seata 事务模式详解
1. AT 模式(Auto Transaction) - 最常用
- 原理:基于数据库快照 + 反向补偿 SQL 实现自动补偿。
- 工作流程:
- TM 向 TC 注册全局事务(生成全局唯一
XID
)。 - RM 执行业务 SQL 前,Seata 解析 SQL 生成前置快照(
before image
)。 - 执行业务 SQL 并提交本地事务。
- 生成后置快照(
after image
),将快照和行锁信息存入undo_log
表。 - 全局事务提交:TC 异步删除所有
undo_log
。 - 全局事务回滚:TC 通知 RM 根据
undo_log
生成反向 SQL 补偿(如update
回滚为update
还原)。
- TM 向 TC 注册全局事务(生成全局唯一
- 优点:对业务零侵入(只需加
@GlobalTransactional
注解),高性能。 - 缺点:依赖数据库本地事务(仅支持支持本地 ACID 的数据库如 MySQL/Oracle)。
2. TCC 模式(Try-Confirm-Cancel)
原理:通过人工编码实现两阶段提交。
- Try:预留资源(如冻结库存)。
- Confirm:提交资源(实际扣减库存)。
- Cancel:回滚预留(释放冻结库存)。
代码示例:
@TwoPhaseBusinessAction(name = "deductStock", commitMethod = "confirm", rollbackMethod = "cancel") public boolean tryDeductStock(BusinessActionContext context, int productId, int count) { // Try: 检查并冻结库存 } public boolean confirm(BusinessActionContext context) { // Confirm: 扣减冻结库存 } public boolean cancel(BusinessActionContext context) { // Cancel: 解冻库存 }
优点:不依赖数据库事务,支持异构系统。
缺点:业务侵入性强,需自行处理空回滚、幂等、悬挂问题。
3. Saga 模式
- 原理:长事务拆分多个子事务,每个子事务提供补偿操作,失败时反向执行补偿。
- 适用场景:业务流程长、无需强一致性的场景(如订单流程包含多个服务)。
- 缺点:需保证补偿操作幂等性,可能出现脏写。
4. XA 模式
- 原理:基于数据库的 XA 协议实现强一致性(两阶段提交)。
- 优点:数据强一致。
- 缺点:性能低(锁定资源时间长),需数据库支持 XA 协议(如 MySQL 5.7+)。
Seata AT 模式核心机制
1. 全局锁机制
- 防止其他事务修改正在被全局事务操作的数据。
- RM 执行 SQL 前,向 TC 申请行级全局锁(
table_name + pk
)。 - 锁冲突时,后发起的事务会重试或回滚。
2. undo_log 表设计
CREATE TABLE `undo_log` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`branch_id` BIGINT(20) NOT NULL, -- 分支事务ID
`xid` VARCHAR(100) NOT NULL, -- 全局事务ID
`context` VARCHAR(128) NOT NULL, -- 上下文(如序列化格式)
`rollback_info` LONGBLOB NOT NULL, -- 回滚信息(快照)
`log_status` INT(11) NOT NULL, -- 状态(0-正常,1-已回滚)
PRIMARY KEY (`id`)
);
3. 事务执行流程
部署与配置
1. TC Server 部署
步骤:
# 下载 Seata Server wget https://seata.io/.../seata-server-1.7.0.tar.gz # 修改配置文件 conf/registry.conf(注册中心) store.mode = "db" # 选择TC事务状态存储方式(db/file/redis) # 启动 sh bin/seata-server.sh -p 8091
2. 业务服务集成
依赖:
<!-- Spring Cloud Alibaba --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency>
配置:
seata: application-id: order-service tx-service-group: my-tx-group # 事务组名(需与TC配置一致) registry: type: nacos # 注册中心类型 nacos: server-addr: 127.0.0.1:8848 config: type: nacos
3. 全局事务注解
@RestController
public class OrderController {
@GlobalTransactional // 开启全局事务
public String createOrder() {
orderService.create(); // 分支事务1
storageService.deduct(); // 分支事务2
// 若此处异常,所有分支事务回滚
}
}
生产环境关键实践
1. 高可用部署 TC
- 方案:TC 集群化 + 数据库持久化(
store.mode=db
) + 注册中心(Nacos/Zookeeper)。 - 负载均衡:客户端通过注册中心发现多个 TC 实例。
2. 性能优化
- 异步化:全局事务提交时异步删除
undo_log
。 - 合并请求:TC 合并 RM 的心beat和事务消息,减少网络开销。
- 线程池调优:调整 TC 的
server.executor
线程池大小。
3. 故障处理
- 全局锁冲突:优化业务逻辑,减少长事务。
- undo_log 堆积:监控清理失败日志(
log_status=1
)。 - TC 宕机恢复:依赖数据库恢复事务状态。
4. 监控与告警
- Metrics:集成 Prometheus 收集事务成功率、耗时等指标。
- 日志追踪:通过 XID 跨服务追踪事务链路。
适用场景对比
模式 | 一致性 | 性能 | 业务侵入 | 适用场景 |
---|---|---|---|---|
AT | 最终一致 | ⭐⭐⭐⭐ | 无 | 标准CRUD操作(80%场景) |
TCC | 强一致 | ⭐⭐⭐ | 高 | 资金交易、需高一致的业务 |
Saga | 最终一致 | ⭐⭐⭐⭐ | 中 | 长流程业务(如订单+物流+支付) |
XA | 强一致 | ⭐⭐ | 低 | 传统银行系统、遗留数据库 |
常见问题解决方案
- 空回滚(TCC模式)
原因:Try未执行,Cancel被调用。
解决:在 Cancel 中检查 Try 是否执行(通过事务状态表)。 - 幂等控制
方案:在try/confirm/cancel
方法中通过xid + branch_id
去重。 - AT模式脏写
场景:全局事务未提交时,其他本地事务修改了相同数据。
防御:开启全局锁(默认开启),冲突事务会回滚。 - undo_log表清理
脚本:定时删除已提交事务的日志(DELETE FROM undo_log WHERE log_status = 0
)。
总结
- 选型建议:优先使用 AT 模式(简单高效),复杂业务用 TCC,历史系统兼容选 XA。
- 核心价值:Seata 通过 TC 统一协调 + 多模式适配,显著降低分布式事务开发复杂度。
- 注意:分布式事务无法100%保证一致性(如极端宕机),需配合人工对账兜底。
部署建议:
TC集群 + 数据库存储 + Nacos注册中心 + Prometheus监控
通过@GlobalTransactional
注解,5分钟快速接入分布式事务!
ZooKeeper
Zookeeper 是一个开源的分布式协调服务,由Appache基金维护,核心解决分布式系统中的一致性、状态同步、配置管理、集群管理等问题。读请求可以被集群中的任意一台机器处理,写请求会同时发给所有的zookeeper机器并且达成一致后,请求才会响应成功。随着zookeeper集群机器增多,读请求吞吐量会提高但写请求的吞吐量会下降。
1、核心定位与特性
特性 | 说明 |
---|---|
分布式一致性 | 基于ZAB协议保证集群节点数据强一致性 |
树形数据结构 | 数据模型为层级命名空间(类似文件系统),节点称为ZNode |
监听机制 | 客户端可监听ZNode变化(创建/删除/数据更新) |
顺序性保证 | 所有写操作全局有序(通过事务IDzxid实现) |
高可用 | 集群部署(通常3/5/7节点),Leader 故障时自动选举新Leader |
2、数据模型:ZNode
节点类型:
- 持久节点(PERSITENT):客户端断开后仍存在(如配置信息)
- 临时节点(EPHEMERAL):客户端会话结束自动删除(适合服务注册)
- 顺序节点(SEQUENTIAL):节点名自动追加全局单调递增序号(如
/lock-00000001
)
节点数据结构
class ZNode { String path; // 节点路径(如 、service/provider) byte[] data; // 存储的数据(最大1MB) Stat stat; // 元数据(版本号,时间戳等) List<String> children; // 子节点列表 }
Zookeeper 为了保证高吞吐和低延迟,在内存中维护了整个树状的目录结构,所以Zookeeper不能存放大量的数据,每个节点存储数据上线 1MB。
3、集群架构与ZAB协议
集群角色
角色 职责 Leader 处理所有写请求,发起事务提案(Proposal) Follower 同步Leader数据,参与写操作的ACK投票,处理读请求 Observer 仅同步数据、处理请求(不参与投票),用于扩展读性能 ZAB 协议工作流程
基于ZAB协议保证集群节点数据强一致性
两阶段提交:
- 广播 Proposal:Leader 生成带
zxid
的提案发送给 Followers - ACK 投票:Followers 持久化提案并返回 ACK
- 提交事务:Leader 收到多数 ACK 后发送 COMMIT
- 广播 Proposal:Leader 生成带
崩溃恢复:
- Leader 宕机后,新 Leader 选举基于 最大 zxid 优先 原则
- 新 Leader 用内存快照 + 事务日志恢复数据
Zookeeper 数据复制
Zookeeper 作为一个集群提供一致的数据服务,自然,它要在 所有机器间 做数据复制。
数据复制的好处:
1、容错:一个节点出错,不致于让整个系统停止工作,别的节点可以接管它的工作;
2、提高系统的扩展能力 :把负载分布到多个节点上,或者增加节点来提高系统的负载能力;
3、提高性能:让 客户端本地访问就近的节点,提高用户访问速度 。数据复制集群系统分下面两种:
1、 写主 (WriteMaster) :对数据的 修改提交给指定的节点 。读无此限制,可以读取任何一个节点。这种情况下
客户端需要对读与写进行区别,俗称 读写分离 ;
2、 写任意 (Write Any):对数据的 修改可提交给任意的节点 ,跟读一样。对 zookeeper 来说,它采用的方式是 写任意 。通过增加机器,它的读吞吐能力和响应能力扩展性非常好,而写请求随着机器的增多吞吐能力肯定下降(这也是它建立 observer 的原因),而响应能力则取决于具体实现方式,是 延迟复制保持最终一致性 ,还是 立即复制快速响应 。
4、核心应用场景
分布式锁
利用zookeeper的一致性文件系统,可以创建一个保持独占且控制时序的锁。
每个客户端都去同一目录下创建临时顺序节点znode,编号最小的获得锁,用完删除。
// 伪代码:基于临时顺序节点
public void lock() {
// 1. 创建临时顺序节点:/lock/lock-00001
String lockPath = zk.create("/lock/lock-", EPHEMERAL_SEQUENTIAL);
// 2. 获取所有子节点并排序
List<String> children = zk.getChildren("/lock");
Collection.sort(children);
// 3. 若当前节点是最小节点,获得锁
if (lockPath.endsWith(children.get(0))) {
return;
}
// 4. 否则监听前一个节点
String prevNode = children.get(Collections.binarySearch(children, lockPath) - 1);
zk.exists("/lock/" + prevNode, watcher -> {
if (watcher.event == NodeDeleted) lock(); // 前节点释放,重新尝试
});
}
服务注册与发现
- 服务注册:服务启动时在
/services/serviceA
下创建临时节点(如ip:192.168.1.10:8080
) - 服务发现:客户端监听
/services/serviceA
的子节点变化,实时获取可用服务列表 - 健康检测:会话超时后临时节点自动删除(相当于服务下线)
配置中心
将应用程序的配置信息放到zookeeper的 下,当有配置发生改变时,也就是znode发生变化时,可以通过改变zk中某个目录节点的内容(已被客户端监听),利用 watcher 通知给各个客户端,从而更改配置
// 全局配置存储于 /config/app
String config = zk.getData("/config/app", true, null);
// 监听配置变更
zk.addWatch("/config/app", (event) -> {
if (event.type == NodeDataChanged) {
reloadConfig(); // 热更新配置
}
});
集群选举
- 所有候选节点尝试创建
/election/leader
(临时节点) - 创建成功者成为 Leader,失败者监听该节点
- Leader 宕机时节点删除,其他节点重新选举
Zookeeper 同步流程
选完 Leader 以后,zk 就进入状态同步过程。
1、Leader 等待 server 连接;
2、Follower 连接 leader,将最大的 zxid 发送给 leader;
3、Leader 根据 follower 的 zxid 确定同步点;
4、完成同步后通知 follower 已经成为 uptodate 状态;
5、Follower 收到 uptodate 消息后,又可以重新接受 client 的请求进行服务了。
5、集群部署与配置
所有机器约定在父目录下创建临时目录节点,然后监听这个父目录节点的子节点变化消息,有新的机器加入或断开,其对应的临时目录节点也会新增和删除,所有的机器都会收到通知。另外所有机器创建临时顺序编号目录节点,可以方便选取编号最小的机器作为master。
集群搭建(3节点示例)
配置文件zoo.cfg:
tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181
initLimit=10 # Follower初始连接Leader的超时(tick倍数)
syncLimit=5 # Follower与Leader同步数据的超时
server.1=node1:2888:3888 # 2888: Leader通信端口, 3888: 选举端口
server.2=node2:2888:3888
server.3=node3:2888:3888
节点标识文件(在 dataDir
下创建 myid
):
# node1 机器执行
echo "1" > /var/lib/zookeeper/myid
关键命令
命令 | 作用 |
---|---|
zkServer.sh start |
启动 ZooKeeper 服务 |
zkCli.sh -server ip:port |
连接集群 |
create /path data |
创建节点 |
get -w /path |
获取数据并监听 |
ls /path |
列出子节点 |
stat /path |
查看节点状态 |
6、生产环境最佳实践
1. 性能优化
分离事务日志与快照:将
dataLogDir
指向 SSD 磁盘增加 Observer 节点:扩展读能力(如跨机房部署)
JVM 调优:设置堆大小(建议 4-8GB),启用 G1 垃圾回收器
export JVMFLAGS="-Xmx8G -Xms8G -XX:+UseG1GC"
2. 高可用设计
集群规模:至少 3 节点(容忍 1 节点故障),5 节点(容忍 2 节点故障)
避免磁盘写满:监控磁盘空间,设置自动清理策略
autopurge.snapRetainCount=3 # 保留3个快照 autopurge.purgeInterval=24 # 每24小时清理
3. 安全加固
SASL 认证:启用 Kerberos 或用户名/密码认证
authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider requireClientAuthScheme=sasl
网络隔离:用防火墙限制访问 IP
加密通信:启用 SSL/TLS
7、常见问题解决方案
问题 | 解决方案 |
---|---|
脑裂(Split-Brain) | 集群节点数必须为奇数(2n+1),确保投票多数决 |
ZNode 数据过大 | 严格限制数据大小(≤1MB),大配置拆分存储 |
Watch 丢失 | 会话超时或网络抖动导致 Watch 失效,需代码重注册监听 |
连接耗尽 | 调整 maxClientCnxns 参数,使用连接池 |
写性能瓶颈 | 写请求必须由 Leader 处理,可通过分片(如 Curator 的 DistributedQueue)缓解 |
8、与同类技术对比
工具 | 一致性模型 | 数据模型 | 适用场景 |
---|---|---|---|
ZooKeeper | 强一致性(CP) | 树形 ZNode | 协调服务(锁、选举、配置) |
etcd | 强一致性(CP) | 键值对+版本号 | K8s 元数据存储、服务发现 |
Consul | 可调一致性(AP/CP) | 键值对+服务目录 | 多数据中心服务网格 |
Redis | 最终一致性(AP) | 多种数据结构 | 缓存、消息队列、分布式锁(Redlock) |
9、总结
- 核心价值:提供分布式系统的基础协调能力(像分布式系统的神经系统)。
- 选型建议:
- ✅ 需要强一致性的协调场景(如金融核心系统)。
- ✅ 中小规模集群(≤1000节点)的选举/配置管理。
- ❌ 超大规模数据存储(改用 etcd 或专用存储)。
- 学习建议:掌握 ZNode 操作、Watch 机制 和 ZAB 协议原理 是三大核心。
部署口诀:
奇数节点保选举,分离日志提性能,监控会话防超时,数据小微忌贪心。
通过zkCli.sh
实操练习,结合 Curator 框架可快速构建分布式应用!
10、面试题
ZooKeeper是什么
ZooKeeper 是一个开放源码的分布式协调服务,它是集群的管理者,监视着集群中各
个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和
性能高效、功能稳定的系统提供给用户。
分布式应用程序可以基于 Zookeeper 实现诸如数据发布/订阅、负载均衡、命名服务、
分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
Zookeeper 保证了如下分布式一致性特性:
● 顺序一致性
● 原子性
● 单一视图
● 可靠性
● 实时性(最终一致性)
客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了监听
器,这个监听器也是由所连接的 zookeeper 机器来处理。对于写请求,这些请求会同
时发给其他 zookeeper 机器并且达成一致后,请求才会返回成功。因此,随着
zookeeper 的集群机器增多,读请求的吞吐会提高但是写请求的吞吐会下降。
有序性是 zookeeper 中非常重要的一个特性,所有的更新都是全局有序的,每个更新
都有一个唯一的时间戳,这个时间戳称为 zxid(Zookeeper Transaction Id)。而读请
求只会相对于更新有序,也就是读请求的返回结果中会带有这个 zookeeper 最新的
zxid。
Zookeeper Watcher机制–数据变更通知
Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher 通知状态和事件类型做出业务上的改变。
工作机制:
● 客户端注册 watcher
● 服务端处理 watcher
● 客户端回调 watcher
Watcher特性总结:
(1)一次性
无论是服务端还是客户端,一旦一个 Watcher 被触发,Zookeeper 都会将其从相应的
存储中移除。这样的设计有效的减轻了服务端的压力,不然对于更新非常频繁的节
点,服务端会不断的向客户端发送事件通知,无论对于网络还是服务端的压力都非常
大。
(2)客户端串行执行
客户端 Watcher 回调的过程是一个串行同步的过程。
(3)轻量
Watcher 通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容。
客户端向服务端注册 Watcher 的时候,并不会把客户端真实的 Watcher 对象实体传
递到服务端,仅仅是在客户端请求中使用 boolean 类型属性进行了标记。
watcher event 异步发送 watcher 的通知事件从 server 发送到 client 是异步的,这就
存在一个问题,不同的客户端和服务器之间通过 socket 进行通信,由于网络延迟或其
他因素导致客户端在不通的时刻监听到事件,由于 Zookeeper 本身提供了 ordering
guarantee,即客户端监听事件后,才会感知它所监视 znode 发生了变化。所以我们
使用 Zookeeper 不能期望能够监控到节点每次的变化。Zookeeper 只能保证最终的一
致性,而无法保证强一致性。
注册 watcher getData、exists、getChildren
触发 watcher create、delete、setData
当一个客户端连接到一个新的服务器上时,watch 将会被以任意会话事件触发。当与
一个服务器失去连接的时候,是无法接收到 watch 的。而当 client 重新连接时,如果
需要的话,所有先前注册过的 watch,都会被重新注册。通常这是完全透明的。只有
在一个特殊情况下,watch 可能会丢失:对于一个未创建的 znode 的 exist watch,如
果在客户端断开连接期间被创建了,并且随后在客户端连接上之前又删除了,这种情
况下,这个 watch 事件可能会被丢失。
ZooKeeper分布式锁的实现原理
使用 zookeeper 创建临时序列节点来实现分布式锁,适用于顺序执行的程序,大体思
路就是创建临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后
此序列节点消失,通过 watch 来监控节点的变化,从剩下的节点的找到最小的序列节
点,获取分布式锁,执行相应处理。
Eureka
一、Eureka核心概念
- 服务注册中心(Eureka Server)
- 管理所有服务的注册信息,提供状态监控页面
- 高可用时支持多节点集群(相互注册)
- 服务提供者/消费者(Eureka Client)
- 提供者:向Eureka注册自身服务
- 消费者:从Eureka获取服务列表并调用其他服务
- 自我保护机制
- 当网络故障导致大量服务心跳丢失时,Eureka保留所有注册信息避免误删,生产环境建议开启,开发环境可关闭
二、搭建Eureka Server
步骤 1:创建项目并添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
步骤 2:配置文件 application.yml
server:
port: 8761 # 默认端口
eureka:
client:
register-with-eureka: false # 不注册自己
fetch-registry: false # 不拉取注册表
server:
enable-self-preservation: false # 开发环境关闭自我保护
步骤 3:启动类添加注解
@SpringBootApplication
@EnableEurekaServer // 启用 Eureka 服务端
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
- 访问控制台:
http://localhost:8761
。
三、注册 Eureka Client(服务提供者)
步骤 1:添加客户端依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
步骤 2:配置文件 application.yml
spring:
application:
name: user-service # 服务名称(需唯一)
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka # 注册到 Eureka Server
instance:
instance-id: user-service-8001 # 控制台显示的名称:cite[1]:cite[7]
步骤 3:启动类添加注解
@SpringBootApplication
@EnableEurekaClient // 启用客户端注册
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
四、服务发现与调用
方法 1:使用 RestTemplate
(负载均衡)
@Bean
@LoadBalanced // 启用负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
// 调用服务
String url = "http://user-service/user/1"; // 通过服务名调用
String result = restTemplate.getForObject(url, String.class);
方法 2:使用 FeignClient
(声明式调用)
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/user/{id}")
String getUser(@PathVariable("id") Long id);
}
- 需添加依赖:
spring-cloud-starter-openfeign
。
五、高可用集群配置
1. 配置多节点 Eureka Server
节点 1(端口 8761):
eureka: instance: hostname: eureka1 client: service-url: defaultZone: http://eureka2:8762/eureka # 注册到其他节点
节点 2(端口 8762):
eureka: instance: hostname: eureka2 client: service-url: defaultZone: http://eureka1:8761/eureka
2. 客户端注册到集群
eureka:
client:
service-url:
defaultZone: http://eureka1:8761/eureka,http://eureka2:8762/eureka
✅ 关键点:所有节点需相互注册,客户端需配置全部节点地址
六、常见问题与优化
1. 服务无法注册
- 检查点:
- 客户端配置的
defaultZone
是否拼写正确(必须包含/eureka
)。 - 确保 Eureka Server 已启动且网络连通58。
- 客户端配置的
2. 状态显示异常
添加
actuator
依赖解决/info
报错:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
配置服务信息:
info: app.name: my-service company: example.com :cite[1]
3. 调优参数
eureka:
instance:
lease-renewal-interval-in-seconds: 30 # 心跳间隔(默认30秒)
lease-expiration-duration-in-seconds: 90 # 服务失效时间(默认90秒)
client:
registry-fetch-interval-seconds: 5 # 客户端刷新注册表间隔:cite[5]:cite[7]
总结与建议
场景 | 配置要点 |
---|---|
单机开发 | 关闭自我保护,快速剔除失效服务 |
生产集群 | 开启自我保护,配置多节点互注册 |
高频调用优化 | 缩短 registry-fetch-interval-seconds |
替代方案 | 考虑 Nacos 或 Consul (Eureka 已维护模式) |
Apollo
一、Apollo 核心概念与架构
核心角色
组件 作用 Config Serviece 提供配置的读取、推送服务(客户端直连) Admin Serviece 管理配置的修改、发布(Portal调用) Portal 配置管理界面(用户操作入口) Client 集成到业务应用,实时同步配置(支持Java/.Net) 核心功能
- 多环境管理:支持DEV/TEST/PROD 等环境独立配置
- 实时推送:配置修改后1秒内推送到客户端
- 灰度发布:新配置可先对部分实例生效
- 权限控制:配置编辑、发布分离,支持操作审计
- 版本回滚:意见回滚到历史版本
二、部署模式详解
1. 快速启动模式(开发环境)
步骤:
环境准备:JDK 1.8+、MySQL 5.6.5+ 3
下载安装包:
wget https://github.com/nobodyiam/apollo-build-scripts/archive/master.zip
初始化数据库:执行
sql/
目录下的脚本创建ApolloConfigDB
和ApolloPortalDB
3修改连接配置:编辑
demo.sh
文件:apollo_config_db_url="jdbc:mysql://localhost:3306/ApolloConfigDB?characterEncoding=utf8" apollo_portal_db_url="jdbc:mysql://localhost:3306/ApolloPortalDB?characterEncoding=utf8" ```:cite[3]
启动服务:
./demo.sh start # 启动后访问 http://localhost:8070 (账号: apollo/admin)
2. 生产环境分布式部署
组件 | 部署要求 | 端口 |
---|---|---|
Config Service | 多节点 + SLB 负载均衡 | 8080 |
Admin Service | 与 Config Service 同机部署 | 8090 |
Portal | 独立部署,连接统一 PortalDB | 8070 |
关键配置: |
- Meta Server 地址指向 SLB(如:
-Dapollo.meta=http://slb-ip:8080
) - 数据库高可用:MySQL 主从 + 读写分离
三、Portal 核心功能操作指南
1. 应用与命名空间管理
- 创建应用:
Portal → “创建应用” → 填写AppId
(如order-service
)、部门 1 - 命名空间(Namespace):
- 私有:仅当前应用可用(如
application
) - 公共:跨应用共享(如
redis-config
)
- 私有:仅当前应用可用(如
- 配置发布流程:
编辑配置 → 保存 → 提交发布 → 审核(可选)→ 生效
2. 集群与灰度发布
- 集群配置:
为同一应用分配不同集群(如SH-AZ
、SZ-AZ
),实现地域差异化配置 - 灰度发布:
- 发布时选择“灰度发布”
- 指定部分 IP 或机器名生效
- 验证后全量推送
3. 权限与用户管理
自定义部门:
修改organizations
参数(格式为 JSON 数组):json
[{"orgId":"dev","orgName":"研发部"}, {"orgId":"ops","orgName":"运维部"}] ```:cite[1]
用户权限:
- 超级管理员:
apollo
- 应用管理员:可管理指定应用的配置(通过“系统权限”分配)
- 超级管理员:
四、客户端集成(SpringBoot 示例)
依赖与配置
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.10.0</version>
</dependency>
配置文件 bootstrap.yml
:
apollo:
bootstrap:
enabled: true
namespaces: application,redis-config # 需加载的命名空间
meta: http://localhost:8080 # Meta Server 地址
app:
id: order-service # 必须与 Portal 中 AppId 一致
```:cite[5]
#### 2. **动态配置读取**
- **注解方式**(实时刷新):
```java
@Value("${redis.host}")
private String redisHost;
五、高级特性与最佳实践
1. 安全加固
- 配置加密:
使用@EnableEncryptableProperties
+jasypt
集成敏感信息加密 - 网络隔离:
Portal 部署在内网,通过 VPN 访问;Client 与 Config Service 间使用 HTTPS
2. 灾备与监控
- 配置备份:定期导出配置到 Git(通过开放平台 API)
- 客户端监控:
访问http://config-service-ip:8080/configs/{AppId}/{ClusterName}/{Namespace}
查看配置加载状态
3. 常见问题解决
问题 | 解决方案 |
---|---|
客户端无法连接 Meta Server | 检查 -Dapollo.meta 参数是否生效 |
@Value 不刷新 | 确保配置在 bootstrap.yml 且启用 @RefreshScope |
公共 Namespace 不生效 | 在 bootstrap.yml 中显式声明 namespaces |
六、与同类产品对比
特性 | Apollo | Nacos | Spring Cloud Config |
---|---|---|---|
配置实时推送 | ✅ 秒级 | ✅ 秒级 | ❌ 依赖客户端轮询 |
权限管理 | ✅ 完善 | ✅ 完善 | ❌ 需自行集成 |
多语言支持 | ✅ Java/.Net | ✅ 多语言 | ❌ 主要支持 Java |
部署复杂度 | ⭐⭐⭐ (需MySQL) | ⭐⭐ (内嵌数据库) | ⭐ (Git 直连) |
Nacos
一、Nacos核心概念
组件 | 作用 |
---|---|
Naming Service | 服务注册与发现(管理微服务实例状态) |
Config Service | 动态配置管理(实时推送配置变更) |
MCP Router | 路由AI服务(3.0新增,支持动态筛选和协议转换) |
Console | 可视化控制台(管理服务、配置、命名空间) |
特性优势:
- 服务发现:支持临时/持久实例,心跳检测(默认5秒心跳,15秒标记异常,30秒剔除)
- 配置管理:基于Data ID + Group + Namespace 三要素隔离环境
- 高可用:集群模式依赖Raft(CP配置一致性) + Distro(AP服务发现最终一致性协议)协议
二、安装与部署
单机模式(开发测试)
wget https://github.com/alibaba/nacos/releases/download/2.3.2/nacos-server-2.3.2.zip
unzip nacos-server-2.3.2.zip
cd nacos/bin
./startup.sh -m standalone # Linux/Mac
startup.cmd -m standalone # Windows
控制台访问:http://localhost:8848/nacos
(默认账号 nacos/nacos
)
集群模式(生产环境)
修改
conf/cluster.conf
,添加节点IP:192.168.0.1:8848 192.168.0.2:8848 192.168.0.3:8848
切换数据库为MySQL(避免数据丢失):
spring.datasource.platform=mysql db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?useUnicode=true ```:cite[1]:cite[10]
启动所有节点:sh startup.sh
安全加固:生产环境需在
conf/application.properties
中启用鉴权:nacos.core.auth.enabled=true
三、服务注册与发现
服务注册(Spring Boot 示例)
步骤:
添加依赖:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
配置
bootstrap.yml
:spring: application: name: user-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 cluster-name: SH-AZ # 集群名称(可选) ```:cite[1]:cite[4]
启动类添加注解(可选,Spring Cloud 2020+ 可省略):
@SpringBootApplication @EnableDiscoveryClient public class UserApplication { ... }
2. 服务发现与调用
方式 1:RestTemplate + 负载均衡
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } // 调用示例 String url = "http://user-service/getUser"; String result = restTemplate.getForObject(url, String.class); ```:cite[1]:cite[9]
方式 2:权重控制
在 Nacos 控制台调整实例权重(范围 0~1),流量按权重分配:- 权重 0.3:接收 30% 流量
- 权重 0:停止接收流量
四、配置中心集成
1. 动态配置管理
配置示例(bootstrap.yml
):
spring:
config:
import: nacos:user-service.yaml # 导入远程配置
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
namespace: dev # 环境隔离(如 dev/test/prod)
group: ORDER_GROUP # 业务分组
```:cite[1]:cite[8]
#### 2. **配置动态刷新**
- **注解方式**:
```java
@RefreshScope
@RestController
public class ConfigController {
@Value("${app.max.retry:3}") // 默认值 3
private int maxRetry;
}
配置类绑定:
@Component @ConfigurationProperties(prefix = "app") @Data public class AppConfig { private int maxRetry; private String env; } ```:cite[1]
3. 配置隔离三要素
维度 | 作用 | 示例 |
---|---|---|
Namespace | 环境隔离(dev/test) | namespace: dev |
Data ID | 配置文件标识 | user-service.yaml |
Group | 业务分组 | group: ORDER_GROUP |
五、高级特性与生产实践
1. MCP Router(Nacos 3.0)
- 功能:动态路由 AI 服务,支持协议转换(如 stdio → streamable)
- 应用场景:
- 筛选与 LLM 关键字相关的服务
- 代理模式统一服务接口
2. 性能调优
通信模型:gRPC 长连接(替代 HTTP 短连接),吞吐量提升 10 倍
JVM 参数:
# bin/startup.sh 中调整 JAVA_OPT="${JAVA_OPT} -Xms4g -Xmx4g -Xmn2g"
3. 安全与监控
措施 | 操作 |
---|---|
开启鉴权 | nacos.core.auth.enabled=true |
Prometheus 监控 | 访问 http://nacos-ip:8848/nacos/actuator/prometheus 获取指标 |
定期清理无效实例 | 控制台手动下线或 API 自动巡检 |
六、对比其他中间件
特性 | Nacos | Eureka | Consul |
---|---|---|---|
服务发现 | ✅ 临时/持久实例 | ✅ 仅临时实例 | ✅ |
配置中心 | ✅ 动态推送 | ❌ | ✅ |
一致性协议 | Raft(CP) + Distro(AP) | AP(最终一致) | Raft(CP) |
多语言支持 | ✅ Java/.Net/Go | ❌ 主要 Java | ✅ |
📌 选型建议:
- 需统一注册中心与配置中心 → Nacos
- 纯 Java 生态轻量级部署 → Eureka
- 多数据中心强一致性场景 → Consul
常见问题解决
问题 | 解决方案 |
---|---|
客户端无法连接 Nacos | 检查 server-addr 配置;确认防火墙开放 8848 端口 |
配置更新未生效 | 确保添加 @RefreshScope ;检查 bootstrap.yml 优先级高于 application.yml |
集群节点数据不同步 | 验证 cluster.conf IP 列表;检查 MySQL 主从状态 |
动态线程池
背景:
转账等核心业务功能往往设计的业务逻辑比较复杂,有一系列的校验规则,所以就引入了线程池来并发处理,但是在设置线程池的几个核心参数时,是凭经验在代码里面写死的,上线后发现线程资源不合理,想要调整就必须改代码重新发布服务,非常麻烦,
以下是一个基于美团DynamicTp设计思想、集成Apollo配置中心的动态线程池完整实现方案,涵盖参数动态调整、实时监控与告警三大核心模块。方案分为六个部分,结合代码实现与架构设计,可直接用于生产环境。
一、整体架构设计
二、基础环境搭建
1. 依赖引入
<dependencies>
<!-- Apollo配置中心 -->
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.9.0</version>
</dependency>
<!-- 线程池监控 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.10.0</version>
</dependency>
</dependencies>
2. Apollo配置初始化
- 在
application.yml
中配置Apollo:
app:
id: your-application-id
apollo:
meta: http://apollo-config-service:8080
bootstrap:
enabled: true
namespaces: application, dynamic-tp-config
三、动态线程池核心实现
1. 线程池动态化改造
public class DynamicThreadPoolExecutor extends ThreadPoolExecutor {
// 重写set方法支持动态调整
@Override
public void setCorePoolSize(int corePoolSize) {
super.setCorePoolSize(corePoolSize);
if (corePoolSize > getPoolSize()) {
prestartCoreThread(); // 立即创建新线程
}
}
@Override
public void setMaximumPoolSize(int maxPoolSize) {
if (maxPoolSize < getLargestPoolSize())
throw new IllegalArgumentException();
super.setMaximumPoolSize(maxPoolSize);
}
// 支持动态队列容量(关键!)
public void setQueueCapacity(int capacity) {
if (getQueue() instanceof ResizableBlockingQueue) {
((ResizableBlockingQueue<Runnable>) getQueue()).setCapacity(capacity);
}
}
}
// 可扩容队列实现(仿RabbitMQ设计)
public class ResizableBlockingQueue<E> extends LinkedBlockingQueue<E> {
public synchronized void setCapacity(int capacity) {
this.capacity = capacity; // 突破JDK队列final限制
}
}
2. 线程池Bean初始化
@Configuration
public class ThreadPoolConfig {
@Bean
public DynamicThreadPoolExecutor orderThreadPool() {
return new DynamicThreadPoolExecutor(
8, 20, 60, TimeUnit.SECONDS,
new ResizableBlockingQueue<>(1000),
new NamedThreadFactory("order-pool"),
new CallerRunsPolicy()
);
}
}
四、Apollo动态配置集成
1. 参数配置设计(Apollo命名空间:dynamic-tp-config)
threadpool:
order:
coreSize: 10
maxSize: 50
queueCapacity: 2000
warn:
activeThreshold: 40 # 活跃线程告警阈值
queueThreshold: 1800 # 队列容量告警阈值
2. 配置变更监听器
@Component
public class ThreadPoolRefresher {
@Autowired
private DynamicThreadPoolExecutor executor;
@ApolloConfigChangeListener("dynamic-tp-config")
public void onConfigChange(ConfigChangeEvent event) {
// 核心线程数变更
if (event.isChanged("threadpool.order.coreSize")) {
executor.setCorePoolSize(
Integer.parseInt(event.getChange("coreSize").getNewValue())
);
}
// 队列容量变更
if (event.isChanged("threadpool.order.queueCapacity")) {
executor.setQueueCapacity(
Integer.parseInt(event.getChange("queueCapacity").getNewValue())
);
}
}
}
五、监控与告警系统
1. 监控指标采集(Micrometer)
@Scheduled(fixedRate = 5000)
public void collectMetrics() {
Metrics.gauge("threadpool.active.threads", executor.getActiveCount());
Metrics.gauge("threadpool.queue.size", executor.getQueue().size());
Metrics.gauge("threadpool.rejected.count", executor.getRejectedCount());
}
2. 告警规则引擎
public class AlertManager {
// 阈值检查
public void checkThresholds() {
if (executor.getActiveCount() > activeThreshold) {
sendAlert("活跃线程超限!当前值: " + executor.getActiveCount());
}
if (executor.getQueue().size() > queueThreshold) {
sendAlert("队列堆积预警!当前值: " + executor.getQueue().size());
}
}
// 钉钉告警实现
private void sendAlert(String message) {
DingTalkSender.send("动态线程池告警: " + message);
}
}
3. 可视化看板(Grafana)
- Prometheus指标配置:
scrape_configs:
- job_name: 'dynamic-tp'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['app-server:8080']
- Grafana面板指标:
threadpool_active_threads
:实时活跃线程数threadpool_queue_size
:任务队列堆积量threadpool_rejected_count
:任务拒绝次数
六、生产环境最佳实践
1. 安全兜底策略
// 参数变更前校验
public void setCorePoolSize(int coreSize) {
if (coreSize < 2 || coreSize > 100)
throw new IllegalArgumentException("核心线程数超出安全范围");
super.setCorePoolSize(coreSize);
}
2. 灰度发布机制
- 在Apollo中为特定实例打标:
# 仅对IP为192.168.1.101的实例生效
curl -X PUT 'http://apollo-portal/api/v1/namespaces/dynamic-tp-config/items/coreSize' \
-d 'value=15&dataChangeCreatedBy=admin&ip=192.168.1.101'
3. 优雅停机处理
@PreDestroy
public void shutdown() {
executor.shutdown();
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 强制终止残留任务
}
}
七、扩展优化方向
- 自适应线程池
基于历史负载预测自动调整参数(如QPS+RT计算理想线程数) - 跨组件线程池管理
适配Tomcat/Dubbo等中间件线程池 - AI驱动的弹性调度
结合时序预测模型(如LSTM)预扩容线程池
方案优势总结
模块 | 传统线程池 | 本方案 |
---|---|---|
参数调整 | 需重启应用 | 秒级动态生效 |
队列容量 | 固定不可变 | 运行时动态扩容/缩容 |
监控粒度 | 需手动埋点 | 全自动指标采集+可视化 |
告警时效 | 依赖日志巡检 | 实时阈值检测+多通道通知 |
(executor.getActiveCount() > activeThreshold) { |
sendAlert("活跃线程超限!当前值: " + executor.getActiveCount());
}
if (executor.getQueue().size() > queueThreshold) {
sendAlert("队列堆积预警!当前值: " + executor.getQueue().size());
}
}
// 钉钉告警实现
private void sendAlert(String message) {
DingTalkSender.send("动态线程池告警: " + message);
}
}
#### 3. **可视化看板(Grafana)**
- **Prometheus指标配置**:
~~~yaml
scrape_configs:
- job_name: 'dynamic-tp'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['app-server:8080']
- Grafana面板指标:
threadpool_active_threads
:实时活跃线程数threadpool_queue_size
:任务队列堆积量threadpool_rejected_count
:任务拒绝次数
六、生产环境最佳实践
1. 安全兜底策略
// 参数变更前校验
public void setCorePoolSize(int coreSize) {
if (coreSize < 2 || coreSize > 100)
throw new IllegalArgumentException("核心线程数超出安全范围");
super.setCorePoolSize(coreSize);
}
2. 灰度发布机制
- 在Apollo中为特定实例打标:
# 仅对IP为192.168.1.101的实例生效
curl -X PUT 'http://apollo-portal/api/v1/namespaces/dynamic-tp-config/items/coreSize' \
-d 'value=15&dataChangeCreatedBy=admin&ip=192.168.1.101'
3. 优雅停机处理
@PreDestroy
public void shutdown() {
executor.shutdown();
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 强制终止残留任务
}
}
七、扩展优化方向
- 自适应线程池
基于历史负载预测自动调整参数(如QPS+RT计算理想线程数) - 跨组件线程池管理
适配Tomcat/Dubbo等中间件线程池 - AI驱动的弹性调度
结合时序预测模型(如LSTM)预扩容线程池
方案优势总结
模块 | 传统线程池 | 本方案 |
---|---|---|
参数调整 | 需重启应用 | 秒级动态生效 |
队列容量 | 固定不可变 | 运行时动态扩容/缩容 |
监控粒度 | 需手动埋点 | 全自动指标采集+可视化 |
告警时效 | 依赖日志巡检 | 实时阈值检测+多通道通知 |
生产安全 | 变更风险高 | 灰度发布+参数校验兜底 |