当前位置: 首页 > news >正文

Redis(一):缓存穿透及其解决方法(SpringBoot+mybatis-plus)

一、概述

 1.缓存穿透(Cache Penetration)* 缓存穿透(Cache Penetration)是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库,导致数据库压力骤增甚至崩溃。* 触发原因:恶意攻击、参数伪造、业务逻辑漏洞。* 核心问题:大量请求访问数据库中不存在的数据,缓存无法拦截。* 攻击方式:* 攻击者使用不存在的用户id频繁请求;* 这些请求都会直接访问数据库,导致数据库压力过大;* 解决缓存穿透的常见方案:* (1)缓存空值:当查询数据库发现数据不存在时,将空结果(如null)写入缓存,并设置较短的过期时间;* (2)布隆过滤器(Bloom Filter):再缓存层前加布隆过滤器,预先存储所有合法Key的哈希值,查询时先检查布隆过滤器,若返回”不存在“,直接拦截请求,若返回”可能存在“,继续查询缓存/数据库;* (3)互斥锁(Mutex Lock):缓存未命中时,通过互斥锁(如 Redis 的 SETNX)保证只有一个线程查询数据库,其他线程等待回填缓存。* (4)接口层校验:在 API 入口处校验参数合法性,拦截明显无效的请求(如非法 ID 格式、负数等)* (5)热点数据永不过期:对高频访问的热点数据设置永不过期,通过后台线程主动更新缓存。* (6)缓存预热:在系统启动或低峰期,预先加载热点数据到缓存中。* (7)实时监控与限流:监控异常流量(如大量 null 响应),触发限流策略(如令牌桶、漏桶算法),保护数据库。
2.缓存击穿(Cache Breakdown)* 缓存击穿的定义:缓存击穿(Cache Breakdown)是指某个热点key(如爆款商品信息)在缓存中过期后,大量并发请求同时访问数据库(请求数据存在),导致数据库压力骤增。* 触发原因:缓存过期时间到期,且高并发场景下请求集中失效。* 核心问题:单个热点key失效后,大量请求同时访问数据库。* 3.缓存雪崩(Cache Avalanche)* 问题描述:* 大量缓存key在同一时间过期;* 大量请求直接访问数据库,导致数据库崩溃;

二、代码

2.1 controller层

