Redis案例

wen java案例 2

Redis实战案例精讲:从缓存加速到分布式锁的完整解决方案

目录导读

  1. Redis能解决哪些真实业务痛点?
  2. 电商秒杀系统的缓存设计
  3. 分布式环境下的锁实现
  4. 排行榜与计数器的实时更新
  5. 会话管理与用户状态持久化
  6. 常见陷阱与性能优化建议

Redis能解决哪些真实业务痛点?

问:为什么现代高并发系统离不开Redis?
答:因为传统关系型数据库在应对海量读写请求时,磁盘I/O和锁竞争会成为瓶颈,Redis基于内存、单线程模型、支持多种数据结构,能将响应时间从毫秒级降至微秒级。

Redis案例

  • 热点数据缓存:将数据库查询结果暂存,减少90%的DB压力。
  • 分布式锁:防止多节点重复执行同一任务。
  • 实时计数器:如微博热搜、视频播放量。
  • 会话共享:确保用户在不同服务器间登录状态不丢失。

问:新手最容易踩的坑是什么?
答:以为Redis是“万能存储”,实际上它不支持复杂联合查询,且内存有限,典型案例:有人把整个用户表存入Redis,导致内存爆炸,正确做法是只缓存高频访问的字段,如用户昵称、头像URL,而非全部数据。


电商秒杀系统的缓存设计

场景描述

某电商平台在大促期间,商品详情页的请求量是平时的100倍,如果每次都查MySQL,数据库会瞬间崩溃。
解决方案:缓存预热 + 缓存穿透防护

实现步骤

  1. 缓存预热
    活动开始前,将商品详情(价格、库存、标题)写入Redis,设置过期时间比活动时长略长。

    SET product:1001 "{price:99.9, stock:50, name:'无线耳机'}" EX 3600
  2. 缓存穿透防护
    黑客可能故意请求不存在的ID(如product:99999),导致Redis查不到直接打穿到DB。

    • 方案A:缓存空值(如product:99999 null 并设置短过期时间)。
    • 方案B:布隆过滤器,对合法ID集合建立过滤层。
  3. 缓存击穿保护
    某个热点key过期瞬间,大量请求涌入,使用互斥锁

    # Python伪代码
    def get_product(id):
        data = redis.get(f"product:{id}")
        if not data:
            if redis.setnx(f"lock:{id}", "1", ex=5):  # 获取分布式锁
                data = db.query(id)
                redis.setex(f"product:{id}", 300, data)
                redis.delete(f"lock:{id}")
            else:
                time.sleep(0.1)  # 等待并重试
                return get_product(id)
        return data

问:为什么不用数据库自带的缓存?
答:MySQL查询缓存命中率低,且表更新会清空缓存,Redis可灵活控制缓存粒度,支持更复杂的数据结构(如Hash存储商品多字段)。


分布式环境下的锁实现

场景描述

微服务架构中,三个节点同时执行定时任务“清理过期订单”,如果不用锁,每个节点都会执行一次,导致重复操作。

经典实现:SETNX + 过期时间

SET lock:order_clean 1 NX EX 30  # 成功返回OK,失败表示已有节点持有锁
  • 优点:简单,原子性。
  • 缺陷:如果持有锁的节点崩溃,锁永远不释放?——设置过期时间可兜底。
  • 风险:业务执行时间超过锁过期时间,导致锁被其他节点误抢。
    解决方案:使用Redisson框架,它自带看门狗自动续期。

进阶方案:Redlock算法(适合对一致性要求极高的场景)

  1. 向5个独立的Redis节点同时请求锁,大多数(如3个)响应成功则视为加锁成功。
  2. 获取锁的时间必须小于有效期,避免死等。

问:用数据库悲观锁代替行不行?
答:数据库行锁会阻塞其他查询,而Redis锁是应用层轻量级互斥,性能高100倍以上,但需要注意锁粒度,比如勿对资源ID的Hash键加锁,而应对具体资源ID加锁。


排行榜与计数器的实时更新

场景描述

直播平台需要显示“礼物榜Top10”和“每日观众人数”,要求实时更新,且每秒可能有上千次操作。

数据结构选型

  • 排行榜:使用有序集合(ZSET)。

    ZINCRBY leaderboard 1 user:123  # 给用户增加1个积分
    ZREVRANGE leaderboard 0 9 WITHSCORES  # 获取前10名

    时间复杂度O(log N),即使有百万用户,也能在毫秒级完成。

  • 计数器:使用INCR命令原子递增。

    INCR views:20250329:live:999  # 当日直播间观看数
    GET views:20250329:live:999  # 读取

    注意:如果数值超过2^64,改用INCRBYFLOAT或拆分多个key。

问:如果排行榜需要多维度排序(比如根据热度+时间)怎么办?
答:可以将分值拆分为(热度值 * 100 + 时间戳),但要注意时间戳对排序的影响,更灵活的做法是维护两个有序集合,用另一份数据辅助筛选。


会话管理与用户状态持久化

场景描述

用户登录后,需要跨多个微服务保持会话(Session),传统做法是存Cookie,但JS不能读写HttpOnly Cookie,且Cookie大小限制在4KB。

基于Redis的Session共享方案

  1. 生成唯一Session ID,存入Redis。
  2. 结构:
    HMSET session:abc123 token "eyJ..." user_id 1001 login_time 17123123
    EXPIRE session:abc123 7200  # 2小时过期
  3. 每个请求携带Session ID,服务端从Redis取数据。

问:如何防止Session被劫持?
答:可结合JWT(JSON Web Token),将Session ID和签名双重校验,但最安全做法是HTTPS + 短有效期 + 令牌刷新机制。


常见陷阱与性能优化建议

单节点内存无限膨胀

表现:Redis占用内存超过物理内存,开始使用Swap或触发淘汰策略导致异常。
解决

  • 设置maxmemory并配合allkeys-lru淘汰策略。
  • 对过期时间不明的key设置EXPIRE
  • 使用Redis Cluster分片,将数据分散到多节点。

Big Key问题

表现:一个Key包含百万个元素(如字符串长度超过1MB),导致阻塞其他命令。
解决

  • 压缩字符串(如用MessagePack)。
  • 拆分大集合为多个小集合,如按时间段或用户ID Hash分桶。

使用事务但未理解MULTI/EXEC局限

Redis事务不支持回滚,仅保证批量执行的原子性,若需要回滚,请使用Lua脚本或分布式事务协调器。

性能优化技巧

  • Pipeline:将多次操作批量发送,减少网络往返。
  • 连接池:避免频繁创建/销毁连接,如jedisPoollettuce
  • 关闭持久化:如果只是缓存用途,可禁用RDB/AOF以提升30%以上性能。

问:项目中怎么判断是否该使用Redis?
答:先问三个问题:是否要求毫秒级响应?数据是否允许短暂不一致?访问量是否远超数据库承载?如果都符合,就用,否则,考虑Nginx缓存或CDN。

抱歉,评论功能暂时关闭!