type
status
date
slug
summary
tags
category
icon
password
冷热分离
核心问题: 如何优化大数据量下的查询性能?
场景: 一个电商平台的工单系统,随着业务发展,工单表数据量达到千万级别,导致查询工单列表非常缓慢,用户体验极差。
解决方案: 冷热分离
具体步骤:
- 识别冷热数据: 作者将“最后更新时间超过一个月且状态为‘关闭’”的工单定义为冷数据,其余为热数据。
- 思考: 这里的判断标准是根据什么来制定的?(答案:根据业务场景和用户查询习惯,大部分情况下用户只关心近期的工单。)
- 数据分离: 将冷数据迁移到新的数据库(冷库),原数据库保留热数据(热库)。
- 触发机制: 作者对比了三种触发机制:
- 修改业务代码: 每次数据更新都判断是否需要进行冷热分离。
- 优点: 逻辑清晰,实时性高。
- 缺点: 代码侵入性强,每次更新都要判断,影响性能。
- 监听数据库binlog: 通过监听数据库的binlog日志来判断数据是否需要迁移。
- 优点: 与业务代码解耦。
- 缺点: 实现相对复杂。
- 定时扫描数据库: 定期扫描数据库,将符合条件的数据迁移到冷库。
- 优点: 实现简单,对业务代码无侵入。
- 缺点: 实时性较差。
作者最终选择了定时扫描数据库,因为业务场景允许一定的延迟,而且实现简单。
- 执行数据迁移: 作者采用的方案是:
- 在热库中给需要迁移的数据加一个标识。
- 找出所有待迁移的数据。
- 在冷库中保存一份数据(要保证幂等性,避免重复插入)。
- 从热库中删除对应的数据。
- 使用多个线程并发执行迁移,提高效率。
关键点:
迁移过程中的数据一致性:
- 问题: 在将数据从热库迁移到冷库的过程中,如果发生错误(例如程序异常、服务器宕机、网络中断等),可能会导致数据丢失或重复。
- 解决方案:
- 事务: 如果数据库支持事务,可以将数据复制和删除操作放在同一个事务中,确保这两个操作要么同时成功,要么同时失败。
- 标识 + 幂等性: 正如书中所说,给要迁移的数据添加一个标识(例如:is_cold=1),然后在冷库中插入数据前先根据数据的唯一标识(例如订单ID)进行查询,如果已经存在则不插入,确保幂等性。
- 重试机制: 如果数据迁移失败,可以进行重试。重试机制需要考虑重试次数、重试间隔等因素,避免无限重试导致系统压力过大。
- 对账机制: 数据迁移完成后,可以进行数据对账,例如对比热库和冷库中迁移的数据量是否一致,确保数据没有丢失或重复
并发操作导致的数据不一致性:
- 问题: 在高并发场景下,多个线程可能同时操作同一条数据,例如一个线程正在将数据迁移到冷库,而另一个线程正在修改这条数据,可能会导致数据不一致。
- 解决方案:
- 加锁: 在迁移数据时,可以对要迁移的数据进行加锁,例如使用数据库的行锁或表锁,防止其他线程同时修改这条数据。但是,加锁会影响系统的并发性能,需要根据实际情况进行权衡。书中提到了锁超时机制,以防止锁长时间不释放导致死锁。
- 乐观锁: 使用版本号或时间戳等机制来实现乐观锁,在更新数据前先检查数据的版本号或时间戳是否发生变化,如果发生变化则说明数据已经被其他线程修改,此时可以选择重试或放弃更新。
- 先处理写,再迁移: 可以将写操作优先于数据迁移操作,例如在数据更新时,先更新热库中的数据,再将其标记为待迁移,最后由定时任务将待迁移的数据迁移到冷库。这样可以避免在数据迁移过程中发生数据更新导致的不一致问题。
冷热库数据同步延迟导致的数据不一致性:
- 问题: 数据从热库迁移到冷库需要一定的时间,在这段时间内,如果用户访问冷库,可能会看到旧的数据。
- 解决方案:
- 根据业务场景容忍延迟: 如果业务场景允许一定的数据延迟,例如用户查询历史订单,可以接受短暂的数据不一致。
- 提示用户: 可以在页面上提示用户数据可能存在延迟,或者让用户手动刷新数据。
- 先查询热库,再查询冷库: 如果业务场景要求较高的数据实时性,可以在查询数据时先查询热库,如果热库中没有找到数据,再去冷库中查询。
读写分离
核心问题: 当数据库读操作成为性能瓶颈时,如何提高查询效率?
场景: 随着业务的发展,数据库的读请求越来越多,导致查询速度变慢,用户体验下降。
解决方案: 查询分离 (读写分离)
基本思路: 将数据库的读操作和写操作分离到不同的数据库实例上。
- 主库(Master): 负责处理写操作(INSERT, UPDATE, DELETE)。
- 从库(Slave): 负责处理读操作(SELECT)。
- 数据同步: 主库的数据需要实时同步到从库。
为什么要进行查询分离?
- 分担压力: 读操作通常比写操作频繁得多,将读操作分离到从库,可以减轻主库的压力,提高系统的整体性能。
- 提高读性能: 从库可以针对读操作进行优化,例如使用更多的索引、使用更快的存储介质等。
- 提高可用性: 即使主库出现故障,从库仍然可以提供读服务,保证系统的可用性。
作者所在的公司有一个工单系统,随着业务发展,工单数据量越来越大,而且查询工单的条件也越来越复杂,例如:
- 根据工单ID查询
- 根据客户名称查询
- 根据工单状态查询
- 根据工单创建时间范围查询
- 根据多个条件的组合进行查询(例如:查询某个时间段内,状态为“处理中”,并且客户名称包含“XX”的所有工单)
这些复杂的查询语句,如果直接在MySQL数据库中执行,会导致查询速度非常慢,影响用户体验。
解决方案:查询分离(读写分离)
为了解决这个问题,作者采用了查询分离的方案,将 写操作 和 读操作 分离到不同的数据库。
架构设计:
- 主库:MySQL
- 职责: 处理工单数据的 写入(创建工单)和 更新(修改工单状态、添加备注等)。
- 特点: MySQL擅长事务处理,能保证数据的一致性和完整性。
- 从库:Elasticsearch
- 职责: 处理工单数据的 查询。
- 特点: Elasticsearch擅长复杂的搜索和分析,可以快速地执行各种查询操作,即使数据量很大也能保持良好的性能。
- 数据同步: 需要将MySQL(主库)中的工单数据 实时同步 到Elasticsearch(从库)。
数据同步流程(基于消息队列的异步方式):
- 应用服务写入数据到MySQL: 当用户创建一个新的工单或更新一个工单时,应用服务会将数据写入到MySQL数据库中。
- MySQL触发binlog: MySQL数据库会记录数据的变更日志(binlog)。
- 消息队列(MQ)订阅binlog: 使用一个消息队列(例如Kafka)来订阅MySQL的binlog。
- 消息队列将数据变更消息发送给Elasticsearch同步服务: 当MySQL中的数据发生变化时,消息队列会将数据变更消息发送给一个专门负责数据同步的服务(例如,可以称之为Elasticsearch同步服务)。
- Elasticsearch同步服务更新Elasticsearch索引: Elasticsearch同步服务接收到消息后,解析消息内容,并根据消息内容更新Elasticsearch中的工单数据索引。
用户查询流程:
- 用户在前端发起查询请求: 用户在工单查询页面输入查询条件,例如工单状态、创建时间等。
- 查询请求发送到Elasticsearch: 前端将查询请求发送到Elasticsearch集群。
- Elasticsearch执行查询: Elasticsearch根据查询条件,在工单数据索引中执行查询操作。
- Elasticsearch返回查询结果: Elasticsearch将查询结果返回给前端。
- 前端展示查询结果: 前端将查询结果展示给用户。
shardingsphere
可以直接实现主从分离的这个模式,很方便。
自动路由规则
- 写操作:所有
INSERT
/UPDATE
/DELETE
语句自动路由到主库(master
)。
- 读操作:所有
SELECT
语句自动路由到从库(slave1
或slave2
)
数据不一致的问题:
数据一致性问题的根源在于主从同步的延迟(主库写入后,从库未及时更新)。需根据业务场景选择不同的一致性级别:
(1) 强一致性(主库读)
- 场景:金融交易、订单支付等对实时性要求极高的操作。
- 方案:
- 写后读主库:写入主库后,后续的读请求强制走主库。
- Hint 强制路由:通过中间件(如 ShardingSphere)的 Hint 机制强制路由到主库。
(2) 会话一致性(同一会话内读主库)
- 场景:用户提交表单后需要立即查看结果(如发布文章后跳转到详情页)。
- 方案:
- 粘性会话(Sticky Session):同一会话内的读请求路由到主库,可通过 Cookie 或 Token 标识会话。
- 时间窗口控制:写入后的一段时间内(如 3 秒),读请求强制走主库。
(3) 最终一致性(容忍延迟)
- 场景:商品列表展示、评论加载等非敏感操作。
- 方案:
- 异步同步:接受主从延迟(如 1 秒),通过监控同步状态确保最终一致。
- 业务降级:若从库延迟过高,自动切换读主库。
数据同步:
主从复制机制优化
- MySQL 半同步复制(Semi-Sync Replication)
- 原理:主库提交事务前,至少一个从库确认收到 Binlog。
- 配置:
- 优点:降低数据丢失风险,提升同步可靠性。
- GTID(全局事务标识)
- 原理:为每个事务分配唯一 ID,便于追踪和修复主从不一致。
- 配置:
ini
Copy
(2) 同步工具的选择与容错
- 工具选择:
- Canal:基于 MySQL Binlog 的增量订阅和消费组件。
- Debezium:支持多种数据库的分布式 CDC(Change Data Capture)工具。
- Maxwell:轻量级的 MySQL Binlog 解析工具。
- 容错设计:
- 断点续传:记录同步位置(如 Binlog Offset),中断后从断点恢复。
- 重试机制:网络波动时自动重试,避免数据丢失。
- 死信队列(DLQ):同步失败的数据存入队列,人工介入修复。
(3) 数据一致性校验与修复
- 定期校验:使用工具(如
pt-table-checksum
)对比主从数据一致性。
- 自动修复:通过
pt-table-sync
修复不一致数据。
分表分库
分库分表解决的核心问题
问题类型 | 具体表现 |
性能瓶颈 | 单表数据量过大导致查询变慢(如全表扫描)、索引效率下降。 |
存储压力 | 单库磁盘空间不足以容纳海量数据(如日志表、订单表)。 |
高并发压力 | 单库连接数、IOPS、CPU 等资源成为瓶颈,无法支撑高并发请求。 |
运维风险 | 单点故障影响全局,备份和恢复困难。 |
二、分库分表的实现方式
1. 垂直分片(Vertical Sharding)
- 定义:按业务模块拆分表到不同的数据库(如用户库、订单库、商品库)。
- 示例:
- 原始单库:
main_db
(包含users
,orders
,products
表)。 - 垂直拆分后:
user_db
:仅含users
表。order_db
:仅含orders
表。product_db
:仅含products
表。
- 优点:业务解耦,降低单库压力。
- 缺点:无法解决单表数据量过大的问题。
2. 水平分片(Horizontal Sharding)
- 定义:将同一张表的数据按规则拆分到多个数据库或表中。
- 示例:将
orders
表按user_id
分片到 4 个库,每个库分 4 张表(共 16 张表)。 - 分库规则:
user_id % 4
→ 路由到order_db_0
至order_db_3
。 - 分表规则:
order_id % 4
→ 路由到orders_0
至orders_3
。
- 优点:解决单表数据量和并发压力。
- 缺点:跨库查询复杂,需业务层或中间件支持。
三、分库分表的具体策略
1. 分片键选择
- 原则:选择查询频率高、数据分布均匀的字段(如
user_id
、order_id
)。
- 常见分片键:
- 用户ID(
user_id
):适合用户维度查询(如查询某用户的所有订单)。 - 订单ID(
order_id
):适合按订单精确查询。 - 时间(
create_time
):适合按时间范围查询(如按月分表)。
2. 分片算法
算法 | 说明 | 示例 |
哈希取模 | 对分片键哈希后取模,均匀分散数据。 | user_id % 16 → 分到 16 个库/表。 |
范围分片 | 按分片键的范围划分(如时间、ID区间)。 | order_id 在 1-1000万 → order_db_0 。 |
一致性哈希 | 减少扩容时的数据迁移量,适合动态扩缩容。 | 使用虚拟节点环映射数据到物理节点。 |
地理位置分片 | 按地区分库,降低跨地域访问延迟。 | 华北用户 → db_beijing ,华南用户 → db_shanghai 。 |
ㅤ | ㅤ | ㅤ |
完整示例:电商订单表水平分库分表
1. 场景需求
- 订单表
orders
数据量已达 10 亿,单库性能无法支撑。
- 高频查询:按
user_id
查订单列表,按order_id
查订单详情。
2. 分片方案
- 分片键:
user_id
(用户维度查询最多)。
- 分库数量:4 个库(
order_db_0
至order_db_3
)。
- 分表数量:每库 4 张表(
orders_0
至orders_3
)。
- 路由规则:
- 库选择:
user_id % 4
→ 确定库。 - 表选择:
user_id % 4
→ 确定表。
3. 数据分布示例
user_id | user_id % 4 | 库 | 表 |
1001 | 1 | order_db_1 | orders_1 |
1002 | 2 | order_db_2 | orders_2 |
1003 | 3 | order_db_3 | orders_3 |
1004 | 0 | order_db_0 | orders_0 |
读缓存
读缓存解决的问题及使用场景(以 Redis 为例)
解决的问题:
- 缓解数据库压力:高频读请求直接命中缓存,避免频繁访问数据库。
- 提升响应速度:内存读写速度远高于磁盘(如 Redis 可达 10万+ QPS)。
- 降低系统耦合:缓存层可作为数据库的缓冲屏障。
应用场景示例:
电商商品详情页:商品信息(如名称、价格)存储在 MySQL 中,通过 Redis 缓存高频访问的商品数据。用户请求时优先读取 Redis,未命中时回源数据库并重建缓存
二、缓存加载策略
1. 预加载(Pre-Loading)
- 原理:系统启动时或数据变更前主动加载数据到缓存。
- 适用场景:已知热点数据(如首页推荐列表)
2. 懒加载(Lazy-Loading)
- 原理:按需加载数据,首次请求时触发缓存写入。
- 适用场景:动态或低频数据(如用户个性化配置)。
- Redis 实现:
缓存失效策略
1. TTL(Time-To-Live)
- Redis 实现:
SETEX
或EXPIRE
命令设置过期时间。
2. 主动淘汰
- 策略:LRU/LFU 算法自动淘汰旧数据。
- Redis 配置:
缓存的更新策略
缓存更新策略的选择直接影响数据一致性、性能和系统复杂度。以下是针对这几种策略的详细分析,结合真实场景和 Redis 实现:
1. 先更新缓存,再更新数据库
流程:
- 更新缓存 → 2. 更新数据库
优点:
- 实现简单:无复杂逻辑,适合简单业务。
- 读性能高:后续读请求直接命中最新缓存。
缺点:
- 数据不一致风险高:若数据库更新失败,缓存中是脏数据。
- 回滚困难:数据库失败后需回滚缓存(Redis 不支持事务回滚)。
场景举例:
- 临时统计数据(如页面 UV 计数),允许短暂不一致。
Redis 伪代码:
2. 先删除缓存,再更新数据库
流程:
- 删除缓存 → 2. 更新数据库
优点:
- 避免旧数据残留:删除缓存后,下次读取会触发缓存重建。
缺点:
- 缓存击穿风险:删除缓存后,若数据库更新未完成时有大量请求涌入,会穿透到数据库。
- 短暂不一致:数据库更新期间,新请求可能读到旧数据并回填缓存。
场景举例:
- 低频写操作(如修改用户昵称),可容忍短暂不一致。
Redis 伪代码:
3. 先更新数据库,再删除缓存
流程:
- 更新数据库 → 2. 删除缓存
优点:
- 降低不一致概率:数据库更新成功后才会删除缓存。
- 实现简单:适合大多数业务场景。
缺点:
- 删除失败风险:若第二步删除缓存失败,缓存中仍是旧数据。
- 并发问题:在删除缓存前,可能有请求读取到旧数据并回填缓存。
场景举例:
- 高频读场景(如商品价格更新),结合重试机制保障删除成功。
Redis 优化方案:
4. 先删除缓存,更新数据库,再删除缓存(延迟双删)
流程:
- 删除缓存 → 2. 更新数据库 → 3. 延迟一段时间后再次删除缓存
优点:
- 进一步降低不一致:处理并发请求在更新期间写入旧数据到缓存的问题。
缺点:
- 实现复杂:需引入延迟队列或定时任务。
- 性能损耗:二次删除增加延迟。
适用场景:
- 高并发强一致性场景(如库存扣减)。
Redis 实现示例:
策略对比与选型指南
策略 | 一致性 | 性能 | 实现复杂度 | 适用场景 |
先更新缓存再更新DB | 低 | 高 | 低 | 允许脏数据的临时数据 |
先删缓存再更新DB | 中 | 中 | 低 | 低频写操作 |
先更新DB再删缓存 | 高 | 高 | 中 | 通用场景(推荐) |
延迟双删 | 极高 | 中 | 高 | 高并发强一致性场景(如秒杀) |
实际工程中的组合策略
- 基础方案:优先使用 「先更新数据库,再删除缓存」,配合以下优化:
- 异步重试:若缓存删除失败,通过消息队列重试。
- 互斥锁:防止缓存击穿(Redis 的
SETNX
或 Redlock)。
- 高阶方案:在金融级场景使用 「延迟双删」,结合以下手段:
- 监听数据库 Binlog:通过 Canal 或 Debezium 监听数据库变更,触发二次删除。
- 设置合理延迟时间:根据业务并发量调整二次删除的延迟(通常 100ms~1s)。
总结
- 优先选择:
先更新数据库再删除缓存
+异步重试
,覆盖 90% 场景。
- 极端场景:
延迟双删
+Binlog 监听
,解决高并发强一致性问题。
- 避免使用:
先更新缓存再更新数据库
,除非业务允许脏数据长期存在。
三大缓存问题解决方案
1. 缓存穿透(Cache Penetration)
- 问题:恶意请求不存在的数据(如 id=-1)。
- 解决方案:
- 布隆过滤器(Bloom Filter)拦截非法请求。
- 缓存空值:
SETEX null 60
并设置较短过期时间。
2. 缓存击穿(Cache Breakdown)
- 问题:热点数据过期瞬间大量请求压垮数据库。
- 解决方案:
- 互斥锁(Redis 的
SETNX
): - 逻辑过期:缓存永不过期,后台异步更新。
3. 缓存雪崩(Cache Avalanche)
- 问题:大量缓存同时失效导致数据库压力激增。
- 解决方案:
- 随机化过期时间:基础过期时间 + 随机偏移量。
- 多级缓存:本地缓存(如 Caffeine) + Redis 分层失效。
- 集群高可用:Redis Sentinel 或 Cluster 避免单点故障。


