快取問題(四) 快取穿透、快取雪崩、快取併發 解決案例
視訊地址: https://www.bilibili.com/video/BV1Ha411c7hB
程式碼地址: https://gitee.com/crazyliyang/video-teaching.git
1. 快取穿透 解決案例
使用布隆過濾器
核心程式碼, 使用 Redisson 庫的布隆過濾器: org.redisson.api.RBloomFilter
<!-- Redis 客戶端工具 redisson 實現對 Redisson 的自動化配置--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.13.4</version> </dependency>
配置類, 例項化布隆過濾器 RBloomFilter
@Configuration public class RedisConfiguration { /** * 預計要插入多少資料 */ private static int size = 100000; // 十萬 /** * 期望的誤判率 */ private static double fpp = 0.001; @Value("${spring.redis.host}") private String redistHost; @Value("${spring.redis.port}") private Integer redistPort; @Autowired private ProductService productService; // 商品服務 @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { // 建立 RedisTemplate 物件 RedisTemplate<String, Object> template = new RedisTemplate<>(); // 設定開啟事務支援 template.setEnableTransactionSupport(true); // 設定 RedisConnection 工廠。 它就是實現多種 Java Redis 客戶端接入的祕密工廠 template.setConnectionFactory(factory); // 使用 String 序列化方式,序列化 KEY 。 template.setKeySerializer(RedisSerializer.string()); // 使用 JSON 序列化方式(庫是 Jackson ),序列化 VALUE 。 template.setValueSerializer(RedisSerializer.json()); return template; } /** * 例項化布隆過濾器 * */ @Bean public RBloomFilter redisBloomFilter(){ String redisAddress = "redis://"+redistHost+ ":"+redistPort; Config config = new Config(); config.useSingleServer().setAddress(redisAddress); RedissonClient redisson = Redisson.create(config); // 建立RedisBloomFilter RBloomFilter<String> redisBloomFilter = redisson.getBloomFilter("productBloomFilter03"); //初始化布隆過濾器:預計元素為10000L ( 十萬), 誤差率為 0.001 redisBloomFilter.tryInit(size, fpp); return redisBloomFilter; } }
使用隆過濾器 RBloomFilter
@Service public class ProductServiceImpl extends ServiceImpl<ProductMapper, ProductEntity> implements ProductService { //redisKey字首 private static final String KEY_PREFIX = "product:"; // 格式化一下 private static String buildKey(String id) { return KEY_PREFIX + id; // product:商品ID } @Resource(name = "redisTemplate") private ValueOperations<Serializable, String> redisOperations; @Autowired private RBloomFilter<String> redisBloomFilter; /** * 過載 getById(id) 做第一點增強 * */ @Override public ProductEntity getById(Serializable id) { String redisKey = buildKey(String.valueOf(id)); // 構建rediskey if(!redisBloomFilter.contains(redisKey)){ // 布隆過濾器判斷 redisKey 不存在 return null; } String redisValue = redisOperations.get(redisKey); // 從快取中去查 if (redisValue != null) { // 查到了 return JSONUtil.parseObject(redisValue, ProductEntity.class); //直接返回; } else { // 沒查到 ProductEntity product = getBaseMapper().selectById(id); // 從資料庫中查, 然後放到快取中; if(product !=null){ String jsonString = JSONUtil.toJSONString(product); redisOperations.set(redisKey, jsonString); // 放到快取中 return product; } return null; } } }
2. 快取雪崩 解決案例
核心程式碼: 使用一個過期時間的隨機數, 讓 key 的過期時間分散化, 防止 key 大批集中過期;
@Service public class ProductServiceImpl extends ServiceImpl<ProductMapper, ProductEntity> implements ProductService { private static final String KEY_PREFIX = "product:"; private static String buildKey(String id) { return KEY_PREFIX + id; // product:商品ID } @Resource(name = "redisTemplate") private ValueOperations<Serializable, String> redisOperations; @Autowired private RBloomFilter<String> redisBloomFilter; /** * 過載 getById(id) 做第一點增強 */ @Override public ProductEntity getById(Serializable id) { String redisKey = buildKey(String.valueOf(id)); // 構建rediskey if(!redisBloomFilter.contains(redisKey)){ // 布隆過濾器判斷 redisKey 不存在 return null; } String redisValue = redisOperations.get(redisKey); // 從快取中去查 if (redisValue != null) { // 查到了 return JSONUtil.parseObject(redisValue, ProductEntity.class); //直接返回; } else { // 沒查到 ProductEntity product = getBaseMapper().selectById(id); // 從資料庫中查, 然後放到快取中; if(product !=null){ String jsonString = JSONUtil.toJSONString(product); long radomLong = radomLong(); // 隨機產生一個數 ( 24 ~ 72 ) redisOperations.set(redisKey, jsonString, radomLong, TimeUnit.HOURS); // 放到快取中, 時間取隨機, 時間單位小時 return product; } return null; } } public static long radomLong(){ long min = 24; long max = 72; long rangeLong = min + (((long) (new Random().nextDouble() * (max - min)))); return rangeLong; } }
3. 快取併發 解決案例
使用Redis 分散式鎖, 核心程式碼:
封裝 RedisLock
@Component public class RedisLock { @Autowired private RedissonClient redissonClient; /** * 加鎖 * @param lockKey * @return */ public RLock lock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.lock(); return lock; } /** * 釋放鎖 * @param lockKey */ public void unlock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.unlock(); } /** * 釋放鎖 * @param lock */ public void unlock(RLock lock) { lock.unlock(); } /** * 帶超時的鎖 * @param lockKey * @param timeout 超時時間 單位:秒 */ public RLock lock(String lockKey, int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, TimeUnit.SECONDS); return lock; } /** * 帶超時的鎖 * @param lockKey * @param unit 時間單位 * @param timeout 超時時間 */ public RLock lock(String lockKey, TimeUnit unit , int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, unit); return lock; } /** * 嘗試獲取鎖 * @param lockKey * @param waitTime 最多等待時間 * @param leaseTime 上鎖後自動釋放鎖時間 * @return */ public boolean tryLock(String lockKey, int waitTime, int leaseTime) { RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS); } catch (InterruptedException e) { return false; } } /** * 嘗試獲取鎖 * @param lockKey * @param unit 時間單位 * @param waitTime 最多等待時間 * @param leaseTime 上鎖後自動釋放鎖時間 * @return */ public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) { RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, unit); } catch (InterruptedException e) { return false; } } }
配置類 例項化 註冊 RedissonClient
@Configuration public class RedisConfiguration { @Value("${spring.redis.host}") private String redistHost; @Value("${spring.redis.port}") private Integer redistPort; @Autowired private ProductService productService; // 商品服務 /** * RedissonClient 註冊 */ @Bean public RedissonClient redissonClient(){ String redisAddress = "redis://"+redistHost+ ":"+redistPort; Config config = new Config(); config.useSingleServer().setAddress(redisAddress); RedissonClient redissonClient = Redisson.create(config); return redissonClient; } }
使用Redis 分散式鎖:
@Service public class ProductServiceImpl extends ServiceImpl<ProductMapper, ProductEntity> implements ProductService { //字首 private static final String KEY_PREFIX = "product:"; private static String buildKey(String id) { return KEY_PREFIX + id; // product:商品ID } @Resource(name = "redisTemplate") private ValueOperations<Serializable, String> redisOperations; @Autowired private RBloomFilter<String> redisBloomFilter; @Autowired private RedisLock redisLock; // 自己封裝的 redisLock /** * 過載 getById(id) 做第一點增強 */ @Override public ProductEntity getById(Serializable id) { String redisKey = buildKey(String.valueOf(id)); // 構建rediskey if (!redisBloomFilter.contains(redisKey)) { // 布隆過濾器判斷 redisKey 不存在 return null; } String redisValue = redisOperations.get(redisKey); // 從快取中去查 if (redisValue != null) { // 查到了 return JSONUtil.parseObject(redisValue, ProductEntity.class); //直接返回; } // 在查詢 DB 之前加 分散式鎖 redisLock.lock(String.valueOf(id)); try{ // 再一次查詢快取, 是為了分散式併發情況下, 其他併發的請求, 能在鎖解開之後, 再次查快取 String redisValue2 = redisOperations.get(redisKey); if (redisValue2 != null) { return JSONUtil.parseObject(redisValue, ProductEntity.class); } ProductEntity product = getBaseMapper().selectById(id); // 從資料庫中查 if (product != null) { String jsonString = JSONUtil.toJSONString(product); redisOperations.set(redisKey, jsonString); // 放到快取中 return product; } return null; }finally { redisLock.unlock(String.valueOf(id)); // 釋放鎖 } } }
相關文章
- 快取問題(一) 快取穿透、快取雪崩、快取併發 核心概念快取穿透
- Redis快取穿透、快取雪崩、redis併發問題分析Redis快取穿透
- 程式碼解決快取穿透和快取雪崩問題快取穿透
- 快取穿透 快取雪崩快取穿透
- 快取穿透、快取擊穿、快取雪崩快取穿透
- 快取穿透、快取雪崩、快取擊穿快取穿透
- 快取穿透、快取擊穿、快取雪崩概念及解決方案快取穿透
- 【Redis】快取穿透,快取擊穿,快取雪崩及解決方案Redis快取穿透
- REDIS快取穿透,快取擊穿,快取雪崩原因+解決方案Redis快取穿透
- Java高併發快取架構,快取雪崩、快取穿透之謎Java快取架構穿透
- 快取穿透、快取擊穿、快取雪崩、快取預熱快取穿透
- Redis快取擊穿、快取穿透、快取雪崩Redis快取穿透
- [Redis]快取穿透/快取擊穿/快取雪崩Redis快取穿透
- 快取穿透,快取擊穿,快取雪崩解決方案分析快取穿透
- Redis 快取擊穿(失效)、快取穿透、快取雪崩怎麼解決?Redis快取穿透
- Redis詳解(十二)------ 快取穿透、快取擊穿、快取雪崩Redis快取穿透
- 快取穿透、快取擊穿、快取雪崩區別快取穿透
- Redis 快取穿透、快取雪崩原理及解決方案Redis快取穿透
- 快取穿透、快取擊穿、快取雪崩區別和解決方案快取穿透
- 快取穿透、快取擊穿、快取雪崩的場景以及解決方法快取穿透
- Redis快取穿透/快取雪崩/快取擊穿(案例:產生的原因 解決方案利/弊)Redis快取穿透
- Redis 面試常見問題———快取雪崩、快取擊穿以及快取穿透Redis面試快取穿透
- 什麼是redis快取雪崩、快取穿透、快取擊穿Redis快取穿透
- Redis——快取穿透、快取擊穿、快取雪崩、分散式鎖Redis快取穿透分散式
- 快取穿透、快取雪崩和快取擊穿是什麼?快取穿透
- Redis快取穿透、快取雪崩、快取擊穿好好說說Redis快取穿透
- 如何設計快取系統:快取穿透,快取擊穿,快取雪崩解決方案分析快取穿透
- 一文讀懂快取穿透、快取擊穿、快取雪崩及其解決方案快取穿透
- 關於快取穿透、快取擊穿、快取雪崩的模擬與解決(Redis)快取穿透Redis
- Redis的快取穿透、快取雪崩、快取擊穿的區別Redis快取穿透
- 面試總結 —— Redis “快取穿透”、“快取擊穿”、“快取雪崩”面試Redis快取穿透
- Redis 的高效能快取機制的三類問題:快取擊穿、快取雪崩 和 快取穿透Redis快取穿透
- 來說說快取穿透、快取擊穿、快取雪崩都是什麼?怎麼解決?快取穿透
- 快取淘汰、快取穿透、快取擊穿、快取雪崩、資料庫快取雙寫一致性快取穿透資料庫
- 一文徹底弄懂並解決Redis的快取雪崩,快取擊穿,快取穿透Redis快取穿透
- 面試官:快取穿透、快取雪崩和快取擊穿是什麼?面試快取穿透
- 十分鐘徹底掌握快取擊穿、快取穿透、快取雪崩快取穿透
- Redis 快取雪崩,快取擊穿和快取穿透技術方案總結Redis快取穿透