J2Cache 知识点总结


一个简洁的博客网站:http://lss-coding.top,欢迎大家来访
学习娱乐导航页:http://miss123.top/


1. 简介

官方参考地址:https://gitee.com/ld/J2Cache

J2Cache —— 基于内存和 Redis 的两级 Java 缓存框架

J2Cache 是 OSChina 目前正在使用的两级缓存框架(要求至少 Java 8)。第一级缓存使用内存(同时支持 Ehcache 2.x、Ehcache 3.x 和 Caffeine),第二级缓存使用 Redis(推荐)/Memcached 。 由于大量的缓存读取会导致 L2 的网络成为整个系统的瓶颈,因此 L1 的目标是降低对 L2 的读取次数。 该缓存框架主要用于集群环境中。单机也可使用,用于避免应用重启导致的缓存冷启动后对后端业务的冲击。

数据读取

  1. 读取顺序 -> L1 -> L2 -> DB

  2. 数据更新

    1 从数据库中读取最新数据,依次更新 L1 -> L2 ,发送广播清除某个缓存信息
    2 接收到广播(手工清除缓存 & 一级缓存自动失效),从 L1 中清除指定的缓存信息

2. 构建测试环境

  1. 创建一个 Spring Boot 项目,引入缓存所需要的依赖

    Spring Boot 版本:2.2.5.RELEASE

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>RELEASE</version>
    <scope>test</scope>
</dependency>
<!-- j2cache 主要依赖-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.60</version>
</dependency>
<dependency>
    <groupId>net.oschina.j2cache</groupId>
    <artifactId>j2cache-spring-boot2-starter</artifactId>
    <version>2.7.6-release</version>
</dependency>
<dependency>
    <groupId>net.oschina.j2cache</groupId>
    <artifactId>j2cache-core</artifactId>
    <version>2.8.2-release</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </exclusion>
        <exclusion>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </exclusion>
        <exclusion>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.1</version>
</dependency>
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
</dependency>
  1. 创建一个 student-manager 数据库,创建表 user
CREATE TABLE `user` (
   `id` bigint(10) NOT NULL,
   `name` varchar(255) DEFAULT NULL,
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8
  1. 在左侧源码部分找到 j2cache-core 包里面的 caffeine.properties、ehcache3.xml、j2cache.properties 三个配置文件复制到 resources 目录下

image-20220117210559855

image-20220117210616247

  1. application.yml 中配置数据源、redis、j2cache
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/student-manager?userUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
    host: 49.xxx.xxx.200
    port: 6379
  cache:
    type: none

j2cache:
  config-location: /j2cache.properties
  redis-client: lettuce
  open-spring-cache: true
  • 修改 caffeine.properties 中的 30m 为 30s,这个设置的是一级缓存的过期时间,30s 比较好进行测试
  • 修改 j2cache.properties 中的 redis 部分为自己的 redis 服务器的 ip 地址等信息
  • ehcache3.xml 配置文件不需要动
  1. 创建 config、domain、mapper、service、impl、controller 各个分层的包,项目虽小,结构不能乱套

  2. 创建 FastJsonRedisSerializer 配置 Redis 存储的序列化

public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    private Class<T> clazz;

    public FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return (T) JSON.parseObject(str, clazz);
    }
}
  1. 创建 RedisConfig 配置类
