1.读数据流程:
bash
1. 先读缓存
2. 缓存命中 → 直接返回
3. 缓存未命中 → 读数据库 → 写入缓存 → 返回数据
2.写数据流程:
bash
1. 更新数据库
2. 删除缓存(而不是更新缓存)
3.完整代码示例
3.1.用户服务实现
java
@Service
@Slf4j
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisCache redisCache;
// 缓存key前缀
private static final String USER_CACHE_KEY = "user:";
/**
* 根据ID获取用户(读操作 - 旁路缓存)
*/
public User getUserById(Long userId) {
String cacheKey = USER_CACHE_KEY + userId;
// 1. 先从缓存读取
User user = redisCache.get(cacheKey);
if (user != null) {
log.info("缓存命中,用户ID: {}", userId);
return user;
}
// 2. 缓存未命中,查询数据库
log.info("缓存未命中,查询数据库,用户ID: {}", userId);
user = userMapper.selectById(userId);
if (user == null) {
return null;
}
// 3. 将数据写入缓存,设置30分钟过期
redisCache.set(cacheKey, user, 30 * 60);
log.info("数据写入缓存,用户ID: {}", userId);
return user;
}
/**
* 更新用户(写操作 - 先更新数据库,再删除缓存)
*/
@Transactional
public boolean updateUser(User user) {
try {
// 1. 更新数据库
int result = userMapper.updateById(user);
if (result <= 0) {
return false;
}
// 2. 删除缓存
String cacheKey = USER_CACHE_KEY + user.getUserId();
redisCache.delete(cacheKey);
log.info("更新用户成功,删除缓存,用户ID: {}", user.getUserId());
return true;
} catch (Exception e) {
log.error("更新用户失败,用户ID: {}", user.getUserId(), e);
throw new RuntimeException("更新用户失败", e);
}
}
/**
* 新增用户
*/
@Transactional
public boolean addUser(User user) {
try {
// 1. 插入数据库
int result = userMapper.insert(user);
if (result <= 0) {
return false;
}
// 2. 新增用户不需要立即缓存,等第一次读取时自然缓存
// 但如果需要立即使用,可以在这里设置缓存
// String cacheKey = USER_CACHE_KEY + user.getUserId();
// redisCache.set(cacheKey, user, 30 * 60);
log.info("新增用户成功,用户ID: {}", user.getUserId());
return true;
} catch (Exception e) {
log.error("新增用户失败", e);
throw new RuntimeException("新增用户失败", e);
}
}
/**
* 删除用户
*/
@Transactional
public boolean deleteUser(Long userId) {
try {
// 1. 删除数据库(逻辑删除)
User user = new User();
user.setUserId(userId);
user.setDelFlag("1"); // 逻辑删除标志
int result = userMapper.updateById(user);
if (result <= 0) {
return false;
}
// 2. 删除缓存
String cacheKey = USER_CACHE_KEY + userId;
redisCache.delete(cacheKey);
log.info("删除用户成功,删除缓存,用户ID: {}", userId);
return true;
} catch (Exception e) {
log.error("删除用户失败,用户ID: {}", userId, e);
throw new RuntimeException("删除用户失败", e);
}
}
}
3.2 控制器层
java
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
/**
* 获取用户信息
*/
@GetMapping("/{userId}")
public AjaxResult getUser(@PathVariable Long userId) {
try {
User user = userService.getUserById(userId);
if (user == null) {
return AjaxResult.error("用户不存在");
}
return AjaxResult.success(user);
} catch (Exception e) {
log.error("获取用户信息失败,用户ID: {}", userId, e);
return AjaxResult.error("系统异常");
}
}
/**
* 更新用户信息
*/
@PutMapping
public AjaxResult updateUser(@RequestBody User user) {
try {
boolean result = userService.updateUser(user);
return result ? AjaxResult.success() : AjaxResult.error("更新失败");
} catch (Exception e) {
log.error("更新用户信息失败,用户ID: {}", user.getUserId(), e);
return AjaxResult.error("更新失败");
}
}
/**
* 新增用户
*/
@PostMapping
public AjaxResult addUser(@RequestBody User user) {
try {
boolean result = userService.addUser(user);
return result ? AjaxResult.success() : AjaxResult.error("新增失败");
} catch (Exception e) {
log.error("新增用户失败", e);
return AjaxResult.error("新增失败");
}
}
/**
* 删除用户
*/
@DeleteMapping("/{userId}")
public AjaxResult deleteUser(@PathVariable Long userId) {
try {
boolean result = userService.deleteUser(userId);
return result ? AjaxResult.success() : AjaxResult.error("删除失败");
} catch (Exception e) {
log.error("删除用户失败,用户ID: {}", userId, e);
return AjaxResult.error("删除失败");
}
}
}
4.使用建议
1. 缓存key设计
java
// 使用统一的命名规范
private static final String USER_CACHE_KEY = "user:";
private static final String PRODUCT_CACHE_KEY = "product:";
private static final String ORDER_CACHE_KEY = "order:";
2. 过期时间策略
java
// 根据业务特点设置不同的过期时间
redisCache.set(key, value, 30 * 60); // 用户数据:30分钟
redisCache.set(key, value, 5 * 60); // 商品数据:5分钟
redisCache.set(key, value, 24 * 60 * 60); // 配置数据:24小时
3. 监控和日志
java// 添加详细的日志,便于排查问题 log.info("缓存命中,key: {}", key); log.info("缓存未命中,查询数据库,key: {}", key); log.info("删除缓存,key: {}", key);5.CAP理论的基本概念
CAP理论主要用于描述分布式系统中的三大特性:
一致性(Consistency):所有节点在同一时刻看到的数据是一致的。即每次读操作都能返回最新的写入结果,确保数据的正确性。
可用性(Availability):系统能够保证每个请求都会收到响应,无论是成功的响应还是失败的响应。即使某些节点出现故障,系统仍然能够继续提供服务。
分区容忍性(Partition Tolerance):系统能够容忍网络分区,即节点之间的通信中断,并且在出现分区时仍然能够继续运作。
6.为什么选择"更新DB + 删除缓存"而不是"更新DB + 更新缓存"?
方案对比:
方案 优点 缺点 更新DB + 删除缓存 简单、避免并发写问题、减少不必要的缓存更新 有极小的不一致窗口期 更新DB + 更新缓存 数据一致性更好 并发写时可能脏数据、浪费资源更新不常读的数据 并发问题分析:
场景:两个并发写操作
线程A更新用户信息 → 更新DB → 更新缓存
线程B更新用户信息 → 更新DB → 更新缓存
可能出现:
javaDB中:用户B的数据 缓存中:用户A的数据(因为A的更新晚于B)而使用删除缓存策略,可以避免这种并发写导致的脏数据问题。
针对删除缓存异常的情况,可以使用 2 个方案避免:
1.消息队列方案:将第二个操作(删除缓存)要操作的数据加入到消息队列,由消费者来操作数据。
2.订阅 MySQL binlog,再操作缓存:
「先更新数据库,再删缓存」的策略的第一步是更新数据库,那么更新数据库成功,就会产生一条变更日志,记录在 binlog 里。于是我们就可以通过订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除。

