Redisson分布式锁与布隆过滤器实战12306项目中的高并发防护方案前言在分布式系统中高并发场景下的数据一致性和缓存安全性是两个永恒的话题。本文将以一个真实的12306火车票项目为背景深入剖析两个核心技术Redisson分布式锁和布隆过滤器在实际项目中的应用。项目技术栈Java 17 Spring Boot 3.0.7Redisson 3.21.3ShardingSphere 5.3.2RocketMQ 2.2.3一、Redisson分布式锁为什么不用原生Redis1.1 分布式锁的三大痛点在分布式系统中简单的SETNX命令并不能解决所有问题问题原生RedisRedisson可重入性❌ 需手动实现✅ 内置支持锁过期时间❌ 需手动设置可能提前过期✅ 看门狗自动续期分布式集合❌ 无✅ RMap/RSet/RDeque1.2 项目实战订单状态反转场景描述在订单系统中需要防止多个请求同时修改订单状态导致数据不一致。源码位置services/order-service/…/service/impl/OrderServiceImpl.javajavaOverridepublic void reversal(OrderStatusReversalDTO requestParam) {// 1. 获取分布式锁RLock lock redissonClient.getLock(StrBuilder.create(“order:status-reversal:order_sn_”).append(requestParam.getOrderSn()).toString());// 2. 尝试获取锁非阻塞 if (!lock.tryLock()) { log.warn(订单重复修改状态状态反转请求参数{}, JSON.toJSONString(requestParam)); } try { // 3. 更新订单状态 OrderDO updateOrderDO new OrderDO(); updateOrderDO.setStatus(requestParam.getOrderStatus()); LambdaUpdateWrapperOrderDO updateWrapper Wrappers.lambdaUpdate(OrderDO.class) .eq(OrderDO::getOrderSn, requestParam.getOrderSn()); int updateResult orderMapper.update(updateOrderDO, updateWrapper); if (updateResult 0) { throw new ServiceException(OrderCanalErrorCodeEnum.ORDER_STATUS_REVERSAL_ERROR); } // 4. 更新订单项状态 OrderItemDO orderItemDO new OrderItemDO(); orderItemDO.setStatus(requestParam.getOrderItemStatus()); LambdaUpdateWrapperOrderItemDO orderItemUpdateWrapper Wrappers.lambdaUpdate(OrderItemDO.class) .eq(OrderItemDO::getOrderSn, requestParam.getOrderSn()); int orderItemUpdateResult orderItemMapper.update(orderItemDO, orderItemUpdateWrapper); if (orderItemUpdateResult 0) { throw new ServiceException(OrderCanalErrorCodeEnum.ORDER_STATUS_REVERSAL_ERROR); } } finally { // 5. 释放锁 lock.unlock(); }}关键点使用 ryLock()非阻塞获取锁避免线程等待inally块中必须释放锁防止死锁锁的粒度是订单号级别不影响其他订单操作1.3 项目实战全局ID生成器节点分配场景描述在雪花算法中需要确保每个节点获得唯一的节点ID0-31。源码位置services/order-service/…/service/orderid/OrderIdGeneratorManager.javajavaComponentRequiredArgsConstructorpublic final class OrderIdGeneratorManager implements InitializingBean {private final RedissonClient redissonClient; private final DistributedCache distributedCache; private static DistributedIdGenerator DISTRIBUTED_ID_GENERATOR; Override public void afterPropertiesSet() throws Exception { String LOCK_KEY distributed_id_generator_lock_key; RLock lock redissonClient.getLock(LOCK_KEY); // 加锁 lock.lock(); try { StringRedisTemplate instance (StringRedisTemplate) distributedCache.getInstance(); String DISTRIBUTED_ID_GENERATOR_KEY distributed_id_generator_config; // Redis自增获取节点ID long incremented Optional.ofNullable( instance.opsForValue().increment(DISTRIBUTED_ID_GENERATOR_KEY)).orElse(0L); int NODE_MAX 32; if (incremented NODE_MAX) { incremented 0; instance.opsForValue().set(DISTRIBUTED_ID_GENERATOR_KEY, 0); } DISTRIBUTED_ID_GENERATOR new DistributedIdGenerator(incremented); } finally { // 解锁 lock.unlock(); } }}关键点使用lock()阻塞式获取锁确保只有一个节点能初始化ID生成器看门狗机制自动续期防止业务执行期间锁过期节点ID循环使用支持32个节点二、布隆过滤器缓存穿透的终极解决方案2.1 什么是缓存穿透场景描述恶意用户频繁查询不存在的数据每次请求都打到数据库。正常流程用户请求 → 查缓存 → 命中 → 返回数据缓存穿透用户请求 → 查缓存不存在→ 查数据库不存在→ 返回null↑ 每次请求都这样数据库压力巨大2.2 布隆过滤器原理布隆过滤器是一种概率型数据结构用于判断元素是否在集合中核心特性✅ 如果布隆过滤器说不存在 → 100%确定不存在❌ 如果布隆过滤器说存在 → 可能有误判假阳性适用场景缓存穿透防护垃圾邮件过滤爬虫URL去重2.3 项目配置用户注册布隆过滤器源码位置services/user-service/…/config/UserRegisterBloomFilterProperties.javajavaDataConfigurationProperties(prefix UserRegisterBloomFilterProperties.PREFIX)public final class UserRegisterBloomFilterProperties {public static final String PREFIX framework.cache.redis.bloom-filter.user-register; /** * 布隆过滤器实例名称 */ private String name user_register_cache_penetration_bloom_filter; /** * 每个元素的预期插入量 */ private Long expectedInsertions 64L; /** * 预期错误概率默认3% */ private Double falseProbability 0.03D;}配置文件pplication.ymlyaml framework: cache: redis: bloom-filter: user-register: name: user_register_cache_penetration_bloom_filter expected-insertions: 1000000 # 预期100万用户 false-probability: 0.01 # 误判率1%2.4 项目配置布隆过滤器Bean源码位置services/user-service/…/config/RBloomFilterConfiguration.javajavaConfigurationEnableConfigurationProperties(UserRegisterBloomFilterProperties.class)public class RBloomFilterConfiguration {Bean public RBloomFilterString userRegisterCachePenetrationBloomFilter( RedissonClient redissonClient, UserRegisterBloomFilterProperties properties) { // 1. 获取布隆过滤器实例 RBloomFilterString bloomFilter redissonClient.getBloomFilter( properties.getName() ); // 2. 初始化布隆过滤器 // expectedInsertions: 预计插入元素数量 // falseProbability: 误判率 bloomFilter.tryInit( properties.getExpectedInsertions(), properties.getFalseProbability() ); return bloomFilter; }}2.5 项目实战用户注册防穿透场景描述在用户注册流程中需要先检查用户是否存在避免直接查询数据库。源码位置services/user-service/…/service/impl/UserServiceImpl.javajavaServiceRequiredArgsConstructorpublic class UserServiceImpl implements UserService {private final UserMapper userMapper; private final RBloomFilterString userRegisterCachePenetrationBloomFilter; Override public UserQueryRespDTO queryUserByUsername(String username) { // 1. 布隆过滤器预检查 if (!userRegisterCachePenetrationBloomFilter.contains(username)) { // 布隆过滤器说不存在 → 100%确定不存在 throw new ClientException(用户不存在请检查用户名是否正确); } // 2. 查缓存 String cacheKey user:info: username; UserQueryRespDTO cached cache.get(cacheKey, UserQueryRespDTO.class); if (cached ! null) { return cached; } // 3. 查数据库 LambdaQueryWrapperUserDO queryWrapper Wrappers.lambdaQuery(UserDO.class) .eq(UserDO::getUsername, username); UserDO userDO userMapper.selectOne(queryWrapper); if (userDO null) { // 4. 数据库也不存在加入布隆过滤器 userRegisterCachePenetrationBloomFilter.add(username); throw new ClientException(用户不存在请检查用户名是否正确); } // 5. 存入缓存 cache.put(cacheKey, BeanUtil.convert(userDO, UserQueryRespDTO.class)); return BeanUtil.convert(userDO, UserQueryRespDTO.class); }}关键点布隆过滤器作为第一道防线快速过滤不存在的请求即使布隆过滤器说存在仍需查缓存和数据库数据库不存在的用户也要加入布隆过滤器防止重复查询2.6 多级缓存框架的完整防护源码位置rameworks/cache/…/StringRedisTemplateProxy.javajavaOverridepublic T safeGet(String key, Class clazz, CacheLoader cacheLoader,long timeout, TimeUnit timeUnit,RBloomFilter bloomFilter,CacheGetFilter cacheCheckFilter,CacheGetIfAbsent cacheGetIfAbsent) {// 1. 先查缓存 T result get(key, clazz); // 2. 缓存命中或布隆过滤器判断不存在 → 直接返回 if (!CacheUtil.isNullOrBlank(result) || Optional.ofNullable(cacheGetFilter).map(each - each.filter(key)).orElse(false) || Optional.ofNullable(bloomFilter).map(each - !each.contains(key)).orElse(false)) { return result; } // 3. 获取分布式锁防止缓存击穿 RLock lock redissonClient.getLock( SAFE_GET_DISTRIBUTED_LOCK_KEY_PREFIX key ); lock.lock(); try { // 4. 双重判定减轻数据库压力 if (CacheUtil.isNullOrBlank(result get(key, clazz))) { // 5. 加载数据并设置缓存 if (CacheUtil.isNullOrBlank(result loadAndSet( key, cacheLoader, timeout, timeUnit, true, bloomFilter))) { // 6. 数据为空执行后置操作 Optional.ofNullable(cacheGetIfAbsent).ifPresent(each - each.execute(key)); } } } finally { lock.unlock(); } return result;}防护流程请求到达 ↓ 布隆过滤器预检查防穿透 ↓ 查缓存 ↓ 缓存命中 → 是 → 返回数据 ↓否 获取分布式锁防击穿 ↓ 双重判定锁内再查缓存 ↓ 缓存仍未命中 → 是 → 加载数据 ↓ 设置缓存 加入布隆过滤器 ↓ 释放锁 ↓ 返回数据三、实战案例购票流程中的安全防护3.1 场景描述在用户购票流程中需要同时使用分布式锁和布隆过滤器分布式锁防止超卖多个用户同时购买同一张票布隆过滤器快速判断车次是否存在3.2 源码实现源码位置services/ticket-service/…/handler/ticket/tokenbucket/TicketAvailabilityTokenBucket.javajavaComponentRequiredArgsConstructorpublic final class TicketAvailabilityTokenBucket {private final RedissonClient redissonClient; private final DistributedCache distributedCache; /** * 获取车票余量令牌桶中的令牌访问 */ public TokenResultDTO acquireTokenInBucket(PurchaseTicketReqDTO requestParam) { StringRedisTemplate stringRedisTemplate (StringRedisTemplate) distributedCache.getInstance(); // 1. 构造锁Key String lockKey lock:ticket:token: requestParam.getTrainId(); RLock lock redissonClient.getLock(lockKey); // 2. 尝试获取锁 if (!lock.tryLock()) { log.warn(获取车票令牌锁失败车次ID{}, requestParam.getTrainId()); return TokenResultDTO.builder().success(Boolean.FALSE).build(); } try { // 3. 执行令牌扣减逻辑 String tokenBucketHashKey TICKET_AVAILABILITY_TOKEN_BUCKET requestParam.getTrainId(); ListRouteDTO takeoutRouteDTOList trainStationService .listTakeoutTrainStationRoute( requestParam.getTrainId(), requestParam.getDeparture(), requestParam.getArrival() ); // 4. 执行Lua脚本原子操作 DefaultRedisScriptString redisScript Singleton.get( LUA_TICKET_AVAILABILITY_TOKEN_BUCKET_PATH, () - { DefaultRedisScriptString script new DefaultRedisScript(); script.setScriptSource(new ResourceScriptSource( new ClassPathResource(LUA_TICKET_AVAILABILITY_TOKEN_BUCKET_PATH) )); script.setResultType(String.class); return script; } ); String resultStr stringRedisTemplate.execute(redisScript, Lists.newArrayList(tokenBucketHashKey, luaScriptKey), JSON.toJSONString(seatTypeCountArray), JSON.toJSONString(takeoutRouteDTOList) ); return JSON.parseObject(resultStr, TokenResultDTO.class); } finally { // 5. 释放锁 lock.unlock(); } }}四、总结4.1 核心技术对比技术解决的问题实现方式性能影响Redisson分布式锁数据一致性看门狗可重入锁低布隆过滤器缓存穿透概率型数据结构极低Lua脚本原子操作Redis服务端执行低4.2 最佳实践分布式锁尽量使用 ryLock()非阻塞方式inally块中必须释放锁锁的粒度要细避免影响其他业务布隆过滤器合理设置expectedInsertions和alseProbability定期重建布隆过滤器避免内存泄漏布隆过滤器说不存在是100%准确的组合使用布隆过滤器 分布式锁 Lua脚本 三重防护适用于高并发、高可用的生产环境