@Configuration
@Slf4j
public class RedisConfig {
    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    @Autowired
    Environment environment;
    @Bean(name = "redisTemplate")
    @ConditionalOnMissingBean(RedisTemplate.class)
    public RedisTemplate<String, Object> redisTemplate() {
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(fastJsonRedisSerializer);
        redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
    @Bean
    public CacheChannel channel() {
        CacheChannel channel = null;
        try {
            String configUrl = environment.resolvePlaceholders("${j2cache.config-location}");
            J2CacheConfig config = J2CacheConfig.initFromConfig(configUrl);
            J2CacheBuilder j2CacheBuilder = J2CacheBuilder.init(config);
            channel = j2CacheBuilder.getChannel();
//            log.info("j2cache建立CacheChannel成功->" + "configUrl->" + configUrl + " 一级缓存->" + config.getL1CacheName() + " 二级缓存->" + config.getL2CacheName());
        } catch (IOException e) {
//            log.error("j2cache建立CacheChannel报错->" + e);
        }
        return channel;
    }
}
  1. 数据库对应的实体类 User
/**
 * @author lishisen
 * @description 用户表实体类
 * @date 2022/1/17 16:34
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User implements Serializable{

    // 主键 用户id
    private Long id;
    // 用户名
    private String name;

}
  1. UserMapper 接口
/**
 * @author lishisen
 * @description 用户的 mapper 接口层
 * @date 2022/1/17 16:37
 **/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
  1. UserService 接口
/**
 * @author lishisen
 * @description 用户的实现层接口
 * @date 2022/1/17 16:44
 **/
public interface UserService {

    // 放入缓存
    User getUser(Long id);

    // 清理缓存中的数据
    public User userClear(Long id);

}
  1. UserServiceImpl 接口实现类
/**
 * @author lishisen
 * @description 用户的实现层接口的实现类
 * @date 2022/1/17 16:36
 **/
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * @author lishisen
     * @description 当访问到这个接口的时候,
     *                  判断用户传输过来的 id 是否在一级缓存中存在,如果存在则直接返回结果,不进入下面的方法
     *                 如果不存在则会进入到 Redis 中查看是否存在,如果存在则返回结果,不进入下面的方法
     *                 如果不存在在则会进入方法查询数据库中的值,并将查询结果放入一级缓存ehcache和二级缓存redis
     *                 然后返回结果。
     * @date 2022/1/17 17:23
     **/
    // 加缓存
    @Override
    @Cacheable(value = "userInfo", key = "#id")
    public User getUser(Long id) {

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formatDate = simpleDateFormat.format(new Date());
        System.out.println("访问数据库中的数据!!!" + formatDate);
        User user  = userMapper.selectById(id);
        return user;
    }
	// 清缓存
    @CacheEvict(value = "userInfo", key = "#id")
    public User userClear(Long id) {
        System.out.println("清理一级缓存中 id:" + id + "的数据");
        User user  = userMapper.selectById(id);
        return user;
    }
}
  1. UserController
/**
 * @author lishisen
 * @description 用户控制器
 * @date 2022/1/17 16:46
 **/
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/user/{id}")
    public String getUserList(@PathVariable Long id) {
        System.out.println("查找id为:" + id + "的用户  " + new Date());
        User user = null;
        try {
            user = userService.getUser(id);
            if (user == null) {
                return "您查询的用户不在!!!";
            }
            return user.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "系统异常,请稍后重试!!!";
        }

    }
    @RequestMapping("/user/clear/{id}")
    public String userClear(@PathVariable Long id) {
        System.out.println("清楚缓存id为:" + id + "的用户  " + new Date());
        User user = null;
        try {
            user = userService.userClear(id);
        } catch (Exception e) {
            e.printStackTrace();
            return "系统异常,请稍后重试!!!";
        }
        return user.toString();
    }
}

至此测试项目的结构搭建完成

image-20220117212004535

3. 启动项目进行测试

  1. 当项目启动后,我们的一级缓存(jvm中) ehcache3 中是没有任何的缓存数据的,二级缓存 Redis 中也是没有数据的,所以当我们请求接口 http://localhost:8080/user/10001 的时候会先判断一级缓存没有数据,二级缓存没有数据,那么就会查询数据库中的数据来进行回显

image-20220117212419039

Redis 中也会加入数据

image-20220117212454453

  1. 当我们在数据库中对数据进行了修改的时候,我们再来请求,当一级缓存过期 访问数据库中的数据 的时候,就会进行缓存中数据的更新,同时 二级缓存 Redis 中的数据也会进行更新

image-20220117213407947

这里的测试写的比较乱,大家可以参考上面给出的官方网址进行自行测试,我这里只是一个demo!!!

有什么不对的地方欢迎大家留言

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值