ehcache结合redis实现二级缓存系统

1、为什么要实现二级缓存呢?

     不为什么,单纯是为了将热点数据和非热点数据分一下类,根本上来讲就是为了提高系统性能,扩大吞吐量。要是在说原因的话,可能是为了避免一级缓存的热点数据失效时,不至于将大量的请求涌向数据库,这也是解决缓存雪崩的方案之一。

2、二级缓存的设计思路?

有了前面学习的ehcache和redis,及他们与springBoot整合的案例后,在springBoot下用ehcache结合redis实现二级缓存系统就比较简单了。首先说一下整体思路,我打算用ehcache结合spring的缓存注解来实现一级缓存,用redis来充当二级缓存。理由如下:参照CPU的L1 cache、L2 cache、L3 cache的设计功能与原则,L1 cache是最快的同时也是容量最小的,这和它使用的存储介质SRAM有关,L2 cache容量比L1大,但速度却比L1慢得多,L3和L2的关系类似。我们横向类比一下,假设我们的程序就是CPU,那么本地缓存ehcache、分布式缓存redis和本地磁盘就相当于CPU的L1、L2、L3缓存了。首先ehcache数据存储在JVM堆内存中,这对于java程序(CPU)来说是天然的优势,因为java程序就是跑在JVM里面的,访问速度不受网络影响,自然要快于redis,而且既然数据处于JVM堆内存中,也注定了数据存储量不会太大,正好符合CPU中L1 cache的特点。用redis来充当二级缓存,因为redis是通过网络socket进行数据传输的,redis服务器可能不和java程序部署在同一台机器上,分离的越远“redis的性能瓶颈”就越明显,所以访问速度上要鳗鱼ehcache的,但由于redis属于分布式缓存,服务本身可以独立部署运行,而且可以做集群,所以容量上可以远大于ehcache,也正好符合的CPU中L2 cache的特点。最底层的当然是我们的数据库了,数据存储在磁盘中,访问速度最慢,但数据不易丢失且容量最大。

我们这么设计,除了根据数据传输速度来划分一级、二级、三级缓存之外,还可以在缓存不同的数据内容上面做一些文章。CPU的L1 cache快于L2 cache根本原因是硬件SRAM的速度快于RAM,我们软件层面无法做硬件层面的区分,但可以通过一些设计使之更像L1和L2 cache,比如,我们在一级缓存中存储最热的那些数据,次热的数据放到二级缓存里面;一级缓存存储由多个二级缓存经过复杂计算后的计算结果等方式。

更多CPU缓存请看百度百科:https://baike.baidu.com/item/%E7%BC%93%E5%AD%98/100710?fr=aladdin

3、一个基本的二级缓存小案例

首先,我们将@Cacheable注解打在了controller的方法上,用来缓存该controller方法的返回值,下次在请求该controller方法时,会直接走ehcache的缓存。若一级缓存中有数据就直接返回;若没有再调用service层的方法,service层首先调用二级缓存redis,若有数据直接返回并将结果回写到一级缓存ehcache的中;若redis中也没有则查询数据库,将结果回写到redis中,再将service层的返回值回写到ehcache中。对于@Cacheable注解标注的方法,若该方法里面只是进行了一些简单的查询计算,而且被调用的次数还不是很频繁,那么该一级缓存的收益就很小,可以不将这样的返回值放到一级缓存中,直接调用service层的方法走二级缓存即可;但如果该方法会被频繁调用,或者方法里面进行了复杂的查询和计算,那么将这样的方法的返回值放到一级缓存里面,收益就很高了。

controller层

@RestController
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    @Cacheable(cacheNames = "employee",key = "#id")
    @GetMapping("/emp/{id}")
    public Employee getEmpById(@PathVariable("id") Integer id){
        Employee employee = employeeService.getEmployeeById(id);
        return employee;
    }


    @CachePut(cacheNames = "employee",key = "#employee.id")
    @PostMapping("/addEmp")
    public Employee addEmp(Employee employee){
        employeeService.addEmployee(employee);
        return employee;
    }

    //@CachePut进行的是更新缓存而不是删除更新,不符合cache-aside pattern,建议手动删除ehcache中的缓存,而不是使用@CachePut注解
    @CachePut(cacheNames = "employee",key = "#employee.id")
    @PostMapping("/updateEmp")
    public Employee updateEmployee(Employee employee){
        employeeService.updateEmployee(employee);
        return employee;
    }

    @CacheEvict(cacheNames = "employee",key = "#id")
    //@CacheEvict(cacheNames = "employee",allEntries = true)
    @GetMapping("/deleteEmp")
    public String deleteEmployee(Integer id){
        System.out.println("删除id为"+id+"的员工。");
        return "删除成功!";
    }


}

