Redis 应用场景及常见问题

1. 引言

Redis(Remote Dictionary Server)是一种高性能的 NoSQL 数据库,主要基于内存存储,提供了丰富的数据结构,适用于多种应用场景,如缓存、分布式锁、计数器、消息队列等。

本文将详细介绍 Redis 的核心应用场景,并结合代码示例进行说明。


2. Redis 典型应用场景

2.1 缓存(Cache)

应用场景:

  • 热点数据缓存:如商品详情、用户信息、文章内容等。
  • 降低数据库压力:减少数据库 I/O,提高查询速度。
  • 提升系统吞吐量:通过缓存减少数据库的直接访问次数。

代码示例:

1
2
3
4
5
6
// 存储数据到 Redis 缓存
stringRedisTemplate.opsForValue().set("user:1001", "{\"name\":\"张三\", \"age\":25}", 10, TimeUnit.MINUTES);

// 从 Redis 获取缓存数据
String userData = stringRedisTemplate.opsForValue().get("user:1001");
System.out.println("用户数据:" + userData);

优化方案:

  • LRU 过期策略 适用于内存受限场景。
  • 逻辑过期 结合后台更新,防止缓存击穿。

2.2 分布式锁(Distributed Lock)

应用场景:

  • 解决 并发 资源竞争问题,如商品秒杀、库存扣减。
  • 适用于 多服务 共同访问同一资源的场景。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
RLock lock = redissonClient.getLock("lock:reduce:store");
try {
lock.lock(); // 加锁
Goods goods = goodsDao.queryById(id);
if (goods.getStore() > 0) {
goodsDao.updateStore(id);
System.out.println("减库存成功");
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock(); // 释放锁
}
}

优化方案:

  • 使用 Redisson 提供的 RLock,支持 可重入锁看门狗机制 续期。
  • 避免 死锁,释放锁前检查 isHeldByCurrentThread()

2.3 计数器(Counter)

应用场景:

  • 统计 网站 PV/UV
  • 统计 点赞数、访问量
  • 限流 控制访问频率

代码示例:

1
2
3
// 用户访问统计
Long pageViews = stringRedisTemplate.opsForValue().increment("page:view:article:1001", 1);
System.out.println("文章 1001 的访问量:" + pageViews);

优化方案:

  • 定期落盘 到 MySQL,避免数据丢失。
  • HyperLogLog 统计 UV(去重效果更好)。

2.4 全局唯一 ID(Distributed ID)

应用场景:

  • 订单号生成
  • 支付流水号
  • 分布式系统唯一标识

代码示例:

1
2
3
Long orderNum = stringRedisTemplate.opsForHash().increment("redis:only:number", "order", 1);
String orderNo = "OD" + System.currentTimeMillis() + orderNum;
System.out.println("订单号:" + orderNo);

优化方案:

  • 避免 UUID,UUID 不可读、不可排序。
  • 使用雪花算法(Snowflake) 生成更稳定的分布式 ID。

2.5 排行榜(Leaderboard)

应用场景:

  • 游戏排行榜
  • 电商销量排名
  • 社交点赞/活跃度排名

代码示例:

1
2
3
4
5
// 增加排行榜数据
stringRedisTemplate.opsForZSet().add("redis:top10", "user:15311115285", 3669);

// 获取排行榜前 5 名
Set<ZSetOperations.TypedTuple<String>> topUsers = stringRedisTemplate.opsForZSet().reverseRangeWithScores("redis:top10", 0, 4);

优化方案:

  • 使用 ZSet(跳表 SkipList) 存储排名。
  • 定期归档 历史数据,防止数据过大影响性能。

2.6 限流(Rate Limiting)

应用场景:

  • API 限流(防止恶意请求)
  • 秒杀防刷
  • 爬虫控制

代码示例:

1
2
3
4
5
6
7
for (int i = 0; i < 112; i++) {
String key = "limit:" + System.currentTimeMillis() / 1000;
Long result = stringRedisTemplate.execute(redisScript, Arrays.asList(key), "100");
if (result == 0) {
System.out.println("限流了......");
}
}

Lua 脚本:

