# 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