Spring Boot2.0+Redis+Ehcache实现二级缓存

本文介绍了一种结合Redis与EhCache的双级缓存策略,旨在优化数据读取速度并减少网络消耗。通过在应用程序内部署EhCache作为一级缓存,配合Redis作为二级缓存,实现了高效的数据缓存机制。文章详细阐述了如何在SpringBoot项目中配置和使用这两种缓存,包括实体类、Controller、Service层的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

EHCache 本地缓存

Redis 分布式缓存(可以共享)

 

一级 Redis 二级Ehcache    当redis挂了 有备胎

 反之:

  先走本地,本地没有再走网络  尽量少走Redis  效率会高一些

 

Redis与数据库的区别: 

   相同点 都是需要进行网络连接

  不同点 是存放的介质  内存 和 硬盘  

   数据库需要做IO操作 性能比直接操作内存效率要低

 

 Ehchache

   不需要走网络 直接从内存中获取

    由于Ehchache容器限制,会持久化在硬盘上,

   

 

 

 

Redis+ehCache实现两级级缓存

spring boot中集成了spring cache,并有多种缓存方式的实现,如:Redis、Caffeine、JCache、EhCache等等。但如果只用一种缓存,要么会有较大的网络消耗(如Redis),要么就是内存占用太大(如Caffeine这种应用内存缓存)。在很多场景下,可以结合起来实现一、二级缓存的方式,能够很大程度提高应用的处理效率。

内容说明:

缓存、两级缓存

spring cache:主要包含spring cache定义的接口方法说明和注解中的属性说明

spring boot + spring cache:RedisCache实现中的缺陷

caffeine简介

spring boot + spring cache 实现两级缓存(redis + caffeine)

 

缓存、两级缓存

简单的理解,缓存就是将数据从读取较慢的介质上读取出来放到读取较快的介质上,如磁盘-->内存。平时我们会将数据存储到磁盘上,如:数据库。如果每次都从数据库里去读取,会因为磁盘本身的IO影响读取速度,所以就有了像redis这种的内存缓存。可以将数据读取出来放到内存里,这样当需要获取数据时,就能够直接从内存中拿到数据返回,能够很大程度的提高速度。但是一般redis是单独部署成集群,所以会有网络IO上的消耗,虽然与redis集群的链接已经有连接池这种工具,但是数据传输上也还是会有一定消耗。所以就有了应用内缓存,如:caffeine。当应用内缓存有符合条件的数据时,就可以直接使用,而不用通过网络到redis中去获取,这样就形成了两级缓存。应用内缓存叫做一级缓存,远程缓存(如redis)叫做二级缓存

过期问题:

一级缓存 过期 时间 比二级要短一些

 

目录结构:

 

实体类:

注意一定要 序列号

复制代码

package com.toov5.entity;

import java.io.Serializable;

import lombok.Data;

@Data
public class Users implements Serializable{
  private String name;
  private Integer age;
}

复制代码

controller层

复制代码

package com.toov5.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.toov5.entity.Users;
import com.toov5.service.UserService;

@RestController
public class IndexController {
    @Autowired
    private UserService userService;
    
    @RequestMapping("/userId")
    public Users getUserId(Long id){
        return userService.getUser(id);  
    }
    

}

复制代码

service层

复制代码

package com.toov5.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.stereotype.Component;

import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;


@Component
public class EhCacheUtils {

    // @Autowired
    // private CacheManager cacheManager;
    @Autowired
    private EhCacheCacheManager ehCacheCacheManager;

    // 添加本地缓存 (相同的key 会直接覆盖)
    public void put(String cacheName, String key, Object value) {
        Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
        Element element = new Element(key, value);
        cache.put(element);
    }

    // 获取本地缓存
    public Object get(String cacheName, String key) {
        Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
        Element element = cache.get(key);
        return element == null ? null : element.getObjectValue();
    }

    public void remove(String cacheName, String key) {
        Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
        cache.remove(key);
    }

}

复制代码

复制代码

package com.toov5.service;

import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class RedisService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    //这样该方法支持多种数据类型 
    public void set(String key , Object object, Long time){
        //开启事务权限
        stringRedisTemplate.setEnableTransactionSupport(true);
        try {
            //开启事务
            stringRedisTemplate.multi();
            
            String argString =(String)object;  //强转下
            stringRedisTemplate.opsForValue().set(key, argString);
            
            //成功就提交
            stringRedisTemplate.exec();
        } catch (Exception e) {
            //失败了就回滚
            stringRedisTemplate.discard();
            
        }
        if (object instanceof String ) {  //判断下是String类型不
            String argString =(String)object;  //强转下
            //存放String类型的
            stringRedisTemplate.opsForValue().set(key, argString);
        }
        //如果存放Set类型
        if (object instanceof Set) {
            Set<String> valueSet =(Set<String>)object;
            for(String string:valueSet){
                stringRedisTemplate.opsForSet().add(key, string);  //此处点击下源码看下 第二个参数可以放好多
            }
        }
        //设置有效期
        if (time != null) {
            stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
        }
        
    }
    //做个封装
    public void setString(String key, Object object){
        String argString =(String)object;  //强转下
        //存放String类型的
        stringRedisTemplate.opsForValue().set(key, argString);
    }
    public void setSet(String key, Object object){
        Set<String> valueSet =(Set<String>)object;
        for(String string:valueSet){
            stringRedisTemplate.opsForSet().add(key, string);  //此处点击下源码看下 第二个参数可以放好多
        }
    }
    
    public String getString(String key){
     return    stringRedisTemplate.opsForValue().get(key);
    }
    
    
}