package com.study.sredis.stept001.controller;import cn.hutool.core.lang.UUID;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.util.concurrent.RateLimiter;
import com.study.sredis.stept001.domain.User;
import com.study.sredis.stept001.service.UserService;
import com.study.sredis.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;/*** 1.缓存穿透(Cache Penetration)* 缓存穿透(Cache Penetration)是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库,导致数据库压力骤增甚至崩溃。* 触发原因:恶意攻击、参数伪造、业务逻辑漏洞。* 核心问题:大量请求访问数据库中不存在的数据,缓存无法拦截。* 攻击方式:* 攻击者使用不存在的用户id频繁请求;* 这些请求都会直接访问数据库,导致数据库压力过大;* 解决缓存穿透的常见方案:* (1)缓存空值:当查询数据库发现数据不存在时,将空结果(如null)写入缓存,并设置较短的过期时间;* (2)布隆过滤器(Bloom Filter):再缓存层前加布隆过滤器,预先存储所有合法Key的哈希值,查询时先检查布隆过滤器,若返回”不存在“,直接拦截请求,若返回”可能存在“,继续查询缓存/数据库;* (3)互斥锁(Mutex Lock):缓存未命中时,通过互斥锁(如 Redis 的 SETNX)保证只有一个线程查询数据库,其他线程等待回填缓存。* (4)接口层校验:在 API 入口处校验参数合法性,拦截明显无效的请求(如非法 ID 格式、负数等)* (5)热点数据永不过期:对高频访问的热点数据设置永不过期,通过后台线程主动更新缓存。* (6)缓存预热:在系统启动或低峰期,预先加载热点数据到缓存中。* (7)实时监控与限流:监控异常流量(如大量 null 响应),触发限流策略(如令牌桶、漏桶算法),保护数据库。* <p>* 2.缓存击穿(Cache Breakdown)* 缓存击穿的定义:缓存击穿(Cache Breakdown)是指某个热点key(如爆款商品信息)在缓存中过期后,大量并发请求同时访问数据库(请求数据存在),导致数据库压力骤增。* 触发原因:缓存过期时间到期,且高并发场景下请求集中失效。* 核心问题:单个热点key失效后,大量请求同时访问数据库。* 3.缓存雪崩(Cache Avalanche)* 问题描述:* 大量缓存key在同一时间过期;* 大量请求直接访问数据库,导致数据库崩溃;*/
@RestController
@RequestMapping("/userRedis")
public class CacheTestController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate UserService userService;//    缓存key前缀private static final String KEY_HOT = "hot:user:";//    热点ID列表——实际可放到配置中心或DBprivate static final List<String> HOT_ID_LIST = Arrays.asList("1001", "1002", "1003");//    缓存前缀private static final String KEY_HOT_PRE = "user:cache:hot:";/* ========== 1. 令牌桶:10 QPS,突发 20 ========== */private final RateLimiter rateLimiter = RateLimiter.create(10.0);/* ========== 2. 异常计数 key 前缀 ========== */private static final String KEY_NULL_COUNT = "user:null:count:";private static final String KEY_BLOCK_FLAG  = "user:block:";   // 阻塞标记private static final int  MAX_NULL_THRESHOLD = 30;            // 1 分钟内最多 30 次 nullprivate static final int  BLOCK_SECONDS      = 60;            // 触发后封 60 秒@PostMapping("/selectById")public R selectById(@RequestBody User user) {User userInfo = userService.getById(user.getId());stringRedisTemplate.opsForValue().set(String.valueOf(userInfo.getId()), userInfo.getUserName());String value = stringRedisTemplate.opsForValue().get(String.valueOf(userInfo.getId()));HashMap<String, User> map = new HashMap<>();map.put(value, userInfo);return R.ok(map);}/*** 解决方案一:缓存空值* 原理:当查询数据库发现数据不存在时,将空结果(如null)写入缓存,并设置较短的过期时间;* 优点:简单易实现,直接拦截后续相同请求;* 缺点:1)内存浪费(存储大量无效null值)* 2)可能出现短时不一致,如:数据已补录,但缓存未及时失效(如需强一致性,可以在更新数据时,删除/覆盖缓存)** @param user* @return*/@PostMapping("/selectByIdNull")public R selectByIdNull(@RequestBody User user) {
//        1.从redis查询用户信息缓存;String key = String.valueOf(user.getId());String userJson = stringRedisTemplate.opsForValue().get(key);
//        2.判断是否存在;if (StrUtil.isNotBlank(userJson)) {//3.存在,直接返回;User user1 = JSONUtil.toBean(userJson, User.class);return R.ok(user1);}
//        判断命中的是否是空值if (userJson != null) {
//            返回错误信息,解决缓存穿透问题return R.fail("用户信息不存在");}
//        4.不存在,根据id查询数据库;User userInfo = userService.getById(user.getId());if (userInfo == null) {//5.如果数据库也不存在,将空字符串写入redis,设置过期事件,解决缓存穿透问题;stringRedisTemplate.opsForValue().set(key, "", 1000, TimeUnit.MINUTES);
//            返回错误信息,解决缓存穿透问题return R.fail("用户信息不存在");}
//        6.存在,写入redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(userInfo), 1000, TimeUnit.MINUTES);return R.ok(userInfo);}/*** 解决方案二:布隆过滤器(Bloom Filter)* 原理:在缓存层前加布隆过滤器,预先存储所有合法Key的哈希值,查询时先检查布隆过滤器,若返回”不存在“,直接拦截请求,若返回”可能存在“,继续查询缓存/数据库;* 优点:内存占用低(没有多余的Key),适合海量数据,查询时间复杂度O(1);* 缺点:1)存在错误的可能(可能将不存在判断未存在);* 2)实现复杂,删除元素困难(需重建过滤器);* 适用场景:数据量大且允许误判(如黑名单校验);** @param user* @return*/@PostMapping("/selectByIdBloomFilter")public R selectByIdBloomFilter(@RequestBody User user) {if (user.getId() == null || user.getId() <= 0) {return R.fail("用户id不合适");}long startTime = System.currentTimeMillis();User userInfo = userService.selectByIdBloomFilter(user);long endTime = System.currentTimeMillis();HashMap<String, Object> result = new HashMap<>();result.put("time", endTime - startTime);if (userInfo != null) {result.put("info", userInfo);return R.ok(result);} else {result.put("info", null);return R.fail(result);}}/*** 直接查询用户信息——与使用布隆过滤器做对比** @param user* @return*/@PostMapping("/comWithSelectByIdBloomFilter")public R comWithSelectByIdBloomFilter(@RequestBody User user) {if (user.getId() == null || user.getId() <= 0) {return R.fail("用户id不合法");}long startTime = System.currentTimeMillis();User userInfo = userService.selectByIdDirect(user);long endTime = System.currentTimeMillis();HashMap<String, Object> result = new HashMap<>();result.put("time", endTime - startTime);if (userInfo != null) {result.put("info", userInfo);return R.ok(result);} else {result.put("info", null);return R.fail(result);}}/*** 解决方案三:互斥锁* 原理:缓存未命中时,通过互斥锁(如 Redis 的 SETNX)保证只有一个线程查询数据库,其他线程等待回填缓存。* 优点:避免大量请求同时穿透到数据库。* 缺点:1)分布式环境下需使用分布式锁(如 Redis RedLock)。* 2)锁竞争可能成为性能瓶颈。** @param user* @return*/@PostMapping("/selectByWithLock")public R selectByWithLock(@RequestBody User user) {if (user.getId() == null || user == null) {return R.fail("用户id不能为空");}long startTime = System.currentTimeMillis();HashMap<String, Object> result = new HashMap<>();try {
//            User userInfo = userService.selectByIdWithLock(user);        //使用Redisson分布式锁
//            User userInfo = userService.selectByIdWithSpringLock(user);  //使用Spring的Redis分布式锁User userInfo = userService.selectByIdWithSimpleLock(user);    //使用RedisTemplate实现简单分布式锁long endTime = System.currentTimeMillis();if (result != null) {result.put("time", endTime - startTime);result.put("info", userInfo);return R.ok(result);} else {result.put("time", endTime - startTime);result.put("info", null);return R.fail(result);}} catch (Exception e) {long endTime = System.currentTimeMillis();result.put("time", endTime - startTime);result.put("info", null);return R.fail(result);}}/*** 解决方案四;接口层校验* 原理:在 API 入口处校验参数合法性,拦截明显无效的请求(如非法 ID 格式、负数等)。* 优点:低成本防御恶意攻击(如扫描全表 ID)。* 缺点:无法拦截合法参数但实际不存在的数据请求。** @param user* @return*/@PostMapping("/selectByIdWithInterfacter")public R selectByIdWithInterfacter(@RequestBody User user) {
//        1.接口层校验:必须是纯数字且正整数Long id = validId(String.valueOf(user.getId()));if (id == null) {return R.fail("非法id格式");}
//        2.业务查询User userInfo = userService.getById(user.getId());return user == null ? R.fail("用户不存在") : R.ok(userInfo);}/*** 校验 ID 合法性:纯数字、正整数、非空* 合法返回 Long,非法返回 null* 作用:用于接口层校验*/private Long validId(String idRaw) {if (idRaw == null || idRaw.isEmpty()) {return null;}try {long id = Long.parseLong(idRaw.trim());return id > 0L ? id : null;} catch (NumberFormatException e) {return null;}}/*** 解决方法五:热点数据永不过期* 原理:对高频访问的热点数据设置永不过期,通过后台线程主动更新缓存。* 优点:彻底避免缓存失效导致的穿透。* 缺点:数据一致性依赖更新机制,需处理脏数据问题。* 实现:结合定时任务或事件驱动更新缓存。** @param user* @return*/@PostMapping("/selectByIdWithHot")public R<User> selectByIdWithHot(@RequestBody User user) {String key = KEY_HOT + user.getId();String lockKey = key + ":lock";String lockVal = UUID.fastUUID().toString();int retry = 3;                       // 最多自旋 3 次while (retry-- > 0) {/* 1. 缓存命中立即返回 */String json = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(json)) {return R.ok(JSON.parseObject(json, User.class));}/* 2. 抢锁(5 s 自动过期) */Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 5, TimeUnit.SECONDS);if (Boolean.TRUE.equals(locked)) {try {/* 3. double check */json = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(json)) {return R.ok(JSON.parseObject(json, User.class));}/* 4. 回源 DB */User userDB = userService.getById(user.getId());if (userDB != null) {stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(userDB));}return userDB == null ? R.fail("用户信息不存在") : R.ok(userDB);} finally {/* 5. 原子释放锁 */String lua ="if redis.call('get', KEYS[1]) == ARGV[1] then " +"  return redis.call('del', KEYS[1]) " +"else return 0 end";stringRedisTemplate.execute(new DefaultRedisScript<>(lua, Long.class),Collections.singletonList(lockKey),lockVal);}}/* 6. 没抢到锁,自旋 100 ms 继续重试 */ThreadUtil.sleep(100);}/* 7. 重试耗尽 */return R.fail("系统繁忙,请稍后再试");}/* ===================== 后台定时更新 ===================== *//*** 每 30 秒刷新一次热点缓存,保证“永不过期”且数据最终一致*/@Scheduled(fixedDelay = 30_000)public void refreshHotCache() {for (String id : HOT_ID_LIST) {String key = KEY_HOT + id;User user = userService.getById(id);if (user != null) {// 直接覆盖旧值,仍不带过期时间stringRedisTemplate.opsForValue().set(key, user.toString());} else {// DB 已删除,同步删缓存stringRedisTemplate.delete(key);}}}/*** 解决方案六:缓存预热* 原理:在系统启动或低峰期,预先加载热点数据到缓存中。* 优点:减少冷启动时的缓存穿透风险。* 缺点:需提前知道热点数据(可通过历史日志分析)。** @param user* @return*/@PostMapping("/selectByIdWithPreHot")public R selectByIdWithPreHot(@RequestBody User user) {String key = KEY_HOT_PRE + user.getId();String json = stringRedisTemplate.opsForValue().get(key);if(StrUtil.isNotBlank(json)){return R.ok(JSON.parseObject(json,User.class));}return R.fail("用户信息不存在");}/*** 解决方案七:实时监控与限流* 原理:监控异常流量(如大量 null 响应),触发限流策略(如令牌桶、漏桶算法),保护数据库。* 优点:兜底防御,避免突发攻击。* 缺点:需配套监控和告警系统。** @param user* @return*/@PostMapping("/selectById4")public R selectById4(@RequestBody User user) {/* 3. 先拿令牌,拿不到直接限流 */if (!rateLimiter.tryAcquire()) {return R.fail("系统繁忙,请稍后再试");}/* 4. 是否处于封禁期 */String blockKey = KEY_BLOCK_FLAG + user.getId();if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(blockKey))) {return R.fail("请求过于频繁,请稍后再试");}/* 5. 查库 */User userInfo = userService.getById(user.getId());/* 6. 异常计数(null 视为异常) */if (userInfo == null) {String countKey = KEY_NULL_COUNT + user.getId();long count = stringRedisTemplate.opsForValue().increment(countKey);stringRedisTemplate.expire(countKey, 60, TimeUnit.SECONDS);          // 1 分钟窗口if (count >= MAX_NULL_THRESHOLD) {stringRedisTemplate.opsForValue().set(blockKey, "1", BLOCK_SECONDS, TimeUnit.SECONDS);return R.fail("触发保护策略,稍后再试");}return R.fail("用户信息不存在");}/* 7. 正常返回 */return R.ok(userInfo);}
}

