Redis在Java项目中的整合
核心定位:Redis 是 Java 后端最常用的中间件,用于缓存、分布式锁、限流、排行榜等场景。本文聚焦 Spring Boot 项目的实战整合。
1. Spring Boot 整合 Redis
1.1 基础配置
xml
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redisson(分布式锁、分布式集合) -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.25.0</version>
</dependency>
<!-- 连接池(Lettuce 需要) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>yaml
# application.yml
spring:
data:
redis:
host: localhost
port: 6379
password: your_password
database: 0
timeout: 3000ms # 连接超时
lettuce:
pool:
max-active: 16 # 最大连接数
max-idle: 8 # 最大空闲连接
min-idle: 2 # 最小空闲连接
max-wait: 2000ms # 获取连接超时
shutdown-timeout: 100ms1.2 RedisTemplate 配置
java
@Configuration
public class RedisConfig {
/**
* ★ 自定义 RedisTemplate 序列化
* 默认使用 JDK 序列化(二进制,不友好)
* 改为 JSON 序列化(可读,跨语言)
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// Key 使用 String 序列化
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// Value 使用 JSON 序列化
GenericJackson2JsonRedisSerializer jsonSerializer =
new GenericJackson2JsonRedisSerializer();
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
/**
* ★ StringRedisTemplate(专门操作字符串)
*/
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
return new StringRedisTemplate(factory);
}
}序列化方式对比:
┌─────────────────────────────────────────────┐
│ RedisTemplate 序列化方式对比 │
├─────────────────────────────────────────────┤
│ JDK 序列化(默认): │
│ - 二进制,不可读 │
│ - 跨语言不兼容 │
│ - 占用空间大(包含类信息) │
│ - 缺点:不推荐生产使用 │
│ │
│ String 序列化: │
│ - 纯字符串 │
│ - 可读性好 │
│ - 适合 Key 和简单字符串 Value │
│ │
│ ★ JSON 序列化(推荐): │
│ - 可读性好 │
│ - 跨语言兼容 │
│ - 占用空间适中 │
│ - ★ 生产推荐 │
│ │
│ Protobuf 序列化: │
│ - 二进制,体积小 │
│ - 性能高 │
│ - 需要定义 proto 文件 │
│ - 适合高性能场景 │
└─────────────────────────────────────────────┘2. Redis 工具类封装
java
@Component
public class RedisUtils {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// ============ String 类型 ============
/**
* 设置缓存(带过期时间)
*/
public void set(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
/**
* 获取缓存
*/
public <T> T get(String key, Class<T> clazz) {
Object value = redisTemplate.opsForValue().get(key);
if (value == null) return null;
if (clazz.isInstance(value)) {
return clazz.cast(value);
}
// JSON 反序列化
return JSON.parseObject(JSON.toJSONString(value), clazz);
}
/**
* 删除缓存
*/
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
/**
* 自增
*/
public Long increment(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 设置过期时间
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
// ============ Hash 类型 ============
/**
* Hash 设置
*/
public void hSet(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
/**
* Hash 获取
*/
public Object hGet(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
/**
* Hash 获取全部
*/
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* Hash 删除字段
*/
public Long hDelete(String key, Object... fields) {
return redisTemplate.opsForHash().delete(key, fields);
}
// ============ List 类型 ============
/**
* List 右推(尾部插入)
*/
public Long lRightPush(String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}
/**
* List 左弹(头部弹出)
*/
public Object lLeftPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
/**
* List 范围查询
*/
public List<Object> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
// ============ Set 类型 ============
/**
* Set 添加
*/
public Long sAdd(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}
/**
* Set 获取全部
*/
public Set<Object> sMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* Set 判断是否包含
*/
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
// ============ ZSet 类型(有序集合) ============
/**
* ZSet 添加(带分数)
*/
public Boolean zAdd(String key, Object value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
/**
* ZSet 范围查询(按分数升序)
*/
public Set<Object> zRange(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
/**
* ZSet 范围查询(按分数降序,常用于排行榜)
*/
public Set<Object> zReverseRange(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**
* ZSet 增加分数
*/
public Double zIncrementScore(String key, Object value, double delta) {
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
}3. Spring Cache 注解缓存
3.1 启用缓存
java
@EnableCaching // ★ 开启 Spring Cache
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}3.2 核心注解
java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* ★ @Cacheable:查询缓存,命中则直接返回,不执行方法
*/
@Cacheable(
value = "user", // 缓存名称(前缀)
key = "#id", // 缓存 Key(SpEL 表达式)
unless = "#result == null", // 结果为 null 不缓存
condition = "#id > 0" // 条件:id > 0 才缓存
)
public User getUserById(Long id) {
log.info("查询数据库,id = {}", id); // 缓存命中时不会打印
return userMapper.selectById(id);
}
/**
* ★ @CachePut:更新缓存(总是执行方法,更新缓存)
*/
@CachePut(value = "user", key = "#user.id")
public User updateUser(User user) {
userMapper.updateById(user);
return user;
}
/**
* ★ @CacheEvict:删除缓存
*/
@CacheEvict(value = "user", key = "#id")
public void deleteUser(Long id) {
userMapper.deleteById(id);
}
/**
* ★ @CacheEvict 清除全部缓存
*/
@CacheEvict(value = "user", allEntries = true)
public void clearUserCache() {
// 清除 user:* 全部缓存
}
/**
* ★ @Caching:组合多个缓存操作
*/
@Caching(
put = @CachePut(value = "user", key = "#user.id"),
evict = @CacheEvict(value = "userList", allEntries = true)
)
public User updateUserAndClearList(User user) {
userMapper.updateById(user);
return user;
}
}缓存 Key 生成规则:
┌─────────────────────────────────────────────┐
│ Spring Cache Key 生成规则(SpEL 表达式) │
├─────────────────────────────────────────────┤
│ #参数名 → 使用方法参数 │
│ #p0, #p1 → 第0个、第1个参数 │
│ #root.methodName → 方法名 │
│ #root.target → 目标对象 │
│ #result → 方法返回值(仅 unless 可用)│
│ │
│ 示例: │
│ key = "#id" → user::1 │
│ key = "#user.id" → user::1 │
│ key = "#root.methodName" → user::getUserById │
│ key = "'user:' + #id" → user:user:1 │
│ key = "#id + ':' + #name" → user::1:张三 │
└─────────────────────────────────────────────┘3.3 缓存配置
java
@Configuration
@EnableCaching
public class CacheConfig {
/**
* ★ 自定义 CacheManager(使用 Redis)
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
// 默认缓存配置
RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) // 默认过期时间 30 分钟
.serializeKeysWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues(); // 不缓存 null 值
// 为不同缓存名设置不同过期时间
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put("user", defaultConfig.entryTtl(Duration.ofHours(2)));
configMap.put("product", defaultConfig.entryTtl(Duration.ofDays(1)));
configMap.put("config", defaultConfig.entryTtl(Duration.ofDays(7)));
return RedisCacheManager.builder(factory)
.cacheDefaults(defaultConfig)
.withInitialCacheConfigurations(configMap)
.transactionAware() // 事务感知(事务提交后才写入缓存)
.build();
}
/**
* ★ 自定义 KeyGenerator(当不指定 key 时使用)
*/
@Bean
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName()).append(":");
sb.append(method.getName()).append(":");
for (Object param : params) {
sb.append(param.toString()).append(",");
}
return sb.toString();
};
}
}4. 缓存三大问题实战
┌─────────────────────────────────────────────┐
│ 缓存三大问题 │
├─────────────────────────────────────────────┤
│ 1. 缓存穿透 │
│ - 查询不存在的数据,每次都打到数据库 │
│ - 解决:布隆过滤器 / 缓存空值 │
│ │
│ 2. 缓存击穿 │
│ - 热点 Key 过期瞬间,大量请求打到数据库 │
│ - 解决:互斥锁 / 永不过期 │
│ │
│ 3. 缓存雪崩 │
│ - 大量 Key 同时过期,数据库压力剧增 │
│ - 解决:随机过期时间 / 多级缓存 │
└─────────────────────────────────────────────┘4.1 缓存穿透解决方案
java
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
@Autowired
private RedisUtils redisUtils;
/**
* ★ 方案1:缓存空值(简单,但浪费内存)
*/
public Product getProductWithNullCache(Long id) {
// 1. 查缓存
String key = "product:" + id;
Product product = redisUtils.get(key, Product.class);
if (product != null) {
// 判断是否是空值标记
if (product.getId() == null) {
return null; // 缓存的空值
}
return product;
}
// 2. 查数据库
product = productMapper.selectById(id);
if (product == null) {
// ★ 缓存空值(防止穿透)
Product empty = new Product();
empty.setId(null); // 标记为空
redisUtils.set(key, empty, 5, TimeUnit.MINUTES); // 空值过期时间短
} else {
redisUtils.set(key, product, 30, TimeUnit.MINUTES);
}
return product;
}
/**
* ★ 方案2:布隆过滤器(推荐,节省内存)
*/
@Autowired
private RBloomFilter<Long> productBloomFilter; // Redisson 布隆过滤器
public Product getProductWithBloomFilter(Long id) {
// 1. 布隆过滤器判断(可能误判,但不会漏判)
if (!productBloomFilter.contains(id)) {
return null; // 一定不存在
}
// 2. 查缓存
String key = "product:" + id;
Product product = redisUtils.get(key, Product.class);
if (product != null) {
return product;
}
// 3. 查数据库
product = productMapper.selectById(id);
if (product != null) {
redisUtils.set(key, product, 30, TimeUnit.MINUTES);
}
return product;
}
}
// 布隆过滤器配置
@Configuration
public class BloomFilterConfig {
@Bean
public RBloomFilter<Long> productBloomFilter(RedissonClient redisson) {
RBloomFilter<Long> bloomFilter = redisson.getBloomFilter("product:bloom");
// 初始化:预计存储 1000 万个元素,误判率 1%
bloomFilter.tryInit(10_000_000L, 0.01);
return bloomFilter;
}
/**
* 应用启动时加载布隆过滤器
*/
@Bean
public CommandLineRunner loadBloomFilter(
RBloomFilter<Long> bloomFilter,
ProductMapper productMapper) {
return args -> {
// 从数据库加载所有商品 ID 到布隆过滤器
List<Long> allIds = productMapper.selectAllIds();
for (Long id : allIds) {
bloomFilter.add(id);
}
log.info("布隆过滤器加载完成,元素数量:{}", allIds.size());
};
}
}4.2 缓存击穿解决方案
java
@Service
public class HotProductService {
@Autowired
private ProductMapper productMapper;
@Autowired
private RedisUtils redisUtils;
@Autowired
private RedissonClient redisson;
/**
* ★ 方案1:互斥锁(Redisson 分布式锁)
*/
public Product getHotProductWithLock(Long id) {
String key = "product:" + id;
// 1. 查缓存
Product product = redisUtils.get(key, Product.class);
if (product != null) {
return product;
}
// 2. 缓存未命中,加分布式锁
RLock lock = redisson.getLock("lock:product:" + id);
try {
// 尝试加锁(最多等待3秒,锁持有10秒自动释放)
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
// ★ 双重检查(防止重复加载)
product = redisUtils.get(key, Product.class);
if (product != null) {
return product;
}
// 3. 查数据库
product = productMapper.selectById(id);
if (product != null) {
redisUtils.set(key, product, 30, TimeUnit.MINUTES);
}
} else {
// 加锁失败,短暂休眠后重试
Thread.sleep(50);
return getHotProductWithLock(id);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁中断", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return product;
}
/**
* ★ 方案2:逻辑过期(永不过期,后台异步更新)
*/
public Product getHotProductWithLogicalExpire(Long id) {
String key = "product:" + id;
String expireKey = key + ":expire";
// 1. 查缓存
Product product = redisUtils.get(key, Product.class);
if (product == null) {
return null;
}
// 2. 检查是否逻辑过期
String expireTime = redisUtils.get(expireKey, String.class);
if (expireTime != null && Long.parseLong(expireTime) < System.currentTimeMillis()) {
// 已过期,异步更新(不阻塞当前请求)
asyncUpdateCache(id, key, expireKey);
}
// 3. 返回旧数据(保证高可用)
return product;
}
@Async
public void asyncUpdateCache(Long id, String key, String expireKey) {
// 加分布式锁(防止多个线程同时更新)
RLock lock = redisson.getLock("lock:update:" + id);
try {
if (lock.tryLock()) {
// 双重检查
String expireTime = redisUtils.get(expireKey, String.class);
if (expireTime != null && Long.parseLong(expireTime) < System.currentTimeMillis()) {
// 查数据库
Product product = productMapper.selectById(id);
if (product != null) {
// 更新缓存
redisUtils.set(key, product); // 不设过期时间
// 更新逻辑过期时间(30分钟后)
long newExpireTime = System.currentTimeMillis() + 30 * 60 * 1000;
redisUtils.set(expireKey, String.valueOf(newExpireTime), 30, TimeUnit.MINUTES);
}
}
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}4.3 缓存雪崩解决方案
java
@Service
public class AntiAvalancheService {
@Autowired
private RedisUtils redisUtils;
/**
* ★ 方案1:随机过期时间(避免同时过期)
*/
public void setWithRandomExpire(String key, Object value) {
// 基础过期时间 30 分钟
long baseExpire = 30 * 60;
// 随机波动 0~5 分钟
long randomExpire = ThreadLocalRandom.current().nextLong(5 * 60);
long totalExpire = baseExpire + randomExpire;
redisUtils.set(key, value, totalExpire, TimeUnit.SECONDS);
}
/**
* ★ 方案2:多级缓存(L1 本地缓存 + L2 Redis)
*/
@Autowired
private Cache<String, Object> caffeineCache; // Caffeine 本地缓存
public Object getWithMultiLevelCache(String key) {
// L1:本地缓存(毫秒级)
Object value = caffeineCache.getIfPresent(key);
if (value != null) {
return value;
}
// L2:Redis 缓存(毫秒级)
value = redisUtils.get(key, Object.class);
if (value != null) {
caffeineCache.put(key, value); // 回填 L1
return value;
}
// L3:数据库(百毫秒级)
value = queryFromDatabase(key);
if (value != null) {
redisUtils.set(key, value, 30, TimeUnit.MINUTES);
caffeineCache.put(key, value);
}
return value;
}
}
// Caffeine 配置
@Configuration
public class CaffeineConfig {
@Bean
public Cache<String, Object> caffeineCache() {
return Caffeine.newBuilder()
.maximumSize(1000) // 最大缓存数
.expireAfterWrite(5, TimeUnit.MINUTES) // 写入后5分钟过期
.recordStats() // 开启统计
.build();
}
}5. 分布式锁实现
5.1 Redisson 分布式锁
java
@Component
public class DistributedLockService {
@Autowired
private RedissonClient redisson;
/**
* ★ 基础分布式锁
*/
public boolean tryLock(String key, long waitTime, long leaseTime, TimeUnit unit) {
RLock lock = redisson.getLock(key);
try {
// waitTime:最多等待时间
// leaseTime:锁持有时间(到期自动释放)
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
public void unlock(String key) {
RLock lock = redisson.getLock(key);
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
/**
* ★ 模板方法:自动加锁解锁
*/
public <T> T executeWithLock(String key, long waitTime, long leaseTime,
TimeUnit unit, Supplier<T> supplier) {
RLock lock = redisson.getLock(key);
try {
if (lock.tryLock(waitTime, leaseTime, unit)) {
return supplier.get();
} else {
throw new RuntimeException("获取分布式锁失败:" + key);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁中断", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
// 使用示例
@Service
public class OrderService {
@Autowired
private DistributedLockService lockService;
@Autowired
private StockMapper stockMapper;
public void deductStock(Long productId, int quantity) {
String lockKey = "lock:stock:" + productId;
lockService.executeWithLock(lockKey, 3, 10, TimeUnit.SECONDS, () -> {
// 查询库存
Stock stock = stockMapper.selectByProductId(productId);
if (stock.getQuantity() < quantity) {
throw new RuntimeException("库存不足");
}
// 扣减库存
stock.setQuantity(stock.getQuantity() - quantity);
stockMapper.updateById(stock);
return null;
});
}
}5.2 可重入锁 vs 不可重入锁
java
@Component
public class ReentrantLockDemo {
@Autowired
private RedissonClient redisson;
/**
* ★ Redisson 默认是可重入锁(同一个线程可以多次加锁)
*/
public void reentrantLock() {
RLock lock = redisson.getLock("lock:reentrant");
lock.lock();
try {
log.info("第一次加锁成功");
// 可重入:同一个线程可以再次加锁
lock.lock();
try {
log.info("第二次加锁成功(可重入)");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}
/**
* ★ 不可重入锁(RedissonLock + tryLock)
*/
public void nonReentrantLock() {
RLock lock = redisson.getLock("lock:nonReentrant");
if (lock.tryLock()) {
try {
log.info("加锁成功");
// 不可重入:同一个线程再次加锁会失败
if (lock.tryLock()) {
try {
log.info("第二次加锁成功(不会执行)");
} finally {
lock.unlock();
}
} else {
log.info("第二次加锁失败(不可重入)");
}
} finally {
lock.unlock();
}
}
}
}5.3 读写锁
java
@Component
public class ReadWriteLockDemo {
@Autowired
private RedissonClient redisson;
/**
* ★ 读写锁:读读不互斥,读写互斥,写写互斥
*/
public void readWriteLock() {
RReadWriteLock rwLock = redisson.getReadWriteLock("lock:rw");
// 读锁
RLock readLock = rwLock.readLock();
readLock.lock();
try {
log.info("获取读锁,执行读操作");
// 多个线程可以同时持有读锁
} finally {
readLock.unlock();
}
// 写锁
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try {
log.info("获取写锁,执行写操作");
// 写锁是独占的
} finally {
writeLock.unlock();
}
}
}6. 分布式限流
java
@Component
public class RateLimiterService {
@Autowired
private RedissonClient redisson;
/**
* ★ 基于 Redisson 的限流器(令牌桶算法)
*/
public boolean tryAcquire(String key, int rate, int rateInterval, RateIntervalUnit unit) {
RRateLimiter rateLimiter = redisson.getRateLimiter(key);
// 初始化限流器(只需要调用一次)
rateLimiter.trySetRate(RateType.OVERALL, rate, rateInterval, unit);
// 尝试获取一个令牌
return rateLimiter.tryAcquire(1);
}
}
// 使用示例:接口限流
@RestController
public class ApiController {
@Autowired
private RateLimiterService rateLimiter;
@GetMapping("/api/order")
public Result<?> createOrder(HttpServletRequest request) {
String userId = getCurrentUserId(request);
String key = "rate:order:" + userId;
// 限制每个用户每分钟最多创建 10 个订单
if (!rateLimiter.tryAcquire(key, 10, 1, RateIntervalUnit.MINUTES)) {
return Result.error("操作过于频繁,请稍后再试");
}
// 业务逻辑
return Result.success("订单创建成功");
}
}7. Redis 实战场景
7.1 排行榜(ZSet)
java
@Service
public class LeaderboardService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LEADERBOARD_KEY = "leaderboard:score";
/**
* 更新分数(增加)
*/
public void addScore(String userId, double score) {
redisTemplate.opsForZSet().incrementScore(LEADERBOARD_KEY, userId, score);
}
/**
* 获取排行榜 Top N
*/
public List<LeaderboardItem> getTopN(int n) {
Set<ZSetOperations.TypedTuple<String>> tuples =
redisTemplate.opsForZSet().reverseRangeWithScores(LEADERBOARD_KEY, 0, n - 1);
List<LeaderboardItem> list = new ArrayList<>();
int rank = 1;
for (ZSetOperations.TypedTuple<String> tuple : tuples) {
LeaderboardItem item = new LeaderboardItem();
item.setRank(rank++);
item.setUserId(tuple.getValue());
item.setScore(tuple.getScore());
list.add(item);
}
return list;
}
/**
* 获取用户排名
*/
public Long getUserRank(String userId) {
Long rank = redisTemplate.opsForZSet().reverseRank(LEADERBOARD_KEY, userId);
return rank != null ? rank + 1 : null; // 排名从1开始
}
}
@Data
public class LeaderboardItem {
private Integer rank;
private String userId;
private Double score;
}7.2 延迟队列(ZSet 实现)
java
@Component
public class DelayQueueService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String DELAY_QUEUE_KEY = "delay:queue";
/**
* 添加延迟任务
*/
public void addDelayTask(String taskId, long delayMillis) {
// score = 执行时间戳
long executeTime = System.currentTimeMillis() + delayMillis;
redisTemplate.opsForZSet().add(DELAY_QUEUE_KEY, taskId, executeTime);
}
/**
* 轮询获取到期任务
*/
public List<String> getExpiredTasks() {
long now = System.currentTimeMillis();
// 获取所有到期的任务(score <= now)
Set<String> tasks = redisTemplate.opsForZSet().rangeByScore(
DELAY_QUEUE_KEY, 0, now);
if (tasks != null && !tasks.isEmpty()) {
// 删除已处理的任务
redisTemplate.opsForZSet().remove(DELAY_QUEUE_KEY, tasks.toArray());
return new ArrayList<>(tasks);
}
return Collections.emptyList();
}
}
// 定时任务消费延迟队列
@Component
@EnableScheduling
public class DelayTaskConsumer {
@Autowired
private DelayQueueService delayQueueService;
@Scheduled(fixedDelay = 1000) // 每秒轮询一次
public void consumeDelayTasks() {
List<String> tasks = delayQueueService.getExpiredTasks();
for (String taskId : tasks) {
// 处理任务
processTask(taskId);
}
}
private void processTask(String taskId) {
log.info("处理延迟任务:{}", taskId);
// 业务逻辑
}
}7.3 分布式 Session
java
// 1. 添加依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
// 2. 开启 Redis Session
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) // 30分钟过期
public class SessionConfig {
}
// 3. 使用(与原生 HttpSession 完全一致)
@RestController
public class SessionController {
@GetMapping("/login")
public Result<?> login(HttpServletRequest request) {
HttpSession session = request.getSession();
session.setAttribute("userId", 123L);
session.setAttribute("username", "张三");
return Result.success("登录成功");
}
@GetMapping("/info")
public Result<?> getUserInfo(HttpServletRequest request) {
HttpSession session = request.getSession();
Long userId = (Long) session.getAttribute("userId");
String username = (String) session.getAttribute("username");
return Result.success(Map.of("userId", userId, "username", username));
}
}8. 生产环境最佳实践
┌─────────────────────────────────────────────┐
│ Redis 生产环境最佳实践 │
├─────────────────────────────────────────────┤
│ 1. 连接池配置 │
│ - max-active: CPU*2(16~32) │
│ - max-idle: max-active / 2 │
│ - min-idle: 2~4 │
│ - timeout: 3000ms │
│ │
│ 2. 内存管理 │
│ - maxmemory: 物理内存的 70% │
│ - maxmemory-policy: allkeys-lru(推荐) │
│ - 监控内存使用率,设置告警 │
│ │
│ 3. 持久化策略 │
│ - AOF + RDB 混合持久化 │
│ - AOF:每秒同步(everysec) │
│ - RDB:定时快照(用于备份) │
│ │
│ 4. 高可用 │
│ - ★ 哨兵模式(1主2从+3哨兵) │
│ - 或集群模式(数据分片) │
│ │
│ 5. 安全 │
│ - 设置密码(requirepass) │
│ - 禁用危险命令(rename-command) │
│ - 绑定内网 IP(bind 127.0.0.1) │
│ │
│ 6. 监控 │
│ - 慢查询日志(slowlog-log-slower-than) │
│ - 内存碎片率(mem_fragmentation_ratio) │
│ - 连接数、QPS、命中率 │
└─────────────────────────────────────────────┘9. 常见问题
┌─────────────────────────────────────────────┐
│ Redis 常见问题 │
├─────────────────────────────────────────────┤
│ 1. 缓存与数据库一致性 │
│ - ★ 先更新数据库,再删除缓存 │
│ - 延迟双删(更新后延迟删除缓存) │
│ - 订阅 binlog 异步删除缓存 │
│ │
│ 2. 大 Key 问题 │
│ - 避免单个 Key 存储过多数据 │
│ - Hash 代替 String(100个字段以下) │
│ - 拆分大 Key(如拆分大 List) │
│ │
│ 3. 热 Key 问题 │
│ - 本地缓存(Caffeine) │
│ - Key 加随机后缀(分散到多个 Key) │
│ - 读写分离(从节点分担读压力) │
│ │
│ 4. 内存碎片 │
│ - activedefrag yes(开启自动碎片整理) │
│ - 定期重启(释放碎片) │
│ │
│ 5. 连接泄漏 │
│ - 使用连接池 │
│ - 设置 timeout(自动释放空闲连接) │
│ - 监控 connected_clients │
└─────────────────────────────────────────────┘10. 总结
┌─────────────────────────────────────────────┐
│ Redis 整合核心知识点 │
├─────────────────────────────────────────────┤
│ 1. 基础整合 │
│ - RedisTemplate 序列化配置 │
│ - ★ JSON 序列化(推荐) │
│ │
│ 2. Spring Cache │
│ - @Cacheable / @CachePut / @CacheEvict │
│ - Key 生成规则(SpEL 表达式) │
│ │
│ 3. 缓存三大问题 │
│ - 穿透:布隆过滤器 / 缓存空值 │
│ - 击穿:互斥锁 / 逻辑过期 │
│ - 雪崩:随机过期 / 多级缓存 │
│ │
│ 4. 分布式锁 │
│ - Redisson(推荐) │
│ - 可重入锁 / 读写锁 / 红锁 │
│ │
│ 5. 实战场景 │
│ - 排行榜(ZSet) │
│ - 延迟队列(ZSet + 定时任务) │
│ - 分布式 Session │
│ │
│ 6. 生产最佳实践 │
│ - 连接池、内存管理、持久化、高可用 │
└─────────────────────────────────────────────┘关联笔记
- [[后端/数据库/Redis/Redis学习笔记]]:Redis 核心原理
- [[后端/SpringBoot/SpringBoot入门与项目搭建]]:Spring Boot 基础
- [[后端/微服务/分布式ID与链路追踪]]:分布式锁应用场景
练习:把「Redis在Java项目中的整合」变成可验证能力
- 从本文任选一个示例,在可丢弃环境重跑;记录运行时/服务版本、输入、关键输出和清理动作。
- 保持其他条件不变,只制造一个边界:空值、重复请求、并发、超时、断连、权限不足或资源耗尽。
- 用四句话复盘:现象是什么、原理在哪一层、证据是什么、生产中如何止损。
验收清单
- [ ] 示例能从空环境重复执行,必要依赖和版本已写明。
- [ ] 同时保留成功路径与至少一个失败路径,不用“应该没问题”冒充证据。
- [ ] 结论写明适用边界,并能链接回对应学习路线的能力域。
- [ ] 练习结束后已清理临时数据、凭据、端口或进程。
对照 数据库练习答案与复盘尺。先交自己的证据再翻答案;答案是镜子,不是传送门。