Redis是我们Java开发中,使用频次非常高的一个nosql数据库,数据以key-value键值对的形式存储在内存中。
redis的常用使用场景,可以做缓存,分布式锁,自增序列等
使用redis的方式和我们使用数据库的方式差不多,首先我们要在自己的本机电脑或者服务器上安装一个redis的服务器,通过我们的java客户端在程序中进行集成,然后通过客户端完成对redis的增删改查操作。redis的Java客户端类型还是很多的,常见的有jedis, redission,lettuce等,所以我们在集成的时候,我们可以选择直接集成这些原生客户端。但是在springBoot中更常见的方式是集成spring-data-redis,这是spring提供的一个专门用来操作redis的项目,封装了对redis的常用操作,里边主要封装了jedis和lettuce两个客户端。相当于是在他们的基础上加了一层门面。
一、基础配置
1. 添加依赖
XML
<!-- pom.xml -->
<dependencies>
<!-- Spring Boot Starter Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池(推荐) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Jackson JSON序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Redisson(分布式锁等高级功能) -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.2</version>
</dependency>
</dependencies>
2. 基础配置类
bash
# application.yml
spring:
redis:
# 单节点配置
host: localhost
port: 6379
password: ${REDIS_PASSWORD:} # 密码,没有可不填
database: 0 # 数据库索引 (0-15)
timeout: 2000ms # 连接超时时间
connect-timeout: 2000ms # 连接超时
# 连接池配置
lettuce:
pool:
max-active: 20 # 最大连接数
max-idle: 10 # 最大空闲连接
min-idle: 5 # 最小空闲连接
max-wait: -1ms # 获取连接最大等待时间(-1表示无限等待)
# 或者使用jedis
# jedis:
# pool:
# max-active: 20
# max-idle: 10
# min-idle: 5
# 集群配置(如果使用集群)
# cluster:
# nodes:
# - 192.168.1.101:6379
# - 192.168.1.102:6379
# - 192.168.1.103:6379
# max-redirects: 3 # 最大重定向次数
# 哨兵配置(如果使用哨兵)
# sentinel:
# master: mymaster
# nodes:
# - 192.168.1.101:26379
# - 192.168.1.102:26379
# - 192.168.1.103:26379
3. Redis配置类
java
package com.example.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
/**
* 配置自定义RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 设置序列化方式
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY
);
jacksonSerializer.setObjectMapper(om);
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// key 和 hashKey 使用 String 序列化
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// value 和 hashValue 使用 JSON 序列化
template.setValueSerializer(jacksonSerializer);
template.setHashValueSerializer(jacksonSerializer);
template.afterPropertiesSet();
return template;
}
/**
* 配置String专用的RedisTemplate
*/
@Bean
public RedisTemplate<String, String> stringRedisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
二、基础使用示例
1. 基础服务类
java
package com.example.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* ============ 通用操作 ============
*/
/**
* 设置缓存过期时间
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
log.error("设置过期时间失败 key: {}", key, e);
return false;
}
}
/**
* 获取过期时间
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*/
public boolean hasKey(String key) {
try {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
} catch (Exception e) {
log.error("判断key是否存在失败 key: {}", key, e);
return false;
}
}
/**
* 删除缓存
*/
@SuppressWarnings("unchecked")
public void delete(String... keys) {
if (keys != null && keys.length > 0) {
if (keys.length == 1) {
redisTemplate.delete(keys[0]);
} else {
redisTemplate.delete(Arrays.asList(keys));
}
}
}
/**
* 批量删除(使用模式匹配)
*/
public void deletePattern(String pattern) {
Set<String> keys = redisTemplate.keys(pattern);
if (!CollectionUtils.isEmpty(keys)) {
redisTemplate.delete(keys);
}
}
/**
* ============ String 操作 ============
*/
public void set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
} catch (Exception e) {
log.error("设置缓存失败 key: {}, value: {}", key, value, e);
}
}
public void set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
} catch (Exception e) {
log.error("设置缓存失败 key: {}, value: {}, time: {}", key, value, time, e);
}
}
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
public <T> T get(String key, Class<T> clazz) {
Object value = get(key);
return value == null ? null : clazz.cast(value);
}
/**
* 递增
*/
public long increment(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*/
public long decrement(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().decrement(key, delta);
}
/**
* ============ Hash 操作 ============
*/
public void hashSet(String key, String hashKey, Object value) {
try {
redisTemplate.opsForHash().put(key, hashKey, value);
} catch (Exception e) {
log.error("设置hash缓存失败 key: {}, hashKey: {}, value: {}",
key, hashKey, value, e);
}
}
public Object hashGet(String key, String hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}
public Map<Object, Object> hashGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
public void hashDelete(String key, Object... hashKeys) {
redisTemplate.opsForHash().delete(key, hashKeys);
}
public boolean hashHasKey(String key, String hashKey) {
return redisTemplate.opsForHash().hasKey(key, hashKey);
}
/**
* ============ List 操作 ============
*/
public void listRightPush(String key, Object value) {
redisTemplate.opsForList().rightPush(key, value);
}
public void listLeftPush(String key, Object value) {
redisTemplate.opsForList().leftPush(key, value);
}
public Object listRightPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}
public Object listLeftPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
public List<Object> listRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
public long listSize(String key) {
Long size = redisTemplate.opsForList().size(key);
return size == null ? 0 : size;
}
/**
* ============ Set 操作 ============
*/
public void setAdd(String key, Object... values) {
redisTemplate.opsForSet().add(key, values);
}
public Set<Object> setMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
public boolean setIsMember(String key, Object value) {
return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, value));
}
public long setSize(String key) {
Long size = redisTemplate.opsForSet().size(key);
return size == null ? 0 : size;
}
/**
* ============ ZSet 操作 ============
*/
public boolean zSetAdd(String key, Object value, double score) {
return Boolean.TRUE.equals(redisTemplate.opsForZSet().add(key, value, score));
}
public Set<Object> zSetRange(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
public Set<Object> zSetReverseRange(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**
* ============ 分布式锁 ============
*/
private static final String LOCK_PREFIX = "lock:";
private static final long DEFAULT_EXPIRE = 30L;
private static final long DEFAULT_WAIT = 10L;
/**
* 获取分布式锁(简单实现)
*/
public boolean tryLock(String key, String value, long expireSeconds) {
String lockKey = LOCK_PREFIX + key;
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, value, expireSeconds, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
/**
* 释放分布式锁
*/
public boolean unlock(String key, String value) {
String lockKey = LOCK_PREFIX + key;
String currentValue = (String) redisTemplate.opsForValue().get(lockKey);
if (currentValue != null && currentValue.equals(value)) {
redisTemplate.delete(lockKey);
return true;
}
return false;
}
}
2. 业务场景示例
java
package com.example.service.impl;
import com.example.model.User;
import com.example.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
@CacheConfig(cacheNames = "user") // 统一配置缓存名称
public class UserServiceImpl implements UserService {
private final RedisTemplate<String, Object> redisTemplate;
public UserServiceImpl(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 使用Spring Cache注解方式
* 自动缓存用户信息,key为 userId,value为 User对象
*/
@Override
@Cacheable(key = "#userId", unless = "#result == null")
public User getUserById(Long userId) {
log.info("从数据库查询用户: {}", userId);
// 模拟数据库查询
return findUserFromDB(userId);
}
/**
* 更新用户信息并更新缓存
*/
@Override
@CachePut(key = "#user.id")
public User updateUser(User user) {
log.info("更新用户信息: {}", user.getId());
// 模拟数据库更新
return updateUserInDB(user);
}
/**
* 删除用户并清除缓存
*/
@Override
@CacheEvict(key = "#userId")
public void deleteUser(Long userId) {
log.info("删除用户: {}", userId);
// 模拟数据库删除
deleteUserFromDB(userId);
}
/**
* 清除所有用户缓存
*/
@Override
@CacheEvict(allEntries = true)
public void clearAllCache() {
log.info("清除所有用户缓存");
}
/**
* 手动缓存操作示例
*/
public User getUserWithManualCache(Long userId) {
String cacheKey = "user:" + userId;
// 1. 先从缓存获取
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
log.info("从缓存获取用户: {}", userId);
return user;
}
// 2. 缓存未命中,查询数据库
log.info("缓存未命中,从数据库查询用户: {}", userId);
user = findUserFromDB(userId);
if (user != null) {
// 3. 写入缓存,设置过期时间
redisTemplate.opsForValue().set(
cacheKey,
user,
30, // 30分钟过期
TimeUnit.MINUTES
);
}
return user;
}
/**
* 防止缓存穿透示例
*/
public User getUserWithPenetrationProtection(Long userId) {
String cacheKey = "user:" + userId;
String nullKey = "null:user:" + userId;
// 1. 检查是否在空值缓存中
if (Boolean.TRUE.equals(redisTemplate.hasKey(nullKey))) {
log.info("命中空值缓存,直接返回null");
return null;
}
// 2. 从缓存获取
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
// 3. 缓存未命中,查询数据库
user = findUserFromDB(userId);
if (user == null) {
// 数据库也没有,缓存空值防止穿透
redisTemplate.opsForValue().set(
nullKey,
"",
5, // 较短时间,比如5分钟
TimeUnit.MINUTES
);
return null;
}
// 4. 缓存正常数据
redisTemplate.opsForValue().set(
cacheKey,
user,
30,
TimeUnit.MINUTES
);
return user;
}
/**
* 使用Redis实现分布式Session
*/
public void handleUserSession(String sessionId, User user) {
String sessionKey = "session:" + sessionId;
// 存储session
redisTemplate.opsForValue().set(
sessionKey,
user,
30, // 30分钟过期
TimeUnit.MINUTES
);
// 同时存储用户ID到session的映射
redisTemplate.opsForValue().set(
"user_session:" + user.getId(),
sessionId,
30,
TimeUnit.MINUTES
);
}
/**
* 使用Redis实现发布订阅
*/
public void publishMessage(String channel, String message) {
redisTemplate.convertAndSend(channel, message);
}
/**
* 模拟数据库操作
*/
private User findUserFromDB(Long userId) {
// 模拟数据库查询
try {
Thread.sleep(100); // 模拟查询耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return userId > 0 ? new User(userId, "用户" + userId) : null;
}
private User updateUserInDB(User user) {
// 模拟数据库更新
return user;
}
private void deleteUserFromDB(Long userId) {
// 模拟数据库删除
}
}
// 用户实体类
package com.example.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private String email;
// 需要实现Serializable接口才能被Redis序列化
}
3.序列化
redis的序列化是我们把对象存入到redis中到底以什么方式存储的。redis的序列化方式是默认的序列化方式。RedisTemplate这个类的泛型是<String,Object>,也就是他是支持写入Object对象的,那么这个对象可以是二进制数据,可以是xml也可以是json。一般情况下会使用 JSON 方式序列化成字符串,存储到 Redis 中 。
Redis本身提供了以下几种序列化的方式:
- GenericToStringSerializer: 可以将任何对象泛化为字符串并序列化
- Jackson2JsonRedisSerializer: 跟JacksonJsonRedisSerializer实际上是一样的
- JacksonJsonRedisSerializer: 序列化object对象为json字符串
- JdkSerializationRedisSerializer: 序列化java对象
- StringRedisSerializer: 简单的字符串序列化
如果我们存储的是String类型,默认使用的是StringRedisSerializer 这种序列化方式。如果我们存储的是对象,默认使用的是 JdkSerializationRedisSerializer,也就是Jdk的序列化方式(通过ObjectOutputStream和ObjectInputStream实现,缺点是我们无法直观看到存储的对象内容)。
java
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> getRedisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer); // key的序列化类型
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 方法过期,改为下面代码
// objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value的序列化类型
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
三、高级特性配置
1. Redisson分布式锁配置
java
package com.example.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password:}")
private String password;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
// 单节点配置
config.useSingleServer()
.setAddress(String.format("redis://%s:%s", host, port))
.setPassword(StringUtils.hasText(password) ? password : null)
.setDatabase(0)
.setConnectionPoolSize(20) // 连接池大小
.setConnectionMinimumIdleSize(5) // 最小空闲连接数
.setIdleConnectionTimeout(10000) // 连接空闲超时时间
.setConnectTimeout(10000) // 连接超时时间
.setTimeout(3000); // 命令等待超时时间
// 集群配置示例
// config.useClusterServers()
// .addNodeAddress("redis://192.168.1.101:6379")
// .addNodeAddress("redis://192.168.1.102:6379")
// .addNodeAddress("redis://192.168.1.103:6379");
return Redisson.create(config);
}
}
// Redisson分布式锁使用
package com.example.service;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class DistributedLockService {
private final RedissonClient redissonClient;
public DistributedLockService(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
/**
* 使用Redisson分布式锁
*/
public boolean doWithLock(String lockKey, Runnable task) {
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁,最多等待10秒,锁持有时间30秒
boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (isLocked) {
try {
// 执行任务
task.run();
return true;
} finally {
// 释放锁
lock.unlock();
}
} else {
log.warn("获取锁失败: {}", lockKey);
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("获取锁被中断", e);
return false;
}
}
/**
* 可重入锁示例
*/
public void processWithReentrantLock(String userId) {
String lockKey = "user:process:" + userId;
RLock lock = redissonClient.getLock(lockKey);
lock.lock(30, TimeUnit.SECONDS);
try {
// 业务逻辑
processUserOrder(userId);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 读写锁示例
*/
public void readWriteLockExample() {
// 获取读写锁
var rwLock = redissonClient.getReadWriteLock("my_rw_lock");
// 读锁
rwLock.readLock().lock();
try {
// 读取操作
readData();
} finally {
rwLock.readLock().unlock();
}
// 写锁
rwLock.writeLock().lock();
try {
// 写入操作
writeData();
} finally {
rwLock.writeLock().unlock();
}
}
private void processUserOrder(String userId) {
// 业务逻辑
}
private void readData() {
// 读取数据
}
private void writeData() {
// 写入数据
}
}
2. Redis消息队列
java
package com.example.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Service
@Slf4j
public class RedisMessageService {
private final RedisTemplate<String, Object> redisTemplate;
private final RedisMessageListenerContainer container;
// 线程池处理消息
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
public RedisMessageService(RedisTemplate<String, Object> redisTemplate,
RedisMessageListenerContainer container) {
this.redisTemplate = redisTemplate;
this.container = container;
}
/**
* 发布消息
*/
public void publish(String channel, Object message) {
redisTemplate.convertAndSend(channel, message);
log.info("发布消息到频道 {}: {}", channel, message);
}
/**
* 订阅频道
*/
public void subscribe(String channel, MessageListener listener) {
container.addMessageListener(listener, new ChannelTopic(channel));
log.info("订阅频道: {}", channel);
}
/**
* 初始化订阅
*/
@PostConstruct
public void init() {
// 订阅用户相关的消息
subscribe("user:created", new UserCreatedListener());
subscribe("order:paid", new OrderPaidListener());
}
/**
* 用户创建消息监听器
*/
private class UserCreatedListener implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
executorService.submit(() -> {
try {
String channel = new String(message.getChannel());
String body = new String(message.getBody());
log.info("收到用户创建消息: 频道={}, 内容={}", channel, body);
// 处理用户创建逻辑
handleUserCreated(body);
} catch (Exception e) {
log.error("处理用户创建消息异常", e);
}
});
}
private void handleUserCreated(String message) {
// 解析消息并处理
log.info("处理用户创建: {}", message);
}
}
/**
* 订单支付消息监听器
*/
private class OrderPaidListener implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
executorService.submit(() -> {
try {
String channel = new String(message.getChannel());
String body = new String(message.getBody());
log.info("收到订单支付消息: 频道={}, 内容={}", channel, body);
// 处理订单支付逻辑
handleOrderPaid(body);
} catch (Exception e) {
log.error("处理订单支付消息异常", e);
}
});
}
private void handleOrderPaid(String message) {
// 解析消息并处理
log.info("处理订单支付: {}", message);
}
}
}
// 消息监听器容器配置
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
@Configuration
public class RedisListenerConfig {
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setTaskExecutor(null); // 使用默认执行器
container.setErrorHandler(e -> {
// 错误处理
System.err.println("Redis监听器错误: " + e.getMessage());
});
return container;
}
}
3. Redis Lua脚本支持
java
package com.example.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Collections;
@Service
@Slf4j
public class RedisScriptService {
private final RedisTemplate<String, Object> redisTemplate;
private DefaultRedisScript<Long> rateLimitScript;
private DefaultRedisScript<Boolean> compareAndSetScript;
public RedisScriptService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@PostConstruct
public void init() {
// 初始化限流脚本
rateLimitScript = new DefaultRedisScript<>();
rateLimitScript.setScriptSource(new ResourceScriptSource(
new ClassPathResource("lua/rate_limiter.lua")
));
rateLimitScript.setResultType(Long.class);
// 初始化CAS脚本
compareAndSetScript = new DefaultRedisScript<>();
compareAndSetScript.setScriptSource(new ResourceScriptSource(
new ClassPathResource("lua/compare_and_set.lua")
));
compareAndSetScript.setResultType(Boolean.class);
}
/**
* 使用Lua脚本实现限流
*/
public boolean rateLimit(String key, int maxRequests, int windowSeconds) {
Long result = redisTemplate.execute(
rateLimitScript,
Collections.singletonList(key),
String.valueOf(maxRequests),
String.valueOf(windowSeconds),
String.valueOf(System.currentTimeMillis())
);
return result != null && result == 1L;
}
/**
* 使用Lua脚本实现原子性比较并设置
*/
public boolean compareAndSet(String key, String expected, String newValue) {
Boolean result = redisTemplate.execute(
compareAndSetScript,
Collections.singletonList(key),
expected,
newValue
);
return Boolean.TRUE.equals(result);
}
}
// resources/lua/rate_limiter.lua
-- KEYS[1]: 限流key
-- ARGV[1]: 最大请求数
-- ARGV[2]: 时间窗口(秒)
-- ARGV[3]: 当前时间戳(毫秒)
local key = KEYS[1]
local maxRequests = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
-- 清除时间窗口之前的记录
redis.call('ZREMRANGEBYSCORE', key, 0, now - window * 1000)
-- 获取当前请求数
local current = redis.call('ZCARD', key)
if current < maxRequests then
-- 添加当前请求
redis.call('ZADD', key, now, now)
-- 设置过期时间
redis.call('EXPIRE', key, window)
return 1
else
return 0
end
// resources/lua/compare_and_set.lua
-- KEYS[1]: 缓存的key
-- ARGV[1]: 期望的值
-- ARGV[2]: 新值
local current = redis.call('GET', KEYS[1])
if current == ARGV[1] then
redis.call('SET', KEYS[1], ARGV[2])
return true
else
return false
end
4. Redis缓存雪崩/穿透/击穿防护
java
package com.example.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@Service
@Slf4j
public class CacheProtectionService {
private final RedisTemplate<String, Object> redisTemplate;
private final Random random = new Random();
// 本地锁,用于防止缓存击穿
private final ReentrantLock localLock = new ReentrantLock();
public CacheProtectionService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 防止缓存击穿 - 使用互斥锁
*/
public Object getWithBreakdownProtection(String key, CacheLoader loader) {
// 1. 从缓存获取
Object value = redisTemplate.opsForValue().get(key);
if (value != null) {
return value;
}
// 2. 获取本地锁(防止多个线程同时查询数据库)
localLock.lock();
try {
// 双重检查
value = redisTemplate.opsForValue().get(key);
if (value != null) {
return value;
}
// 3. 从数据源加载
value = loader.load();
if (value != null) {
// 4. 写入缓存
redisTemplate.opsForValue().set(
key,
value,
30 + random.nextInt(30), // 基础30分钟 + 随机0-30分钟
TimeUnit.MINUTES
);
}
return value;
} finally {
localLock.unlock();
}
}
/**
* 防止缓存穿透 - 布隆过滤器(简化版)
*/
public Object getWithPenetrationProtection(String key, CacheLoader loader) {
// 1. 检查是否存在(可以使用布隆过滤器)
String existsKey = "exists:" + key;
Boolean exists = redisTemplate.opsForValue().get(existsKey);
if (Boolean.FALSE.equals(exists)) {
log.info("数据不存在,防止缓存穿透: {}", key);
return null;
}
// 2. 正常缓存逻辑
Object value = redisTemplate.opsForValue().get(key);
if (value != null) {
return value;
}
// 3. 从数据源加载
value = loader.load();
if (value == null) {
// 数据不存在,设置标记防止穿透
redisTemplate.opsForValue().set(
existsKey,
"false",
5, // 较短时间
TimeUnit.MINUTES
);
} else {
// 数据存在,写入缓存
redisTemplate.opsForValue().set(
key,
value,
30 + random.nextInt(30), // 随机过期时间
TimeUnit.MINUTES
);
}
return value;
}
/**
* 防止缓存雪崩 - 热点数据永不过期 + 异步刷新
*/
public void setupHotDataRefresh(String key, CacheLoader loader) {
// 1. 加载数据
Object value = loader.load();
if (value != null) {
// 2. 设置缓存,永不过期
redisTemplate.opsForValue().set(key, value);
// 3. 异步刷新线程
new Thread(() -> {
while (true) {
try {
Thread.sleep(60 * 60 * 1000); // 每小时刷新一次
Object newValue = loader.load();
if (newValue != null) {
redisTemplate.opsForValue().set(key, newValue);
log.info("热点数据刷新成功: {}", key);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}).start();
}
}
/**
* 缓存加载器接口
*/
@FunctionalInterface
public interface CacheLoader {
Object load();
}
}
5. Redis集群/哨兵配置
bash
# application-cluster.yml
spring:
redis:
cluster:
nodes:
- 192.168.1.101:6379
- 192.168.1.102:6379
- 192.168.1.103:6379
- 192.168.1.104:6379
- 192.168.1.105:6379
- 192.168.1.106:6379
max-redirects: 3
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 5
cluster:
refresh:
adaptive: true
period: 2000
# application-sentinel.yml
spring:
redis:
sentinel:
master: mymaster
nodes:
- 192.168.1.101:26379
- 192.168.1.102:26379
- 192.168.1.103:26379
password: ${REDIS_PASSWORD}
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 5
四、监控和健康检查
java
package com.example.config;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class RedisHealthIndicator implements HealthIndicator {
private final RedisConnectionFactory connectionFactory;
private final MeterRegistry meterRegistry;
public RedisHealthIndicator(RedisConnectionFactory connectionFactory,
MeterRegistry meterRegistry) {
this.connectionFactory = connectionFactory;
this.meterRegistry = meterRegistry;
}
@Override
public Health health() {
long startTime = System.nanoTime();
try {
// 执行PING命令检查连接
String result = connectionFactory.getConnection().ping();
long duration = System.nanoTime() - startTime;
// 记录监控指标
meterRegistry.timer("redis.health.check.duration")
.record(duration, TimeUnit.NANOSECONDS);
if ("PONG".equals(result)) {
return Health.up()
.withDetail("response", result)
.withDetail("duration_ms", duration / 1_000_000.0)
.build();
} else {
return Health.down()
.withDetail("error", "Unexpected response: " + result)
.build();
}
} catch (Exception e) {
long duration = System.nanoTime() - startTime;
meterRegistry.timer("redis.health.check.duration")
.record(duration, TimeUnit.NANOSECONDS);
log.error("Redis健康检查失败", e);
return Health.down(e)
.withDetail("duration_ms", duration / 1_000_000.0)
.build();
}
}
}
// 监控配置
package com.example.config;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
import io.micrometer.core.instrument.binder.cache.RedisCacheMetrics;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import javax.annotation.PostConstruct;
@Configuration
public class MetricsConfig {
private final MeterRegistry meterRegistry;
private final RedisCacheManager cacheManager;
public MetricsConfig(MeterRegistry meterRegistry,
RedisCacheManager cacheManager) {
this.meterRegistry = meterRegistry;
this.cacheManager = cacheManager;
}
@PostConstruct
public void initMetrics() {
// 绑定JVM指标
new JvmMemoryMetrics().bindTo(meterRegistry);
new JvmThreadMetrics().bindTo(meterRegistry);
new ProcessorMetrics().bindTo(meterRegistry);
// 绑定Redis缓存指标
if (cacheManager.getCache("user") != null) {
RedisCacheMetrics.monitor(
meterRegistry,
cacheManager.getCache("user"),
"user_cache"
);
}
}
}
五、最佳实践总结
1. 键设计规范
java
public class RedisKeyBuilder {
// 统一前缀
private static final String PREFIX = "app:";
// 业务模块
private static final String MODULE_USER = "user:";
private static final String MODULE_ORDER = "order:";
private static final String MODULE_PRODUCT = "product:";
// 键类型
private static final String TYPE_CACHE = "cache:";
private static final String TYPE_LOCK = "lock:";
private static final String TYPE_COUNTER = "counter:";
private static final String TYPE_RATE_LIMIT = "rate:limit:";
public static String userCacheKey(Long userId) {
return PREFIX + MODULE_USER + TYPE_CACHE + userId;
}
public static String userSessionKey(String sessionId) {
return PREFIX + MODULE_USER + "session:" + sessionId;
}
public static String orderLockKey(String orderNo) {
return PREFIX + MODULE_ORDER + TYPE_LOCK + orderNo;
}
public static String productStockKey(Long productId) {
return PREFIX + MODULE_PRODUCT + "stock:" + productId;
}
public static String apiRateLimitKey(String apiPath, String userId) {
return PREFIX + TYPE_RATE_LIMIT + apiPath + ":" + userId;
}
}
2. 配置优化建议
bash
spring:
redis:
# 生产环境建议配置
lettuce:
pool:
max-active: 50 # 根据并发调整
max-idle: 20
min-idle: 10
max-wait: 1000ms # 避免无限等待
shutdown-timeout: 100ms # 关闭超时时间
# 超时配置
timeout: 1000ms
connect-timeout: 1000ms
3. 性能优化技巧
java
// 1. 使用Pipeline批量操作
public void batchOperations() {
List<Object> results = redisTemplate.executePipelined(
(RedisCallback<Object>) connection -> {
for (int i = 0; i < 100; i++) {
connection.stringCommands()
.set(("key:" + i).getBytes(), ("value:" + i).getBytes());
}
return null;
}
);
}
// 2. 使用SCAN代替KEYS
public Set<String> scanKeys(String pattern) {
Set<String> keys = new HashSet<>();
Cursor<byte[]> cursor = redisTemplate.getConnectionFactory()
.getConnection()
.scan(ScanOptions.scanOptions().match(pattern).count(100).build());
while (cursor.hasNext()) {
keys.add(new String(cursor.next()));
}
return keys;
}
// 3. 合理设置过期时间
public void setWithRandomExpire(String key, Object value, long baseExpire) {
Random random = new Random();
long randomExpire = baseExpire + random.nextInt((int) baseExpire / 2);
redisTemplate.opsForValue().set(key, value, randomExpire, TimeUnit.SECONDS);
}
4. 常见问题处理
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 连接泄漏 | 连接数持续增长 | 1. 检查连接池配置 2. 确保连接正确关闭 3. 使用连接池监控 |
| 内存溢出 | Redis内存使用率高 | 1. 设置合理过期时间 2. 监控大key 3. 使用内存淘汰策略 |
| 缓存穿透 | 大量请求不存在的key | 1. 布隆过滤器 2. 空值缓存 3. 接口限流 |
| 缓存雪崩 | 大量key同时过期 | 1. 设置随机过期时间 2. 热点数据永不过期 3. 降级熔断机制 |
| 缓存击穿 | 热点key失效 | 1. 互斥锁 2. 永不过期 + 异步更新 3. 逻辑过期 |
六、生产环境部署建议
-
高可用架构
-
使用 Redis Sentinel 或 Redis Cluster
-
配置合理的副本数量
-
设置自动故障转移
-
-
备份策略
-
定期 RDB 快照
-
AOF 持久化开启
-
异地备份
-
-
监控告警
-
监控内存使用率
-
监控连接数
-
监控命中率
-
设置慢查询日志
-
-
安全配置
-
启用密码认证
-
限制网络访问
-
禁用危险命令
-
定期更新密码
-