0、源码地址
Redis缓存穿透+缓存击穿+缓存雪崩案例及解决方案代码地址 (github.com)
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
3.2、解决方案
3.2.1、互斥锁
3.2.2、异步定时任务
4、缓存雪崩
4.1、模拟缓存雪崩
1、初始化缓存,把数据库中所有数据加入缓存中
2、多线程访问 30至40间的热点key,查询是否全部命中缓存
3、删除 30至40间的热点key的缓存,模拟多个热点key同时过期,多线程访问
4、多线程访问 30至40间的热点key
4.2、解决方案
4.2.1、互斥锁
4.2.2、异步定时任务
4.2.3、随机过期时间
5、代码实现
5.0、建表语句
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、目录结构
5.2、依赖
<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、配置文件
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
缓存穿透
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);
}
}
缓存击穿
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);
}
}
缓存雪崩
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
缓存穿透
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);
}
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----------------------------*/
}
缓存击穿
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);
}
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----------------------------*/
}
缓存雪崩
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);
}
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
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 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、异步定时任务
缓存击穿定时任务
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);
}
}
缓存雪崩定时任务
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、公共方法
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;
}
}