2.2 service层

2.2.1 service接口

package com.study.sredis.stept001.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.study.sredis.stept001.domain.User;public interface UserService extends IService<User> {/*** 使用布隆过滤器查询用户信息** @param user* @return*/User selectByIdBloomFilter(User user);/*** 直接查询用户信息——不使用布隆过滤器** @param user* @return*/User selectByIdDirect(User user);/*** 根据id查询用户 —— 使用Redisson分布式锁(推荐!!!)** @param user* @return*/public User selectByIdWithLock(User user);/*** 根据id查询用户 —— 使用Spring的Redis分布式锁** @param user* @return*/public User selectByIdWithSpringLock(User user);/*** 根据id查询用户 —— 使用RedisTemplate实现简单分布式锁** @param user* @return*/public User selectByIdWithSimpleLock(User user);
}

2.2.2 serviceImpl

package com.study.sredis.stept001.service.impl;import cn.hutool.bloomfilter.BloomFilter;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.study.sredis.stept001.domain.User;
import com.study.sredis.stept001.mapper.userMapper;
import com.study.sredis.stept001.service.UserService;
import com.study.sredis.utils.RedisLockUtil;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;
import java.time.Duration;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;@Service
public class UserServiceImpl extends ServiceImpl<userMapper, User> implements UserService {@Autowiredprivate BloomFilter bloomFilter;@Autowiredprivate userMapper userMapper;@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate RedisLockUtil redisLockUtil;private static final String CACHE_PREFIX = "user:";private static final String LOCK_PREFIX = "lock:user";private static final long CACHE_EXPIRE = 300;  //5分钟/*** 使用布隆过滤器查询用户信息** @param user* @return*/@Overridepublic User selectByIdBloomFilter(User user) {
//        1.先检查布隆过滤器;if (!bloomFilter.contains("user:" + user.getId())) {return null;}
//        2.布隆过滤器通过,查询数据库;User userInfo = userMapper.selectById(user.getId());return userInfo;}/*** 直接查询用户信息** @param user* @return*/@Overridepublic User selectByIdDirect(User user) {User userInfo = userMapper.selectById(user.getId());return userInfo;}/*** 根据id查询用户 - 使用Redisson分布式锁(推荐!!!)** @param user* @return*/@Overridepublic User selectByIdWithLock(User user) {String cacheKey = CACHE_PREFIX + user.getId();String lockKey = LOCK_PREFIX + user.getId();
//        1.先查缓存User userInfo = (User) redisTemplate.opsForValue().get(cacheKey);if (userInfo != null) {return "NULL".equals(userInfo) ? null : userInfo;}
//        2.获取分布式锁RLock lock = redissonClient.getLock(lockKey);try {
//            尝试获取锁:等待时间5秒,锁过期时间是30秒boolean locked = lock.tryLock(5, 30, TimeUnit.SECONDS);if (locked) {
//                3.双重检查缓存userInfo = (User) redisTemplate.opsForValue().get(cacheKey);if (userInfo != null) {return "NULL".equals(userInfo) ? null : userInfo;}
//                4.查询数据库userInfo = userMapper.selectById(user.getId());
//                5.回填缓存if (userInfo != null) {redisTemplate.opsForValue().set(cacheKey, userInfo, CACHE_EXPIRE, TimeUnit.SECONDS);} else {redisTemplate.opsForValue().set(cacheKey, "NULL", 60, TimeUnit.SECONDS);  //短时间缓存空值}return userInfo;} else {
//                6.获取锁失败,等待并重试Thread.sleep(100);  //短暂等待return selectByIdWithLock(user); //递归重试}} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("获取锁被中断", e);} finally {
//            7.释放锁if (lock.isHeldByCurrentThread()) {lock.unlock();}}}/*** 根据id查询用户 —— 使用Spring的Redis分布式锁** @param user* @return*/@Overridepublic User selectByIdWithSpringLock(User user) {String cacheKey = CACHE_PREFIX + user.getId();String lockKey = LOCK_PREFIX + user.getId();
//        1.先查缓存User userInfo = (User) redisTemplate.opsForValue().get(cacheKey);if (userInfo != null) {return "NULL".equals(userInfo) ? null : userInfo;}
//        2.使用Spring的分布式锁RedisLockRegistry lockRegistry = new RedisLockRegistry(redisTemplate.getConnectionFactory(), "user-lock");Lock lock = lockRegistry.obtain(lockKey);try {
//            尝试获取锁if (lock.tryLock(5, TimeUnit.SECONDS)) {try {
//                    3.双重检查缓存userInfo = (User) redisTemplate.opsForValue().get(cacheKey);if (userInfo != null) {return "NULL".equals(userInfo) ? null : userInfo;}
//                    4.查询数据库userInfo = userMapper.selectById(user.getId());
//                    5.回填缓存if (userInfo != null) {redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(5));} else {redisTemplate.opsForValue().set(cacheKey, "NULL", Duration.ofMinutes(1));}return userInfo;} finally {lock.unlock();}} else {
//                获取锁失败,递归重试Thread.sleep(100);return selectByIdWithLock(user);}} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("获取锁被中断", e);}}/*** 根据id查询用户 —— 使用RedisTemplate实现简单分布式锁** @param user* @return*/@Overridepublic User selectByIdWithSimpleLock(User user) {String cacheKey = "user:" + user.getId();String lockKey = "lock:user:" + user.getId();String lockValue = UUID.randomUUID().toString();
//        1.先查缓存User userInfo = (User) redisTemplate.opsForValue().get(cacheKey);if (userInfo != null) {return "NULL".equals(userInfo) ? null : userInfo;}boolean locked = false;try {
//            2.尝试获取锁for (int i = 0; i < 3; i++) {locked = redisLockUtil.tryLock(lockKey, lockValue, 30, TimeUnit.SECONDS);if (locked) break;Thread.sleep(100);}if (locked) {
//                3.双重检查缓存userInfo = (User) redisTemplate.opsForValue().get(cacheKey);if (userInfo != null) {return "NULL".equals(userInfo) ? null : userInfo;}
//                4.查询数据库userInfo = userMapper.selectById(user.getId());
//                5.回填缓存if (userInfo != null) {redisTemplate.opsForValue().set(cacheKey, userInfo, Duration.ofMinutes(5));} else {redisTemplate.opsForValue().set(cacheKey, "NULL", Duration.ofMinutes(1));}return userInfo;} else {
//                获取锁失败,直接查询数据(降级)return userMapper.selectById(user.getId());}} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("获取锁被中断", e);} finally {if (locked) {redisLockUtil.releaseLock(lockKey, lockValue);}}}/*** 初始化布隆过滤器*/@PostConstructpublic void initBloomFilter() {
//        1.查询所有用户信息List<User> userList = userMapper.selectList(null);for (User user : userList) {bloomFilter.add("user:" + user.getId());}}
}