复制代码

复制代码

package com.toov5.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSONObject;
import com.toov5.entity.Users;
import com.toov5.mapper.UserMapper;

import ch.qos.logback.core.net.SyslogOutputStream;
import io.netty.util.internal.StringUtil;

@Component
public class UserService {
    @Autowired
    private EhCacheUtils ehCacheUtils;
    @Autowired
    private RedisService redisService;
    @Autowired
    private UserMapper userMapper;
    //定义个全局的cache名字
    private String cachename ="userCache";
    
    public Users getUser(Long id){
        //先查询一级缓存  key以当前的类名+方法名+id+参数值
        String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
                + "-id:" + id;
        //查询一级缓存数据有对应值的存在 如果有 返回
        Users user = (Users)ehCacheUtils.get(cachename, key);
        if (user != null) {
            System.out.println("key"+key+",直接从一级缓存获取数据"+user.toString());
            return user;
        }
        //一级缓存没有对应的值存在,接着查询二级缓存    
        // redis存对象的方式  json格式 然后反序列号
        String userJson = redisService.getString(key);
        //如果rdis缓存中有这个对应的值,修改一级缓存    最下面的会有的 相同会覆盖的    
        if (!StringUtil.isNullOrEmpty(userJson)) {  //有 转成json
            JSONObject jsonObject = new JSONObject();//用的fastjson
            Users resultUser = jsonObject.parseObject(userJson,Users.class);
            ehCacheUtils.put(cachename, key, resultUser);
            return resultUser;
        }
        //都没有 查询DB 
        Users user1 = userMapper.getUser(id);
        if (user1 == null) {
            return null;
        }
        //存放到二级缓存 redis中
        redisService.setString(key, new JSONObject().toJSONString(user1));
        //存放到一级缓存 Ehchache
        ehCacheUtils.put(cachename, key, user1);
        return user1;
    }
    
    
    
}

复制代码

启动类:

复制代码

package com.toov5.app;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@EnableCaching //开启缓存
@MapperScan(basePackages={"com.toov5.mapper"})
@SpringBootApplication(scanBasePackages={"com.toov5.*"})
public class app {
   public static void main(String[] args) {
    SpringApplication.run(app.class, args);
}
    
}

复制代码

app1.ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">

 

    <diskStore path="java.io.tmpdir/ehcache-rmi-4000" />

 

 

    <!-- 默认缓存 -->

    <defaultCache maxElementsInMemory="1000" eternal="true"

        timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true"

        diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000"

        diskPersistent="true" diskExpiryThreadIntervalSeconds="120"

        memoryStoreEvictionPolicy="LRU">

    </defaultCache>

   

    <!-- demo缓存 --><!-- name="userCache" 对应我们在 @CacheConfig(cacheNames={"userCache"}) !!!!! -->

    <!--Ehcache底层也是用Map集合实现的 -->

    <cache name="userCache" maxElementsInMemory="1000" eternal="false"

        timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true"

        diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000"

        diskPersistent="false" diskExpiryThreadIntervalSeconds="120"

        memoryStoreEvictionPolicy="LRU">  <!-- LRU缓存策略 -->

        <cacheEventListenerFactory

            class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" />

        <!-- 用于在初始化缓存,以及自动设置 -->

        <bootstrapCacheLoaderFactory

            class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" />

    </cache>

</ehcache>

yml

###端口号配置

server:

  port: 8080

###数据库配置 

spring:

  datasource:

    url: jdbc:mysql://localhost:3306/test

    username: root

    password: root

    driver-class-name: com.mysql.jdbc.Driver

    test-while-idle: true

    test-on-borrow: true

    validation-query: SELECT 1 FROM DUAL

    time-between-eviction-runs-millis: 300000

    min-evictable-idle-time-millis: 1800000

# 缓存配置读取

  cache:

    type: ehcache

    ehcache:

      config: classpath:app1_ehcache.xml

 

  redis:

    database: 0  

    host:  192.168.91.3

    port:  6379

    password:  123

    jedis:

      pool:

        max-active: 8

        max-wait: -1

        max-idle: 8

        min-idle: 0

    timeout: 10000

 

运行结果:

 

 

 

 后面的访问,在控制台打印:

 

 

其中一定要注意

 

这里的 两级缓存时间问题  执行设置二级缓存时候 需要时间的  所以这两个时间设置问题一定要注意了哦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值