Redis缓存穿透+缓存击穿+缓存雪崩 案例及解决方案(代码实现)

1、介绍

1.1、缓存穿透

什么是缓存穿透?

用户请求的数据在缓存和数据库中都不存在,用户每次请求数据都需要查询数据库,导致对后台数据库的频繁访问,数据库负载压力过大,这种现象就叫做缓存穿透。

产生原因

黑客大量访问不存在的key,导致数据库处理大量请求

解决方案

1、缓存空对象

2、布隆过滤器

1.2、缓存击穿

什么是缓存击穿?

redis中存在某些热点数据时,即有大量请求并发访问的key-value数据。当极热点key-value数据突然失效时,缓存未命中引起对后台数据库的频繁访问,这种现象叫缓存击穿。

产生原因

缓存上热点数据突然失效

解决方案

1、热点key设置永不过期,或者异步后台更新

2、采用互斥锁

1.3、缓存雪崩

什么是缓存雪崩?

缓存数据在同一时间点失效,导致大量的请求到数据库,从而使得是数据库崩溃。

产生原因

1、大量数据使用了同一过期时间

2、redis故障

解决方案

1、缓存数据的过期时间设置随机。防止同一时间大量缓存数据集体失效,导致数据库压力过大。

2、采用Redis高可用架构。可将热点数据分别存放到不同缓存数据库中,避免某一点由于压力过大而down掉。

3、请求限流、熔断机制、服务降级等手段。降低服务器负载。

2、缓存穿透

2.1、模拟缓存穿透

1、请求一个数据库中没有的key值,导致一直访问数据库,造成缓存穿透

2.2、解决方案

2.2.1、缓存空对象

2.2.2、布隆过滤器

3、缓存击穿

3.1、模拟缓存击穿

1、初始化缓存,把数据库中所有数据加入缓存中

2、多线程访问热点key 88,查询是否全部命中缓存

3、删除热点key为88的缓存,模拟热点key过期,多线程访问

4、多线程访问热点key 88

PixPin_2024-07-07_20-17-56

Snipaste_2024-07-07_11-07-07

Snipaste_2024-07-07_11-13-58

3.2、解决方案

3.2.1、互斥锁

缓存击穿解决方案1-互斥锁PixPin_2024-07-11_22-49-26.gif

image-20240712092721619

3.2.2、异步定时任务

缓存击穿解决方案2-异步定时任务PixPin_2024-07-11_22-53-36.gif

 

image-20240712092917288

4、缓存雪崩

image.png

4.1、模拟缓存雪崩

1、初始化缓存,把数据库中所有数据加入缓存中

2、多线程访问 30至40间的热点key,查询是否全部命中缓存

3、删除 30至40间的热点key的缓存,模拟多个热点key同时过期,多线程访问

4、多线程访问 30至40间的热点key

PixPin_2024-07-07_20-30-05

Snipaste_2024-07-07_11-20-24

Snipaste_2024-07-07_11-41-59

4.2、解决方案

4.2.1、互斥锁

缓存雪崩解决方案1-互斥锁PixPin_2024-07-11_22-57-26.gif

image-20240712093155767

4.2.2、异步定时任务

缓存雪崩解决方案2-异步定时任务PixPin_2024-07-11_22-59-59.gif

image-20240712095913145

4.2.3、随机过期时间

缓存雪崩解决方案3-均匀随机过期时间PixPin_2024-07-11_23-02-15.gif

 

缓存雪崩解决方案3-均匀随机过期时间PixPin_2024-07-11_23-02-15.gif

image-20240712100050664

5、代码实现

5.0、建表语句

 

sql

代码解读

复制代码

CREATE TABLE `user_bank` ( `id` int NOT NULL AUTO_INCREMENT, `user_id` int DEFAULT NULL COMMENT '用户Id', `user_name` varchar(50) DEFAULT NULL COMMENT '用户名称', `amount` int NOT NULL DEFAULT '0' COMMENT '用户余额', PRIMARY KEY (`id`), UNIQUE KEY `user_bank_id_uindex` (`id`), UNIQUE KEY `user_bank_user_id_uindex` (`user_id`) ) ENGINE=InnoDB AUTO_INCREMENT=118 DEFAULT CHARSET=utf8mb3 COMMENT='用户余额表';

