【 Redis | 实战篇 秒杀优化 】

Redis 秒杀优化实战

秒杀场景的挑战

秒杀活动是高并发场景的典型代表,主要面临以下挑战:

  1. 高并发访问:短时间内大量用户涌入
  2. 库存超卖:库存扣减的原子性问题
  3. 系统性能:数据库压力过大
  4. 公平性:防止机器人抢购

Redis 优化方案

1. 页面静态化 + CDN 加速

java 复制代码
// 使用 Thymeleaf 或其他模板引擎提前生成静态页面
@GetMapping("/seckill/item/{id}")
public String getSeckillItem(@PathVariable Long id, Model model) {
    // 从Redis获取商品信息
    SeckillItem item = redisService.get("seckill:item:" + id);
    model.addAttribute("item", item);
    return "seckill_item_static";
}

2. Redis 预减库存

java 复制代码
// 活动开始前将库存加载到Redis
public void loadSeckillItemToRedis(Long itemId, Integer stock) {
    String key = "seckill:stock:" + itemId;
    redisTemplate.opsForValue().set(key, stock);
}

3. 内存标记 + Redis 原子操作

java 复制代码
// 使用本地内存标记减少Redis访问
private Map<Long, Boolean> localOverMap = new ConcurrentHashMap<>();

public boolean seckill(Long userId, Long itemId) {
    // 1. 检查内存标记
    if (localOverMap.get(itemId) != null) {
        return false;
    }
    
    // 2. Redis预减库存
    Long stock = redisTemplate.opsForValue().decrement("seckill:stock:" + itemId);
    if (stock == null || stock < 0) {
        localOverMap.put(itemId, true);
        return false;
    }
    
    // 3. 进入下单队列
    SeckillMessage message = new SeckillMessage(userId, itemId);
    rabbitTemplate.convertAndSend("seckill.exchange", "seckill.routingKey", message);
    
    return true;
}

4. Lua 脚本保证原子性

lua 复制代码
-- seckill.lua
local userId = KEYS[1]
local itemId = KEYS[2]
local stockKey = "seckill:stock:" .. itemId
local orderKey = "seckill:order:" .. itemId

-- 检查库存
local stock = tonumber(redis.call('get', stockKey))
if stock <= 0 then
    return 0
end

-- 检查是否已购买
if redis.call('sismember', orderKey, userId) == 1 then
    return 1
end

-- 扣减库存并记录订单
redis.call('decr', stockKey)
redis.call('sadd', orderKey, userId)
return 2

Java 调用 Lua 脚本:

java 复制代码
public class SeckillScript {
    private static final String SECKILL_SCRIPT =
        "local userId = KEYS[1]\n" +
        "local itemId = KEYS[2]\n" +
        // ... 上面Lua脚本内容
        
    public static final RedisScript<Long> script = 
        new DefaultRedisScript<>(SECKILL_SCRIPT, Long.class);
}

// 使用脚本
Long result = redisTemplate.execute(
    SeckillScript.script,
    Arrays.asList(userId.toString(), itemId.toString())
);

5. 消息队列异步处理

java 复制代码
@RabbitListener(queues = "seckill.queue")
public void processSeckillOrder(SeckillMessage message) {
    Long userId = message.getUserId();
    Long itemId = message.getItemId();
    
    // 1. 数据库校验库存
    SeckillItem item = seckillItemMapper.selectById(itemId);
    if (item.getStock() <= 0) {
        return;
    }
    
    // 2. 检查是否重复购买
    SeckillOrder exist = seckillOrderMapper.findByUserIdAndItemId(userId, itemId);
    if (exist != null) {
        return;
    }
    
    // 3. 创建订单
    createOrder(userId, item);
}

6. 分布式锁防止重复提交

java 复制代码
public boolean trySeckillLock(Long userId, Long itemId) {
    String lockKey = "seckill:lock:" + userId + ":" + itemId;
    String value = UUID.randomUUID().toString();
    
    Boolean success = redisTemplate.opsForValue().setIfAbsent(
        lockKey, value, 10, TimeUnit.SECONDS);
    
    if (Boolean.TRUE.equals(success)) {
        try {
            // 执行业务逻辑
            return true;
        } finally {
            // 释放锁时要验证value,防止误删其他线程的锁
            if (value.equals(redisTemplate.opsForValue().get(lockKey))) {
                redisTemplate.delete(lockKey);
            }
        }
    }
    return false;
}

完整优化流程

  1. 系统初始化:将秒杀商品库存加载到Redis
  2. 请求入口
    • 先查内存标记 → 快速失败
    • 执行Lua脚本 → 原子性操作
  3. 异步下单
    • 通过消息队列削峰
    • 后台服务处理订单
  4. 结果返回
    • 轮询查询订单结果
    • 或使用WebSocket推送结果

监控与降级

  1. Redis监控:监控QPS、内存使用、连接数等
  2. 限流措施
    • 接口限流(如Guava RateLimiter)
    • Nginx层限流
  3. 降级方案
    • 活动过于火爆时展示友好提示
    • 关闭非核心服务保证秒杀进行

通过以上优化,可以极大提升秒杀系统的性能和可靠性,轻松应对高并发场景。

相关推荐
我真的是大笨蛋3 小时前
Redis的String详解
java·数据库·spring boot·redis·spring·缓存
zhengzizhe5 小时前
Redssion出现attempt to unlock lock, not locked by current thread by node id
redis
兜兜风d'9 小时前
redis字符串命令
数据库·redis·缓存
西瓜er10 小时前
Docker 一键部署指南:GitLab、Nacos、Redis、MySQL 与 MinIO 全解析
redis·docker·gitlab
道可到11 小时前
别再瞎拼技术栈!Postgres 已经能干 Redis 的活了
redis·后端·postgresql
野犬寒鸦11 小时前
从零起步学习Redis || 第十二章:Redis Cluster集群如何解决Redis单机模式的性能瓶颈及高可用分布式部署方案详解
java·数据库·redis·后端·缓存
悟能不能悟21 小时前
redis的红锁
数据库·redis·缓存
qq_5470261791 天前
SpringBoot+Redis实现电商秒杀方案
spring boot·redis·后端
野犬寒鸦1 天前
从零起步学习Redis || 第十一章:主从切换时的哨兵机制如何实现及项目实战
java·服务器·数据库·redis·后端·缓存
problc1 天前
PostgreSQL + Redis + Elasticsearch 实时同步方案实践:从触发器到高性能搜索
redis·elasticsearch·postgresql