2.3 依赖

 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- 集成redis依赖  --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!--mybatis-plus依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.5</version></dependency><!-- mysql依赖--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency><!--        hutool依赖--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.16</version></dependency><!-- 分布式锁 + Redis 客户端一站式 --><!-- 分布式锁 starter:redisson-spring-boot-starter --><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.24.3</version></dependency><!-- 或者使用Spring Boot Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-integration</artifactId></dependency><!--Spring分布式锁--><dependency><groupId>org.springframework.integration</groupId><artifactId>spring-integration-redis</artifactId></dependency><!--用于json类型转换--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version></dependency><!-- 若用 Guava 令牌桶 --><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>32.1.3-jre</version></dependency></dependencies>

2.4 工具类

2.4.1 布隆过滤器配置类——BloomFilterConfig

package com.study.sredis.utils;import cn.hutool.bloomfilter.BitMapBloomFilter;
import cn.hutool.bloomfilter.BloomFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 布隆过滤器配置类*/
@Configuration
public class BloomFilterConfig {/*** 创建布隆过滤器* 参数说明:预计插入数量,误判率*/@Beanpublic BloomFilter bloomFilter() {// 预计插入1000个元素,误判率0.01return new BitMapBloomFilter(1000);}
}

2.4.2 缓存预热配置类——CacheWarmRunner

package com.study.sredis.utils;import com.alibaba.fastjson.JSON;
import com.study.sredis.stept001.domain.User;
import com.study.sredis.stept001.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.Arrays;
import java.util.List;/*** 缓存预热配置类*/
@Component
@RequiredArgsConstructor
@Slf4j
public class CacheWarmRunner implements ApplicationRunner {private final StringRedisTemplate redisTpl;private final UserService userService;private static final String KEY_HOT = "user:cache:hot:";   // 与 Controller 保持一致private static final List<Long> HOT_ID_LIST = Arrays.asList(1001L, 1002L, 1003L);@Overridepublic void run(ApplicationArguments args) {log.info("====== 缓存预热开始 ======");for (Long id : HOT_ID_LIST) {String key = KEY_HOT + id;if (Boolean.TRUE.equals(redisTpl.hasKey(key))) continue;User user = userService.getById(id);if (user != null) {redisTpl.opsForValue().set(key, JSON.toJSONString(user));log.info("已预热 -> {}", key);}}log.info("====== 缓存预热结束 ======");}
}

2.4.3 redis配置类——RedisConfig

package com.study.sredis.utils;import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.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.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());return redisTemplate;}
//    redisson锁@Beanpublic RedissonClient redissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);return Redisson.create(config);}
}