5.1、目录结构

image-20240712101851472

5.2、依赖

 

xml

代码解读

复制代码

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </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> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <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>17.0</version> </dependency>

5.3、配置文件

 

yml

代码解读

复制代码

server: port: 8087 servlet: context-path: / # mysql连接 spring: datasource: url: jdbc:mysql://localhost:3309/redis_study?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 password: redis database: 3

5.4、controller

缓存穿透

 

java

代码解读

复制代码

package com.wangzhan.controller; import com.wangzhan.domain.UserBank; import com.wangzhan.service.CachePenetrationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * @author wangzhan * @version 1.0 * @description 缓存穿透 * @date 2024/7/6 11:09:30 */ @RestController @RequestMapping("/cachePenetration") public class CachePenetrationController { @Resource private CachePenetrationService cachepenetrationService; /*** * @description 模拟缓存穿透 * @param id 主键id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/3 20:33:11 */ @GetMapping("imitateCachePenetration") public UserBank imitateCachePenetration(Integer id) { return cachepenetrationService.imitateCachePenetration(id); } /*** * @description 解决方案一:缓存空对象 * @param id 主键id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/3 20:33:11 */ @GetMapping("cacheNullKey") public Object cacheNullKey(Integer id) { return cachepenetrationService.cacheNullKey(id); } /*** * @description 解决方案二:布隆过滤器 * @param id 主键id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/3 20:33:11 */ @GetMapping("bloomFilter") public Object bloomFilter(Integer id) { return cachepenetrationService.bloomFilter(id); } }

缓存击穿

 

java

代码解读

复制代码

package com.wangzhan.controller; import com.wangzhan.domain.UserBank; import com.wangzhan.service.HotspotInvalidService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * @author wangzhan * @version 1.0 * @description 缓存击穿 * @date 2024/7/6 11:10:17 */ @RestController @RequestMapping("/hotspotInvalid") public class HotspotInvalidController { @Resource private HotspotInvalidService hotspotInvalidService; /*** * @description 1、模拟缓存击穿-初始化缓存 * @return void * @author wangzhan * @date 2024/7/3 20:33:11 */ @GetMapping("initCache") public void initCache() { hotspotInvalidService.initCache(); } /*** * @description 2、模拟缓存击穿(数据从缓存中获取) 4、模拟缓存击穿(数据从数据库中获取) * @param id 主键id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/3 20:33:11 */ @GetMapping("imitateHotspotInvlid") public UserBank imitateHotspotInvlid(Integer id) { return hotspotInvalidService.imitateHotspotInvlid(id); } /*** * @description 3、模拟缓存击穿-删除缓存id为88的数据 * @return void * @author wangzhan * @date 2024/7/3 20:33:11 */ @GetMapping("delete88IdData") public void delete88IdData() { hotspotInvalidService.delete88IdData(); } /*** * @description 解决方案一:采用互斥锁 * @param id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/9 16:50:24 */ @GetMapping("lock") public UserBank lock(Integer id) { return hotspotInvalidService.lock(id); } /*** * @description 解决方案二:异步定时任务 * @return void * @author wangzhan * @date 2024/7/9 16:50:24 */ @GetMapping("cornJob") public UserBank cornJob(Integer id) { return hotspotInvalidService.cornJob(id); } }

缓存雪崩

 

java

代码解读

复制代码