写缓存
场景: 某电商平台策划了一场秒杀活动,预计在秒杀开始的一瞬间会有大量用户同时提交订单,数据库的写入压力会非常大。
解决方案: 写缓存
基本思路: 将用户的写请求先写入到性能较高的缓存(例如Redis)中,然后异步地将缓存中的数据批量写入到数据库中。
为什么需要写缓存?
- 削峰填谷: 写缓存可以将突发的写请求流量平滑地写入到数据库中,避免数据库被瞬间压垮。就像一个水库,可以拦蓄洪水,然后在下游慢慢泄洪。
- 提高响应速度: 写缓存的性能通常比数据库高得多,可以快速地响应用户的写请求,提升用户体验。
- 保护数据库: 通过将写请求先写入缓存,可以减少直接对数据库的写入操作,保护数据库免受高并发写请求的冲击
1. 触发机制:请求数量 + 时间间隔双触发
- 实现方式:
- 计数器触发:使用Redis的
List
或Stream
结构缓存请求,每次写入时检查列表长度。当长度达到阈值(如1000条)时,触发批量落库。 - 定时器触发:结合后台任务(如Redis的键空间通知或外部定时任务框架),每隔固定时间(如5秒)强制触发一次落库,防止低流量时数据滞留。
- 示例代码逻辑:
2. 批量落库大小:动态调整 + 性能压测
- 动态配置:将批量大小(如
BATCH_SIZE=500
)存储在Redis或配置中心,支持运行时调整。
- 性能优化:
- 通过压测确定数据库的吞吐量极限(如单次批量插入最多1000条时,数据库延迟在50ms内)。
- 根据业务高峰/低谷动态调整批次大小(如高峰时减小批次,避免数据库过载)。
3. 数据丢失风险:持久化 + 重试 + 备份
- Redis持久化:
- 启用AOF(Append-Only File)并配置为
appendfsync everysec
,平衡性能与数据安全。 - 定期备份RDB快照到云存储(如S3)。
- 落库失败处理:
- 重试机制:失败时记录重试次数,使用指数退避策略(如1s、2s、4s...重试)。
- 死信队列:将多次重试失败的数据转移到独立队列(如Redis的
dead_letter_queue
),后续人工干预或异步修复。
- 冗余备份:
- 写入Redis前,先发送到消息队列(如Kafka)作为备份,确保即使Redis宕机,数据仍可通过消息队列恢复。
4. 并发控制:原子操作 + 分布式锁
- 写入缓存时的并发:
- 利用Redis单线程特性,直接使用
LPUSH
或XADD
命令,天然原子性。
- 批量落库时的并发:
- 分布式锁:使用
SETNX
或Redisson实现锁,确保同一时间只有一个线程处理批量落库。 - 数据库事务:在落库时开启事务,确保批量操作原子提交,失败时整体回滚。
- 数据一致性:
- 使用乐观锁(如版本号)或悲观锁(SELECT FOR UPDATE)避免数据库并发写入冲突。
5. 扩展优化与监控
- 顺序保证:
- 使用Redis Streams结构,确保消息顺序消费,避免乱序落库。
- 内存控制:
- 为Redis设置最大内存限制,配合淘汰策略(如
volatile-lru
),避免OOM。
- 监控告警:
- 监控Redis内存、批量落库延迟、失败重试次数等指标。
- 配置告警(如Prometheus + Grafana),当队列堆积或失败率超标时通知运维。
设计电商秒杀系统
设计一个能够应对高并发、保证数据一致性、防止超卖的秒杀系统,需要从架构设计、流量控制、数据一致性、容灾机制等多个层面进行优化。以下是分阶段的详细设计方案:
1. 架构设计
分层架构
核心组件
- 前端层:静态页面 + 动态验证(防止脚本刷单)
- 网关层:流量控制、熔断降级、请求过滤
- 服务层:无状态服务集群,处理核心秒杀逻辑
- 缓存层:Redis(库存预热、原子扣减)
- 数据库层:MySQL(最终数据持久化)+ 分库分表
- 消息队列:Kafka/RocketMQ(异步削峰、订单处理)
2. 高并发应对策略
(1) 流量控制
- 前端优化:
- 静态资源CDN加速,减少服务器带宽压力。
- 按钮防重复点击(点击后置灰,倒计时结束恢复)。
- 动态验证码(滑块/算术验证码)过滤机器人请求。
- 网关层限流:
- 令牌桶算法:限制每秒请求量(如10万QPS)。
- IP/用户限流:同一用户每秒最多1次请求。
- 队列排队:请求超出阈值时返回“排队中”提示(如小米手机抢购)。
(2) 异步削峰
- 请求异步化:
- 用户点击后,请求直接进入消息队列,返回“处理中”状态。
- 后端消费队列生成订单,完成后通知用户。
- 消息队列设计:
- 使用Kafka分区,按商品ID分片,保证同一商品的请求顺序处理。
- 消费者集群并行处理,提升吞吐量。
(3) 无状态服务横向扩展
- 服务层采用Kubernetes动态扩缩容,根据CPU/内存负载自动增减实例。
3. 数据一致性保障
(1) 库存防超卖
- Redis原子扣减:
- 秒杀开始前,将库存预热到Redis(
INIT_STOCK = 1000
)。 - 使用
DECR
命令扣减库存(原子操作,无需锁):
- 数据库最终一致性:
- Redis扣减成功后,发送消息到MQ,异步更新MySQL库存。
- 使用事务消息(如RocketMQ事务消息)保证本地事务与消息发送的原子性。
(2) 订单幂等性
- 用户ID+商品ID生成唯一订单号,结合数据库唯一索引防止重复下单。
4. 容灾与降级
(1) 熔断降级
- 监控Redis、MySQL、MQ的响应时间,超出阈值时触发降级策略:
- 限流模式:直接返回“活动太火爆,请稍后再试”。
- 兜底数据:展示静态库存数量(如“仅剩最后100件”)。
(2) 数据持久化
- Redis持久化:AOF每秒刷盘 + RDB定时快照。
- MySQL Binlog同步:通过Canal监听Binlog,补偿Redis与数据库的数据不一致。
(3) 故障转移
- Redis主从集群 + Sentinel哨兵模式,主节点宕机自动切换。
- MySQL主从读写分离,主库宕机切从库(半同步复制)。
5. 性能压测与监控
(1) 压测指标
- 模拟10万用户并发,验证:
- Redis吞吐量(目标:10万QPS)。
- MySQL写入延迟(目标:<50ms)。
- 系统整体TP99响应时间(目标:<200ms)。
(2) 监控告警
- 核心监控项:
- Redis内存、连接数、命中率。
- MySQL主从延迟、慢查询。
- MQ堆积量、消费延迟。
- 告警阈值:
- Redis内存 >80% → 触发扩容。
- 订单处理延迟 >5秒 → 触发消费者扩容。
6. 完整流程示例
- 用户请求:
- 前端:点击秒杀按钮 → 发送请求到网关,携带用户ID和商品ID。
- 网关层:
- 校验用户令牌 → 限流(如令牌桶10万QPS) → 放行到服务层。
- 服务层:
- 调用Redis执行Lua脚本扣减库存。
- 扣减成功 → 生成订单号,发送消息到MQ。
- 扣减失败 → 返回“库存不足”。
- 异步处理:
- MQ消费者读取消息,写入MySQL订单表,更新库存(
UPDATE stock SET stock = stock - 1 WHERE id=#{item_id} AND stock > 0
)。
- 结果通知:
- 订单创建成功 → 短信/站内信通知用户。
- 订单创建失败(如数据库唯一键冲突) → 回滚Redis库存(
INCR
)。
7. 优化扩展
- 热点数据隔离:将热门商品(如iPhone)的库存拆分到独立Redis实例。
- 分布式锁兜底:极端情况下(如Redis宕机),使用Redisson分布式锁 + MySQL行锁扣减库存。
- 动态库存预热:根据历史数据预测秒杀商品,提前加载到缓存。
总结
该方案通过分层限流、异步削峰、Redis原子操作、最终一致性,实现高并发下的秒杀系统,核心指标:
- 吞吐量:支持百万级QPS。
- 一致性:通过Redis原子操作 + 事务消息,超卖率趋近于0。
- 可用性:故障自动转移,99.99%可用性。
适用于电商秒杀、票务系统、限量优惠券发放等高并发场景。
注册和发现
在微服务架构下,服务数量众多,服务实例的IP地址和端口号经常变化,如何让服务之间相互发现并进行通信?
比如java用的nacos, go用的etcd
以nacos为例子:
一、Nacos 是什么?
一句话定义:Nacos 是阿里巴巴开源的 动态服务发现、配置管理和服务管理平台,是构建云原生微服务架构的核心基础设施。
核心能力:
- 服务注册与发现:服务实例自动注册,消费者动态发现服务列表。
- 配置管理:集中管理所有环境、应用的配置,支持动态更新。
- 服务健康监测:自动检测服务实例健康状态,剔除异常节点。
- 动态DNS服务:基于权重的负载均衡和路由策略。
二、Nacos 核心机制详解(面试必问!)
1. 服务注册与发现
- 工作流程:
- 服务启动时,向Nacos Server发送注册请求(包含IP、端口、服务名)。
- Nacos Server将服务信息存储到注册表(内存或持久化存储)。
- 消费者通过服务名查询Nacos,获取可用实例列表。
- 客户端本地缓存列表,定时(默认1秒)同步更新。
- 关键配置:
- 面试问题:
- 临时实例 vs 持久实例:
- 临时实例(默认):通过客户端心跳保持存活,宕机自动剔除。
- 持久实例:需服务端主动探测,适合数据库等有状态服务。
- 注册表存储方式:
- 默认内存存储(AP模式高可用),可选MySQL持久化(CP模式)。
2. 配置管理
- 核心概念:
- Data ID:配置的唯一标识,格式:
${prefix}-${spring.profile.active}.${file-extension}
。 - Group:配置分组,默认
DEFAULT_GROUP
,用于环境隔离。 - Namespace:命名空间,实现多租户隔离(如dev/test/prod环境)。
- 动态配置更新:
- 配置监听原理:
- 客户端通过长轮询(Long Polling)机制监听配置变更。
- 默认每30秒检查一次,服务端有变更立即返回,否则hold住29.5秒。
- 面试问题:
- 如何保证配置更新的实时性?
- 长轮询机制 + 客户端本地缓存 + 版本号比对。
- 配置回滚如何实现?
- Nacos控制台提供历史版本一键回滚功能。
三、Nacos 高可用与集群(生产必会!)
1. 集群部署方案
- 最小集群:3个Nacos节点 + 1个MySQL集群(或内嵌Derby)。
- 架构图:
- 配置要点:
- 修改
conf/cluster.conf
,列出所有节点IP:Port。 - 数据库配置
application.properties
:
2. 数据一致性模型
- AP模式(默认):
- 使用自研的
Distro
协议,保证最终一致性。 - 适用场景:服务发现(允许短暂不一致)。
- CP模式:
- 使用
Raft
协议,保证强一致性。 - 适用场景:配置管理、数据库注册(要求强一致)。
四、实际应用场景与代码示例
1. 服务发现 + 负载均衡
2. 多环境配置隔离
- 命名空间:为每个环境(dev/test/prod)创建独立Namespace。
- 配置示例:
3. 灰度发布
- 基于元数据路由:
五、常见问题排查(实战经验)
- 服务注册失败:
- 检查Nacos Server是否可达。
- 查看客户端日志中的心跳发送状态。
- 配置不生效:
- 确认
@RefreshScope
注解已添加。 - 检查Data ID、Group、Namespace是否匹配。
- 集群节点数据不一致:
- AP模式下短暂不一致是正常的,持续不一致需检查网络分区。
六、面试加分项
- Nacos 2.0 新特性:
- 长连接代替HTTP轮询,减少网络开销。
- 增强鉴权和安全机制。
- 与Spring Cloud Alibaba整合:
- 性能调优:
- 调整心跳间隔(不宜过短,避免服务端压力)。
- 使用MySQL分库分表存储海量配置。
总结
掌握Nacos的核心在于理解其服务治理模型和配置管理机制。在面试中,结合项目经验说明以下场景会大大加分:
- 如何用Nacos实现服务平滑上下线?
- 如何设计多环境配置隔离?
- 如何处理服务发现中的网络分区问题?
全链路日志
Spring Cloud Sleuth + Zipkin + ELK的组合,可实现:
- 全链路追踪:可视化请求路径,快速定位瓶颈服务。
- 日志集中分析:通过
Trace ID
关联所有相关日志。
- 业务上下文增强:在Span和日志中添加业务标签(如订单ID、仓库编号)
熔断
场景: 某个电商平台中,用户服务依赖于基础数据服务,基础数据服务又依赖于第三方的位置服务。突然,第三方位置服务响应缓慢,导致基础数据服务线程阻塞,进而导致用户服务也出现大量线程阻塞,最终整个系统不可用。
解决方案: 熔断
基本思路: 当某个服务出现故障(例如响应时间过长、错误率过高)时,熔断器会自动或手动地 切断 对该服务的调用,并快速返回一个预定义的 fallback(回退) 结果,避免请求长时间阻塞,防止故障扩散。就像电路中的保险丝,当电流过大时会自动熔断,保护电路。
- 熔断器(Circuit Breaker): 熔断机制的核心组件,负责监控服务的调用情况,并在服务出现故障时触发熔断。
- 触发条件: 判断服务是否需要熔断的条件,例如:
- 错误率: 当服务调用的错误率达到一定阈值时触发熔断。
- 响应时间: 当服务调用的响应时间超过一定阈值时触发熔断。
- 请求数: 当服务调用的请求数达到一定阈值时触发熔断。
- 熔断状态: 熔断器通常有三种状态:
- 关闭(Closed): 正常状态,允许请求通过。
- 打开(Open): 熔断状态,所有请求都被拒绝,并快速返回fallback结果。
- 半开(Half-Open): 熔断器打开一段时间后,会进入半开状态,允许少量请求通过,如果这些请求都成功,则将熔断器关闭,否则继续保持打开状态。
- Fallback(回退): 当熔断器打开时,返回的预定义结果。例如,可以返回一个默认值、一个错误提示或一个备用方案。
通过熔断器(如Sentinel) + 降级策略 + 实时监控,可有效防止微服务雪崩效应