2.4.4 锁配置类——RedisLockUtil

package com.study.sredis.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;/*** 锁配置类*/
@Component
public class RedisLockUtil {@Autowiredprivate StringRedisTemplate redisTemplate;/*** 尝试获取锁*/public boolean tryLock(String key, String value, long expire, TimeUnit timeUnit) {return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value, expire, timeUnit));}/*** 释放锁*/public boolean releaseLock(String key, String value) {String currentValue = redisTemplate.opsForValue().get(key);if (value.equals(currentValue)) {redisTemplate.delete(key);return true;}return false;}
}
http://www.dtcms.com/a/515600.html

相关文章:

  • Tanh 函数详解
  • 【手机篇】AI深度学习在手机中框/RT四周外观检测应用方案
  • 遂宁网站优化东莞市住房和城乡建设局门户网站
  • 基于专家经验的网络异常流量检测技术研究
  • 生成模型实战 | MUNIT详解与实现
  • 网站建设的费用包括微信app下载找回微信
  • JAVA攻防-常规漏洞SQL注入四类型XXE引用点RCE原生框架URL跳转URL处理类
  • Disk Drill Enterprise Windows数据恢复工具
  • 合合信息亮相PRCV:多模态文本智能与内容安全双擎驱动新突破
  • PCIe协议之 Equalization篇 之 理论篇 之 DFE CTLE
  • 接单做一个网站多少钱网站后台修改图片
  • 操作系统4.3.1 文件系统的层次结构
  • 做2手物品通过网站去卖掉好做吗基于.net的个人网站开发实录
  • 三轴云台之线性控制特性
  • c++注意点(15)----状态模式
  • Delmia 软件 Teach 模块 interpolationMode 插补模式应用说明
  • Android Studio新手开发第二十八天
  • 系统与网络安全------弹性交换网络(4)
  • 功能网站首页模板微信小说分销平台
  • 网站建设管理规定php网站怎么建设
  • 【软考备考】物联网架构:感知层、网络层、平台层、应用层详解
  • LeetCode每日一题——二进制求和
  • 【LeetCode】长度最小的子数组
  • 从什么网站建网站好百度seo优化哪家好
  • 深度学习——基于 PyTorch 的蔬菜图像分类
  • 【设计模式】适配器模式(Adapter)
  • docker安装中间件
  • 系统架构设计师备考第48天——机器人边缘计算
  • 门头沟高端网站建设阿里云服务器win系统建站教程
  • ui设计培训机构哪个比较好cpu优化软件