package com.wangzhan.controller; import com.wangzhan.domain.UserBank; import com.wangzhan.service.CacheAvalancheService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * @author wangzhan * @version 1.0 * @description 缓存雪崩 * @date 2024/7/6 11:08:19 */ @RestController @RequestMapping("/cacheAvalanche") public class CacheAvalancheController { @Resource private CacheAvalancheService cacheAvalancheService; /*** * @description 1、模拟缓存击穿-初始化缓存 * @return void * @author wangzhan * @date 2024/7/3 20:33:11 */ @GetMapping("initCache") public void initCache() { cacheAvalancheService.initCache(); } /*** * @description 2、模拟缓存雪崩(数据从缓存中获取) 4、模拟缓存雪崩(数据从数据库中获取) * @param id 主键id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/3 20:33:11 */ @GetMapping("imitateCacheAvalanche") public UserBank imitateCacheAvalanche(Integer id) { return cacheAvalancheService.imitateCacheAvalanche(id); } /*** * @description 3、模拟缓存雪崩-删除30-40的数据 * @return void * @author wangzhan * @date 2024/7/3 20:33:11 */ @GetMapping("delete30_40IdData") public void delete30_40IdData() { cacheAvalancheService.delete30_40IdData(); } /*** * @description 解决方案一:采用互斥锁 * @param id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/10 22:28:42 */ @GetMapping("lock") public UserBank lock(Integer id) { return cacheAvalancheService.lock(id); } /*** * @description 解决方案二:异步定时任务 * @param id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/10 22:29:04 */ @GetMapping("cornJob") public UserBank cornJob(Integer id) { return cacheAvalancheService.cornJob(id); } /*** * @description 解决方案三:随机过期时间 * @return int * @author wangzhan * @date 2024/7/10 22:29:04 */ @GetMapping("randomExpire") public UserBank randomExpireTime(Integer id) { return cacheAvalancheService.randomExpireTime(id); } }

5.5、service

缓存穿透

 

java

代码解读

复制代码

package com.wangzhan.service; import com.wangzhan.domain.UserBank; /** * @author wangzhan * @version 1.0 * @description * @date 2024/7/7 10:35:45 */ public interface CachePenetrationService { /*** * @description 初始化缓存 * @return void * @author wangzhan * @date 2024/7/3 17:01:01 */ void initCache(); /*** * @description 模拟缓存穿透 * @param id 主键id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/3 16:59:45 */ UserBank imitateCachePenetration(Integer id); /*** * @description 解决方案一:缓存空对象 * @param id 主键id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/3 16:59:45 */ Object cacheNullKey(Integer id); /*** * @description 解决方案二:布隆过滤器 * @param id 主键id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/3 16:59:45 */ Object bloomFilter(Integer id); }

 

java

代码解读

复制代码

