一、概念
 * 缓存击穿* 定义:指一个热点key在缓存过期的瞬间,同时有大量请求访问这个key,导致所有请求都直接打到数据库上,造成数据库压力激增* 解决方案:* (1)互斥锁 (Mutex Lock)* 核心思想:只允许一个线程去查询数据库,其他线程等待* 实现方式:使用Redis的setnx命令或Redisson分布式锁* (2)热点数据永不过期* 核心思想:对热点数据不设置过期时间,通过其他机制更新* 实现方式:逻辑过期时间 + 异步更新* (3)接口限流与降级* 核心思想:控制访问数据库的并发量* 实现方式:使用限流组件如Sentinel
二、代码
2.1 controller层
package com.study.sredis.stept001.controller;import com.study.sredis.stept001.domain.User;
import com.study.sredis.stept001.service.User5Service;
import com.study.sredis.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
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;@RestController
@RequestMapping("/snow")
public class CacheTest5Controller {@Autowiredpublic User5Service user5Service;@PostMapping("/mutexById")public R getUserWithMutex(@RequestBody User user) {try {User user1 = user5Service.getUserWithMutex(user);return R.ok(user1);} catch (Exception e) {return R.fail("用户信息不存在");}}@PostMapping("/redissonById")public R getUserWithRedisson(@RequestBody User user) {try {User userInfo = user5Service.getUserWithRedissonLock(user);return R.ok(userInfo);} catch (Exception e) {return R.fail("用户信息不存在");}}@PostMapping("/logicalByid")public R getUserWithLogical(@RequestBody User user) {try {User userInfo = user5Service.getUserWithLogicalExpire(user);return R.ok(userInfo);} catch (Exception e) {return R.fail("用户信息不存在");}}@PostMapping("/hotByid")public R getHotUser(@RequestBody User user) {try {User userInfo = user5Service.getHotUser(user);return R.ok(userInfo);} catch (Exception e) {return R.fail("用户信息不存在");}}@PostMapping("/updateUser")public R updateUser(@RequestBody User user) {try {boolean success = user5Service.updateUser(user);return R.ok(success);} catch (Exception e) {return R.fail("修改失败");}}
}
2.2 service层
2.2.1 service接口层
package com.study.sredis.stept001.service;import com.study.sredis.stept001.domain.User;public interface User5Service {User getUserWithMutex(User user);User getUserWithRedissonLock(User user);User getUserWithLogicalExpire(User user);User getHotUser(User user);boolean updateUser(User user);
}
2.2.2 service实现类
package com.study.sredis.stept001.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.study.sredis.stept001.domain.User;
import com.study.sredis.stept001.dto.RedisData;
import com.study.sredis.stept001.mapper.userMapper;
import com.study.sredis.stept001.service.User5Service;
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.scheduling.annotation.Async;
import org.springframework.stereotype.Service;import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@Service
public class User5ServiceImpl extends ServiceImpl<userMapper, User> implements User5Service {@Autowiredprivate userMapper userMapper;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate RedissonClient redissonClient;private static final String PRODOUCT_KEY_PREFIX = "product";private static final String LOCK_KEY_PREFIX = "lock:product";private static final Duration CACHE_TIMEOUT = Duration.ofMinutes(30);private static final Duration LOCK_TIMEOUT = Duration.ofSeconds(10);@Overridepublic User getUserWithMutex(User user) {String cachekey = PRODOUCT_KEY_PREFIX + user.getId();User user1 = getFromCache(cachekey);if (user1 != null && user1.getId() != null) {return user1;}
String lockKey = LOCK_KEY_PREFIX + user.getId();boolean locked = false;try {
locked = tryLock(lockKey);if (locked) {
user1 = getFromCache(cachekey);if (user1 != null && user1.getId() != null) {return user1;}
user1 = this.getById(user.getId());if (user1 != null) {
setCache(cachekey, user1, CACHE_TIMEOUT);} else {
setCache(cachekey, new User(), Duration.ofMinutes(5));}return user1;} else {
Thread.sleep(50);return getUserWithMutex(user);}} catch (Exception e) {Thread.currentThread().interrupt();throw new RuntimeException("获取用户信息失败", e);} finally {if (locked) {releaseLock(lockKey);}}}private User getFromCache(String key) {try {return (User) redisTemplate.opsForValue().get(key);} catch (Exception e) {return null;}}private void setCache(String key, Object value, Duration timeout) {try {redisTemplate.opsForValue().set(key, value, timeout);} catch (Exception e) {}}private boolean tryLock(String lockKey) {return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, "1", LOCK_TIMEOUT));}private void releaseLock(String lockKey) {try {redisTemplate.delete(lockKey);} catch (Exception e) {}}@Overridepublic User getUserWithRedissonLock(User user) {String cacheKey = PRODOUCT_KEY_PREFIX + user.getId();
User user1 = getFromCache(cacheKey);if (user1 != null && user1.getId() != null) {return user1;}String lockKey = LOCK_KEY_PREFIX + user.getId();RLock lock = redissonClient.getLock(lockKey);try {
boolean isLock = lock.tryLock(3, 10, TimeUnit.SECONDS);if (isLock) {try {
user1 = getFromCache(cacheKey);if (user1 != null && user1.getId() != null) {return user1;}
user1 = this.getById(user.getId());if (user1 != null) {setCache(cacheKey, user1, CACHE_TIMEOUT);} else {setCache(cacheKey, new User(), Duration.ofMinutes(5));}return user1;} finally {lock.unlock();}} else {
Thread.sleep(100);return getUserWithRedissonLock(user);}} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("获取用户信息失败", e);}}@Overridepublic User getUserWithLogicalExpire(User user) {String cacheKey = PRODOUCT_KEY_PREFIX + user.getId();
RedisData<User> redisData = getRedisDataFromCache(cacheKey);if (redisData == null) {
return getAndSetProduct(user);}User user1 = redisData.getData();LocalDateTime expireTime = redisData.getExpireTime();
if (expireTime.isAfter(LocalDateTime.now())) {
return user1;}String lockKey = LOCK_KEY_PREFIX + user.getId();RLock lock = redissonClient.getLock(lockKey);if (lock.tryLock()) {try {
redisData = getRedisDataFromCache(cacheKey);if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {return redisData.getData();}
CompletableFuture.runAsync(() -> {rebuildUserCache(user);});} finally {lock.unlock();}}return user1;}private User getAndSetProduct(User user) {User user1 = this.getById(user.getId());if (user1 != null) {RedisData<User> redisData = new RedisData<>(user1,LocalDateTime.now().plusMinutes(30)  );setRedisDataCache(PRODOUCT_KEY_PREFIX + user.getId(), redisData);}return user1;}@Asyncpublic void rebuildUserCache(User user) {String lockKey = LOCK_KEY_PREFIX + user.getId();RLock lock = redissonClient.getLock(lockKey);if (lock.tryLock()) {try {
User user1 = this.getById(user.getId());if (user1 != null) {RedisData<User> redisData = new RedisData<>(user1,LocalDateTime.now().plusMinutes(30));setRedisDataCache(PRODOUCT_KEY_PREFIX + user.getId(), redisData);}} catch (Exception e) {} finally {lock.unlock();}}}private RedisData<User> getRedisDataFromCache(String key) {try {return (RedisData<User>) redisTemplate.opsForValue().get(key);} catch (Exception e) {return null;}}private void setRedisDataCache(String key, RedisData<User> redisData) {try {
redisTemplate.opsForValue().set(key, redisData);} catch (Exception e) {}}@Overridepublic User getHotUser(User user) {return null;}@Overridepublic boolean updateUser(User user) {return false;}
}