缓存

linpeilie大约 4 分钟拓展

缓存分层

EasyRelation 中共设计有两级缓存,参考了 Mybatis 中的设计,在进行数据关联时,会依次经过 一级缓存 --> 二级缓存 ---> 数据提供源,从而提高数据获取的效率。

这里简单了解一下一级缓存与二级缓存:

  • 一级缓存:单次数据关联操作内的缓存,缓存的数据只在这个关联过程内有效,一级缓存根据一定规则,会自动开启。

这里的单次数据关联操作指的是调用一次 injectRelation 方法内的执行流程。

  • 二级缓存:全局缓存,比如使用 Redis 作为缓存,二级缓存需要手动开启。

一级缓存

为什么需要一级缓存

在进行数据关联时,在对一个集合进行关联,或者一个对象内有平铺关联的操作时, 针对同一个数据提供者,且条件相同时,每一次关联都查询一次数据库,或者调用一次 Rpc 接口,会白白浪费系统的资源,并且还会导致类似于数据库中的脏读、幻读、不可重复读等问题。

为了解决这个问题,EasyRelation 会在执行时,进行判断是否需要一级缓存,当需要时,会自动打开缓存,缓存条件和相应的结果,从而保证性能和同一次关联操作中的一致性问题。

一级缓存实现

当一级缓存开启状态时,会先到缓存中查询是否有,如果没有,则经过二级缓存、数据提供者,获取数据,最终存储到一级缓存中。

一级缓存是在当前线程的 ThreadLocal 中绑定了一个 Map 对象,执行前根据一定规则开启,执行后,会自动清除。

一级缓存的开启条件

为了进一步节省性能,一级缓存并不会在每一次关联操作中开启,只会在判断需要的时候才会开启。

目前当下面两种情况时,会自动开启:

  1. 当一次对一个集合中的元素进行关联时;
  2. 当在一个对象中出现平铺关联的操作时;

即,EasyRelation 会判断不会对同一个数据提供源进行多次调用时,则不会开启一级缓存。

二级缓存

二级缓存主要是为了全局缓存,当没有命中一级缓存时,首先会到二级缓存中查找,如果找到相应的值则直接解析返回。

二级缓存的实现

框架中提供了一个二级缓存的实现,实现比较简单,因为一般项目中也会用 Redis 来实现缓存。

针对二级缓存定义了一个接口 —— RelationCache,接口定义如下:

public interface RelationCache {

    /**
     * 指定键值是否存在
     *
     * @param key
     * @return 指定键值是否存在
     */
    boolean hasKey(String key);

    /**
     * 保存缓存
     *
     * @param key   键值
     * @param value 实际值
     * @param time  过期时间,单位:ms
     */
    void set(String key, Object value, long time);

    /**
     * 获取缓存数据
     *
     * @param key 键值
     * @return {@link Object} key对应的值
     */
    Object get(String key);

}

默认实现

框架中的默认实现,是基于两个 MapTasker 来实现的。

保存一个 KV 时,会同时保存其过期时间,通过 Tasker 来过期 key。

集成 Redis 作为 Key

EasyRelation 可以提供了 Redis 缓存的插件包,其中定义了 JDK、Jackson 两种 value 序列化方式。

具体使用可以参考Redis 缓存

自定义缓存

当要实现一个二级缓存时,需要实现该接口,并且声明 InjectRelation 对象时,传入该实现类。

如果使用 SpringBoot,将自定义缓存的实现类声明为一个 Spring 的 Bean 即可。

二级缓存的开启

二级缓存默认是禁用状态,需要手动开启

首先,在配置 DataProvider 时,可以通过 useCache 配置当前数据提供者的默认缓存开关配置,当前配置只有在配置 @Relation 时, cacheStrategyDEFAULT 时才会生效,详情可以参考配置关联关系#启用缓存

同样还支持每个关联类型内部的个性化配置,比如通过 @Relation 中的 cacheStrategy 配置强制开启缓存或强制关闭缓存。

当通过该属性配置强制开启/关闭时,会忽略 DataProvider 中的缓存开关配置。详情可以参考配置关联关系#启用缓存

缓存 Key 的生成规则

数据提供者唯一标识 + - + 参数列表(参数名称 + - + 参数值)。

例如:

getUserByUsername-username-admin