package com.wangzhan.service.impl; import com.alibaba.fastjson.JSON; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; import com.wangzhan.domain.UserBank; import com.wangzhan.mapper.UserBankMapper; import com.wangzhan.service.CachePenetrationService; import com.wangzhan.util.UserBankUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.util.List; import java.util.concurrent.TimeUnit; /** * @author wangzhan * @version 1.0 * @description * @date 2024/7/7 10:35:55 */ @Service public class CachePenetrationServiceImpl implements CachePenetrationService { private static final Logger logger = LoggerFactory.getLogger(UserBankServiceImpl.class); private static final String IMITATE_KEY = "user_bank_key:wangzhan:cachePenetration:imitate"; // 缓存key private static final String SOLVE1_IMITATE_KEY = "user_bank_key:wangzhan:cachePenetration:solve1"; // 解决方案的缓存key private static final String SOLVE2_IMITATE_KEY = "user_bank_key:wangzhan:cachePenetration:solve2"; // 解决方案的缓存key public static final int _1W = 10000; //布隆过滤器里预计要插入多少数据 public static int size = _1W; //误判率,它越小误判的个数也就越少(思考,是不是可以设置的无限小,没有误判岂不更好) public static double fpp = 0.03; // 构建布隆过滤器 private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp); @Resource private UserBankMapper userBankMapper; @Resource private UserBankUtil userBankUtil; private StringRedisTemplate stringRedisTemplate; public CachePenetrationServiceImpl(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } /*--------------------------------缓存穿透模拟-----------------------start----------------------------*/ @Override public UserBank imitateCachePenetration(Integer id) { return userBankUtil.queryUserBankOfHash(id, IMITATE_KEY, 1, TimeUnit.HOURS); } /*--------------------------------缓存穿透模拟-----------------------end----------------------------*/ /*--------------------------------缓存穿透解决方案-----------------------start----------------------------*/ @Override public Object cacheNullKey(Integer id) { String idStr = String.valueOf(id); UserBank userBank = userBankUtil.queryUserBankOfHash(id, SOLVE1_IMITATE_KEY, 1, TimeUnit.HOURS); if (userBank != null) { return userBank; } // 缓存和数据库都不存在 userBank = new UserBank(); userBank.setId(id); userBank.setUserName("当前用户不存在"); userBank.setAmount(0); stringRedisTemplate.opsForHash().put(SOLVE1_IMITATE_KEY, idStr, JSON.toJSONString(userBank)); stringRedisTemplate.expire(SOLVE1_IMITATE_KEY, 1, TimeUnit.HOURS); logger.info("缓存和数据库都不存在"); return userBank; } @PostConstruct @Override public void initCache() { new Thread(() -> { // 查询所有数据进行缓存 List<UserBank> userBanks = userBankMapper.list(); userBanks.forEach(userBank -> { // 初始化时,先添加到布隆过滤器 bloomFilter.put(userBank.getId()); }); // 通过手动删除缓存(delete30And40IdData),模拟缓存失效 //stringRedisTemplate.expire("imitateHotspotInvlid", 1, TimeUnit.SECONDS); }).start(); } @Override public Object bloomFilter(Integer id) { if (bloomFilter.mightContain(id)) { // 可能包含 return userBankUtil.queryUserBankOfHash(id, SOLVE2_IMITATE_KEY, 1, TimeUnit.HOURS); } logger.info("布隆过滤器中一定不存在该数据,id为:{}", id); return null; } /*--------------------------------缓存穿透解决方案-----------------------end----------------------------*/ }

缓存击穿

 

java

代码解读

复制代码

package com.wangzhan.service; import com.wangzhan.domain.UserBank; /** * @author wangzhan * @version 1.0 * @description * @date 2024/7/7 10:36:28 */ public interface HotspotInvalidService { /*** * @description 模拟缓存击穿 * @param id 主键id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/3 16:59:45 */ UserBank imitateHotspotInvlid(Integer id); /*** * @description 删除缓存为30~40的数据 * * @return void * @author wangzhan * @date 2024/7/4 08:51:08 */ void delete88IdData(); /*** * @description 初始化缓存 * @return void * @author wangzhan * @date 2024/7/3 17:01:01 */ void initCache(); /*** * @description 解决方案一:采用互斥锁 * @param id 主键id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/3 16:59:45 */ UserBank lock(Integer id); /*** * @description 解决方案二:异步定时任务 * @return void * @author wangzhan * @date 2024/7/3 17:01:01 */ UserBank cornJob(Integer id); }

 

java

代码解读

复制代码

package com.wangzhan.service.impl; import com.alibaba.fastjson.JSON; import com.wangzhan.domain.UserBank; import com.wangzhan.mapper.UserBankMapper; import com.wangzhan.service.HotspotInvalidService; import com.wangzhan.task.HotspotInvlidTask; import com.wangzhan.util.UserBankUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author wangzhan * @version 1.0 * @description * @date 2024/7/7 10:36:40 */ @Service public class HotspotInvalidServiceImpl implements HotspotInvalidService { private static final Logger logger = LoggerFactory.getLogger(UserBankServiceImpl.class); private static final String IMITATE_KEY = "user_bank_key:wangzhan:hotspotInvalid:"; private static final String SOLVE1_IMITATE_KEY = "user_bank_key:wangzhan:hotspotInvalid:solve1:"; private static final String SOLVE2_IMITATE_KEY = "user_bank_key:wangzhan:hotspotInvalid:solve2:"; // 可重入锁 private static final Lock lock = new ReentrantLock(); @Resource private UserBankMapper userBankMapper; @Resource private UserBankUtil userBankUtil; @Resource private HotspotInvlidTask hotspotInvlidTask; private StringRedisTemplate stringRedisTemplate; public HotspotInvalidServiceImpl(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } /*--------------------------------缓存击穿模拟-----------------------start----------------------------*/ @Override public void initCache() { new Thread(() -> { // 查询所有数据进行缓存 List<UserBank> userBanks = userBankMapper.list(); userBanks.forEach(userBank -> { stringRedisTemplate.opsForValue().set(IMITATE_KEY + userBank.getId(), JSON.toJSONString(userBank)); stringRedisTemplate.expire(IMITATE_KEY + userBank.getId(), 1, TimeUnit.HOURS); }); // 通过手动删除缓存(delete30And40IdData),模拟缓存失效 //stringRedisTemplate.expire("imitateHotspotInvlid", 1, TimeUnit.SECONDS); }).start(); } @Override public UserBank imitateHotspotInvlid(Integer id) { return userBankUtil.queryUserBankOfStr(id, IMITATE_KEY + id, 1, TimeUnit.MINUTES); } @Override public void delete88IdData() { // 模拟id为88 缓存失效的情况 stringRedisTemplate.delete(IMITATE_KEY + 88); } /*--------------------------------缓存击穿模拟-----------------------end----------------------------*/ /*--------------------------------缓存击穿解决方案-----------------------start----------------------------*/ @Override public UserBank lock(Integer id) { String userBankJson = stringRedisTemplate.opsForValue().get(SOLVE1_IMITATE_KEY + id); UserBank userBank = JSON.parseObject(userBankJson, UserBank.class); if (userBank != null) { logger.info("从【缓存】中获取数据,id为:{},结果为:{}", id, userBank); return userBank; } try { lock.lock(); return userBankUtil.queryUserBankOfStr(id, SOLVE1_IMITATE_KEY + id, 1, TimeUnit.HOURS); } catch (Exception e) { e.printStackTrace(); }finally { // 释放锁 lock.unlock(); } return userBank; } // 初始化88的缓存,同时启动定时任务 @PostConstruct public void init88IdData() { hotspotInvlidTask.enableScheduledUpdate(); } @Override public UserBank cornJob(Integer id) { return userBankUtil.queryUserBankOfStr(id, SOLVE2_IMITATE_KEY + id, 1, TimeUnit.MINUTES); } /*--------------------------------缓存击穿解决方案-----------------------end----------------------------*/ }

缓存雪崩

 

java

代码解读

复制代码

package com.wangzhan.service; import com.wangzhan.domain.UserBank; /** * @author wangzhan * @version 1.0 * @description * @date 2024/7/7 10:34:44 */ public interface CacheAvalancheService { /*** * @description 初始化缓存 * @return void * @author wangzhan * @date 2024/7/3 17:01:01 */ void initCache(); /**** * @description 2、模拟缓存雪崩(数据从缓存中获取) 4、模拟缓存雪崩(数据从数据库中获取) * @param id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/6 11:02:38 */ UserBank imitateCacheAvalanche(Integer id); /*** * @description 3、模拟缓存雪崩-删除30-40的数据 * @return void * @author wangzhan * @date 2024/7/3 17:01:01 */ void delete30_40IdData(); /*** * @description 解决方案一:采用互斥锁 * @return void * @author wangzhan * @date 2024/7/3 17:01:01 */ UserBank lock(Integer id); /*** * @description 解决方案二:异步定时任务 * @param id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/6 11:02:38 */ UserBank cornJob(Integer id); /*** * @description 解决方案三:随机过期时间 * @param id * @return com.wangzhan.domain.UserBank * @author wangzhan * @date 2024/7/11 09:39:47 */ UserBank randomExpireTime(Integer id); }

 

java

代码解读

复制代码

package com.wangzhan.service.impl; import com.alibaba.fastjson.JSON; import com.wangzhan.domain.UserBank; import com.wangzhan.mapper.UserBankMapper; import com.wangzhan.service.CacheAvalancheService; import com.wangzhan.task.CacheAvalancheTask; import com.wangzhan.util.UserBankUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author wangzhan * @version 1.0 * @description * @date 2024/7/7 10:34:57 */ @Service public class CacheAvalancheServiceImpl implements CacheAvalancheService { private static final Logger logger = LoggerFactory.getLogger(UserBankServiceImpl.class); private static final String IMITATE_KEY = "user_bank_key:wangzhan:cacheAvalanche:imitate:"; // 解决方案1的key - 互斥锁 private static final String SOLVE1_IMITATE_KEY = "user_bank_key:wangzhan:cacheAvalanche:solve1:"; // 解决方案2的key - 异步定时更新缓存 private static final String SOLVE2_IMITATE_KEY = "user_bank_key:wangzhan:cacheAvalanche:solve2:"; // 解决方案3的key - 随机缓存失效时间 private static final String SOLVE3_IMITATE_KEY = "user_bank_key:wangzhan:cacheAvalanche:solve3:"; // 可重入锁 private static final Lock lock = new ReentrantLock(); @Resource private UserBankMapper userBankMapper; @Resource private UserBankUtil userBankUtil; @Resource private CacheAvalancheTask cacheAvalancheTask; private StringRedisTemplate stringRedisTemplate; public CacheAvalancheServiceImpl(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } /*--------------------------------缓存雪崩模拟-----------------------start----------------------------*/ @Override public void initCache() { new Thread(() -> { // 查询所有数据进行缓存 List<UserBank> userBanks = userBankMapper.list(); userBanks.forEach(userBank -> { stringRedisTemplate.opsForValue().set(IMITATE_KEY + userBank.getId(), JSON.toJSONString(userBank)); stringRedisTemplate.expire(IMITATE_KEY + userBank.getId(), 1, TimeUnit.HOURS); }); // 通过手动删除缓存(delete30And40IdData),模拟缓存失效 //stringRedisTemplate.expire("imitateHotspotInvlid", 1, TimeUnit.SECONDS); }).start(); } @Override public UserBank imitateCacheAvalanche(Integer id) { return userBankUtil.queryUserBankOfStr(id, IMITATE_KEY + id, 1, TimeUnit.HOURS); } @Override public void delete30_40IdData() { for (int i = 30; i < 40; i++) { stringRedisTemplate.delete(IMITATE_KEY + i); } } /*--------------------------------缓存雪崩模拟-----------------------end----------------------------*/ /*--------------------------------缓存雪崩解决方案-----------------------start----------------------------*/ @Override public UserBank lock(Integer id) { String userBankJson = stringRedisTemplate.opsForValue().get(SOLVE1_IMITATE_KEY + id); UserBank userBank = JSON.parseObject(userBankJson, UserBank.class); if (userBank != null) { logger.info("从【缓存】中获取数据,id为:{},结果为:{}", id, userBank); return userBank; } try { lock.lock(); // 再次检查数据,确保在获取锁期间没有其他线程更新缓存 userBankUtil.queryUserBankOfStr(id, SOLVE1_IMITATE_KEY + id, 1, TimeUnit.HOURS); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } return userBank; } // 初始化所有数据的缓存,同时启动定时任务 @PostConstruct public void initData() { cacheAvalancheTask.enableScheduledUpdate(); } @Override public UserBank cornJob(Integer id) { return userBankUtil.queryUserBankOfStr(id, SOLVE2_IMITATE_KEY + id, 1, TimeUnit.MINUTES); } // TODO 这里只是展示 随机过期时间的方案,并不保证线程安全。 @Override public UserBank randomExpireTime(Integer id) { return userBankUtil.queryUserBankOfStr(id, SOLVE3_IMITATE_KEY + id, randomExpireTime(), TimeUnit.SECONDS); } // 随机生成过期时间 public int randomExpireTime() { // 基本时间为60秒 int baseExpireTime = 60; // 随机偏移量范围 int offsetRange = 40; // 生成随机偏移量 int randomOffset = ThreadLocalRandom.current().nextInt(-offsetRange, offsetRange + 1); // 返回最终的过期时间 return baseExpireTime + randomOffset; } /*--------------------------------缓存雪崩解决方案-----------------------end----------------------------*/ }

5.6、mapper

 

java

代码解读

复制代码

package com.wangzhan.mapper; import com.wangzhan.domain.UserBank; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; import java.util.List; /** * @description user_bank * @author wangzhan * @date 2024-04-22 */ @Mapper //@Repository public interface UserBankMapper { List<UserBank> list(); /** * 新增 * @author wangzhan * @date 2024/04/22 **/ int insert(UserBank userBank); /** * 刪除 * @author wangzhan * @date 2024/04/22 **/ int delete(int id); /** * 更新 * @author wangzhan * @date 2024/04/22 **/ int update(UserBank userBank); /** * 查询 根据主键 id 查询 * @author wangzhan * @date 2024/04/22 **/ UserBank load(int id); /** * 查询 分页查询 * @author wangzhan * @date 2024/04/22 **/ List<UserBank> pageList(int offset, int pagesize); /** * 查询 分页查询 count * @author wangzhan * @date 2024/04/22 **/ int pageListCount(int offset,int pagesize); }

 

xml

代码解读

复制代码

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.wangzhan.mapper.UserBankMapper"> <resultMap id="BaseResultMap" type="com.wangzhan.domain.UserBank" > <id column="id" property="id" /> <result column="user_id" property="userId" /> <result column="user_name" property="userName" /> <result column="amount" property="amount" /> </resultMap> <sql id="Base_Column_List"> id , user_id, user_name, amount </sql> <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id" parameterType="com.wangzhan.domain.UserBank"> INSERT INTO user_bank <trim prefix="(" suffix=")" suffixOverrides=","> <if test="null != userId and '' != userId"> user_id, </if> <if test="null != userName and '' != userName"> user_name, </if> <if test="null != amount and '' != amount"> amount </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="null != userId and '' != userId"> #{userId}, </if> <if test="null != userName and '' != userName"> #{userName}, </if> <if test="null != amount and '' != amount"> #{amount} </if> </trim> </insert> <delete id="delete" > DELETE FROM user_bank WHERE id = #{id} </delete> <update id="update" parameterType="com.wangzhan.domain.UserBank"> UPDATE user_bank <set> <if test="null != userId and '' != userId">user_id = #{userId},</if> <if test="null != userName and '' != userName">user_name = #{userName},</if> <if test="null != amount and '' != amount">amount = #{amount}</if> </set> WHERE id = #{id} </update> <select id="load" resultMap="BaseResultMap"> SELECT <include refid="Base_Column_List" /> FROM user_bank WHERE id = #{id} </select> <select id="pageList" resultMap="BaseResultMap"> SELECT <include refid="Base_Column_List" /> FROM user_bank LIMIT #{offset}, #{pageSize} </select> <select id="pageListCount" resultType="java.lang.Integer"> SELECT count(1) FROM user_bank </select> <select id="list" resultType="com.wangzhan.domain.UserBank" resultMap="BaseResultMap"> SELECT <include refid="Base_Column_List" /> FROM user_bank </select> </mapper>

5.7、异步定时任务

缓存击穿定时任务

 

java

代码解读

复制代码

package com.wangzhan.task; import com.alibaba.fastjson.JSON; import com.wangzhan.domain.UserBank; import com.wangzhan.mapper.UserBankMapper; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.concurrent.TimeUnit; /** * @author wangzhan * @version 1.0 * @description 异步定时更新缓存 * @date 2024/7/10 08:53:18 */ @Component @EnableScheduling public class HotspotInvlidTask { @Resource private UserBankMapper userBankMapper; private StringRedisTemplate stringRedisTemplate; public HotspotInvlidTask(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } private volatile boolean enableScheduledUpdate = false; private static final String SOLVE2_IMITATE_KEY = "user_bank_key:wangzhan:hotspotInvalid:solve2:"; /*** * @description 间隔55秒更新缓存 * * @return void * @author wangzhan * @date 2024/7/10 11:12:51 */ @Scheduled(fixedDelay = 55, timeUnit = TimeUnit.SECONDS) public void scheduledUpdate() { if (!enableScheduledUpdate) { return; } updateDataAsync(); } /** * 开启定时更新缓存 */ public void enableScheduledUpdate() { enableScheduledUpdate = true; scheduledUpdate(); } /** * 异步更新缓存 */ public void updateDataAsync() { UserBank userBank = userBankMapper.load(88); stringRedisTemplate.opsForValue().set(SOLVE2_IMITATE_KEY + 88, JSON.toJSONString(userBank), 1, TimeUnit.MINUTES); } }

缓存雪崩定时任务

 

java

代码解读

复制代码

package com.wangzhan.task; import com.alibaba.fastjson.JSON; import com.wangzhan.domain.UserBank; import com.wangzhan.mapper.UserBankMapper; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.List; import java.util.concurrent.TimeUnit; /** * @author wangzhan * @version 1.0 * @description 异步定时更新缓存 * @date 2024/7/10 08:53:18 */ @Component @EnableScheduling public class CacheAvalancheTask { @Resource private UserBankMapper userBankMapper; private StringRedisTemplate stringRedisTemplate; public CacheAvalancheTask(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } private volatile boolean enableScheduledUpdate = false; private static final String SOLVE2_IMITATE_KEY = "user_bank_key:wangzhan:cacheAvalanche:solve2:"; /*** * @description 间隔55秒更新缓存 * * @return void * @author wangzhan * @date 2024/7/10 11:12:51 */ @Scheduled(fixedDelay = 55, timeUnit = TimeUnit.SECONDS) public void scheduledUpdate() { if (!enableScheduledUpdate) { return; } updateDataAsync(); } /** * 开启定时更新缓存 */ public void enableScheduledUpdate() { enableScheduledUpdate = true; scheduledUpdate(); } /** * 异步更新缓存 */ public void updateDataAsync() { List<UserBank> list = userBankMapper.list(); if (list.isEmpty()) { return; } list.forEach(userBank -> { stringRedisTemplate.opsForValue().set(SOLVE2_IMITATE_KEY + userBank.getId(), JSON.toJSONString(userBank), 1, TimeUnit.MINUTES); }); } }

5.8、公共方法

 

java

代码解读

复制代码

package com.wangzhan.util; import com.alibaba.fastjson.JSON; import com.wangzhan.domain.UserBank; import com.wangzhan.mapper.UserBankMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; /** * @author wangzhan * @version 1.0 * @description * @date 2024/7/11 16:54:36 */ @Component public class UserBankUtil { private static final Logger logger = LoggerFactory.getLogger(UserBankUtil.class); @Resource private UserBankMapper userBankMapper; private StringRedisTemplate stringRedisTemplate; public UserBankUtil(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } /** * @param id 主键id * @param key 缓存key * @param timeout 过期时间 * @param timeUnit 时间单位 * @description: 查询缓存,如果缓存不存在,则查询数据库,并将数据缓存 * @return: com.wangzhan.domain.UserBank * @author: wangzhan * @date: 2024/7/7 11:04:04 */ public UserBank queryUserBankOfStr(Integer id, String key, int timeout, TimeUnit timeUnit) { String userBankJson = stringRedisTemplate.opsForValue().get(key); UserBank userBank = JSON.parseObject(userBankJson, UserBank.class); if (userBank == null) { userBank = userBankMapper.load(id); if (userBank != null) { logger.info("==========设置数据到缓存======="); // 设置缓存 stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(userBank), timeout, timeUnit); } logger.info("从【数据库】中获取数据,id为:{},结果为:{}", id, userBank); return userBank; } logger.info("从【缓存】中获取数据,id为:{},结果为:{}", id, userBank); return userBank; } /** * @param id 主键id * @param key 缓存key * @param timeout 过期时间 * @param timeUnit 时间单位 * @description: 查询缓存,如果缓存不存在,则查询数据库,并将数据缓存 * @return: com.wangzhan.domain.UserBank * @author: wangzhan * @date: 2024/7/7 11:04:04 */ public UserBank queryUserBankOfHash(Integer id, String key, int timeout, TimeUnit timeUnit) { String idStr = String.valueOf(id); String userBankJson = (String) stringRedisTemplate.opsForHash().get(key, idStr); UserBank userBank = JSON.parseObject(userBankJson, UserBank.class); if (userBank == null) { // 查询数据库 userBank = userBankMapper.load(id); if (userBank != null) { logger.info("==========设置数据到缓存======="); stringRedisTemplate.opsForHash().put(key, idStr, JSON.toJSONString(userBank)); stringRedisTemplate.expire(key, timeout, timeUnit); } logger.info("从【数据库】中获取数据,id为:{},结果为:{}", idStr, userBank); return userBank; } logger.info("从【缓存】中获取数据,id为:{},结果为:{}", idStr, userBank); return userBank; } }

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值