serviceImpl层 

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeMapper employeeMapper;



    //@Cacheable(cacheNames = "employee",key = "#id")
    @Override
    public Employee getEmployeeById(Integer id) {
        String redisValue = RedisUtils.get("employee:" + id);
        if (!StringUtils.isEmpty(redisValue)) {
            return JsonUtils.stringToObject(redisValue, Employee.class);
        }else {
            //这里可以加个锁,防止大并发下的缓存击穿
            Employee employee = employeeMapper.getEmployeeById(id);
            //回写数据到redis中
            String jsonEmp = JsonUtils.objectToString(employee);
            RedisUtils.set("employee:"+id,jsonEmp);
            return employee;
        }
    }

    @Override
    public void addEmployee(Employee employee) {
        employeeMapper.addEmployee(employee);
    }

    @Override
    public void updateEmployee(Employee employee) {
        int b = employeeMapper.updateEmployee(employee);
        //更新完数据库后,删除缓存,使用的是cache-aside pattern 模式
        if (b == 1){
            RedisUtils.del("employee:" + employee.getId());
        }
    }
}

可以看到serviceImpl层的第一个方法,我们光操作缓存就写了很多代码,而且多个这种方法的缓存处理逻辑几乎全一样,所以我们可以参照阿里的java开发规范中的工程规范中讲的那样,在service层和dao层之间引入manager层,用来处理这些通用的缓存处理代码。如果项目中使用的缓存模式确定下来后,还可以定义一个专用的缓存注解,利用aop来处理这些二级缓存和数据库之间的缓存操作。

 

行了,本文只是提供了一种二级缓存的设计思路,并配上了一个简单的小例子。实际在项目中去应用肯定也会遇到其他问题,不要怕,去探索吧!

上一篇:2、ehcache与springBoot整合

下一篇:缓存设计和使用中的经典问题

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis-Plus 支持内置的 Redis 作为二级缓存,但是可能会遇到一些不可避免的性能问题,例如大量查询时的 Redis 连接池瓶颈,Redis key 命名空间冲突等。 因此,我们可以采用自定义 Redis 缓存管理器来替代默认的 RedisCacheManager,自定义缓存管理器需要实现 org.apache.ibatis.cache.Cache 接口。 以下是自定义 Redis 缓存管理器的示例代码: ```java public class CustomRedisCache implements Cache { private final String id; // 缓存实例名称 private final RedisTemplate<String, Object> redisTemplate; // RedisTemplate 实例 private static final long EXPIRE_TIME_IN_SECONDS = 3600; // 缓存过期时间,单位:秒 public CustomRedisCache(String id, RedisTemplate<String, Object> redisTemplate) { if (id == null || redisTemplate == null) { throw new IllegalArgumentException("缓存实例名称和 RedisTemplate 实例均不能为空!"); } this.id = id; this.redisTemplate = redisTemplate; } @Override public String getId() { return this.id; } @Override public void putObject(Object key, Object value) { if (key == null) { return; } redisTemplate.opsForValue().set(key.toString(), value, EXPIRE_TIME_IN_SECONDS, TimeUnit.SECONDS); } @Override public Object getObject(Object key) { return key == null ? null : redisTemplate.opsForValue().get(key.toString()); } @Override public Object removeObject(Object key) { if (key == null) { return null; } redisTemplate.delete(key.toString()); return null; } @Override public void clear() { Set<String> keys = redisTemplate.keys("*" + getId() + "*"); if (keys != null && keys.size() > 0) { redisTemplate.delete(keys); } } @Override public int getSize() { return redisTemplate.keys("*" + getId() + "*").size(); } @Override public ReadWriteLock getReadWriteLock() { return null; } } ``` 接下来,我们需要将自定义 Redis 缓存管理器注册到 MyBatis 中,示例代码如下: ```java @Configuration public class MybatisConfig { @Autowired private RedisTemplate<String, Object> redisTemplate; @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean(); sqlSessionFactory.setDataSource(dataSource); sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml")); sqlSessionFactory.setPlugins(new Interceptor[]{new PaginationInterceptor()}); sqlSessionFactory.setCache(new CustomRedisCache("mybatis_cache", redisTemplate)); // 注册自定义缓存管理器 return sqlSessionFactory; } } ``` 最后,我们还需要在 MyBatis-Plus 的配置文件 mybatis-plus.yml 中添加一条配置,将 MyBatis 的缓存管理器设置为自定义的缓存管理器: ```yml mybatis-plus: configuration: cache-enabled: true # 允许缓存 # 使用自定义 Redis 缓存管理器 local-cache: # 是否使用一级缓存,默认为 true enabled: true # 默认缓存过期时间,单位:毫秒,默认值为 -1,即永不过期 ttl: -1 # 一级缓存最大数量,默认值为 1024 size: 1024 second-cache: # 是否使用二级缓存,默认为 true enabled: true # 默认缓存过期时间,单位:毫秒,默认值为 -1,即永不过期 ttl: -1 # 内置二级缓存管理器,支持RedisEhcache、Caffeine、H2、LevelDB、J2Cache等 cache-manager: com.baomidou.mybatisplus.extension.caches.RedisCacheManager # 自定义二级缓存管理器,必须实现 org.apache.ibatis.cache.Cache 接口 custom-cache: com.example.CustomRedisCache ``` 参考链接: - https://mp.weixin.qq.com/s/GvF8ffYQbeytE0glCNV9Xg

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值