1. 什么是高并发系统
1.1 定义
高并发系统:在同一时间段内,能够处理大量用户请求(通常是成千上万甚至百万级别的并发请求),并保证系统稳定性、高可用性和良好用户体验的系统。
1.2 核心指标
| 指标 | 说明 | 典型值 |
|---|---|---|
| QPS (每秒查询数) | 系统每秒处理的请求数量 | 中等系统:1000-5000 高并发系统:10000+ |
| TPS (每秒事务数) | 系统每秒处理的事务数量 | 电商下单:5000+ 秒杀场景:50000+ |
| RT (响应时间) | 从请求发出到收到响应的时间 | 优秀:< 100ms 可接受:< 500ms 较差:> 1s |
| 并发用户数 | 同时在线并发访问的用户数 | 小型:1000-5000 中型:1万-10万 大型:100万+ |
| 可用性 | 系统正常运行时间占比 | 99.9% (年停机8.76小时) 99.99% (年停机52.56分钟) |
1.3 高并发 vs 多线程
┌─────────────────────────────────────────────────────────┐
│ 高并发 (High Concurrency) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 多线程 (Multi-Threading) │ │
│ │ 单机内通过多线程提高并发处理能力 │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ 高并发 = 多线程 + 分布式 + 缓存 + 消息队列 + ... │
└─────────────────────────────────────────────────────────┘
通俗理解:
- 多线程:一个厨师同时炒多个菜(单机多线程)
- 高并发:开多家分店,每家店多个厨师同时炒菜(分布式 + 多线程)
2. 高并发的核心挑战
2.1 五大核心问题
高并发系统面临的挑战
│
├─ 1. 性能瓶颈
│ ├─ CPU密集型计算
│ ├─ 内存不足
│ ├─ 磁盘IO阻塞
│ └─ 网络带宽受限
│
├─ 2. 数据库压力
│ ├─ 连接数耗尽
│ ├─ 慢SQL拖垮系统
│ ├─ 锁竞争激烈
│ └─ 主库单点瓶颈
│
├─ 3. 缓存问题
│ ├─ 缓存穿透
│ ├─ 缓存击穿
│ ├─ 缓存雪崩
│ └─ 缓存一致性
│
├─ 4. 系统稳定性
│ ├─ 服务雪崩
│ ├─ 流量洪峰
│ ├─ 热点数据
│ └─ 资源耗尽
│
└─ 5. 数据一致性
├─ 分布式事务
├─ 最终一致性
└─ 脑裂问题
2.2 实际案例:12306崩溃分析
2012年春运12306崩溃事件:
问题现象:
- 同时在线用户:500万+
- 页面打开时间:> 30秒
- 大量用户无法登录
- 订单提交失败率:> 80%
根本原因:
1. 数据库单点瓶颈(Oracle单库)
2. 没有缓存层(每次都查数据库)
3. 没有消息队列(订单同步处理)
4. 没有限流机制(无限制接收请求)
5. 没有CDN(静态资源从服务器加载)
后果:
- 用户体验极差
- 品牌形象受损
- 系统多次瘫痪
3. 高并发系统设计原则
3.1 四大核心原则
1. 垂直拆分 + 水平扩展
垂直拆分(Scale Up):提升单机性能
CPU: 4核 → 16核
内存: 8G → 64G
磁盘: HDD → SSD
水平扩展(Scale Out):增加机器数量 ⭐ 推荐
1台服务器 → 10台服务器 → 100台服务器
为什么推荐水平扩展?
- 成本低(普通服务器便宜)
- 无上限(理论上可无限扩展)
- 高可用(单台故障不影响整体)
2. 无状态服务
java
// ❌ 错误:有状态服务
public class OrderService {
private List<Order> orderCache = new ArrayList<>(); // 状态保存在内存
public void createOrder(Order order) {
orderCache.add(order); // 其他服务器看不到这个订单
}
}
// ✅ 正确:无状态服务
public class OrderService {
@Autowired
private RedisTemplate redisTemplate; // 状态保存在外部存储
public void createOrder(Order order) {
redisTemplate.opsForValue().set("order:" + order.getId(), order);
}
}
无状态的好处:
- 任意服务器处理任意请求
- 服务器可随意增减
- 负载均衡更简单
3. 异步处理
同步处理(用户等待):
用户下单 → 减库存 → 创建订单 → 发送短信 → 发送邮件 → 积分增加 → 返回结果
↓ ↓ ↓ ↓ ↓
50ms 100ms 200ms 150ms 100ms
总耗时:600ms ❌
异步处理(立即返回):
用户下单 → 减库存 → 创建订单 → 返回结果 ✅ (150ms)
↓
发送到消息队列
↓
后台异步处理(发短信、邮件、积分)
4. 服务降级与限流
正常流量: ████░░░░░░ (40%) → 全功能运行
高峰流量: ██████████ (100%) → 触发限流 + 降级
限流策略:
- 超过阈值的请求返回"系统繁忙,请稍后再试"
- 保护核心功能,牺牲次要功能
降级策略:
- 关闭推荐系统(不影响核心购买)
- 关闭评论功能
- 使用默认数据代替实时查询
4. 架构层面的高并发解决方案
4.1 架构演进路线
阶段1: 单体应用
┌─────────────────┐
│ Tomcat │
│ ┌───────────┐ │
│ │Application│ │
│ └───────────┘ │
└────────┬────────┘
│
┌────▼─────┐
│ MySQL │
└──────────┘
并发能力:~500 QPS
↓ 演进 ↓
阶段2: 应用集群 + 读写分离
┌─────────────┐
│Load Balancer│
└──────┬──────┘
┌───┴───┬──────┬──────┐
┌────▼───┐ ┌─▼───┐ ┌─▼───┐
│Tomcat 1│ │Tomcat│ │Tomcat│
└────┬───┘ └──┬──┘ └──┬──┘
└────────┼───────┘
┌───▼────┐
│ 主库 │
└───┬────┘
┌────────┼────────┐
┌────▼───┐ ┌─▼───┐ ┌─▼───┐
│从库1 │ │从库2│ │从库3│
└────────┘ └─────┘ └─────┘
并发能力:~5000 QPS
↓ 演进 ↓
阶段3: 微服务 + 缓存 + 消息队列
┌─────────────────────────────────────────┐
│ API Gateway │
└───┬─────────────┬───────────┬───────────┘
┌───▼──────┐ ┌────▼─────┐ ┌───▼──────┐
│用户服务 │ │订单服务 │ │商品服务 │
└───┬──────┘ └────┬─────┘ └───┬──────┘
│ ┌─────────▼─────────┐ │
│ │ Redis Cluster │ │
│ └───────────────────┘ │
│ ┌─────────▼─────────┐ │
└───│ RabbitMQ │──┘
└───────────────────┘
并发能力:~50000+ QPS
4.2 负载均衡算法实现
轮询(Round Robin)
java
public class RoundRobinLoadBalancer {
private final List<String> servers;
private final AtomicInteger position = new AtomicInteger(0);
public RoundRobinLoadBalancer(List<String> servers) {
this.servers = servers;
}
public String selectServer() {
int pos = position.getAndIncrement() % servers.size();
return servers.get(pos);
}
}
// 使用示例
List<String> servers = Arrays.asList("192.168.1.1", "192.168.1.2", "192.168.1.3");
RoundRobinLoadBalancer lb = new RoundRobinLoadBalancer(servers);
lb.selectServer(); // 192.168.1.1
lb.selectServer(); // 192.168.1.2
lb.selectServer(); // 192.168.1.3
lb.selectServer(); // 192.168.1.1 (循环)
加权轮询(Weighted Round Robin)
java
public class WeightedRoundRobinLoadBalancer {
private static class Server {
String ip;
int weight; // 权重(配置的固定值)
int currentWeight; // 当前权重(动态变化)
public Server(String ip, int weight) {
this.ip = ip;
this.weight = weight;
this.currentWeight = 0;
}
}
private final List<Server> servers = new ArrayList<>();
public void addServer(String ip, int weight) {
servers.add(new Server(ip, weight));
}
public String selectServer() {
if (servers.isEmpty()) return null;
// 计算总权重
int totalWeight = servers.stream().mapToInt(s -> s.weight).sum();
// 找到当前权重最大的服务器
Server selected = null;
for (Server server : servers) {
server.currentWeight += server.weight; // 增加当前权重
if (selected == null || server.currentWeight > selected.currentWeight) {
selected = server;
}
}
// 选中的服务器减去总权重
selected.currentWeight -= totalWeight;
return selected.ip;
}
}
// 使用示例(权重 5:3:2)
WeightedRoundRobinLoadBalancer lb = new WeightedRoundRobinLoadBalancer();
lb.addServer("192.168.1.1", 5); // 性能好的服务器
lb.addServer("192.168.1.2", 3);
lb.addServer("192.168.1.3", 2); // 性能差的服务器
// 10次请求分布:
// 192.168.1.1 → 5次
// 192.168.1.2 → 3次
// 192.168.1.3 → 2次
底层原理详解:
初始状态(权重 5:3:2):
Server1: currentWeight=0, weight=5
Server2: currentWeight=0, weight=3
Server3: currentWeight=0, weight=2
第1次选择:
1. 所有服务器 currentWeight += weight
Server1: 0+5=5 ← 最大,被选中
Server2: 0+3=3
Server3: 0+2=2
2. 选中服务器 currentWeight -= 总权重(10)
Server1: 5-10=-5
第2次选择:
1. currentWeight += weight
Server1: -5+5=0
Server2: 3+3=6 ← 最大,被选中
Server3: 2+2=4
2. Server2: 6-10=-4
第3次选择:
1. currentWeight += weight
Server1: 0+5=5 ← 最大,被选中
Server2: -4+3=-1
Server3: 4+2=6
...循环10次后,分布为 5:3:2
最小连接数(Least Connections)
java
public class LeastConnectionsLoadBalancer {
private static class Server {
String ip;
AtomicInteger activeConnections = new AtomicInteger(0);
public Server(String ip) {
this.ip = ip;
}
}
private final List<Server> servers = new CopyOnWriteArrayList<>();
public void addServer(String ip) {
servers.add(new Server(ip));
}
public String selectServer() {
// 找到当前连接数最少的服务器
Server selected = servers.stream()
.min(Comparator.comparingInt(s -> s.activeConnections.get()))
.orElse(null);
if (selected != null) {
selected.activeConnections.incrementAndGet();
}
return selected != null ? selected.ip : null;
}
public void releaseConnection(String ip) {
servers.stream()
.filter(s -> s.ip.equals(ip))
.findFirst()
.ifPresent(s -> s.activeConnections.decrementAndGet());
}
}
5. 缓存体系深度解析
5.1 多级缓存架构
请求流程(从上到下查找):
┌──────────────────────────────────────────┐
│ 1. 浏览器缓存 (Browser Cache) │ 命中率: 20%
│ - LocalStorage / SessionStorage │ 耗时: 0ms
│ - Cookie │
└──────────────────┬───────────────────────┘
│ Miss
┌──────────────────▼───────────────────────┐
│ 2. CDN缓存 │ 命中率: 30%
│ - 静态资源 (图片、CSS、JS) │ 耗时: 10-50ms
└──────────────────┬───────────────────────┘
│ Miss
┌──────────────────▼───────────────────────┐
│ 3. Nginx本地缓存 (Proxy Cache) │ 命中率: 15%
│ - 热点数据 │ 耗时: 1-5ms
└──────────────────┬───────────────────────┘
│ Miss
┌──────────────────▼───────────────────────┐
│ 4. 应用层本地缓存 (Caffeine/Guava) │ 命中率: 20%
│ - JVM堆内缓存 │ 耗时: < 1ms
└──────────────────┬───────────────────────┘
│ Miss
┌──────────────────▼───────────────────────┐
│ 5. Redis分布式缓存 │ 命中率: 10%
│ - 热点数据 │ 耗时: 1-5ms
└──────────────────┬───────────────────────┘
│ Miss
┌──────────────────▼───────────────────────┐
│ 6. 数据库 (MySQL) │ 命中率: 5%
│ - 持久化数据 │ 耗时: 10-100ms
└──────────────────────────────────────────┘
总缓存命中率 ≈ 95%,平均响应时间 < 10ms
5.2 本地缓存实现(Caffeine)
java
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
@Service
public class ProductService {
// 本地缓存配置
private final Cache<Long, Product> localCache = Caffeine.newBuilder()
.maximumSize(10000) // 最大缓存数量
.expireAfterWrite(5, TimeUnit.MINUTES) // 写入5分钟后过期
.recordStats() // 记录统计信息
.build();
@Autowired
private ProductMapper productMapper;
public Product getProduct(Long productId) {
// 先查本地缓存
Product product = localCache.getIfPresent(productId);
if (product != null) {
return product;
}
// 缓存未命中,查数据库
product = productMapper.selectById(productId);
if (product != null) {
localCache.put(productId, product); // 写入缓存
}
return product;
}
public void updateProduct(Product product) {
productMapper.updateById(product);
localCache.invalidate(product.getId()); // 删除缓存
}
}
Caffeine底层原理:
1. 数据结构:ConcurrentHashMap(线程安全)
┌─────────────────────────────────────┐
│ Key: productId │ Value: Product │
│ Hash: xxxxxxx │ 访问时间、频率 │
└─────────────────────────────────────┘
2. 淘汰算法:W-TinyLFU(Window Tiny LFU)
- LFU (Least Frequently Used): 淘汰访问频率最低的
- Window: 保护新数据
- Tiny: 使用Count-Min Sketch节省内存
Window Cache (1%) Main Cache (99%)
┌────────────┐ ┌──────────────┐
│新数据保护区│ ───> │ LFU淘汰区 │
└────────────┘ └──────────────┘
3. 性能优化:
- 读操作:O(1) 时间复杂度
- 写操作:异步执行淘汰(不阻塞)
- 内存占用:比Guava少10%
5.3 Redis分布式缓存实战
缓存穿透解决方案
问题:查询一个不存在的数据,缓存和数据库都没有,导致每次请求都打到数据库。
java
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ProductMapper productMapper;
// 解决方案1: 缓存空对象
public Product getProduct(Long productId) {
String key = "product:" + productId;
// 1. 查询Redis
Product product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) {
return product;
}
// 2. 查询数据库
product = productMapper.selectById(productId);
if (product != null) {
// 3. 存在则缓存
redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
} else {
// 4. 不存在也缓存(空对象),设置较短过期时间
redisTemplate.opsForValue().set(key, new Product(), 5, TimeUnit.MINUTES);
}
return product;
}
// 解决方案2: 布隆过滤器
@Autowired
private RedissonClient redissonClient;
private RBloomFilter<Long> bloomFilter;
@PostConstruct
public void initBloomFilter() {
bloomFilter = redissonClient.getBloomFilter("product:bloom");
// 预期元素数量100万,误判率1%
bloomFilter.tryInit(1000000L, 0.01);
// 将所有商品ID加入布隆过滤器
List<Long> productIds = productMapper.selectAllIds();
productIds.forEach(bloomFilter::add);
}
public Product getProductWithBloom(Long productId) {
// 1. 布隆过滤器判断
if (!bloomFilter.contains(productId)) {
return null; // 一定不存在,直接返回
}
// 2. 可能存在,查询缓存和数据库
String key = "product:" + productId;
Product product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) {
return product;
}
product = productMapper.selectById(productId);
if (product != null) {
redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
}
return product;
}
}
布隆过滤器底层原理:
1. 数据结构:位数组(Bit Array)
初始状态(所有位都是0):
[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0]
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
2. 添加元素(productId = 1001):
使用3个哈希函数计算:
Hash1(1001) = 3
Hash2(1001) = 7
Hash3(1001) = 12
将对应位置设置为1:
[0][0][0][1][0][0][0][1][0][0][0][0][1][0][0][0]
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
3. 判断元素是否存在(productId = 1001):
Hash1(1001) = 3 → 位为1 ✓
Hash2(1001) = 7 → 位为1 ✓
Hash3(1001) = 12 → 位为1 ✓
结论:可能存在(有误判可能)
4. 判断元素是否存在(productId = 9999):
Hash1(9999) = 5 → 位为0 ✗
结论:一定不存在(不会误判)
优点:
- 空间效率极高(1亿数据只需12MB)
- 查询速度快(O(k),k为哈希函数数量)
缺点:
- 有误判率(可以通过增加位数组大小降低)
- 不支持删除(删除会影响其他元素)
缓存击穿解决方案
问题:热点key过期瞬间,大量请求同时打到数据库。
java
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private Redisson redisson;
@Autowired
private ProductMapper productMapper;
// 解决方案1: 互斥锁
public Product getProduct(Long productId) {
String key = "product:" + productId;
String lockKey = "lock:product:" + productId;
// 1. 查询缓存
Product product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) {
return product;
}
// 2. 缓存未命中,获取分布式锁
RLock lock = redisson.getLock(lockKey);
try {
// 尝试加锁,最多等待10秒,锁自动释放时间30秒
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
// 3. 再次检查缓存(双重检查)
product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) {
return product;
}
// 4. 查询数据库
product = productMapper.selectById(productId);
// 5. 写入缓存
if (product != null) {
redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
}
return product;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
return null;
}
// 解决方案2: 逻辑过期(热点数据永不过期)
@Data
static class ProductWithExpire {
private Product product;
private LocalDateTime expireTime; // 逻辑过期时间
}
private static final ExecutorService CACHE_REBUILD_EXECUTOR =
Executors.newFixedThreadPool(10);
public Product getProductWithLogicalExpire(Long productId) {
String key = "product:" + productId;
// 1. 查询缓存(永不过期)
ProductWithExpire productWithExpire =
(ProductWithExpire) redisTemplate.opsForValue().get(key);
if (productWithExpire == null) {
return null; // 数据不存在
}
// 2. 检查逻辑过期时间
if (productWithExpire.getExpireTime().isAfter(LocalDateTime.now())) {
// 未过期,直接返回
return productWithExpire.getProduct();
}
// 3. 已过期,尝试获取锁
String lockKey = "lock:product:" + productId;
RLock lock = redisson.getLock(lockKey);
if (lock.tryLock()) {
// 4. 获取锁成功,开启异步线程重建缓存
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
// 查询数据库
Product product = productMapper.selectById(productId);
// 写入缓存(设置逻辑过期时间)
ProductWithExpire newData = new ProductWithExpire();
newData.setProduct(product);
newData.setExpireTime(LocalDateTime.now().plusMinutes(30));
redisTemplate.opsForValue().set(key, newData);
} finally {
lock.unlock();
}
});
}
// 5. 返回过期数据(不阻塞用户)
return productWithExpire.getProduct();
}
}
缓存雪崩解决方案
问题:大量key同时过期,导致请求全部打到数据库。
java
@Service
public class ProductService {
// 解决方案1: 过期时间加随机值
public void saveProduct(Product product) {
String key = "product:" + product.getId();
// 基础过期时间30分钟 + 随机0-5分钟
long expireTime = 30 + new Random().nextInt(5);
redisTemplate.opsForValue().set(key, product, expireTime, TimeUnit.MINUTES);
}
// 解决方案2: 热点数据永不过期
public void saveHotProduct(Product product) {
String key = "hot:product:" + product.getId();
// 设置为永不过期
redisTemplate.opsForValue().set(key, product);
}
// 解决方案3: Redis集群 + 持久化
// 配置Redis主从复制 + 哨兵模式
// 即使Redis宕机,也能快速恢复数据
}
5.4 缓存一致性解决方案
java
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 方案1: Cache Aside Pattern(旁路缓存)⭐ 推荐
// 读:先读缓存,未命中再读数据库,然后写缓存
// 写:先更新数据库,再删除缓存
public Product getProduct(Long productId) {
String key = "product:" + productId;
// 1. 读缓存
Product product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) {
return product;
}
// 2. 读数据库
product = productMapper.selectById(productId);
// 3. 写缓存
if (product != null) {
redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
}
return product;
}
@Transactional
public void updateProduct(Product product) {
String key = "product:" + product.getId();
// 1. 先更新数据库
productMapper.updateById(product);
// 2. 再删除缓存(而不是更新缓存)
redisTemplate.delete(key);
// 为什么删除而不是更新?
// 1. 避免并发问题(两个线程同时更新,顺序不确定)
// 2. 懒加载(只有被访问时才写缓存,节省资源)
}
// 方案2: 延迟双删(解决并发问题)
@Transactional
public void updateProductWithDoubleDelete(Product product) {
String key = "product:" + product.getId();
// 1. 删除缓存(第一次)
redisTemplate.delete(key);
// 2. 更新数据库
productMapper.updateById(product);
// 3. 延迟删除缓存(第二次)
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(500); // 延迟500ms
redisTemplate.delete(key);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 方案3: 订阅MySQL Binlog(终极方案)
// 使用Canal监听MySQL binlog变化,异步更新缓存
@Component
public class CanalClient {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 监听product表的变化
public void onProductChange(CanalEntry.RowChange rowChange) {
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
if (rowChange.getEventType() == CanalEntry.EventType.UPDATE
|| rowChange.getEventType() == CanalEntry.EventType.DELETE) {
// 获取主键ID
String productId = getColumnValue(rowData, "id");
// 删除缓存
redisTemplate.delete("product:" + productId);
}
}
}
}
}
为什么是"先更新数据库,再删除缓存"?
情况1: 先删除缓存,再更新数据库(❌ 不推荐)
时间轴:
t1: 线程A删除缓存
t2: 线程B读取缓存(未命中)
t3: 线程B读取数据库(旧数据)
t4: 线程B写入缓存(旧数据)
t5: 线程A更新数据库(新数据)
结果:缓存是旧数据,数据库是新数据 ❌
情况2: 先更新数据库,再删除缓存(✅ 推荐)
时间轴:
t1: 线程A更新数据库
t2: 线程A删除缓存
t3: 线程B读取缓存(未命中)
t4: 线程B读取数据库(新数据)
t5: 线程B写入缓存(新数据)
结果:缓存和数据库都是新数据 ✅
6. 数据库层面的高并发优化
6.1 读写分离架构
主从复制原理:
┌─────────────────┐
│ 主库(Master) │ ← 写操作
└────────┬────────┘
│ Binlog同步
┌────┼─────┬──────┐
┌───▼──┐ ┌─▼──┐ ┌───▼──┐
│从库1 │ │从库2│ │从库3 │ ← 读操作
└──────┘ └────┘ └──────┘
Binlog同步流程:
1. 主库执行SQL,写入Binlog
2. 从库IO线程读取主库Binlog
3. 从库写入Relay Log(中继日志)
4. 从库SQL线程执行Relay Log
MyBatis实现读写分离:
java
// 1. 配置多数据源
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource routingDataSource(
@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource);
targetDataSources.put("slave", slaveDataSource);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
return dynamicDataSource;
}
}
// 2. 动态数据源路由
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
CONTEXT_HOLDER.set(dataSource);
}
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSource() {
CONTEXT_HOLDER.remove();
}
}
// 3. 注解实现读写分离
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Master {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Slave {
}
@Aspect
@Component
public class DataSourceAspect {
@Around("@annotation(master)")
public Object routeToMaster(ProceedingJoinPoint point, Master master) throws Throwable {
try {
DataSourceContextHolder.setDataSource("master");
return point.proceed();
} finally {
DataSourceContextHolder.clearDataSource();
}
}
@Around("@annotation(slave)")
public Object routeToSlave(ProceedingJoinPoint point, Slave slave) throws Throwable {
try {
DataSourceContextHolder.setDataSource("slave");
return point.proceed();
} finally {
DataSourceContextHolder.clearDataSource();
}
}
}
// 4. 使用示例
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
@Slave // 从库读取
public Product getProduct(Long productId) {
return productMapper.selectById(productId);
}
@Master // 主库写入
public void updateProduct(Product product) {
productMapper.updateById(product);
}
}
6.2 分库分表(Sharding)
垂直分库
单库(所有表在一起):
┌──────────────────────┐
│ MySQL库 │
│ ┌────────────────┐ │
│ │ 用户表 │ │
│ │ 订单表 │ │
│ │ 商品表 │ │
│ │ 支付表 │ │
│ └────────────────┘ │
└──────────────────────┘
↓ 按业务拆分 ↓
垂直分库(按业务模块拆分):
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 用户库 │ │ 订单库 │ │ 商品库 │ │ 支付库 │
│ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │
│ │用户表│ │ │ │订单表│ │ │ │商品表│ │ │ │支付表│ │
│ └─────┘ │ │ └─────┘ │ │ └─────┘ │ │ └─────┘ │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
优点:
- 降低单库压力
- 业务隔离,互不影响
- 微服务友好
水平分表
单表(1亿数据):
┌──────────────────────┐
│ 订单表 (1亿行) │
│ ┌────────────────┐ │
│ │ order_id │ │
│ │ user_id │ │
│ │ product_id │ │
│ │ ... │ │
│ └────────────────┘ │
└──────────────────────┘
问题:
- 单表太大,查询慢
- 索引失效
- 磁盘IO高
↓ 按用户ID hash分表 ↓
水平分表(分成16张表):
┌─────────┐ ┌─────────┐ ┌──────────┐
│ order_0 │ │ order_1 │ ... │ order_15 │
│(625万行)│ │(625万行)│ │(625万行) │
└─────────┘ └─────────┘ └──────────┘
分表规则:
tableIndex = user_id % 16
优点:
- 单表数据量减少
- 查询性能提升
- 可扩展性好
ShardingSphere实现分库分表:
java
// 1. 配置分片规则
@Configuration
public class ShardingConfig {
@Bean
public DataSource dataSource() throws SQLException {
// 配置真实数据源
Map<String, DataSource> dataSourceMap = new HashMap<>();
dataSourceMap.put("ds0", createDataSource("jdbc:mysql://localhost:3306/db0"));
dataSourceMap.put("ds1", createDataSource("jdbc:mysql://localhost:3306/db1"));
// 配置分片规则
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration());
shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(
new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}")
);
return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, new Properties());
}
private TableRuleConfiguration getOrderTableRuleConfiguration() {
TableRuleConfiguration result = new TableRuleConfiguration("t_order", "ds${0..1}.t_order_${0..15}");
// 分库策略:按user_id % 2
result.setDatabaseShardingStrategyConfig(
new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}")
);
// 分表策略:按user_id % 16
result.setTableShardingStrategyConfig(
new InlineShardingStrategyConfiguration("user_id", "t_order_${user_id % 16}")
);
return result;
}
}
// 2. 使用示例
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
// 插入订单(自动路由到对应分片)
public void createOrder(Order order) {
// user_id = 1001
// 分库:ds${1001 % 2} = ds1
// 分表:t_order_${1001 % 16} = t_order_9
// 实际插入到:ds1.t_order_9
orderMapper.insert(order);
}
// 查询订单(带分片键,直接路由)
public Order getOrder(Long userId, Long orderId) {
return orderMapper.selectByUserIdAndOrderId(userId, orderId);
}
// 查询订单(不带分片键,全表扫描)⚠️ 性能差
public Order getOrderById(Long orderId) {
// 会查询所有分片,性能很差
return orderMapper.selectById(orderId);
}
}
分片键选择原则:
1. 查询频繁的字段(避免跨分片查询)
✅ user_id(用户查询自己的订单)
❌ order_status(需要查询所有分片)
2. 数据分布均匀的字段
✅ user_id % 16(哈希均匀)
❌ create_time(时间可能集中)
3. 不经常变更的字段
✅ user_id(不会变)
❌ order_status(经常变化)
6.3 数据库连接池优化
java
// HikariCP 配置(性能最优的连接池)
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.hikari")
public HikariDataSource dataSource() {
HikariConfig config = new HikariConfig();
// 核心配置
config.setJdbcUrl("jdbc:mysql://localhost:3306/db");
config.setUsername("root");
config.setPassword("password");
// 连接池大小(核心参数)⭐
config.setMaximumPoolSize(20); // 最大连接数
config.setMinimumIdle(10); // 最小空闲连接数
// 连接超时配置
config.setConnectionTimeout(30000); // 获取连接超时时间(30秒)
config.setIdleTimeout(600000); // 空闲连接超时时间(10分钟)
config.setMaxLifetime(1800000); // 连接最大存活时间(30分钟)
// 性能优化
config.setAutoCommit(true); // 自动提交
config.setConnectionTestQuery("SELECT 1"); // 连接测试查询
// 泄漏检测
config.setLeakDetectionThreshold(60000); // 连接泄漏检测(60秒)
return new HikariDataSource(config);
}
}
连接池大小如何计算?
公式:
connections = ((core_count * 2) + effective_spindle_count)
core_count: CPU核心数
effective_spindle_count: 磁盘数量
示例:
4核CPU + 1块磁盘 = (4 * 2) + 1 = 9
建议配置:最大连接数 10-20
实际调优:
1. 压测观察
2. 监控连接使用情况
3. 根据业务特点调整
- IO密集型:可以适当增加
- CPU密集型:不宜过大
7. 消息队列削峰填谷
7.1 消息队列核心作用
场景1: 秒杀场景(削峰)
┌─────────────────────────────────────────┐
│ 瞬时请求:10万QPS │
└────────────────┬────────────────────────┘
│
┌───────▼────────┐
│ 消息队列 │ ← 缓冲
│ (RabbitMQ) │
└───────┬────────┘
│
┌───────▼────────┐
│ 订单服务 │ ← 平稳处理:1000QPS
│ (消费者) │
└────────────────┘
优点:
- 保护下游系统
- 提升用户体验(快速返回)
- 系统稳定性高
场景2: 异步处理(解耦)
用户下单 ─┐
├─→ 消息队列 ─┬─→ 订单服务
│ ├─→ 库存服务
│ ├─→ 积分服务
│ └─→ 短信服务
└─ 立即返回(200ms)
同步处理:800ms(用户等待)
异步处理:200ms(用户体验好)
7.2 RabbitMQ实战
java
// 1. 生产者(订单服务)
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private OrderMapper orderMapper;
public String createOrder(Order order) {
// 1. 创建订单(快速返回)
orderMapper.insert(order);
// 2. 发送消息到队列(异步处理)
OrderMessage message = new OrderMessage();
message.setOrderId(order.getId());
message.setUserId(order.getUserId());
rabbitTemplate.convertAndSend("order.exchange", "order.create", message);
// 3. 立即返回
return "订单创建成功,正在处理中...";
}
}
// 2. 消费者(库存服务)
@Component
public class StockConsumer {
@Autowired
private StockService stockService;
@RabbitListener(queues = "stock.queue")
public void handleStockDeduction(OrderMessage message) {
try {
// 扣减库存
stockService.deductStock(message.getOrderId());
} catch (Exception e) {
// 处理失败,进入重试队列
throw new AmqpRejectAndDontRequeueException("库存扣减失败", e);
}
}
}
// 3. 配置死信队列(处理失败消息)
@Configuration
public class RabbitMQConfig {
// 业务队列
@Bean
public Queue stockQueue() {
return QueueBuilder.durable("stock.queue")
.deadLetterExchange("dlx.exchange") // 死信交换机
.deadLetterRoutingKey("dlx.stock") // 死信路由键
.ttl(60000) // 消息过期时间60秒
.build();
}
// 死信队列
@Bean
public Queue deadLetterQueue() {
return new Queue("dlx.stock.queue", true);
}
@Bean
public DirectExchange deadLetterExchange() {
return new DirectExchange("dlx.exchange");
}
@Bean
public Binding deadLetterBinding() {
return BindingBuilder
.bind(deadLetterQueue())
.to(deadLetterExchange())
.with("dlx.stock");
}
}
// 4. 死信队列消费者(人工处理)
@Component
public class DeadLetterConsumer {
@RabbitListener(queues = "dlx.stock.queue")
public void handleDeadLetter(OrderMessage message) {
// 记录日志,人工介入处理
log.error("订单{}库存扣减失败,进入死信队列", message.getOrderId());
// 发送告警
alertService.sendAlert("库存扣减失败", message);
}
}
7.3 消息可靠性保证
java
// 1. 生产者确认(确保消息发送成功)
@Configuration
public class RabbitMQConfig {
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
// 开启发送确认
template.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.info("消息发送成功");
} else {
log.error("消息发送失败:{}", cause);
// 重试或记录数据库
}
});
// 开启返回确认(消息未路由到队列)
template.setReturnsCallback(returned -> {
log.error("消息未路由到队列:{}", returned.getMessage());
});
return template;
}
}
// 2. 消费者手动ACK(确保消息处理成功)
@Component
public class OrderConsumer {
@RabbitListener(queues = "order.queue", ackMode = "MANUAL")
public void handleOrder(OrderMessage message, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) {
try {
// 处理业务
processOrder(message);
// 手动ACK(确认消息)
channel.basicAck(tag, false);
} catch (Exception e) {
try {
// 处理失败,NACK(重新入队)
channel.basicNack(tag, false, true);
} catch (IOException ex) {
log.error("NACK失败", ex);
}
}
}
}
// 3. 消息持久化(防止MQ宕机丢失消息)
@Bean
public Queue orderQueue() {
return QueueBuilder
.durable("order.queue") // 队列持久化
.build();
}
@Bean
public DirectExchange orderExchange() {
return new DirectExchange("order.exchange", true, false); // 交换机持久化
}
// 发送消息时设置持久化
rabbitTemplate.convertAndSend("order.exchange", "order.create", message, msg -> {
msg.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 消息持久化
return msg;
});
消息可靠性保证机制:
完整链路:
生产者 ────────────> RabbitMQ ────────────> 消费者
│ │ │
├─ 1. 确认机制 ├─ 2. 持久化 ├─ 3. 手动ACK
│ (ConfirmCallback)│ (Durable) │ (basicAck)
│ │ │
└─ 4. 本地消息表 └─ 5. 集群 └─ 6. 幂等性
(防止丢失) (高可用) (防止重复)
消息丢失的3个环节:
1. 生产者 → MQ:网络故障 → 解决方案:发送确认
2. MQ存储:MQ宕机 → 解决方案:持久化 + 集群
3. MQ → 消费者:消费失败 → 解决方案:手动ACK + 重试
8. 限流降级熔断机制
8.1 限流算法实现
固定窗口计数器
java
public class FixedWindowRateLimiter {
private final int maxRequests; // 最大请求数
private final long windowSize; // 窗口大小(毫秒)
private AtomicInteger counter = new AtomicInteger(0);
private long windowStart;
public FixedWindowRateLimiter(int maxRequests, long windowSize) {
this.maxRequests = maxRequests;
this.windowSize = windowSize;
this.windowStart = System.currentTimeMillis();
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 判断是否进入新窗口
if (now - windowStart >= windowSize) {
counter.set(0);
windowStart = now;
}
// 判断是否超过限制
if (counter.get() < maxRequests) {
counter.incrementAndGet();
return true;
}
return false;
}
}
// 使用示例:每秒最多100个请求
FixedWindowRateLimiter limiter = new FixedWindowRateLimiter(100, 1000);
if (limiter.tryAcquire()) {
// 处理请求
} else {
// 限流,返回错误
return "系统繁忙,请稍后再试";
}
固定窗口问题:
窗口边界流量突刺问题:
第1秒: 00:00 ─────────────────────> 00:01
请求: 0个 100个 (允许)
第2秒: 00:01 ─────────────────────> 00:02
请求: 100个 (允许) 0个
在00:01前后1秒内,实际处理了200个请求(超过限制)❌
滑动窗口计数器
java
public class SlidingWindowRateLimiter {
private final int maxRequests;
private final long windowSize;
private final int subWindowCount; // 子窗口数量
private final AtomicInteger[] subWindowCounters;
private long windowStart;
public SlidingWindowRateLimiter(int maxRequests, long windowSize, int subWindowCount) {
this.maxRequests = maxRequests;
this.windowSize = windowSize;
this.subWindowCount = subWindowCount;
this.subWindowCounters = new AtomicInteger[subWindowCount];
for (int i = 0; i < subWindowCount; i++) {
subWindowCounters[i] = new AtomicInteger(0);
}
this.windowStart = System.currentTimeMillis();
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
long subWindowSize = windowSize / subWindowCount;
// 计算当前子窗口索引
int currentIndex = (int) ((now / subWindowSize) % subWindowCount);
// 计算过期的子窗口并清零
long elapsedSubWindows = (now - windowStart) / subWindowSize;
if (elapsedSubWindows > 0) {
for (int i = 0; i < Math.min(elapsedSubWindows, subWindowCount); i++) {
int expiredIndex = (int) (((windowStart / subWindowSize) + i) % subWindowCount);
subWindowCounters[expiredIndex].set(0);
}
windowStart = (now / subWindowSize) * subWindowSize;
}
// 计算总请求数
int totalCount = Arrays.stream(subWindowCounters)
.mapToInt(AtomicInteger::get)
.sum();
if (totalCount < maxRequests) {
subWindowCounters[currentIndex].incrementAndGet();
return true;
}
return false;
}
}
滑动窗口原理:
将1秒分为10个100ms的子窗口:
|100ms|100ms|100ms|100ms|100ms|100ms|100ms|100ms|100ms|100ms|
| 10 | 10 | 10 | 10 | 10 | 10 | 10 | 10 | 10 | 10 | = 100个请求
时间推移,窗口滑动:
↓ 当前时间
|100ms|100ms|100ms|100ms|100ms|100ms|100ms|100ms|100ms|100ms|
过期 过期 ← 统计这个窗口内的请求 →
优点:平滑限流,没有突刺问题
令牌桶算法(Token Bucket)⭐ 推荐
java
public class TokenBucketRateLimiter {
private final long capacity; // 桶容量
private final long refillRate; // 令牌生成速率(每秒)
private AtomicLong tokens; // 当前令牌数
private long lastRefillTime; // 上次补充令牌时间
public TokenBucketRateLimiter(long capacity, long refillRate) {
this.capacity = capacity;
this.refillRate = refillRate;
this.tokens = new AtomicLong(capacity);
this.lastRefillTime = System.currentTimeMillis();
}
public synchronized boolean tryAcquire(long tokensNeeded) {
// 1. 补充令牌
refillTokens();
// 2. 尝试获取令牌
if (tokens.get() >= tokensNeeded) {
tokens.addAndGet(-tokensNeeded);
return true;
}
return false;
}
private void refillTokens() {
long now = System.currentTimeMillis();
long elapsedTime = now - lastRefillTime;
// 计算应该补充的令牌数
long tokensToAdd = (elapsedTime * refillRate) / 1000;
if (tokensToAdd > 0) {
long newTokens = Math.min(capacity, tokens.get() + tokensToAdd);
tokens.set(newTokens);
lastRefillTime = now;
}
}
}
// 使用示例:桶容量100,每秒生成10个令牌
TokenBucketRateLimiter limiter = new TokenBucketRateLimiter(100, 10);
if (limiter.tryAcquire(1)) {
// 处理请求
} else {
// 限流
return "系统繁忙";
}
令牌桶原理:
令牌桶模型:
┌──────────────────────────┐
│ 令牌桶 (容量100) │
│ ┌────────────────────┐ │
│ │ ●●●●●●●●●●●●●●●●●● │ │ ← 令牌
│ └────────────────────┘ │
└────────▲──────────┬──────┘
│ │
每秒生成10个 │ 请求消费令牌
令牌(固定速率) │
│
┌──────▼──────┐
│ 请求处理 │
└─────────────┘
优点:
1. 允许突发流量(桶内有预存的令牌)
2. 平均速率限制(令牌生成速率固定)
3. 实现简单
Guava RateLimiter(生产级实现)
java
@Service
public class OrderService {
// 每秒允许10个请求
private final RateLimiter rateLimiter = RateLimiter.create(10.0);
public String createOrder(Order order) {
// 尝试获取令牌(最多等待1秒)
if (rateLimiter.tryAcquire(1, TimeUnit.SECONDS)) {
// 处理订单
return processOrder(order);
} else {
// 限流
return "系统繁忙,请稍后再试";
}
}
// 平滑预热限流(适用于系统启动场景)
private final RateLimiter warmupLimiter =
RateLimiter.create(100, 10, TimeUnit.SECONDS); // 10秒预热到100QPS
}
Redis实现分布式限流
java
@Component
public class RedisRateLimiter {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 基于Redis的滑动窗口限流
* @param key 限流键
* @param maxRequests 最大请求数
* @param windowSize 窗口大小(秒)
*/
public boolean tryAcquire(String key, int maxRequests, int windowSize) {
long now = System.currentTimeMillis();
long windowStart = now - windowSize * 1000;
String redisKey = "rate_limit:" + key;
// Lua脚本保证原子性
String luaScript =
"redis.call('zremrangebyscore', KEYS[1], 0, ARGV[1]) " + // 删除过期记录
"local count = redis.call('zcard', KEYS[1]) " + // 获取当前数量
"if count < tonumber(ARGV[2]) then " + // 未超过限制
" redis.call('zadd', KEYS[1], ARGV[3], ARGV[3]) " + // 添加当前请求
" redis.call('expire', KEYS[1], ARGV[4]) " + // 设置过期时间
" return 1 " +
"else " +
" return 0 " +
"end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
Long result = redisTemplate.execute(
redisScript,
Collections.singletonList(redisKey),
windowStart, maxRequests, now, windowSize
);
return result != null && result == 1;
}
}
// 使用示例
@RestController
public class OrderController {
@Autowired
private RedisRateLimiter rateLimiter;
@PostMapping("/order")
public String createOrder(@RequestBody Order order, HttpServletRequest request) {
String userId = request.getHeader("userId");
// 每个用户每秒最多10个请求
if (!rateLimiter.tryAcquire("user:" + userId, 10, 1)) {
return "请求过于频繁,请稍后再试";
}
// 处理订单
return orderService.createOrder(order);
}
}
8.2 服务降级
java
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
@Autowired
private RecommendService recommendService;
// 降级开关(可通过配置中心动态修改)
@Value("${service.degradation.recommend:false}")
private boolean recommendDegraded;
public ProductDetailVO getProductDetail(Long productId) {
ProductDetailVO detail = new ProductDetailVO();
// 1. 核心功能:商品基本信息(不降级)
Product product = productMapper.selectById(productId);
detail.setProduct(product);
// 2. 次要功能:推荐商品(可降级)
if (!recommendDegraded) {
try {
List<Product> recommendations = recommendService.getRecommendations(productId);
detail.setRecommendations(recommendations);
} catch (Exception e) {
log.error("获取推荐商品失败,使用降级方案", e);
detail.setRecommendations(getDefaultRecommendations()); // 降级:返回默认推荐
}
} else {
// 降级:直接返回默认推荐
detail.setRecommendations(getDefaultRecommendations());
}
// 3. 次要功能:用户评论(可降级)
try {
List<Comment> comments = getComments(productId);
detail.setComments(comments);
} catch (Exception e) {
detail.setComments(Collections.emptyList()); // 降级:返回空列表
}
return detail;
}
private List<Product> getDefaultRecommendations() {
// 返回热门商品或缓存的推荐结果
return redisTemplate.opsForList().range("hot:products", 0, 9);
}
}
降级策略分级:
服务分级(金字塔模型):
┌─────────────────┐
│ L0: 核心服务 │ ← 永不降级(登录、下单、支付)
├─────────────────┤
│ L1: 重要服务 │ ← 高峰时降级(搜索、详情页)
├─────────────────┤
│ L2: 一般服务 │ ← 优先降级(推荐、评论)
├─────────────────┤
│ L3: 边缘服务 │ ← 立即降级(广告、统计)
└─────────────────┘
降级触发条件:
1. 错误率 > 50%
2. 响应时间 > 1秒
3. 系统负载 > 80%
4. 人工触发(大促活动)
8.3 熔断机制(Sentinel)
java
// 1. 添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
// 2. 配置熔断规则
@Configuration
public class SentinelConfig {
@PostConstruct
public void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<>();
// 慢调用比例熔断
DegradeRule slowCallRule = new DegradeRule("getProduct")
.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
.setCount(0.5) // 慢调用比例阈值 50%
.setTimeWindow(10) // 熔断时长 10秒
.setSlowRatioThreshold(0.5) // 慢调用比例
.setMinRequestAmount(10) // 最小请求数
.setStatIntervalMs(1000); // 统计时长 1秒
// 异常比例熔断
DegradeRule errorRateRule = new DegradeRule("createOrder")
.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType())
.setCount(0.5) // 异常比例阈值 50%
.setTimeWindow(10) // 熔断时长 10秒
.setMinRequestAmount(10); // 最小请求数
rules.add(slowCallRule);
rules.add(errorRateRule);
DegradeRuleManager.loadRules(rules);
}
}
// 3. 使用熔断
@Service
public class ProductService {
@SentinelResource(
value = "getProduct",
blockHandler = "getProductBlockHandler", // 熔断降级方法
fallback = "getProductFallback" // 异常降级方法
)
public Product getProduct(Long productId) {
// 调用远程服务
return remoteProductService.getProduct(productId);
}
// 熔断降级方法
public Product getProductBlockHandler(Long productId, BlockException ex) {
log.warn("服务被熔断,productId={}", productId);
// 返回降级数据
return getCachedProduct(productId);
}
// 异常降级方法
public Product getProductFallback(Long productId, Throwable ex) {
log.error("服务调用异常,productId={}", productId, ex);
// 返回默认数据
return getDefaultProduct();
}
}
熔断状态机:
熔断器三种状态:
1. 关闭状态 (Closed)
┌─────────────┐
│ 正常请求 │
│ 错误率 < 50% │
└──────┬──────┘
│ 错误率 >= 50%
↓
2. 打开状态 (Open)
┌─────────────┐
│ 快速失败 │ ← 所有请求直接降级,不调用服务
│ (熔断10秒) │
└──────┬──────┘
│ 10秒后
↓
3. 半开状态 (Half-Open)
┌─────────────┐
│ 尝试恢复 │ ← 允许少量请求通过
│ 放行10%请求 │
└──────┬──────┘
│
成功 ↓ ↓ 失败
┌─────┴─┐ ┌──┴─────┐
│ 关闭 │ │ 打开 │
└───────┘ └────────┘
9. 负载均衡深度解析
9.1 Nginx负载均衡配置
nginx
# nginx.conf
http {
# 定义上游服务器组
upstream backend_servers {
# 负载均衡算法选择
# 1. 轮询(默认)
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
# 2. 加权轮询
# server 192.168.1.10:8080 weight=5; # 高性能服务器
# server 192.168.1.11:8080 weight=3;
# server 192.168.1.12:8080 weight=2; # 低性能服务器
# 3. IP Hash(同一IP固定路由到同一服务器)
# ip_hash;
# 4. 最少连接
# least_conn;
# 健康检查
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
# 备用服务器(所有服务器都挂了才使用)
server 192.168.1.99:8080 backup;
# Keepalive连接池(提升性能)
keepalive 32;
}
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://backend_servers;
# 传递真实IP
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
# 超时配置
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Keepalive
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
}
9.2 四层负载均衡 vs 七层负载均衡
四层负载均衡(传输层):
┌─────────────────────────────────────┐
│ LVS / F5 │
│ 根据 IP + Port 转发 │
└────────────┬────────────────────────┘
│
┌────────┼────────┐
┌───▼──┐ ┌───▼──┐ ┌───▼──┐
│服务器1│ │服务器2│ │服务器3│
└──────┘ └──────┘ └──────┘
优点:
- 性能极高(不解析HTTP)
- 支持任何协议(TCP/UDP)
缺点:
- 功能单一(无法根据URL路由)
七层负载均衡(应用层):
┌─────────────────────────────────────┐
│ Nginx / HAProxy │
│ 根据 URL / Cookie / Header 转发 │
└────────────┬────────────────────────┘
│
┌────────┼────────┐
┌───▼──┐ ┌───▼──┐ ┌───▼──┐
│订单服务│ │用户服务│ │商品服务│
└──────┘ └──────┘ └──────┘
优点:
- 功能丰富(URL路由、会话保持)
- 可以缓存、压缩、SSL卸载
缺点:
- 性能略低(需要解析HTTP)
10. 代码层面的优化
10.1 避免大事务
java
// ❌ 错误:大事务
@Transactional
public void processOrder(Order order) {
// 1. 创建订单(需要事务)
orderMapper.insert(order);
// 2. 扣减库存(需要事务)
stockMapper.deduct(order.getProductId(), order.getQuantity());
// 3. 发送短信(不需要事务)⚠️ 耗时操作
smsService.sendOrderNotification(order.getUserId());
// 4. 发送邮件(不需要事务)⚠️ 耗时操作
emailService.sendOrderEmail(order.getUserId());
// 5. 增加积分(需要事务)
pointMapper.addPoints(order.getUserId(), order.getAmount());
}
// 问题:事务时间太长,数据库连接被长时间占用
// ✅ 正确:拆分事务 + 异步处理
@Transactional
public void processOrder(Order order) {
// 1. 核心事务:创建订单 + 扣库存
orderMapper.insert(order);
stockMapper.deduct(order.getProductId(), order.getQuantity());
}
@Async
public void processOrderAsync(Order order) {
// 2. 异步处理:发送通知(不需要事务)
smsService.sendOrderNotification(order.getUserId());
emailService.sendOrderEmail(order.getUserId());
}
@Transactional
public void addPoints(Long userId, BigDecimal amount) {
// 3. 独立事务:增加积分
pointMapper.addPoints(userId, amount);
}
10.2 批量操作优化
java
// ❌ 错误:循环插入
public void importProducts(List<Product> products) {
for (Product product : products) {
productMapper.insert(product); // 1000次SQL
}
}
// 耗时:1000次 × 10ms = 10秒
// ✅ 正确:批量插入
public void importProducts(List<Product> products) {
// MyBatis批量插入
productMapper.batchInsert(products); // 1次SQL
}
// 耗时:100ms
// Mapper XML
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO product (id, name, price, stock)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.id}, #{item.name}, #{item.price}, #{item.stock})
</foreach>
</insert>
// 大数据量分批插入(避免SQL太长)
public void importProductsBatch(List<Product> products) {
int batchSize = 500;
for (int i = 0; i < products.size(); i += batchSize) {
int end = Math.min(i + batchSize, products.size());
List<Product> batch = products.subList(i, end);
productMapper.batchInsert(batch);
}
}
10.3 N+1查询问题
java
// ❌ 错误:N+1查询
public List<OrderVO> getOrders(Long userId) {
// 1次查询:查询订单列表
List<Order> orders = orderMapper.selectByUserId(userId);
List<OrderVO> result = new ArrayList<>();
for (Order order : orders) {
OrderVO vo = new OrderVO();
vo.setOrder(order);
// N次查询:查询每个订单的商品信息
Product product = productMapper.selectById(order.getProductId());
vo.setProduct(product);
result.add(vo);
}
return result; // 总共 1 + N 次查询
}
// ✅ 正确:一次查询
public List<OrderVO> getOrders(Long userId) {
// 1次联表查询
return orderMapper.selectOrdersWithProduct(userId);
}
// Mapper XML
<select id="selectOrdersWithProduct" resultMap="OrderVOMap">
SELECT
o.*,
p.id as product_id,
p.name as product_name,
p.price as product_price
FROM
`order` o
LEFT JOIN
product p ON o.product_id = p.id
WHERE
o.user_id = #{userId}
</select>
10.4 索引优化
sql
-- 1. 创建合适的索引
-- ❌ 错误:没有索引
SELECT * FROM order WHERE user_id = 1001 AND status = 'PAID';
-- 全表扫描,耗时 1000ms
-- ✅ 正确:创建联合索引
CREATE INDEX idx_user_status ON `order`(user_id, status);
-- 索引扫描,耗时 10ms
-- 2. 联合索引顺序(最左前缀原则)
CREATE INDEX idx_user_status_time ON `order`(user_id, status, create_time);
-- ✅ 能使用索引
SELECT * FROM order WHERE user_id = 1001;
SELECT * FROM order WHERE user_id = 1001 AND status = 'PAID';
SELECT * FROM order WHERE user_id = 1001 AND status = 'PAID' AND create_time > '2024-01-01';
-- ❌ 不能使用索引
SELECT * FROM order WHERE status = 'PAID'; -- 没有user_id
SELECT * FROM order WHERE create_time > '2024-01-01'; -- 没有user_id和status
-- 3. 覆盖索引(避免回表)
-- ❌ 需要回表
SELECT id, user_id, status, amount FROM order WHERE user_id = 1001;
-- 索引:idx_user_status(user_id, status)
-- 需要回表查询amount字段
-- ✅ 覆盖索引,不需要回表
CREATE INDEX idx_user_status_amount ON `order`(user_id, status, amount);
SELECT id, user_id, status, amount FROM order WHERE user_id = 1001;
-- 所有字段都在索引中,不需要回表
-- 4. 避免索引失效
-- ❌ 索引失效
SELECT * FROM order WHERE YEAR(create_time) = 2024; -- 函数导致失效
SELECT * FROM order WHERE user_id + 1 = 1002; -- 运算导致失效
SELECT * FROM order WHERE name LIKE '%手机%'; -- 前缀模糊查询失效
-- ✅ 正确使用索引
SELECT * FROM order WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';
SELECT * FROM order WHERE user_id = 1001;
SELECT * FROM order WHERE name LIKE '手机%'; -- 后缀模糊查询可以使用索引
10.5 线程池配置
java
@Configuration
public class ThreadPoolConfig {
@Bean("asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数 = CPU核心数 + 1
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() + 1);
// 最大线程数 = CPU核心数 * 2
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
// 队列容量
executor.setQueueCapacity(500);
// 线程空闲时间(60秒)
executor.setKeepAliveSeconds(60);
// 线程名称前缀
executor.setThreadNamePrefix("async-");
// 拒绝策略:CallerRunsPolicy(调用者线程执行)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待任务完成后关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
}
// 使用示例
@Service
public class OrderService {
@Async("asyncExecutor")
public void sendNotification(Order order) {
// 异步发送通知
smsService.send(order.getUserId(), "订单创建成功");
}
}
线程池参数如何配置?
1. CPU密集型任务:
核心线程数 = CPU核心数 + 1
最大线程数 = CPU核心数 * 2
示例:图片处理、视频编码
2. IO密集型任务:
核心线程数 = CPU核心数 * 2
最大线程数 = CPU核心数 * 4
示例:数据库查询、HTTP请求
3. 混合型任务:
核心线程数 = CPU核心数 + 1
最大线程数 = CPU核心数 * 2
队列容量 = 根据业务调整
11. 完整案例:电商秒杀系统
11.1 系统架构
秒杀系统架构(高并发场景):
┌──────────────┐
│ 用户浏览器 │
└──────┬───────┘
│
┌──────▼───────┐
│ CDN静态化 │ ← 秒杀页面静态化
└──────┬───────┘
│
┌──────▼───────┐
│ Nginx限流 │ ← 单机10万QPS → 只放行1万QPS
└──────┬───────┘
│
┌──────▼───────┐
│ 网关限流 │ ← 用户级限流(每用户5次/秒)
└──────┬───────┘
│
┌────────────────┼────────────────┐
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│秒杀服务1 │ │秒杀服务2 │ │秒杀服务3 │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
└───────────────┼───────────────┘
│
┌──────▼──────┐
│ Redis预减库存│ ← 内存级别,QPS百万级
└──────┬──────┘
│
┌──────▼──────┐
│ 消息队列 │ ← 异步处理订单
└──────┬──────┘
│
┌──────▼──────┐
│ 订单服务 │ ← 慢慢消费(1000QPS)
└──────┬──────┘
│
┌──────▼──────┐
│ MySQL │ ← 最终持久化
└──────────────┘
11.2 核心代码实现
秒杀接口
java
@RestController
@RequestMapping("/seckill")
public class SeckillController {
@Autowired
private SeckillService seckillService;
@Autowired
private RedisRateLimiter rateLimiter;
/**
* 秒杀接口
*/
@PostMapping("/{productId}")
public Result<String> seckill(
@PathVariable Long productId,
@RequestHeader("userId") Long userId) {
// 1. 用户级限流(每用户每秒5次)
String rateLimitKey = "seckill:limit:user:" + userId;
if (!rateLimiter.tryAcquire(rateLimitKey, 5, 1)) {
return Result.fail("请求过于频繁,请稍后再试");
}
// 2. 参数校验
if (productId == null || userId == null) {
return Result.fail("参数错误");
}
// 3. 执行秒杀
try {
String orderId = seckillService.doSeckill(productId, userId);
return Result.success(orderId);
} catch (SeckillException e) {
return Result.fail(e.getMessage());
}
}
}
秒杀业务逻辑
java
@Service
public class SeckillService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private RedissonClient redissonClient;
private static final String STOCK_KEY_PREFIX = "seckill:stock:";
private static final String USER_KEY_PREFIX = "seckill:user:";
/**
* 秒杀主流程
*/
public String doSeckill(Long productId, Long userId) {
String stockKey = STOCK_KEY_PREFIX + productId;
String userKey = USER_KEY_PREFIX + productId + ":" + userId;
// 1. 判断用户是否已经秒杀过(防止重复秒杀)
Boolean hasOrdered = redisTemplate.hasKey(userKey);
if (Boolean.TRUE.equals(hasOrdered)) {
throw new SeckillException("您已经参与过该商品的秒杀");
}
// 2. Redis预减库存(Lua脚本保证原子性)
Long stock = decrStock(stockKey);
if (stock < 0) {
// 库存不足
throw new SeckillException("商品已售罄");
}
// 3. 记录用户已秒杀(防止重复)
redisTemplate.opsForValue().set(userKey, "1", 24, TimeUnit.HOURS);
// 4. 生成订单ID
String orderId = generateOrderId();
// 5. 发送消息到队列(异步创建订单)
SeckillMessage message = new SeckillMessage();
message.setOrderId(orderId);
message.setProductId(productId);
message.setUserId(userId);
rabbitTemplate.convertAndSend("seckill.exchange", "seckill.order", message);
// 6. 立即返回(不等待订单创建完成)
return orderId;
}
/**
* Redis预减库存(Lua脚本保证原子性)
*/
private Long decrStock(String stockKey) {
String luaScript =
"local stock = redis.call('get', KEYS[1]) " +
"if not stock then " +
" return -1 " +
"end " +
"if tonumber(stock) > 0 then " +
" return redis.call('decr', KEYS[1]) " +
"else " +
" return -1 " +
"end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
return redisTemplate.execute(redisScript, Collections.singletonList(stockKey));
}
private String generateOrderId() {
// 雪花算法生成订单ID
return String.valueOf(SnowflakeIdWorker.generateId());
}
/**
* 初始化库存到Redis(秒杀开始前调用)
*/
public void initStock(Long productId, Integer stock) {
String stockKey = STOCK_KEY_PREFIX + productId;
redisTemplate.opsForValue().set(stockKey, stock);
}
}
订单消费者
java
@Component
public class SeckillOrderConsumer {
@Autowired
private OrderService orderService;
@Autowired
private StockService stockService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@RabbitListener(queues = "seckill.order.queue")
public void handleSeckillOrder(SeckillMessage message) {
try {
// 1. 创建订单
Order order = new Order();
order.setId(message.getOrderId());
order.setUserId(message.getUserId());
order.setProductId(message.getProductId());
order.setStatus("UNPAID");
orderService.createOrder(order);
// 2. 扣减数据库库存(最终一致性)
stockService.deductStock(message.getProductId(), 1);
log.info("秒杀订单创建成功:{}", message.getOrderId());
} catch (Exception e) {
log.error("秒杀订单创建失败:{}", message, e);
// 3. 失败则恢复Redis库存
String stockKey = "seckill:stock:" + message.getProductId();
redisTemplate.opsForValue().increment(stockKey);
// 4. 删除用户秒杀记录
String userKey = "seckill:user:" + message.getProductId() + ":" + message.getUserId();
redisTemplate.delete(userKey);
}
}
}
数据库库存扣减(防止超卖)
java
@Service
public class StockService {
@Autowired
private StockMapper stockMapper;
/**
* 扣减库存(防止超卖)
*/
@Transactional
public void deductStock(Long productId, Integer quantity) {
// 使用乐观锁防止超卖
int rows = stockMapper.deductStockWithOptimisticLock(productId, quantity);
if (rows == 0) {
throw new StockException("库存不足");
}
}
}
// Mapper
@Mapper
public interface StockMapper {
/**
* 乐观锁扣减库存(防止超卖)
*/
@Update("UPDATE stock SET stock = stock - #{quantity}, version = version + 1 " +
"WHERE product_id = #{productId} AND stock >= #{quantity} AND version = #{version}")
int deductStockWithOptimisticLock(
@Param("productId") Long productId,
@Param("quantity") Integer quantity);
/**
* 悲观锁扣减库存(性能较差,但绝对安全)
*/
@Select("SELECT * FROM stock WHERE product_id = #{productId} FOR UPDATE")
Stock selectForUpdate(@Param("productId") Long productId);
}
11.3 秒杀优化技巧总结
秒杀优化技巧(10万QPS → 100QPS):
1. 前端优化:
✅ 静态页面(CDN缓存)
✅ 按钮防重复点击
✅ 验证码(防止机器人)
2. 网关层优化:
✅ Nginx限流(10万 → 1万)
✅ 网关限流(用户级)
3. 服务层优化:
✅ Redis预减库存(QPS百万级)
✅ 布隆过滤器(防止缓存穿透)
✅ 分布式锁(防止超卖)
4. 异步处理:
✅ 消息队列(削峰填谷,1万 → 1000)
✅ 订单异步创建
5. 数据库优化:
✅ 乐观锁(防止超卖)
✅ 索引优化
✅ 读写分离
最终效果:
- 前端QPS:100万(静态页面,CDN承载)
- 后端QPS:1万(限流后)
- Redis QPS:1万(预减库存)
- 消息队列:1万入队,1000出队(削峰)
- 数据库QPS:1000(异步处理)
12. 性能指标与监控
12.1 关键性能指标
java
@Component
public class PerformanceMonitor {
private final MeterRegistry meterRegistry;
public PerformanceMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
/**
* 记录请求耗时
*/
public void recordRequestTime(String api, long duration) {
Timer.builder("http.request.duration")
.tag("api", api)
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);
}
/**
* 记录QPS
*/
public void recordQPS(String api) {
Counter.builder("http.request.count")
.tag("api", api)
.register(meterRegistry)
.increment();
}
/**
* 记录错误率
*/
public void recordError(String api, String errorType) {
Counter.builder("http.request.error")
.tag("api", api)
.tag("error_type", errorType)
.register(meterRegistry)
.increment();
}
}
// 使用示例
@RestController
public class OrderController {
@Autowired
private PerformanceMonitor monitor;
@PostMapping("/order")
public Result createOrder(@RequestBody Order order) {
long startTime = System.currentTimeMillis();
try {
// 处理业务
orderService.createOrder(order);
// 记录QPS
monitor.recordQPS("/order");
return Result.success();
} catch (Exception e) {
// 记录错误
monitor.recordError("/order", e.getClass().getSimpleName());
throw e;
} finally {
// 记录耗时
long duration = System.currentTimeMillis() - startTime;
monitor.recordRequestTime("/order", duration);
}
}
}
12.2 监控大盘
Grafana监控大盘:
┌─────────────────────────────────────────────┐
│ 系统监控大盘 │
├─────────────────────────────────────────────┤
│ QPS: 15000 ↑ │
│ ├─ 秒杀接口: 12000 │
│ ├─ 订单接口: 2000 │
│ └─ 商品接口: 1000 │
├─────────────────────────────────────────────┤
│ 响应时间(P99): 150ms ✓ │
│ ├─ 秒杀接口: 100ms │
│ ├─ 订单接口: 200ms │
│ └─ 商品接口: 120ms │
├─────────────────────────────────────────────┤
│ 错误率: 0.5% ✓ │
│ ├─ 超时错误: 0.3% │
│ └─ 业务错误: 0.2% │
├─────────────────────────────────────────────┤
│ 系统资源: │
│ ├─ CPU使用率: 65% ✓ │
│ ├─ 内存使用率: 70% ✓ │
│ ├─ 磁盘IO: 45% ✓ │
│ └─ 网络带宽: 500Mbps / 1Gbps ✓ │
├─────────────────────────────────────────────┤
│ 缓存命中率: │
│ ├─ Redis: 95% ✓ │
│ ├─ 本地缓存: 80% ✓ │
│ └─ CDN: 90% ✓ │
├─────────────────────────────────────────────┤
│ 数据库: │
│ ├─ 连接池使用率: 60% ✓ │
│ ├─ 慢查询数量: 2 ⚠️ │
│ └─ 主从延迟: 0.1s ✓ │
└─────────────────────────────────────────────┘
告警规则:
✅ 正常:绿色
⚠️ 警告:黄色(需要关注)
❌ 严重:红色(立即处理)
13. 面试高频问题
Q1: 如何设计一个高并发系统?
完整回答模板:
我会从以下几个层面来设计高并发系统:
1. 架构层面(Scale Out):
- 采用微服务架构,服务水平扩展
- 无状态服务设计,便于负载均衡
- 使用Nginx/Gateway做负载均衡
2. 缓存层面(减少DB压力):
- 多级缓存:浏览器 → CDN → Nginx → 本地缓存 → Redis
- 缓存预热:提前加载热点数据
- 解决缓存三大问题:穿透、击穿、雪崩
3. 数据库层面:
- 读写分离(主库写,从库读)
- 分库分表(水平拆分)
- 索引优化、SQL优化
- 连接池配置优化
4. 异步处理:
- 消息队列削峰填谷(RabbitMQ/Kafka)
- 核心业务同步,次要业务异步
5. 限流降级:
- 限流:令牌桶、滑动窗口
- 降级:关闭次要功能
- 熔断:快速失败,避免雪崩
6. 代码层面:
- 避免大事务
- 批量操作
- 线程池复用
- 索引优化
实际案例:
我们做过一个秒杀系统,峰值QPS 10万,通过上述优化,
最终实现了99.9%的成功率和平均100ms的响应时间。
Q2: Redis和MySQL如何保证数据一致性?
回答:
我们采用 Cache Aside Pattern(旁路缓存)策略:
读操作:
1. 先查Redis
2. 未命中则查MySQL
3. 写入Redis
写操作:
1. 先更新MySQL
2. 再删除Redis缓存
为什么是"先更新DB,再删除缓存"?
- 如果"先删缓存,再更新DB",可能出现:
t1: A删除缓存
t2: B读取缓存(未命中)
t3: B读取DB(旧数据)
t4: B写入缓存(旧数据)
t5: A更新DB(新数据)
结果:缓存是旧数据❌
- "先更新DB,再删除缓存":
t1: A更新DB
t2: A删除缓存
t3: B读取缓存(未命中)
t4: B读取DB(新数据)
t5: B写入缓存(新数据)
结果:一致✅
进阶方案:延迟双删
1. 删除缓存
2. 更新DB
3. 延迟500ms再删除缓存(兜底)
终极方案:订阅MySQL Binlog(Canal)
异步更新缓存,保证最终一致性
Q3: 如何防止缓存穿透、击穿、雪崩?
回答:
1. 缓存穿透(查询不存在的数据):
问题:恶意请求不存在的数据,绕过缓存直接打DB
解决方案:
① 布隆过滤器:提前判断数据是否存在
② 缓存空对象:不存在的数据也缓存(TTL较短)
2. 缓存击穿(热点key过期):
问题:热点key过期瞬间,大量请求打到DB
解决方案:
① 互斥锁:只允许一个线程查DB,其他线程等待
② 逻辑过期:热点数据永不过期,异步更新
③ 热点数据永不过期
3. 缓存雪崩(大量key同时过期):
问题:大量key同时过期,请求全部打到DB
解决方案:
① 过期时间加随机值(30分钟 + 随机0-5分钟)
② Redis集群 + 持久化
③ 限流降级(保底方案)
Q4: 秒杀系统如何防止超卖?
回答:
防止超卖的多重保障:
1. Redis预减库存(第一道防线):
- 使用Lua脚本保证原子性
- 库存不足直接返回,不查DB
2. 消息队列(削峰):
- 秒杀成功的请求进入队列
- 慢慢消费,避免DB压力
3. 数据库乐观锁(最后防线):
UPDATE stock
SET stock = stock - 1, version = version + 1
WHERE product_id = ?
AND stock > 0
AND version = ?
- 版本号不匹配则更新失败
- 绝对不会超卖
4. 分布式锁(可选):
- 使用Redisson分布式锁
- 同一商品同一时刻只有一个线程扣库存
实际效果:
我们的秒杀系统支持10万QPS,通过上述方案,
实现了0超卖、99.9%成功率。
Q5: 分库分表后如何查询?
回答:
分库分表后的查询策略:
1. 带分片键的查询(推荐):
SELECT * FROM order WHERE user_id = 1001 AND order_id = 123
- 直接路由到对应分片(ds1.t_order_9)
- 性能最优
2. 不带分片键的查询(避免):
SELECT * FROM order WHERE order_id = 123
- 需要查询所有分片(全表扫描)
- 性能很差
解决方案:
① 冗余分片键:在其他需要查询的地方也存储user_id
② 使用ES:将订单数据同步到ES,支持任意字段查询
③ 分库分表中间件:ShardingSphere自动聚合结果
3. 跨分片聚合查询:
SELECT COUNT(*) FROM order
解决方案:
① 分别查询每个分片,应用层聚合
② 使用数据仓库(OLAP):Hive、ClickHouse
4. 分布式事务:
订单服务 + 库存服务
解决方案:
① TCC(Try-Confirm-Cancel)
② Saga(长事务)
③ 本地消息表 + 定时任务
总结
高并发系统的核心思想:
1. 能缓存就缓存(Cache)
2. 能异步就异步(Async)
3. 能拆分就拆分(Partition)
4. 能限流就限流(Rate Limit)
┌─────────────────────────────────────┐
│ 高并发系统 = 削峰 + 限流 │
│ │
│ 请求洪峰: 10万QPS │
│ ↓ │
│ ① CDN + 静态化(9万被拦截) │
│ ↓ │
│ ② Nginx限流(8000被拦截) │
│ ↓ │
│ ③ Redis缓存(命中率95%) │
│ ↓ │
│ ④ 消息队列削峰(1万入队 → 1000出队) │
│ ↓ │
│ ⑤ 数据库承载: 1000 QPS ✅ │
└─────────────────────────────────────┘
记住这个公式:
高并发能力 =
(系统吞吐量 × 缓存命中率)
÷
(平均响应时间 × (1 - 降级比例))
提升方向:
↑ 增加系统吞吐量(水平扩展)
↑ 提高缓存命中率(多级缓存)
↓ 降低响应时间(异步、优化SQL)
↑ 增加降级比例(关闭次要功能)