【Redis】【实战项目】

管理员
# Redis实战应用:从场景到项目落地 ## 目录 1. [Redis应用场景总览](#1-redis应用场景总览) 2. [项目实战一:高并发秒杀系统](#2-项目实战一高并发秒杀系统) 3. [项目实战二:分布式锁服务](#3-项目实战二分布式锁服务) 4. [项目实战三:排行榜系统](#4-项目实战三排行榜系统) 5. [项目实战四:Session共享与会话管理](#5-项目实战四session共享与会话管理) 6. [项目实战五:消息队列与发布订阅](#6-项目实战五消息队列与发布订阅) 7. [项目实战六:缓存预热与更新策略](#7-项目实战六缓存预热与更新策略) 8. [Redis性能监控与优化](#8-redis性能监控与优化) 9. [Redis集群与高可用](#9-redis集群与高可用) 10. [总结与最佳实践](#10-总结与最佳实践) --- ## 1. Redis应用场景总览 ### 1.1 Redis核心数据结构与应用场景 ```java /** * Redis数据结构与应用场景映射 */ public class RedisDataStructureMapping { /** * String(字符串) * 应用场景: * - 缓存对象 * - 计数器 * - 分布式Session * - 分布式ID生成 */ public static class StringExample { // 缓存对象 // user:1001 -> {"id":1001,"name":"张三"} // 计数器 // article:1001:views -> 1000 // Session // session:abc123 -> {"userId":1001,"expireTime":"2024-03-26 23:59:59"} // 分布式ID // id:order -> 10000 } /** * Hash(哈希) * 应用场景: * - 对象存储 * - 购物车 * - 用户信息 * - 配置信息 */ public static class HashExample { // 用户信息 // user:1001 -> {"name":"张三","age":25,"email":"zhangsan@qq.com"} // 购物车 // cart:1001 -> {"1001":1,"1002":2,"1003":1} // 配置信息 // config:system -> {"max_connections":1000,"timeout":30} } /** * List(列表) * 应用场景: * - 消息队列 * - 最新列表 * - 关注列表 * - 排行榜(时间序) */ public static class ListExample { // 消息队列 // queue:order -> LPUSH/RPOP // 最新文章 // articles:latest -> ["1001","1000","999"] // 关注列表 // follow:1001 -> [1002,1003,1004] // 时间线 // timeline:1001 -> [msg1,msg2,msg3] } /** * Set(集合) * 应用场景: * - 标签系统 * - 共同关注 * - 抽奖系统 * - 好友推荐 */ public static class SetExample { // 标签 // tags:article:1001 -> ["java","redis","mq"] // 共同关注 // SINTER follow:1001 follow:1002 // 抽奖 // lottery:users -> [user1,user2,user3,...] // 好友推荐 // SDIFF all:users follow:1001 } /** * ZSet(有序集合) * 应用场景: * - 排行榜 * - 延迟队列 * - 优先级队列 * - 热点数据 */ public static class ZSetExample { // 排行榜 // ranking:score -> {1001:9.8,1002:9.7,1003:9.6} // 延迟队列 // delay:queue -> {order1:1711473600,order2:1711473660} // 热点数据 // hot:articles -> {1001:1000,1002:800,1003:600} // 历史记录 // history:1001 -> {article1:1711473600,article2:1711473500} } } ``` --- ## 2. 项目实战一:高并发秒杀系统 ### 2.1 项目背景 构建一个高并发秒杀系统,核心需求: - 支持百万级并发请求 - 防止超卖 - 保证公平性 - 提升用户体验 ### 2.2 架构设计 ``` 用户 → Nginx负载均衡 → 网关 → 秒杀服务 → Redis → 消息队列 → 订单服务 ↓ MySQL(异步) ``` ### 2.3 核心实现 ```java /** * 秒杀服务实现 */ @Service @Slf4j public class SeckillService { @Autowired private RedisTemplate redisTemplate; @Autowired private ProductRepository productRepository; @Autowired private OrderRepository orderRepository; @Autowired private RabbitTemplate rabbitTemplate; /** * 秒杀商品信息Key */ private static final String SECKILL_STOCK_KEY = "seckill:stock:"; private static final String SECKILL_LIMIT_KEY = "seckill:limit:"; private static final String SECKILL_USER_KEY = "seckill:user:"; /** * 预热秒杀商品库存 */ public void warmUp(Long productId, Integer stock) { String stockKey = SECKILL_STOCK_KEY + productId; // 设置库存到Redis redisTemplate.opsForValue().set(stockKey, stock); // 设置过期时间 redisTemplate.expire(stockKey, 1, TimeUnit.HOURS); log.info("秒杀商品预热成功: productId={}, stock={}", productId, stock); } /** * 执行秒杀 * @param productId 商品ID * @param userId 用户ID * @return 秒杀结果 */ @Transactional public SeckillResult doSeckill(Long productId, Long userId) { String stockKey = SECKILL_STOCK_KEY + productId; String limitKey = SECKILL_LIMIT_KEY + productId; String userKey = SECKILL_USER_KEY + productId + ":" + userId; // 1. 检查用户是否已参与 Boolean hasParticipated = redisTemplate.hasKey(userKey); if (Boolean.TRUE.equals(hasParticipated)) { return SeckillResult.fail("您已参与过此秒杀"); } // 2. 检查并扣减库存(原子操作) Long currentStock = redisTemplate.opsForValue().decrement(stockKey); if (currentStock < 0) { // 库存不足,回滚 redisTemplate.opsForValue().increment(stockKey); return SeckillResult.fail("库存不足"); } // 3. 标记用户已参与(设置过期时间,防止用户重复购买) redisTemplate.opsForValue().set(userKey, "1", 1, TimeUnit.HOURS); // 4. 记录限购信息 redisTemplate.opsForValue().increment(limitKey + ":" + userId); // 5. 发送消息到MQ,异步创建订单 SeckillMessage message = new SeckillMessage(productId, userId); rabbitTemplate.convertAndSend("seckill.exchange", "seckill.order", message); log.info("秒杀成功: productId={}, userId={}, remainingStock={}", productId, userId, currentStock); return SeckillResult.success("秒杀成功"); } /** * Lua脚本实现原子性秒杀 */ public SeckillResult doSeckillWithLua(Long productId, Long userId) { String luaScript = "local stockKey = KEYS[1] " + "local userKey = KEYS[2] " + "local userId = ARGV[1] " + // 检查用户是否已参与 "if redis.call('exists', userKey .. userId) == 1 then " + " return -1 " + "end " + // 检查并扣减库存 "local stock = redis.call('get', stockKey) " + "if not stock then " + " return -2 " + "end " + "stock = tonumber(stock) " + "if stock <= 0 then " + " return -3 " + "end " + // 扣减库存 "redis.call('decr', stockKey) " + // 标记用户已参与 "redis.call('set', userKey .. userId, '1', 'EX', 3600) " + "return 1"; String stockKey = SECKILL_STOCK_KEY + productId; String userKey = SECKILL_USER_KEY + productId + ":"; // 执行Lua脚本 DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptText(luaScript); redisScript.setResultType(Long.class); Long result = redisTemplate.execute(redisScript, Arrays.asList(stockKey, userKey), userId.toString()); // 处理结果 if (result == null || result == -2) { return SeckillResult.fail("秒杀活动不存在或已结束"); } else if (result == -3) { return SeckillResult.fail("库存不足"); } else if (result == -1) { return SeckillResult.fail("您已参与过此秒杀"); } else if (result == 1) { // 发送消息到MQ SeckillMessage message = new SeckillMessage(productId, userId); rabbitTemplate.convertAndSend("seckill.exchange", "seckill.order", message); return SeckillResult.success("秒杀成功"); } return SeckillResult.fail("秒杀失败"); } /** * 获取剩余库存 */ public Integer getRemainingStock(Long productId) { String stockKey = SECKILL_STOCK_KEY + productId; Integer stock = (Integer) redisTemplate.opsForValue().get(stockKey); return stock != null ? stock : 0; } /** * 重置库存(管理员操作) */ public void resetStock(Long productId, Integer stock) { String stockKey = SECKILL_STOCK_KEY + productId; redisTemplate.opsForValue().set(stockKey, stock); // 清空用户购买记录 String userPattern = SECKILL_USER_KEY + productId + ":*"; Set keys = redisTemplate.keys(userPattern); if (keys != null && !keys.isEmpty()) { redisTemplate.delete(keys); } } } ``` ### 2.4 消息队列处理订单 ```java /** * 秒杀消息消费者 */ @Component @Slf4j public class SeckillMessageConsumer { @Autowired private OrderService orderService; @Autowired private InventoryService inventoryService; /** * 消费秒杀消息,创建订单 */ @RabbitListener(queues = "seckill.order.queue") public void handleSeckillMessage(SeckillMessage message) { try { log.info("处理秒杀订单: productId={}, userId={}", message.getProductId(), message.getUserId()); // 1. 创建订单 Order order = new Order(); order.setProductId(message.getProductId()); order.setUserId(message.getUserId()); order.setStatus(OrderStatus.PENDING_PAYMENT); order.setCreateTime(LocalDateTime.now()); order = orderService.createOrder(order); // 2. 扣减库存(数据库) inventoryService.deductStock(message.getProductId(), 1); log.info("秒杀订单创建成功: orderId={}", order.getId()); } catch (Exception e) { log.error("处理秒杀订单失败: productId={}, userId={}", message.getProductId(), message.getUserId(), e); // 可以在这里实现重试或补偿机制 } } } ``` ### 2.5 接口层实现 ```java /** * 秒杀控制器 */ @RestController @RequestMapping("/api/seckill") @Slf4j public class SeckillController { @Autowired private SeckillService seckillService; /** * 预热秒杀商品 */ @PostMapping("/warmup") public ResponseEntity warmUp(@RequestBody SeckillWarmupRequest request) { seckillService.warmUp(request.getProductId(), request.getStock()); return ResponseEntity.ok(Result.success("预热成功")); } /** * 秒杀接口 */ @PostMapping("/doSeckill") @RateLimiter(value = 10, timeUnit = TimeUnit.SECONDS) // 限流 public ResponseEntity doSeckill(@RequestBody SeckillRequest request) { // 参数校验 if (request.getProductId() == null || request.getUserId() == null) { return ResponseEntity.ok(Result.fail("参数错误")); } // 执行秒杀 SeckillResult result = seckillService.doSeckillWithLua( request.getProductId(), request.getUserId()); if (result.isSuccess()) { return ResponseEntity.ok(Result.success("秒杀成功")); } else { return ResponseEntity.ok(Result.fail(result.getMessage())); } } /** * 查询剩余库存 */ @GetMapping("/stock/{productId}") public ResponseEntity getStock(@PathVariable Long productId) { Integer stock = seckillService.getRemainingStock(productId); return ResponseEntity.ok(Result.success(stock)); } } ``` ### 2.6 防刷与限流 ```java /** * 秒杀限流注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RateLimiter { int value() default 100; // 限流数量 TimeUnit timeUnit() default TimeUnit.SECONDS; // 时间单位 } /** * 限流切面 */ @Aspect @Component public class RateLimiterAspect { @Autowired private RedisTemplate redisTemplate; @Around("@annotation(rateLimiter)") public Object around(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable { // 获取请求IP HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); String ip = getIpAddress(request); // 构造限流Key String key = "rate:limit:" + ip + ":" + joinPoint.getSignature().getName(); // 计算过期时间(秒) long timeout = rateLimiter.timeUnit().toSeconds(1L); // 尝试增加计数器 Long count = redisTemplate.opsForValue().increment(key); // 第一次设置过期时间 if (count != null && count == 1) { redisTemplate.expire(key, timeout, TimeUnit.SECONDS); } // 判断是否超过限流 if (count != null && count > rateLimiter.value()) { throw new RateLimitException("操作过于频繁,请稍后再试"); } // 继续执行方法 return joinPoint.proceed(); } private String getIpAddress(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } } ``` --- ## 3. 项目实战二:分布式锁服务 ### 3.1 项目背景 在分布式系统中,多个服务实例同时访问共享资源时,需要使用分布式锁来保证数据一致性。 ### 3.2 Redis分布式锁实现 ```java /** * Redis分布式锁实现 */ @Component @Slf4j public class RedisDistributedLock { @Autowired private RedisTemplate redisTemplate; /** * 锁的过期时间(毫秒) */ private static final long LOCK_EXPIRE_TIME = 30000; /** * 获取锁 * @param lockKey 锁的Key * @param requestId 请求ID * @return 是否获取成功 */ public boolean tryLock(String lockKey, String requestId) { return tryLock(lockKey, requestId, LOCK_EXPIRE_TIME); } /** * 获取锁(带过期时间) * @param lockKey 锁的Key * @param requestId 请求ID * @param expireTime 过期时间(毫秒) * @return 是否获取成功 */ public boolean tryLock(String lockKey, String requestId, long expireTime) { try { // 使用SETNX命令实现分布式锁 Boolean result = redisTemplate.opsForValue().setIfAbsent( lockKey, requestId, expireTime, TimeUnit.MILLISECONDS); if (Boolean.TRUE.equals(result)) { log.info("获取锁成功: lockKey={}, requestId={}", lockKey, requestId); return true; } else { log.info("获取锁失败: lockKey={}, requestId={}", lockKey, requestId); return false; } } catch (Exception e) { log.error("获取锁异常: lockKey={}, requestId={}", lockKey, requestId, e); return false; } } /** * 释放锁 * @param lockKey 锁的Key * @param requestId 请求ID * @return 是否释放成功 */ public boolean unlock(String lockKey, String requestId) { String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " + " return redis.call('del', KEYS[1]) " + "else " + " return 0 " + "end"; try { DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptText(luaScript); redisScript.setResultType(Long.class); Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId); if (result != null && result == 1) { log.info("释放锁成功: lockKey={}, requestId={}", lockKey, requestId); return true; } else { log.warn("释放锁失败: lockKey={}, requestId={}", lockKey, requestId); return false; } } catch (Exception e) { log.error("释放锁异常: lockKey={}, requestId={}", lockKey, requestId, e); return false; } } /** * 尝试获取锁(带重试) * @param lockKey 锁的Key * @param requestId 请求ID * @param waitTime 等待时间(毫秒) * @param expireTime 锁的过期时间(毫秒) * @return 是否获取成功 */ public boolean tryLockWithRetry(String lockKey, String requestId, long waitTime, long expireTime) { long startTime = System.currentTimeMillis(); while (true) { // 尝试获取锁 boolean locked = tryLock(lockKey, requestId, expireTime); if (locked) { return true; } // 检查是否超时 long elapsedTime = System.currentTimeMillis() - startTime; if (elapsedTime >= waitTime) { log.warn("获取锁超时: lockKey={}, requestId={}, waitTime={}", lockKey, requestId, waitTime); return false; } // 短暂休眠后重试 try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } } /** * 续期锁(防止业务执行时间超过锁的过期时间) * @param lockKey 锁的Key * @param requestId 请求ID * @param expireTime 新的过期时间(毫秒) * @return 是否续期成功 */ public boolean renewLock(String lockKey, String requestId, long expireTime) { String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " + " return redis.call('pexpire', KEYS[1], ARGV[2]) " + "else " + " return 0 " + "end"; try { DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptText(luaScript); redisScript.setResultType(Long.class); Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId, String.valueOf(expireTime)); return result != null && result == 1; } catch (Exception e) { log.error("续期锁异常: lockKey={}, requestId={}", lockKey, requestId, e); return false; } } } ``` ### 3.3 可重入分布式锁 ```java /** * 可重入分布式锁 */ @Component @Slf4j public class RedisReentrantLock { @Autowired private RedisTemplate redisTemplate; /** * 线程本地变量,存储锁的重入次数 */ private ThreadLocal> lockCounters = ThreadLocal.withInitial(HashMap::new); /** * 获取锁(可重入) */ public boolean lock(String lockKey, String requestId, long expireTime) { // 检查是否已持有锁 Map counters = lockCounters.get(); Integer count = counters.get(lockKey); if (count != null && count > 0) { // 已持有锁,增加重入次数 counters.put(lockKey, count + 1); log.info("锁重入: lockKey={}, requestId={}, count={}", lockKey, requestId, count + 1); return true; } // 尝试获取锁 Boolean result = redisTemplate.opsForValue().setIfAbsent( lockKey, requestId, expireTime, TimeUnit.MILLISECONDS); if (Boolean.TRUE.equals(result)) { counters.put(lockKey, 1); log.info("获取锁成功: lockKey={}, requestId={}", lockKey, requestId); return true; } return false; } /** * 释放锁(可重入) */ public boolean unlock(String lockKey, String requestId) { Map counters = lockCounters.get(); Integer count = counters.get(lockKey); if (count == null || count <= 0) { log.warn("未持有锁: lockKey={}, requestId={}", lockKey, requestId); return false; } // 减少重入次数 int newCount = count - 1; counters.put(lockKey, newCount); if (newCount == 0) { // 重入次数为0,真正释放锁 String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " + " return redis.call('del', KEYS[1]) " + "else " + " return 0 " + "end"; DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptText(luaScript); redisScript.setResultType(Long.class); Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId); if (result != null && result == 1) { log.info("释放锁成功: lockKey={}, requestId={}", lockKey, requestId); counters.remove(lockKey); return true; } return false; } else { log.info("锁释放(仍持有): lockKey={}, requestId={}, count={}", lockKey, requestId, newCount); return true; } } } ``` ### 3.4 使用示例 ```java /** * 分布式锁使用示例 */ @Service @Slf4j public class OrderService { @Autowired private RedisDistributedLock distributedLock; /** * 扣减库存(使用分布式锁) */ public boolean deductInventory(Long productId, Integer quantity) { String lockKey = "lock:inventory:" + productId; String requestId = UUID.randomUUID().toString(); try { // 获取锁 boolean locked = distributedLock.tryLockWithRetry(lockKey, requestId, 5000, 30000); if (!locked) { log.warn("获取锁失败: productId={}", productId); return false; } // 执行业务逻辑 Inventory inventory = inventoryRepository.findById(productId).orElse(null); if (inventory == null) { return false; } if (inventory.getStock() < quantity) { return false; } inventory.setStock(inventory.getStock() - quantity); inventoryRepository.save(inventory); log.info("库存扣减成功: productId={}, quantity={}", productId, quantity); return true; } catch (Exception e) { log.error("扣减库存失败: productId={}", productId, e); return false; } finally { // 释放锁 distributedLock.unlock(lockKey, requestId); } } } ``` --- ## 4. 项目实战三:排行榜系统 ### 4.1 项目背景 构建一个排行榜系统,支持: - 实时排行榜 - 历史排行榜 - 排行榜查询 - 分页查询 ### 4.2 排行榜服务实现 ```java /** * 排行榜服务 */ @Service @Slf4j public class RankingService { @Autowired private RedisTemplate redisTemplate; /** * 排行榜Key */ private static final String RANKING_KEY = "ranking:"; /** * 添加分数(增加积分) * @param rankingKey 排行榜Key * @param member 成员 * @param score 分数 */ public void addScore(String rankingKey, String member, double score) { String key = RANKING_KEY + rankingKey; redisTemplate.opsForZSet().add(key, member, score); log.info("添加分数: rankingKey={}, member={}, score={}", rankingKey, member, score); } /** * 增加分数(累加) * @param rankingKey 排行榜Key * @param member 成员 * @param delta 增量 * @return 新的分数 */ public Double incrementScore(String rankingKey, String member, double delta) { String key = RANKING_KEY + rankingKey; Double newScore = redisTemplate.opsForZSet().incrementScore(key, member, delta); log.info("增加分数: rankingKey={}, member={}, delta={}, newScore={}", rankingKey, member, delta, newScore); return newScore; } /** * 获取成员排名 * @param rankingKey 排行榜Key * @param member 成员 * @return 排名(从0开始) */ public Long getRank(String rankingKey, String member) { String key = RANKING_KEY + rankingKey; Long rank = redisTemplate.opsForZSet().reverseRank(key, member); if (rank != null) { return rank + 1; // 转换为从1开始 } return null; } /** * 获取成员分数 * @param rankingKey 排行榜Key * @param member 成员 * @return 分数 */ public Double getScore(String rankingKey, String member) { String key = RANKING_KEY + rankingKey; return redisTemplate.opsForZSet().score(key, member); } /** * 获取Top N * @param rankingKey 排行榜Key * @param topN 前N名 * @return 前N名列表 */ public List getTopN(String rankingKey, int topN) { String key = RANKING_KEY + rankingKey; // 获取前N名(按分数降序) Set> set = redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, topN - 1); List result = new ArrayList<>(); if (set != null) { int rank = 1; for (ZSetOperations.TypedTuple tuple : set) { RankingItem item = new RankingItem(); item.setRank(rank); item.setMember(tuple.getValue().toString()); item.setScore(tuple.getScore()); result.add(item); rank++; } } return result; } /** * 获取排行榜分页数据 * @param rankingKey 排行榜Key * @param page 页码(从1开始) * @param pageSize 每页数量 * @return 分页数据 */ public PageResult getRankingPage(String rankingKey, int page, int pageSize) { String key = RANKING_KEY + rankingKey; // 计算分页范围 long start = (page - 1) * pageSize; long end = start + pageSize - 1; // 获取总数 Long total = redisTemplate.opsForZSet().size(key); // 获取分页数据 Set> set = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end); List list = new ArrayList<>(); if (set != null) { long rank = start + 1; for (ZSetOperations.TypedTuple tuple : set) { RankingItem item = new RankingItem(); item.setRank(rank); item.setMember(tuple.getValue().toString()); item.setScore(tuple.getScore()); list.add(item); rank++; } } return new PageResult<>(list, total, page, pageSize); } /** * 获取成员范围内的排名 * @param rankingKey 排行榜Key * @param start 开始排名(从0开始) * @param end 结束排名 * @return 排名列表 */ public List getRankingByRange(String rankingKey, long start, long end) { String key = RANKING_KEY + rankingKey; Set> set = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end); List result = new ArrayList<>(); if (set != null) { long rank = start + 1; for (ZSetOperations.TypedTuple tuple : set) { RankingItem item = new RankingItem(); item.setRank(rank); item.setMember(tuple.getValue().toString()); item.setScore(tuple.getScore()); result.add(item); rank++; } } return result; } /** * 获取指定分数范围内的成员 * @param rankingKey 排行榜Key * @param minScore 最小分数 * @param maxScore 最大分数 * @return 成员列表 */ public List getRankingByScoreRange(String rankingKey, double minScore, double maxScore) { String key = RANKING_KEY + rankingKey; Set> set = redisTemplate.opsForZSet().rangeByScoreWithScores(key, minScore, maxScore); List result = new ArrayList<>(); if (set != null) { for (ZSetOperations.TypedTuple tuple : set) { RankingItem item = new RankingItem(); item.setMember(tuple.getValue().toString()); item.setScore(tuple.getScore()); result.add(item); } } return result; } /** * 移除成员 * @param rankingKey 排行榜Key * @param member 成员 * @return 是否移除成功 */ public boolean removeMember(String rankingKey, String member) { String key = RANKING_KEY + rankingKey; Long count = redisTemplate.opsForZSet().remove(key, member); log.info("移除成员: rankingKey={}, member={}, count={}", rankingKey, member, count); return count != null && count > 0; } /** * 清空排行榜 * @param rankingKey 排行榜Key */ public void clearRanking(String rankingKey) { String key = RANKING_KEY + rankingKey; redisTemplate.delete(key); log.info("清空排行榜: rankingKey={}", rankingKey); } /** * 获取排行榜总数 * @param rankingKey 排行榜Key * @return 总数 */ public Long getRankingSize(String rankingKey) { String key = RANKING_KEY + rankingKey; return redisTemplate.opsForZSet().size(key); } } ``` ### 4.3 排行榜实体类 ```java /** * 排行榜项 */ @Data public class RankingItem { /** * 排名 */ private Long rank; /** * 成员 */ private String member; /** * 分数 */ private Double score; } /** * 分页结果 */ @Data @AllArgsConstructor public class PageResult { /** * 数据列表 */ private List list; /** * 总数 */ private Long total; /** * 当前页 */ private Integer page; /** * 每页数量 */ private Integer pageSize; /** * 总页数 */ public Integer getTotalPages() { return pageSize == 0 ? 0 : (int) Math.ceil((double) total / pageSize); } } ``` ### 4.4 实际应用示例 ```java /** * 积分排行榜应用 */ @Service @Slf4j public class PointsRankingService { @Autowired private RankingService rankingService; private static final String POINTS_RANKING_KEY = "points"; /** * 增加用户积分 */ public void addPoints(Long userId, Integer points) { String member = "user:" + userId; rankingService.incrementScore(POINTS_RANKING_KEY, member, points); } /** * 获取用户排名 */ public Long getUserRank(Long userId) { String member = "user:" + userId; return rankingService.getRank(POINTS_RANKING_KEY, member); } /** * 获取Top 10 */ public List getTop10() { return rankingService.getTopN(POINTS_RANKING_KEY, 10); } /** * 获取用户积分 */ public Double getUserPoints(Long userId) { String member = "user:" + userId; return rankingService.getScore(POINTS_RANKING_KEY, member); } } ``` --- ## 5. 项目实战四:Session共享与会话管理 ### 5.1 项目背景 在分布式系统中,多个服务实例需要共享Session,实现单点登录和会话管理。 ### 5.2 Session管理实现 ```java /** * Redis Session管理器 */ @Component @Slf4j public class RedisSessionManager { @Autowired private RedisTemplate redisTemplate; /** * Session Key */ private static final String SESSION_KEY = "session:"; /** * Session过期时间(秒) */ private static final long SESSION_EXPIRE_TIME = 1800; // 30分钟 /** * 创建Session * @param userId 用户ID * @param sessionData Session数据 * @return Session ID */ public String createSession(Long userId, Map sessionData) { String sessionId = generateSessionId(); String key = SESSION_KEY + sessionId; // 构建Session对象 Session session = new Session(); session.setSessionId(sessionId); session.setUserId(userId); session.setData(sessionData); session.setCreateTime(LocalDateTime.now()); session.setUpdateTime(LocalDateTime.now()); // 保存到Redis redisTemplate.opsForValue().set(key, JSON.toJSONString(session), SESSION_EXPIRE_TIME, TimeUnit.SECONDS); log.info("创建Session: sessionId={}, userId={}", sessionId, userId); return sessionId; } /** * 获取Session * @param sessionId Session ID * @return Session对象 */ public Session getSession(String sessionId) { String key = SESSION_KEY + sessionId; String json = redisTemplate.opsForValue().get(key); if (json != null) { Session session = JSON.parseObject(json, Session.class); // 刷新过期时间 redisTemplate.expire(key, SESSION_EXPIRE_TIME, TimeUnit.SECONDS); return session; } return null; } /** * 更新Session数据 * @param sessionId Session ID * @param sessionData Session数据 * @return 是否更新成功 */ public boolean updateSession(String sessionId, Map sessionData) { String key = SESSION_KEY + sessionId; Session session = getSession(sessionId); if (session == null) { return false; } session.setData(sessionData); session.setUpdateTime(LocalDateTime.now()); redisTemplate.opsForValue().set(key, JSON.toJSONString(session), SESSION_EXPIRE_TIME, TimeUnit.SECONDS); log.info("更新Session: sessionId={}", sessionId); return true; } /** * 删除Session(登出) * @param sessionId Session ID * @return 是否删除成功 */ public boolean deleteSession(String sessionId) { String key = SESSION_KEY + sessionId; Boolean result = redisTemplate.delete(key); if (Boolean.TRUE.equals(result)) { log.info("删除Session: sessionId={}", sessionId); } return Boolean.TRUE.equals(result); } /** * 刷新Session过期时间 * @param sessionId Session ID * @return 是否刷新成功 */ public boolean refreshSession(String sessionId) { String key = SESSION_KEY + sessionId; Boolean result = redisTemplate.expire(key, SESSION_EXPIRE_TIME, TimeUnit.SECONDS); if (Boolean.TRUE.equals(result)) { log.info("刷新Session: sessionId={}", sessionId); } return Boolean.TRUE.equals(result); } /** * 获取用户所有Session * @param userId 用户ID * @return Session列表 */ public List getUserSessions(Long userId) { String pattern = SESSION_KEY + "*"; Set keys = redisTemplate.keys(pattern); List sessions = new ArrayList<>(); if (keys != null) { for (String key : keys) { String json = redisTemplate.opsForValue().get(key); if (json != null) { Session session = JSON.parseObject(json, Session.class); if (session.getUserId().equals(userId)) { sessions.add(session); } } } } return sessions; } /** * 删除用户所有Session(踢出所有设备) * @param userId 用户ID * @return 删除数量 */ public long deleteUserSessions(Long userId) { List sessions = getUserSessions(userId); long count = 0; for (Session session : sessions) { if (deleteSession(session.getSessionId())) { count++; } } log.info("删除用户所有Session: userId={}, count={}", userId, count); return count; } /** * 生成Session ID * @return Session ID */ private String generateSessionId() { return UUID.randomUUID().toString().replace("-", ""); } } ``` ### 5.3 Session实体类 ```java /** * Session实体 */ @Data public class Session implements Serializable { /** * Session ID */ private String sessionId; /** * 用户ID */ private Long userId; /** * Session数据 */ private Map data; /** * 创建时间 */ private LocalDateTime createTime; /** * 更新时间 */ private LocalDateTime updateTime; } ``` ### 5.4 拦截器实现 ```java /** * Session拦截器 */ @Component @Slf4j public class SessionInterceptor implements HandlerInterceptor { @Autowired private RedisSessionManager sessionManager; private static final String SESSION_HEADER = "X-Session-Id"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取Session ID String sessionId = request.getHeader(SESSION_HEADER); if (sessionId == null || sessionId.isEmpty()) { sessionId = request.getParameter("sessionId"); } if (sessionId == null || sessionId.isEmpty()) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("未登录"); return false; } // 验证Session Session session = sessionManager.getSession(sessionId); if (session == null) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("Session已过期"); return false; } // 将Session放入请求属性 request.setAttribute("session", session); request.setAttribute("userId", session.getUserId()); return true; } } ``` --- ## 6. 项目实战五:消息队列与发布订阅 ### 6.1 Redis消息队列实现 ```java /** * Redis消息队列服务 */ @Service @Slf4j public class RedisMessageQueue { @Autowired private RedisTemplate redisTemplate; /** * 队列Key */ private static final String QUEUE_KEY = "queue:"; /** * 发送消息(左侧插入) * @param queueName 队列名称 * @param message 消息内容 */ public void send(String queueName, Object message) { String key = QUEUE_KEY + queueName; redisTemplate.opsForList().leftPush(key, message); log.info("发送消息: queueName={}, message={}", queueName, message); } /** * 接收消息(右侧弹出) * @param queueName 队列名称 * @return 消息内容 */ public Object receive(String queueName) { String key = QUEUE_KEY + queueName; Object message = redisTemplate.opsForList().rightPop(key); if (message != null) { log.info("接收消息: queueName={}, message={}", queueName, message); } return message; } /** * 阻塞接收消息 * @param queueName 队列名称 * @param timeout 超时时间(秒) * @return 消息内容 */ public Object receiveBlocking(String queueName, long timeout) { String key = QUEUE_KEY + queueName; Object message = redisTemplate.opsForList().rightPop(key, timeout, TimeUnit.SECONDS); if (message != null) { log.info("接收消息: queueName={}, message={}", queueName, message); } return message; } /** * 批量发送消息 * @param queueName 队列名称 * @param messages 消息列表 */ public void sendBatch(String queueName, List messages) { String key = QUEUE_KEY + queueName; redisTemplate.opsForList().leftPushAll(key, messages); log.info("批量发送消息: queueName={}, count={}", queueName, messages.size()); } /** * 获取队列长度 * @param queueName 队列名称 * @return 队列长度 */ public Long getQueueLength(String queueName) { String key = QUEUE_KEY + queueName; return redisTemplate.opsForList().size(key); } /** * 清空队列 * @param queueName 队列名称 */ public void clearQueue(String queueName) { String key = QUEUE_KEY + queueName; redisTemplate.delete(key); log.info("清空队列: queueName={}", queueName); } } ``` ### 6.2 发布订阅实现 ```java /** * Redis发布订阅服务 */ @Service @Slf4j public class RedisPubSubService { @Autowired private RedisTemplate redisTemplate; /** * 发布消息 * @param channel 频道 * @param message 消息 */ public void publish(String channel, Object message) { redisTemplate.convertAndSend(channel, message); log.info("发布消息: channel={}, message={}", channel, message); } } /** * Redis消息监听器 */ @Component @Slf4j public class RedisMessageListener implements MessageListener { @Override public void onMessage(Message message, byte[] pattern) { try { String channel = new String(message.getChannel()); String body = new String(message.getBody()); log.info("收到消息: channel={}, message={}", channel, body); // 处理消息 handleMessage(channel, body); } catch (Exception e) { log.error("处理消息失败", e); } } private void handleMessage(String channel, String body) { switch (channel) { case "order.created": handleOrderCreated(body); break; case "user.registered": handleUserRegistered(body); break; default: log.warn("未知频道: {}", channel); } } private void handleOrderCreated(String body) { // 处理订单创建 } private void handleUserRegistered(String body) { // 处理用户注册 } } /** * Redis监听器配置 */ @Configuration public class RedisListenerConfig { @Bean public RedisMessageListenerContainer redisContainer( RedisConnectionFactory connectionFactory, RedisMessageListener redisMessageListener) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); // 订阅频道 container.addMessageListener(redisMessageListener, new ChannelTopic("order.created")); container.addMessageListener(redisMessageListener, new ChannelTopic("user.registered")); return container; } } ``` ### 6.3 延迟队列实现 ```java /** * Redis延迟队列 */ @Service @Slf4j public class RedisDelayQueue { @Autowired private RedisTemplate redisTemplate; /** * 延迟队列Key */ private static final String DELAY_QUEUE_KEY = "delay:queue:"; /** * 添加延迟任务 * @param queueName 队列名称 * @param taskId 任务ID * @param delayTime 延迟时间(毫秒) * @param taskData 任务数据 */ public void addDelayTask(String queueName, String taskId, long delayTime, Object taskData) { String key = DELAY_QUEUE_KEY + queueName; // 计算执行时间戳 long executeTime = System.currentTimeMillis() + delayTime; // 使用ZSet存储延迟任务,score为执行时间戳 redisTemplate.opsForZSet().add(key, taskId, executeTime); // 保存任务数据 String dataKey = "delay:task:" + taskId; redisTemplate.opsForValue().set(dataKey, JSON.toJSONString(taskData), delayTime / 1000 + 3600, TimeUnit.SECONDS); log.info("添加延迟任务: queueName={}, taskId={}, delayTime={}", queueName, taskId, delayTime); } /** * 获取到期的任务 * @param queueName 队列名称 * @return 任务ID列表 */ public List getExpiredTasks(String queueName) { String key = DELAY_QUEUE_KEY + queueName; // 获取当前时间戳之前的所有任务 long now = System.currentTimeMillis(); Set tasks = redisTemplate.opsForZSet().rangeByScore(key, 0, now); List taskIds = new ArrayList<>(); if (tasks != null) { for (Object task : tasks) { taskIds.add(task.toString()); } } return taskIds; } /** * 移除任务 * @param queueName 队列名称 * @param taskId 任务ID * @return 是否移除成功 */ public boolean removeTask(String queueName, String taskId) { String key = DELAY_QUEUE_KEY + queueName; Long count = redisTemplate.opsForZSet().remove(key, taskId); // 删除任务数据 String dataKey = "delay:task:" + taskId; redisTemplate.delete(dataKey); log.info("移除延迟任务: queueName={}, taskId={}", queueName, taskId); return count != null && count > 0; } /** * 获取任务数据 * @param taskId 任务ID * @param clazz 数据类型 * @return 任务数据 */ public T getTaskData(String taskId, Class clazz) { String dataKey = "delay:task:" + taskId; String json = redisTemplate.opsForValue().get(dataKey); if (json != null) { return JSON.parseObject(json, clazz); } return null; } } /** * 延迟任务处理器 */ @Component @Slf4j public class DelayTaskProcessor { @Autowired private RedisDelayQueue delayQueue; /** * 处理延迟订单 */ @Scheduled(fixedDelay = 1000) public void processDelayOrder() { List taskIds = delayQueue.getExpiredTasks("order"); for (String taskId : taskIds) { try { OrderDelayTask task = delayQueue.getTaskData(taskId, OrderDelayTask.class); // 处理任务 handleDelayOrder(task); // 移除任务 delayQueue.removeTask("order", taskId); } catch (Exception e) { log.error("处理延迟订单失败: taskId={}", taskId, e); } } } private void handleDelayOrder(OrderDelayTask task) { log.info("处理延迟订单: orderId={}", task.getOrderId()); // 取消订单、释放库存等 } } ``` --- ## 7. 项目实战六:缓存预热与更新策略 ### 7.1 缓存预热实现 ```java /** * 缓存预热服务 */ @Service @Slf4j public class CacheWarmUpService { @Autowired private RedisTemplate redisTemplate; @Autowired private ProductRepository productRepository; /** * 缓存Key */ private static final String PRODUCT_CACHE_KEY = "product:"; private static final String HOT_PRODUCT_KEY = "hot:product"; /** * 预热热门商品缓存 */ public void warmUpHotProducts(int count) { log.info("开始预热热门商品缓存: count={}", count); // 查询热门商品(按销量排序) List products = productRepository.findTopByOrderBySalesDesc(count); for (Product product : products) { // 缓存商品信息 String key = PRODUCT_CACHE_KEY + product.getId(); redisTemplate.opsForValue().set(key, product, 1, TimeUnit.HOURS); // 添加到热门商品列表 redisTemplate.opsForZSet().add(HOT_PRODUCT_KEY, product.getId(), product.getSales()); log.info("缓存热门商品: productId={}, sales={}", product.getId(), product.getSales()); } log.info("热门商品缓存预热完成: count={}", products.size()); } /** * 预热指定商品缓存 */ public void warmUpProducts(List productIds) { log.info("开始预热指定商品缓存: count={}", productIds.size()); for (Long productId : productIds) { Product product = productRepository.findById(productId).orElse(null); if (product != null) { String key = PRODUCT_CACHE_KEY + productId; redisTemplate.opsForValue().set(key, product, 1, TimeUnit.HOURS); log.info("缓存商品: productId={}", productId); } } log.info("指定商品缓存预热完成"); } /** * 预热分类商品缓存 */ public void warmUpCategoryProducts(Long categoryId) { log.info("开始预热分类商品缓存: categoryId={}", categoryId); List products = productRepository.findByCategoryId(categoryId); for (Product product : products) { String key = PRODUCT_CACHE_KEY + product.getId(); redisTemplate.opsForValue().set(key, product, 1, TimeUnit.HOURS); } log.info("分类商品缓存预热完成: categoryId={}, count={}", categoryId, products.size()); } } ``` ### 7.2 缓存更新策略 ```java /** * 缓存更新服务 */ @Service @Slf4j public class CacheUpdateService { @Autowired private RedisTemplate redisTemplate; @Autowired private ProductRepository productRepository; /** * 缓存Key */ private static final String PRODUCT_CACHE_KEY = "product:"; /** * Cache Aside模式:先更新数据库,再删除缓存 */ @Transactional public void updateProductWithCacheAside(Product product) { try { // 1. 更新数据库 productRepository.save(product); // 2. 删除缓存 String cacheKey = PRODUCT_CACHE_KEY + product.getId(); redisTemplate.delete(cacheKey); log.info("更新商品并删除缓存: productId={}", product.getId()); } catch (Exception e) { log.error("更新商品失败: productId={}", product.getId(), e); throw e; } } /** * Write Through模式:写时更新缓存 */ @Transactional public void updateProductWithWriteThrough(Product product) { try { // 1. 更新数据库 productRepository.save(product); // 2. 更新缓存 String cacheKey = PRODUCT_CACHE_KEY + product.getId(); redisTemplate.opsForValue().set(cacheKey, product, 1, TimeUnit.HOURS); log.info("更新商品并更新缓存: productId={}", product.getId()); } catch (Exception e) { log.error("更新商品失败: productId={}", product.getId(), e); throw e; } } /** * Write Behind模式:异步更新缓存 */ @Async public void updateProductWithWriteBehind(Product product) { try { // 1. 更新数据库 productRepository.save(product); // 2. 异步更新缓存(使用消息队列) // rabbitTemplate.convertAndSend("cache.update.queue", product); log.info("异步更新缓存: productId={}", product.getId()); } catch (Exception e) { log.error("异步更新缓存失败: productId={}", product.getId(), e); } } /** * 删除缓存 */ public void deleteCache(Long productId) { String cacheKey = PRODUCT_CACHE_KEY + productId; redisTemplate.delete(cacheKey); log.info("删除缓存: productId={}", productId); } /** * 批量删除缓存 */ public void deleteCacheBatch(List productIds) { List keys = productIds.stream() .map(id -> PRODUCT_CACHE_KEY + id) .collect(Collectors.toList()); redisTemplate.delete(keys); log.info("批量删除缓存: count={}", productIds.size()); } } ``` ### 7.3 缓存穿透、击穿、雪崩解决方案 ```java /** * 缓存问题解决方案 */ @Service @Slf4j public class CacheProtectionService { @Autowired private RedisTemplate redisTemplate; @Autowired private ProductRepository productRepository; private static final String PRODUCT_CACHE_KEY = "product:"; private static final String NULL_CACHE_KEY = "null:product:"; private static final String LOCK_KEY = "lock:product:"; /** * 解决缓存穿透:缓存空值 */ public Product getProductWithNullCache(Long productId) { String cacheKey = PRODUCT_CACHE_KEY + productId; // 尝试从缓存获取 Product product = (Product) redisTemplate.opsForValue().get(cacheKey); if (product != null) { return product; } // 检查是否是空值缓存 String nullCacheKey = NULL_CACHE_KEY + productId; Boolean isNull = redisTemplate.hasKey(nullCacheKey); if (Boolean.TRUE.equals(isNull)) { return null; // 缓存中存在空值,直接返回null } // 从数据库查询 product = productRepository.findById(productId).orElse(null); if (product != null) { // 缓存商品信息 redisTemplate.opsForValue().set(cacheKey, product, 1, TimeUnit.HOURS); } else { // 缓存空值(设置较短的过期时间) redisTemplate.opsForValue().set(nullCacheKey, "", 5, TimeUnit.MINUTES); } return product; } /** * 解决缓存击穿:互斥锁 */ public Product getProductWithLock(Long productId) { String cacheKey = PRODUCT_CACHE_KEY + productId; String lockKey = LOCK_KEY + productId; // 尝试从缓存获取 Product product = (Product) redisTemplate.opsForValue().get(cacheKey); if (product != null) { return product; } // 获取锁 String requestId = UUID.randomUUID().toString(); boolean locked = redisTemplate.opsForValue().setIfAbsent( lockKey, requestId, 30, TimeUnit.SECONDS); if (Boolean.TRUE.equals(locked)) { try { // 双重检查 product = (Product) redisTemplate.opsForValue().get(cacheKey); if (product != null) { return product; } // 从数据库查询 product = productRepository.findById(productId).orElse(null); if (product != null) { // 缓存商品信息 redisTemplate.opsForValue().set(cacheKey, product, 1, TimeUnit.HOURS); } return product; } finally { // 释放锁 String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " + " return redis.call('del', KEYS[1]) " + "else " + " return 0 " + "end"; DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptText(luaScript); redisScript.setResultType(Long.class); redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId); } } else { // 获取锁失败,短暂休眠后重试 try { Thread.sleep(100); return getProductWithLock(productId); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return null; } } } /** * 解决缓存雪崩:随机过期时间 */ public void cacheProductWithRandomExpire(Product product) { String cacheKey = PRODUCT_CACHE_KEY + product.getId(); // 随机过期时间:30分钟到60分钟之间 Random random = new Random(); long expireTime = 1800 + random.nextInt(1800); redisTemplate.opsForValue().set(cacheKey, product, expireTime, TimeUnit.SECONDS); log.info("缓存商品(随机过期时间): productId={}, expireTime={}", product.getId(), expireTime); } } ``` --- ## 8. Redis性能监控与优化 ### 8.1 性能监控 ```java /** * Redis性能监控服务 */ @Service @Slf4j public class RedisMonitorService { @Autowired private RedisTemplate redisTemplate; /** * 获取Redis信息 */ public Map getRedisInfo() { return redisTemplate.execute((RedisCallback>) connection -> { Properties info = connection.info(); Map result = new HashMap<>(); for (String key : info.stringPropertyNames()) { result.put(key, info.getProperty(key)); } return result; }); } /** * 获取内存使用情况 */ public Map getMemoryInfo() { Map info = getRedisInfo(); Map memoryInfo = new HashMap<>(); memoryInfo.put("used_memory", info.get("used_memory")); memoryInfo.put("used_memory_human", info.get("used_memory_human")); memoryInfo.put("used_memory_rss", info.get("used_memory_rss")); memoryInfo.put("used_memory_peak", info.get("used_memory_peak")); memoryInfo.put("used_memory_peak_human", info.get("used_memory_peak_human")); memoryInfo.put("maxmemory", info.get("maxmemory")); memoryInfo.put("mem_fragmentation_ratio", info.get("mem_fragmentation_ratio")); return memoryInfo; } /** * 获取连接数 */ public Integer getConnectedClients() { Map info = getRedisInfo(); String clients = (String) info.get("connected_clients"); return Integer.parseInt(clients); } /** * 获取命令统计 */ public Map getCommandStats() { Map info = getRedisInfo(); return (Map) info.get("commandstats"); } /** * 慢查询日志 */ public List getSlowLog(int count) { return redisTemplate.execute((RedisCallback>) connection -> { List slowlogs = connection.slowLogGet(count); List result = new ArrayList<>(); for (Slowlog slowlog : slowlogs) { result.add(String.format("ID=%d, Time=%d, Command=%s", slowlog.getId(), slowlog.getExecutionTime(), Arrays.toString(slowlog.getArguments()))); } return result; }); } /** * 获取数据库大小 */ public Map getDbSize() { return redisTemplate.execute((RedisCallback>) connection -> { Map result = new HashMap<>(); Properties info = connection.info("keyspace"); for (String key : info.stringPropertyNames()) { if (key.startsWith("db")) { int dbIndex = Integer.parseInt(key.substring(2)); String[] parts = info.getProperty(key).split("="); long keys = Long.parseLong(parts[1]); result.put(dbIndex, keys); } } return result; }); } } ``` ### 8.2 性能优化建议 ```java /** * Redis性能优化配置 */ @Configuration public class RedisPerformanceConfig { /** * Redis连接池配置 */ @Bean public LettuceConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); config.setHostName("localhost"); config.setPort(6379); config.setPassword("yourpassword"); LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() .commandTimeout(Duration.ofSeconds(10)) .shutdownTimeout(Duration.ofSeconds(10)) .poolConfig(redisPoolConfig()) .build(); return new LettuceConnectionFactory(config, clientConfig); } /** * 连接池配置 */ @Bean public GenericObjectPoolConfig redisPoolConfig() { GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig<>(); poolConfig.setMaxTotal(20); // 最大连接数 poolConfig.setMaxIdle(10); // 最大空闲连接数 poolConfig.setMinIdle(5); // 最小空闲连接数 poolConfig.setMaxWaitMillis(3000); // 最大等待时间 poolConfig.setTestOnBorrow(true); // 获取连接时测试 poolConfig.setTestOnReturn(true); // 返回连接时测试 poolConfig.setTestWhileIdle(true); // 空闲时测试 poolConfig.setTimeBetweenEvictionRunsMillis(60000); // 1分钟检查一次 poolConfig.setMinEvictableIdleTimeMillis(300000); // 5分钟空闲则回收 return poolConfig; } /** * RedisTemplate配置 */ @Bean public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); // Key序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); // Value序列化 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); template.setValueSerializer(jackson2JsonRedisSerializer); template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } } ``` --- ## 9. Redis集群与高可用 ### 9.1 主从复制配置 ```bash # 主节点配置(redis-master.conf) port 6379 bind 0.0.0.0 daemonize yes requirepass yourpassword masterauth yourpassword # 从节点配置(redis-slave.conf) port 6380 bind 0.0.0.0 daemonize yes requirepass yourpassword masterauth yourpassword replicaof 127.0.0.1 6379 ``` ### 9.2 哨兵模式配置 ```bash # 哨兵配置(sentinel.conf) port 26379 daemonize yes sentinel monitor mymaster 127.0.0.1 6379 2 sentinel auth-pass mymaster yourpassword sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 60000 sentinel parallel-syncs mymaster 1 ``` ### 9.3 集群模式配置 ```bash # 集群节点配置(redis-cluster-7000.conf) port 7000 cluster-enabled yes cluster-config-file nodes-7000.conf cluster-node-timeout 15000 appendonly yes ``` ### 9.4 Spring Boot集成哨兵 ```yaml # application.yml spring: redis: sentinel: master: mymaster nodes: - 127.0.0.1:26379 - 127.0.0.1:26380 - 127.0.0.1:26381 password: yourpassword lettuce: pool: max-active: 20 max-idle: 10 min-idle: 5 ``` --- ## 10. 总结与最佳实践 ### 10.1 核心要点 1. **Redis数据结构选择** - String:缓存、计数器、Session - Hash:对象存储、购物车 - List:消息队列、最新列表 - Set:标签、共同关注 - ZSet:排行榜、延迟队列 2. **性能优化** - 使用连接池 - 合理设置过期时间 - 避免大Key - 使用Pipeline批量操作 3. **高可用** - 主从复制 - 哨兵模式 - 集群模式 ### 10.2 常见问题 ```java /** * 常见问题解决方案 */ public class CommonProblems { /** * 问题1:缓存穿透 * 解决方案: * - 缓存空值 * - 布隆过滤器 * - 互斥锁 */ /** * 问题2:缓存击穿 * 解决方案: * - 互斥锁 * - 永不过期(逻辑过期) * - 热点数据预热 */ /** * 问题3:缓存雪崩 * 解决方案: * - 随机过期时间 * - 高可用架构 * - 限流降级 */ /** * 问题4:大Key问题 * 解决方案: * - 拆分大Key * - 使用Hash存储对象 * - 定期清理 */ /** * 问题5:内存不足 * 解决方案: * - 设置maxmemory * - 配置淘汰策略 * - 使用Hash压缩 */ } ``` ### 10.3 最佳实践 1. **Key命名规范** - 使用冒号分隔:`user:1001` - 统一前缀:`app:user:1001` - 有意义的命名 2. **过期时间设置** - 根据业务需求设置 - 避免同时过期 - 使用随机过期时间 3. **监控告警** - 监控内存使用 - 监控慢查询 - 监控连接数 4. **备份与恢复** - 定期RDB备份 - AOF持久化 - 跨机房复制 --- ## 结语 通过本文的学习,你应该已经掌握了: 1. **Redis应用场景**:秒杀、分布式锁、排行榜、Session管理等 2. **核心数据结构**:String、Hash、List、Set、ZSet的实际应用 3. **性能优化**:缓存预热、更新策略、问题解决方案 4. **高可用架构**:主从复制、哨兵模式、集群模式 Redis是一个功能强大的内存数据库,合理使用可以大幅提升系统性能。继续深入学习,你会发现更多精彩的应用场景!
评论 0

发表评论 取消回复

Shift+Enter 换行  ·  Enter 发送
还没有评论,来发表第一条吧