谷粒商城实战笔记-167-缓存-SpringCache-简介

发布于:2024-08-11 ⋅ 阅读:(118) ⋅ 点赞:(0)

一,使用缓存的痛点

在使用缓存时,有一个通用的编程模式:

  • 首先查询缓存,如果命中缓存,返回缓存中的结果
  • 如果没有命中,则查询数据库,返回结果的同时将查询结果写入缓存
  • 为了防止缓存击穿,需要加分布式锁以及查询结束后解锁

这个模式下有大量的模板代码,如果有大量需要使用缓存的方法,会导致大量的冗余,代码可读性差、可维护性差。

举例说明

在这种情况下,需要手动编写逻辑来处理缓存的读写和分布式锁。

用户服务类 (UserService)

@Service
public class UserService {
  
    public User findUserById(Long id) throws InterruptedException {
        // 尝试从 Redis 中获取用户
        User user = (User) redisTemplate.opsForValue().get("user:" + id);

        if (user == null) {
            // 如果 Redis 中没有,则尝试获取分布式锁
            String lockKey = "lock:user:" + id;
            Boolean lockAcquired = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS);

            if (Boolean.TRUE.equals(lockAcquired)) {
                try {
                    // 在锁保护下再次检查缓存
                    user = (User) redisTemplate.opsForValue().get("user:" + id);
                    if (user == null) {
                        // 如果仍然没有,则查询数据库
                        user = userRepository.findById(id).orElse(null);
                        // 存储到 Redis 中
                        if (user != null) {
                            redisTemplate.opsForValue().set("user:" + id, user);
                        } else {
                            // 防止缓存穿透,存储空对象或特殊标记
                            redisTemplate.opsForValue().set("user:" + id, "not-found", 5, TimeUnit.MINUTES);
                        }
                    }
                } finally {
                    // 释放锁
                    stringRedisTemplate.delete(lockKey);
                }
            } else {
                // 如果未能获取锁,则等待一段时间后重试
                Thread.sleep(1000);
                return findUserById(id); // 递归调用
            }
        }
        return user;
    }
}

上面代码,有大量嵌套的if/else和try/catch,导致代码冗长,可读性差,可维护性差。

理想情况

理想情况下,利用注解来简化缓存逻辑,开发者只需要专注于读取数据库的实现。

用户服务类 (UserService)

@Service
public class UserService {

    @Cacheable
    public User findUserByIdWithLock(Long id) throws InterruptedException {
        // 查询数据库
        return userRepository.findById(id).orElse(null);
	}
}

实际上,Spring3.1开始,提供了这样的解决方案Spring Cache。

二,Spring Cache

1,简介

  • Spring 从 3.1 开始定义了 org.springframework.cache.Cache
    和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术;并支持使用 JCache(JSR-107)注解简化我们开发
  • Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;
    Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现,如 RedisCache,EhCacheCache,ConcurrentMapCache 等
  • 使用SpringCache后,每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户,下次调用直接从缓存中获取。这写逻辑的实现完全由SpringCache完成,开发者只需要提供查询数据库的方法,并在方法上添加SpringCache的注解,这种开发方式大大简化了缓存的使用。
  • 使用 Spring 缓存时我们需要关注以下两点
    • 1,确定方法需要被缓存以及缓存策略
    • 2,从缓存中读取之前缓存存储的数据

网站公告

今日签到

点亮在社区的每一天
去签到