限流
核心问题: 如何在高并发场景下,控制进入系统的流量,防止系统被过多的请求压垮?
场景: 电商平台举行秒杀活动,预计在秒杀开始的一瞬间会有大量用户同时涌入,服务器和数据库都可能无法承受如此巨大的流量冲击。
解决方案: 限流
基本思路: 限制在单位时间内允许通过的请求数量,将多余的请求拒绝或延迟处理,从而保护系统免受过载的影响。
限流与熔断的区别:
- 限流: 主动预防,控制进入系统的流量,防止系统过载。
- 熔断: 被动保护,当系统出现故障时,快速失败,防止故障扩散。
通过多级限流策略(网关层+服务层) + 动态规则配置 + 智能监控告警,可构建高并发场景下的流量防护体系。关键点:
- 算法选择:优先使用令牌桶应对突发流量
- 维度设计:结合业务特征设置限流维度
- 动态调整:根据实时负载弹性伸缩
- 降级策略:限流后返回队列位置/等待时间
实际部署时建议:
- 网关层:用Nginx/Sentinel做粗粒度防护
- 服务层:用Sentinel/Resilience4j实现细粒度规则
- 数据层:限制连接池大小防止拖垮数据库

微服务的问题
核心问题: 微服务架构虽然带来了很多好处,但也存在不少问题,如何避免或解决这些问题?
1. 微服务职责划分:
- 问题: 如何合理地划分微服务的职责边界? 职责划分过细,会导致服务数量过多,增加系统复杂性;职责划分过粗,又失去了微服务的意义。 实际工作中,职责划分往往受到人员、工期、组织架构的影响,甚至最后与最初的设计完全不同。
- 例子: 一个商品库存,应该放在商品服务还是门店服务?一个相关商品设置功能,因为门店服务开发人员忙,最后放到了商品服务里。
- 反思: 微服务划分需要结合业务、团队、技术等多方面因素综合考虑,没有银弹,需要在实践中不断调整。
2. 微服务粒度拆分:
- 问题: 微服务应该拆分到多细的粒度? 拆得过细,会导致服务数量过多,增加运维和管理的复杂度;拆得过粗,又失去了微服务的优势。
- 例子: 一个加盟商管理,从最开始的一个服务,到拆分出财务、员工管理、返点、子门店等多个服务,最后变成了一个团队维护几十个服务,新员工难以入手。
- 反思: 需要根据团队规模、业务复杂度、技术能力等因素综合考虑, 找到一个平衡点。作者建议以3~4人维护为宜。 警惕为了“微服务”而“微服务”。
3. 没人知道系统整体架构的全貌:
- 问题: 随着微服务数量的增多,没有人能够清楚地了解整个系统的架构,难以对系统进行整体把控和优化。
- 例子: 领导要求各个团队提供微服务清单,并解释每个微服务的作用。因为服务太多,且涉及多个团队,大家都难以描述清楚。
- 反思: 需要建立完善的文档和监控机制,并定期进行架构评审,确保架构的清晰和可控。
4. 重复代码多:
- 问题: 由于缺乏有效的代码共享机制,导致不同微服务中存在大量重复的代码,降低了开发效率,也增加了维护成本。
- 例子: 一个日志埋点工具,因为版本依赖问题,不得不在多个微服务中复制粘贴代码。
- 反思: 需要建立统一的代码规范和公共组件库,并思考更有效的方式解决版本冲突。
5. 耗费更多服务器资源:
- 问题: 相比单体应用,微服务架构需要部署更多的服务实例,占用更多的服务器资源。
- 例子: 一个简单的应用拆分成多个微服务后,服务器数量从5台增加到15台。
- 反思: 需要根据实际的业务需求和系统负载来评估微服务的拆分粒度,避免过度拆分。
6. 分布式事务:
- 问题: 在微服务架构下,一个业务操作可能需要跨多个服务和多个数据库,如何保证数据的一致性?
- 例子: 一个下单流程,需要调用订单服务、库存服务、支付服务等多个服务,如何保证这些服务要么全部成功,要么全部失败?
- 反思: 分布式事务是微服务架构中的一个难题,没有完美的解决方案,需要根据具体的业务场景选择合适的方案,例如TCC模式、Seata等。
7. 服务之间的依赖:
- 问题: 微服务之间存在复杂的依赖关系,一个服务的变更可能会影响到其他服务,导致系统不稳定。
- 例子: 商品服务依赖门店服务,门店服务又依赖商品服务,形成循环依赖;财务系统作为底层服务,几乎依赖了所有其他业务系统。
- 反思: 需要合理设计服务之间的依赖关系,避免循环依赖和复杂的依赖链。
8. 联调的痛苦:
- 问题: 微服务架构下,一个业务流程通常需要多个服务协同完成,如何进行高效的联调测试?
- 例子: 一个项目涉及30多个服务,接口文档核对、联调花费了大量的时间。
- 反思: 需要建立完善的接口文档管理机制,并使用Mock服务等工具来提高联调效率。
9. 部署上的难题:
- 问题: 微服务数量众多,如何快速、高效地部署和管理这些服务?
- 例子: 开发人员希望在本地搭建完整的微服务环境进行调试,但由于服务数量太多,本地环境无法支撑。
- 反思: 需要借助容器技术(例如Docker、Kubernetes)来实现服务的快速部署和管理。
多个服务数据库的数据一致性
场景: 用户在电商平台下单,需要扣减库存(商品服务),生成订单(订单服务),生成支付信息(支付服务)。这三个操作需要保证原子性,要么全部成功,要么全部失败。
在微服务架构下,保证跨多个服务和数据库的数据一致性,尤其是需要原子性操作的场景(如电商下单),需采用分布式事务解决方案。以下是关键方案及实施步骤:
1. Saga 模式
核心思想:将长事务拆分为多个本地事务,每个事务对应补偿操作,通过正向流程和反向补偿实现最终一致性。
实现方式:
- 编排式(Choreography):
- 各服务通过事件发布/订阅驱动流程。
- 流程示例:
- 订单服务创建订单(Pending状态),发布
OrderCreated
事件。 - 库存服务监听事件,扣减库存并发布
StockReduced
事件。 - 支付服务监听事件,生成支付单并发布
PaymentCreated
事件。 - 若所有成功,订单服务确认订单;若某一步失败(如支付失败),触发补偿事件(如
PaymentFailed
),各服务回滚操作。 - 优点:去中心化,服务间松耦合。
- 缺点:复杂度高,难调试。
- 编配式(Orchestration):
- 引入协调器(Orchestrator)集中管理流程。
- 流程示例:
- 协调器调用订单服务创建订单。
- 成功后调用库存服务扣减库存。
- 最后调用支付服务生成支付信息。
- 若某步失败,协调器触发补偿操作(如恢复库存、删除订单)。
- 优点:流程可控,易监控。
- 缺点:协调器可能成为单点故障。
2. TCC(Try-Confirm-Cancel)模式
核心思想:通过预留资源实现两阶段提交,需业务层实现
Try
、Confirm
、Cancel
接口。流程示例:
- Try阶段:
- 库存服务:预扣库存(状态为
冻结
)。 - 订单服务:生成预订单(状态为
待确认
)。 - 支付服务:预生成支付单(状态为
冻结
)。
- Confirm阶段:
- 若所有
Try
成功,各服务提交操作(库存扣减、订单生效、支付完成)。
- Cancel阶段:
- 若任一
Try
失败,各服务回滚(恢复库存、删除预订单、取消支付)。
优点:强一致性保证,适合高并发场景。
缺点:业务侵入性强,需实现补偿逻辑。
3. 基于消息队列的最终一致性
核心思想:通过消息中间件(如Kafka、RabbitMQ)异步传递事件,结合本地事务表确保可靠性。
流程示例:
- 订单服务创建订单,同时插入本地事务表(状态为
进行中
),发布OrderCreated
事件。
- 库存服务消费事件,扣减库存并发布
StockReduced
事件。
- 支付服务消费事件,生成支付单并发布
PaymentCreated
事件。
- 若所有成功,订单服务更新事务状态为
完成
;若失败,触发补偿消息。
关键设计:
- 消息幂等性:通过唯一ID去重,避免重复消费。
- 事务状态表:记录本地事务状态,定时扫描未完成事务进行重试或补偿。
- 死信队列:处理多次重试失败的消息,人工介入修复。
优点:高吞吐量,适合最终一致性场景。
缺点:需处理消息丢失和重复消费问题。
4. 分布式事务框架(如Seata)
核心思想:通过框架(如Seata)自动处理事务提交和回滚,支持AT、TCC、Saga模式。
AT模式(自动补偿)示例:
- 全局事务协调器(TC)拦截业务SQL,生成前后镜像(用于回滚)。
- 各服务执行本地事务,提交前向TC注册分支事务。
- 若全部成功,TC通知提交;若失败,TC通过镜像数据自动回滚。
优点:对业务代码侵入低,自动化回滚。
缺点:依赖数据库快照,可能锁竞争。
方案对比与选型建议
方案 | 一致性强度 | 性能 | 复杂度 | 适用场景 |
Saga编排式 | 最终 | 高 | 高 | 长流程、松耦合 |
Saga编配式 | 最终 | 中 | 中 | 需集中控制的复杂流程 |
TCC | 强 | 中 | 高 | 高一致性要求,短事务 |
消息队列最终一致 | 最终 | 高 | 中 | 高吞吐、接受短暂不一致 |
Seata AT | 强 | 中 | 低 | 简单事务,希望低侵入 |
实施要点
- 幂等性设计:所有操作需支持重复执行(如通过唯一事务ID)。
- 补偿完备性:确保每个正向操作都有对应的补偿逻辑。
- 监控与告警:跟踪事务状态,及时发现和修复不一致。
- 超时与重试:合理设置超时时间,结合指数退避重试策略。
- 人工兜底:提供管理界面手动干预异常事务。
结论:在电商下单场景中,推荐结合Saga编配式(通过协调器管理)和消息队列,在保证最终一致性的同时降低复杂度。若需强一致性,可采用TCC模式,但需权衡开发成本。
微服务之间的数据同步
同步:数据同步更多考虑的是如何在不同服务之间复制或共享数据,保持一致性,而不是事务的原子性。
核心问题: 在微服务架构下,如何实现不同服务之间的数据同步,避免数据冗余和不一致问题?
场景: 一个供应链系统,包含商品、订单、采购三个服务。订单服务和采购服务都需要用到商品服务中的商品信息(例如:商品名称、分类、型号等)
在微服务架构下,不同服务间的数据同步需通过合理的数据冗余设计和可靠的事件驱动机制实现最终一致性,避免强耦合。以下是针对供应链系统(商品、订单、采购服务)的解决方案:
1. 数据冗余设计原则
- 最小化冗余:仅同步必要字段(如商品ID、名称、分类、型号),避免全量复制。
- 业务场景驱动:
- 订单服务:存储商品快照(如下单时的名称、价格),用于历史订单展示。
- 采购服务:存储库存、供应商信息,用于采购计划。
- 明确所有权:商品信息以商品服务为唯一权威数据源,其他服务冗余数据为只读副本。
2. 数据同步方案
方案一:事件驱动异步同步(最终一致性)
- 核心流程:
- 商品服务在商品信息变更时(如名称修改、库存更新),发布领域事件(如
ProductUpdated
),包含变更后的关键字段。 - 消息队列(如Kafka、RabbitMQ)可靠传递事件。
- 订单服务和采购服务订阅事件,更新本地冗余数据。
- 关键设计:
- 幂等性:事件携带唯一ID或版本号,避免重复更新。
- 数据快照:事件中携带完整字段,避免多次查询商品服务。
- 补偿机制:若消费失败,通过死信队列重试或人工干预。
- 优点:松耦合、高可用、适合高频变更。
- 缺点:短暂数据不一致窗口(通常毫秒级)。
方案二:CDC(变更数据捕获)工具同步
- 实现方式:
- 使用工具(如Debezium)监听商品服务数据库的
binlog
,捕获数据变更。 - 将变更事件发送到消息队列。
- 订单和采购服务消费事件,更新本地数据库。
- 适用场景:需同步大量字段或无法修改商品服务代码时。
- 优点:无业务代码侵入,实时性强。
- 缺点:需处理数据库Schema变更,运维复杂度高。
方案三:API聚合查询(避免冗余)
- 核心流程:
- 订单和采购服务在需要商品信息时,实时调用商品服务的API(如
GET /products/{id}
)。
- 适用场景:数据变更频繁但查询频率低,或对数据实时性要求极高。
- 优化措施:
- 本地缓存:使用Redis缓存商品信息,设置合理TTL。
- 批量查询:合并多个商品ID请求,减少网络开销。
- 优点:无冗余数据,一致性高。
- 缺点:依赖商品服务可用性,高并发下可能性能瓶颈。
3. 混合策略:按业务场景选择
场景 | 推荐方案 | 说明 |
订单服务历史快照 | 事件驱动异步同步 | 下单时存储商品快照,后续商品信息变更不影响历史订单。 |
采购服务库存管理 | CDC同步 + 事件驱动 | 实时同步库存变更,确保采购计划基于最新数据。 |
商品详情页展示 | API聚合查询 + 本地缓存 | 前端调用商品服务API,结合缓存降低延迟。 |
4. 一致性保障措施
- 版本控制:
- 商品信息表增加
version
字段,每次更新递增。 - 事件中携带版本号,服务更新时校验版本,防止旧数据覆盖。
- 数据校对(Reconciliation):
- 定时任务对比商品服务与订单/采购服务的数据差异,修复不一致。
- 监控与告警:
- 监控消息队列积压、同步延迟、API错误率,及时发现异常。
5. 技术栈示例
- 事件驱动:Kafka(消息队列) + Avro(Schema序列化)。
- CDC工具:Debezium + MySQL binlog。
- 缓存:Redis(缓存商品信息) + 本地Caffeine(高频查询)。
- API网关:聚合商品、订单、采购接口,减少客户端调用次数。
6. 注意事项
- 避免跨服务事务:不要为同步数据引入分布式事务(如2PC),改用最终一致性。
- 领域事件设计:事件应携带业务语义(如
ProductPriceUpdated
而非TableRowUpdated
)。
- 数据生命周期:订单服务的商品快照需永久保留,采购服务的冗余数据可设置TTL。
总结
在供应链系统中,事件驱动异步同步是平衡一致性与性能的最佳选择:
- 订单服务订阅
ProductUpdated
事件,存储商品快照。
- 采购服务通过CDC监听库存变更,实时更新本地数据。
- 商品服务保持权威数据源地位,通过API提供实时查询。 结合版本控制、幂等性和监控,实现高效、可靠的数据同步。