共计 2925 个字符,预计需要花费 8 分钟才能阅读完成。
背景与痛点
微服务架构通过解耦服务提升了系统的灵活性和可扩展性,但同时也带来了新的技术挑战。常用 skill 如缓存、消息队列、分布式锁等,在微服务环境中发挥着至关重要的作用。然而,不当的使用往往会导致以下问题:

- 缓存一致性问题 :数据在缓存和数据库之间不一致,导致业务逻辑错误
- 消息丢失或重复消费 :消息队列使用不当引发数据丢失或重复处理
- 分布式锁失效 :在高并发场景下,锁机制失效导致数据竞争
- 性能瓶颈 :skill 选择不当或配置不合理引发系统性能下降
这些问题如果处理不当,轻则影响系统性能,重则导致业务故障。因此,理解这些常用 skill 的正确使用方法至关重要。
技术选型对比
缓存系统:Redis vs Memcached
- Redis:
- 支持丰富的数据结构(字符串、哈希、列表等)
- 提供持久化功能,数据安全性更高
- 支持 Lua 脚本和事务
-
适合需要复杂操作和数据持久化的场景
-
Memcached:
- 纯内存缓存,性能极高
- 支持多线程,吞吐量优秀
- 数据结构简单(仅键值对)
- 适合简单缓存场景,追求极致性能
消息队列:Kafka vs RabbitMQ
- Kafka:
- 高吞吐量,适合大数据量场景
- 支持消息持久化和批量处理
- 分区和副本机制保证高可用
-
适合日志收集、流处理等场景
-
RabbitMQ:
- 支持多种消息协议(AMQP、STOMP 等)
- 提供丰富的消息路由功能
- 消息确认机制完善
- 适合需要复杂路由的业务场景
核心实现
分布式锁实现(基于 Redis)
/**
* 获取分布式锁
* @param lockKey 锁的 key
* @param requestId 请求标识(避免误删其他线程的锁)* @param expireTime 锁的过期时间(毫秒)* @return 是否获取成功
*/
public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) {
// 使用 SET 命令的 NX 和 PX 选项实现原子性操作
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
return "OK".equals(result);
}
/**
* 释放分布式锁
* @param lockKey 锁的 key
* @param requestId 请求标识
* @return 是否释放成功
*/
public boolean releaseDistributedLock(String lockKey, String requestId) {
// 使用 Lua 脚本保证原子性
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then" +
"return redis.call('del', KEYS[1])" +
"else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
return Long.valueOf(1L).equals(result);
}
缓存雪崩防护
/**
* 获取缓存数据(防止雪崩)* @param key 缓存 key
* @param expire 过期时间(秒)* @param loader 数据加载器(当缓存不存在时调用)* @return 缓存数据
*/
public <T> T getWithAvalancheProtection(String key, int expire, Callable<T> loader) {
// 1. 尝试从缓存获取
T value = (T) redisTemplate.opsForValue().get(key);
if (value != null) {return value;}
// 2. 获取分布式锁,防止并发重建缓存
String lockKey = "lock:" + key;
boolean locked = false;
try {locked = tryGetDistributedLock(lockKey, Thread.currentThread().getName(), 5000);
if (locked) {
// 3. 再次检查缓存,防止其他线程已经重建
value = (T) redisTemplate.opsForValue().get(key);
if (value != null) {return value;}
// 4. 调用 loader 加载数据
value = loader.call();
// 5. 设置缓存,并添加随机过期时间防止同时失效
int randomExpire = expire + new Random().nextInt(300); // 增加 0 - 5 分钟的随机时间
redisTemplate.opsForValue().set(key, value, randomExpire, TimeUnit.SECONDS);
return value;
} else {
// 6. 未获取到锁,短暂等待后重试
Thread.sleep(100);
return getWithAvalancheProtection(key, expire, loader);
}
} catch (Exception e) {throw new RuntimeException("获取缓存失败", e);
} finally {if (locked) {releaseDistributedLock(lockKey, Thread.currentThread().getName());
}
}
}
性能考量
不同的实现方式对系统性能有着显著影响:
- 分布式锁性能 :
- 基于 Redis 的 SETNX 实现简单但网络开销大
- 使用 RedLock 算法提高可靠性但性能下降
-
建议:根据业务需求选择,非关键路径可使用简单实现
-
缓存性能 :
- 本地缓存(如 Caffeine)性能最好但一致性难保证
- 分布式缓存(如 Redis)性能次之但能保证一致性
-
建议:热点数据可使用多级缓存(本地 + 分布式)
-
消息队列性能 :
- Kafka 吞吐量高但延迟较大
- RabbitMQ 延迟低但吞吐量有限
- 建议:根据业务对吞吐和延迟的需求选择
避坑指南
- 缓存穿透 :
- 问题:大量查询不存在的数据,导致请求直接打到数据库
-
解决:对不存在的数据也进行缓存(空对象或布隆过滤器)
-
缓存击穿 :
- 问题:热点 key 失效瞬间大量请求直接访问数据库
-
解决:使用互斥锁重建缓存(如上面的示例)
-
消息丢失 :
- 问题:消息队列崩溃导致消息丢失
-
解决:开启持久化,使用生产者确认机制
-
分布式锁失效 :
- 问题:锁过期时间设置不当导致业务未完成锁已释放
-
解决:合理设置超时时间,或使用看门狗机制自动续期
-
事务消息处理不当 :
- 问题:本地事务成功但消息发送失败,导致数据不一致
- 解决:使用事务消息(如 RocketMQ)或本地消息表
思考题
- 在你的项目中,如何评估是否真的需要引入分布式锁?是否有更轻量级的替代方案?
- 当缓存系统和数据库出现数据不一致时,你会采取哪些策略来恢复一致性?
- 对于订单支付这种关键业务,你会如何设计消息队列的使用方案来确保可靠性?
微服务架构中的常用 skill 看似简单,但要真正用好却需要深入理解其原理和适用场景。希望通过本文的分享,能够帮助你在实际项目中更加游刃有余地运用这些技术,构建出稳定高效的微服务系统。
正文完
