假设 Redis 依赖:
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Redis 配置:
XML
spring:
redis:
host: localhost
port: 6379
一、缓存(Cache)
场景:
查询商品信息,先查缓存,没有再查数据库
代码:
java
@Service
public class ProductService {
@Autowired
private StringRedisTemplate redisTemplate;
public String getProduct(Long id) {
String key = "product:" + id;
// 1 查询缓存
String product = redisTemplate.opsForValue().get(key);
if (product != null) {
return product;
}
// 2 查询数据库(模拟)
product = queryProductFromDB(id);
// 3 写入缓存
redisTemplate.opsForValue().set(key, product, 10, TimeUnit.MINUTES);
return product;
}
private String queryProductFromDB(Long id) {
return "商品信息:" + id;
}
}
Redis 数据:
product:1001 -> 商品信息
优点:
减少数据库压力 提高查询速度
二、消息队列(简单队列)
Redis 可以用 List 实现简单 MQ。
生产者:
java
@Service
public class MessageProducer {
@Autowired
private StringRedisTemplate redisTemplate;
public void sendMessage(String msg) {
redisTemplate.opsForList().leftPush("order_queue", msg);
System.out.println("发送消息:" + msg);
}
}
消费者:
java
@Service
public class MessageConsumer {
@Autowired
private StringRedisTemplate redisTemplate;
@PostConstruct
public void consume() {
new Thread(() -> {
while (true) {
String msg = redisTemplate.opsForList()
.rightPop("order_queue", 10, TimeUnit.SECONDS);
if (msg != null) {
System.out.println("消费消息:" + msg);
}
}
}).start();
}
}
Redis 结构:
List: order_queue
生产消费流程:
Producer -> LPUSH Consumer -> BRPOP
三、排行榜(Leaderboard)
Redis Sorted Set 非常适合排行榜。
场景:
游戏积分排行榜
代码:
java
@Service
public class RankService {
@Autowired
private StringRedisTemplate redisTemplate;
// 添加用户分数
public void addScore(String userId, double score) {
redisTemplate.opsForZSet()
.add("game_rank", userId, score);
}
// 获取排行榜前10
public Set<ZSetOperations.TypedTuple<String>> top10() {
return redisTemplate.opsForZSet()
.reverseRangeWithScores("game_rank", 0, 9);
}
// 查询用户排名
public Long getRank(String userId) {
return redisTemplate.opsForZSet()
.reverseRank("game_rank", userId);
}
}
Redis 数据:
ZSET game_rank user1 1000 user2 900 user3 800
排行榜获取:
ZRANGE game_rank 0 9 WITHSCORES
四、会话存储(Session)
常见于:
用户登录状态 Token Session
代码:
java
@Service
public class SessionService {
@Autowired
private StringRedisTemplate redisTemplate;
public String login(Long userId) {
String token = UUID.randomUUID().toString();
String key = "login:token:" + token;
redisTemplate.opsForValue()
.set(key, userId.toString(), 30, TimeUnit.MINUTES);
return token;
}
public String getUser(String token) {
String key = "login:token:" + token;
return redisTemplate.opsForValue().get(key);
}
public void logout(String token) {
redisTemplate.delete("login:token:" + token);
}
}
Redis 数据:
login:token:xxx -> userId
访问流程:
登录 ↓ 生成 token ↓ 存入 Redis ↓ 后续请求带 token ↓ Redis 校验
五、真实互联网系统 Redis 使用比例
常见场景占比大概:
缓存 60% 分布式锁 10% 排行榜 10% 消息队列 5% Session 10% 限流 5%
六、缓存穿透(布隆过滤器)
问题:
请求不存在的数据 缓存没有 数据库没有 每次都打到数据库
解决:
BloomFilter
代码:
java
@Service
public class ProductService {
@Autowired
private StringRedisTemplate redisTemplate;
private BloomFilter<Long> bloomFilter =
BloomFilter.create(Funnels.longFunnel(), 1000000);
@PostConstruct
public void init() {
// 假设初始化数据库已有ID
for(long i = 1; i <= 100000; i++){
bloomFilter.put(i);
}
}
public String getProduct(Long id) {
if (!bloomFilter.mightContain(id)) {
return null;
}
String key = "product:" + id;
String value = redisTemplate.opsForValue().get(key);
if (value != null) {
return value;
}
String product = queryDB(id);
if (product == null) {
redisTemplate.opsForValue().set(key,"null",5,TimeUnit.MINUTES);
return null;
}
redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
return product;
}
private String queryDB(Long id) {
return "商品" + id;
}
}
七、缓存击穿(互斥锁)
问题:
热点key突然过期 大量请求同时查数据库
解决:
Redis互斥锁
代码:
java
public String getProduct(Long id) {
String key = "product:" + id;
String value = redisTemplate.opsForValue().get(key);
if(value != null){
return value;
}
String lockKey = "lock:product:" + id;
Boolean lock = redisTemplate.opsForValue()
.setIfAbsent(lockKey,"1",10,TimeUnit.SECONDS);
if(Boolean.TRUE.equals(lock)){
try {
value = redisTemplate.opsForValue().get(key);
if(value != null){
return value;
}
value = queryDB(id);
redisTemplate.opsForValue()
.set(key,value,30,TimeUnit.MINUTES);
}finally {
redisTemplate.delete(lockKey);
}
}else{
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
return getProduct(id);
}
return value;
}
八、缓存雪崩
问题:
大量key同一时间过期 数据库瞬间被打爆
解决:
过期时间 + 随机值
代码:
java
public void setCache(String key,String value){
int base = 30;
int random = new Random().nextInt(10);
redisTemplate.opsForValue().set(
key,
value,
base + random,
TimeUnit.MINUTES
);
}
效果:
原来: 10:00 同时过期 现在: 10:00 10:03 10:07 10:09
避免同时失效。
九、分布式锁(Redisson)
真实项目 99% 用 Redisson。
代码:
java
@Service
public class OrderService {
@Autowired
private RedissonClient redissonClient;
public void createOrder(Long userId){
RLock lock = redissonClient.getLock("order_lock:" + userId);
try{
lock.lock();
System.out.println("创建订单");
}finally {
lock.unlock();
}
}
}
Redisson内部:
SET NX PX Lua解锁 WatchDog自动续期
十、接口限流(Lua + Redis)
场景:
接口每秒最多10次请求
Lua脚本:
Lua
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("INCR", key)
redis.call("EXPIRE", key, 1)
return 1
end
Java代码:
java
public boolean limit(String ip){
String script = "...lua脚本...";
DefaultRedisScript<Long> redisScript =
new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute(
redisScript,
Collections.singletonList("limit:" + ip),
"10"
);
return result == 1;
}
请求流程:
请求 ↓ Redis计数 ↓ 超过10次 ↓ 拒绝
十一、延迟队列
Redis ZSet实现延迟任务。
场景:
订单30分钟未支付自动取消
生产者:
java
public void addDelayTask(String orderId){
long time = System.currentTimeMillis() + 30 * 60 * 1000;
redisTemplate.opsForZSet()
.add("order_delay", orderId, time);
}
消费者:
java
@PostConstruct
public void start(){
new Thread(() -> {
while(true){
Set<String> orders =
redisTemplate.opsForZSet()
.rangeByScore("order_delay",
0,
System.currentTimeMillis(),
0,
1);
if(orders == null || orders.isEmpty()){
continue;
}
String orderId = orders.iterator().next();
redisTemplate.opsForZSet().remove("order_delay",orderId);
cancelOrder(orderId);
}
}).start();
}
private void cancelOrder(String orderId){
System.out.println("取消订单:" + orderId);
}
Redis结构:
ZSET order_delay order1 1710000000 order2 1710000500
总结(互联网Redis核心场景)
| 场景 | Redis结构 |
|---|---|
| 缓存 | String |
| 布隆过滤 | BloomFilter |
| 排行榜 | ZSet |
| 消息队列 | List |
| 延迟队列 | ZSet |
| 分布式锁 | String |
| 限流 | String + Lua |