1
2
3
4
5
6
7
8
9
10
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then
return 0
else
redis.call("INCRBY", key, "1")
redis.call("EXPIRE", key, "5")
return 1
end

优化方案:

  • 使用 Lua 脚本 保证原子性。
  • 滑动窗口限流,平滑限流效果。

3. Redis 常见问题

Redis 缓存穿透

缓存穿透定义

缓存穿透指的是请求一个不存在的数据,导致直接查询数据库,并且频繁查询数据库,造成缓存和数据库的双重压力。

解决方案

方案一:缓存空结果
对于不存在的数据,将空结果缓存一段时间,减少数据库查询频率。

  • 优点:实现简单。
  • 缺点:会缓存无效数据,占用Redis内存,可能导致缓存和数据库不一致。

方案二:布隆过滤器
布隆过滤器用于判断某个数据是否存在于集合中,适用于缓存穿透问题。布隆过滤器通过多个哈希函数和二进制数组实现高效判断。

  • 优点:不会缓存无效数据。
  • 缺点:实现较复杂,可能存在误判。

布隆过滤器工作原理

  • 添加元素时,使用多个哈希函数对元素进行哈希,计算出多个位置,并将这些位置的值设置为1。
  • 查询元素时,计算元素对应的多个位置,若所有位置均为1,则认为元素存在(可能存在误判);若有任何位置为0,则认为元素一定不存在。

布隆过滤器实现方式

  • Guava
  • Hutool
  • Redisson
  • 手写实现

缓存击穿

缓存击穿定义

缓存击穿是指当热点数据缓存失效或者没有缓存时,所有请求都会访问数据库,导致数据库负载过高。

解决方案

方案一:全局锁
在访问数据库之前,先请求全局锁,获得锁的线程才有资格访问数据库。由于分布式系统中本地锁无法控制其他服务的线程,所以要使用分布式锁。

方案二:设置热点数据永不过期
通过不设置过期时间来确保热点数据不会被删除。实现方式:

  • 方式一:Redis不设置热点数据的过期时间(物理不过期)。
  • 方式二:将过期时间设置在key的value中,发现过期后通过异步线程更新缓存(逻辑过期)。

缓存雪崩

缓存雪崩定义

缓存雪崩是指在某一时刻,大量的缓存数据同时过期,导致瞬间请求都落入数据库,进而压垮数据库。

缓存雪崩的解决方案

  • Redis高可用:通过配置Redis Sentinel或Redis Cluster,避免Redis单点故障。
  • 设置不同的过期时间:通过给不同的缓存key设置不同的过期时间,避免大量数据同时过期。
  • 本地缓存+限流和降级:在Redis不可用的情况下,通过本地缓存和限流措施保护数据库。

Redis的内存使用策略

Redis内存策略

Redis提供了多种内存淘汰策略,确保内存使用的最大限度。策略包括:

  • volatile-lru:使用LRU算法清除最近最少使用的键,仅对设置了过期时间的键有效。
  • allkeys-lru:对所有键使用LRU算法。
  • volatile-lfu:使用LFU算法清除最不常用的键,仅对设置了过期时间的键有效。
  • allkeys-lfu:对所有键使用LFU算法。
  • volatile-random:随机淘汰过期键。
  • allkeys-random:对所有键随机淘汰。
  • volatile-ttl:根据TTL(过期时间)淘汰。
  • noeviction:不淘汰任何键,返回错误。

Redis集群能部署多少个主节点

从 Redis 3.0 版本开始,Redis支持集群模式,通过槽位(slot)来实现数据分片。
Redis集群支持最多16384个槽位,每个槽位对应一个主节点。所以最多支持16384个主节点。

redis

Redis与数据库一致性

如何保证数据库与Redis的一致性?

当数据库进行增删改操作时,需要确保Redis缓存中的数据与数据库中的数据保持一致,避免数据不一致的问题。

  • 缓存更新策略:当数据库更新时,需要主动更新Redis中的缓存数据。
  • 双写一致性:在高并发环境下,可以通过分布式事务、消息队列等机制来保证数据库与缓存的一致性。
  • 缓存失效机制:设置合理的缓存过期时间,定期从数据库同步数据到缓存中,避免因缓存过期导致的一致性问题。

** 延迟双删策略 **
redis