这篇文章讲解OA中缓存组件,开发中很多场景都要用到缓存,看完这篇文章希望大家对OA的缓存有所了解。
1
为什么要使用缓存
1、缓解数据库压力,提升响应速率。
2、节点之间数据同步,内存共享。
3、非常适用读多写少的场景。
平台封装了统一的缓存API组件,单机和集群下由平台统一控制使用对应的缓存实现,真正做到一处开发多处使用的能力。
单机默认采用内存缓存,集群采用集中式Redis缓存。
2
CacheMap
键值对数据缓存,提供类似于java.util.Map的功能,支持常用的get,put,remove等方法。
/**举例:创建CacheMap**/CacheMap<String, RestUser> restuserCache = factory.createMap("restuser",new RestUserMapLoader());/**Rest用户加载*/public class RestUserMapLoader implements MapDataLoader<String, RestUser>{
@Override public RestUser load(String key) { Map<String, RestUser> loadLocal = loadBatch(Arrays.asList(key)); if(loadLocal != null) return loadLocal.get(key); return null; }
@Override public Map<String, RestUser> loadBatch(Collection<String> keys) { Map<String, RestUser> map = null; if(keys.size() > 0) { RestUserDao restUserDao = (RestUserDao) AppContext.getBean("restUserDao"); List<RestUser> restUsers = restUserDao.findListUserByNames(new ArrayList<String>(keys)); map = convertToMap(restUsers); } return map; }
}
3
AdvancedCacheMap
CacheMap的高级实现,除了CacheMap的功能之外,还支持批量数据获取、懒加载、本地模式、索引等。
/**举例:枚举缓存**/AdvancedCacheMap<Long, CtpEnumItem, EnumItemIndex> advanceCtpEnumItemCache = factory.createAdvancedMap("ctpEnumItemCache", new EnumItemCacheMapLoader());/**枚举缓存加载及丢失补偿**/ public class EnumItemCacheMapLoader implements L2CacheMapLoader_Long<Long,CtpEnumItem, HashMap> {
@Override public Map loadIndexData() {
//如果需要使用index功能需要实现该接口,同时设置下方的hasIndex返回true
}
@Override public CtpEnumItem loadDataFromDB(Long id) {
//单个数据加载
}
@Override public boolean hasIndex() { return false; }
@Override public int getL2CacheSize() {
//设置本地LRU缓存的大小,如果在接口中不覆盖,默认大小为1000 return 200000; }
/** * 按照key集合从数据库中批量加载数据 * @param ids * @return */ @Override public Map loadDatasFromDB(Long... ids){
//批量获取
}
/** * 从数据库中批量加载数据 * * @return */ @Override public Map loadAllDatasFromDB() {
}
}
4
IndexCacheMap
主要的方法同CacheMap,但是加入了索引存储机制,主要用于关系缓存。
/**举例:登录token和用户Id的关系缓存**/IndexCacheMap<String, Long> cacheThirdUser = factory.createIndexMap("cacheThirdUser", new MemberTransformationLoader());/**按需加载*/public class MemberTransformationLoader implements L2IndexCacheMapLoader_String<String,Long> {
private ThirdpartyUserMapperDao thirdpartyUserMapperDao;
@Override public Map<String, Long> loadIndexData() { Map<String, Long> userMap = new HashMap<String, Long>();
List<ThirdpartyUserMapper> mapperUserList = getMapperDao().getAllUserMapper(); for (ThirdpartyUserMapper thirdpartyUserMapper : mapperUserList) { //TODO } return userMap; }}
5
CacheObject
单一对象缓存,提供get,set两个方法。
/**举例:集群配置缓存**/CacheObject<Properties> cache = factory.createObject("masterProperties",null);
6
CacheSet
Set缓存,管理集合类的缓存,提供类似java.util.Set的功能。
/**举例:用户访问Token缓存**/CacheSet<String> UserIdOfCanAccessMobile = cacheFactory.createSet("UserIdOfCanAccessMobile");
7
AdvanceCacheMap实现原理
缓存读取【get方法】
读取缓存数据步骤
初始化
检查NoCheck机制,在Nocheck中包含了无效Key检查,处理逻辑:AutoRefresh重新加载Index,从AutoRefresh HashMap中获取需要更新的Key;非AutoRefresh检查本地Key的标记位。使用了Index不要设置短时间Nocheck
在NoCheck时间内,直接查询本地缓存返回数据
查询Redis标记位
标记为和缓存不一致,获取Redis中缓存数据
标记位和本地一致,数据没发生变化,直接取本地缓存中数据返回
Redis中读取到数据,更新本地缓存,返回数据
Redis中读取不到数据,从数据库加载数据
缓存写入【put方法】
写入缓存数据
所有的修改操作基本都走这个逻辑,包括put、remove、update等。
如果标记为AutoRefresh,放入数据和Flag的同时,放入Flag刷新标记位,刷新数据2分钟有效,各节点需要在2分钟之内,去拉取Flag并同步数据到本地。
其他缓存,直接放入数据和Flag。
放入本地缓存,并设置needCheck为true,下次获取时,强制做数据对比。
清除本地Index,如果实现了Index。
8
AdvancedCacheMap缓存创建和使用(推荐)
// 第一步,创建缓存群组:PrivilegeCacheImpl.class 为缓存的组名称private CacheAccessable factory = CacheFactory.getInstance(PrivilegeCacheImpl.class);// 第二步,创建缓存:menuMap 为缓存名称,在同一个组内缓存名称不能相同private AdvancedCacheMap<Long, PrivMenuBO, Long> menuMap = factory.createAdvancedMap("menuMap", new PrivMenuL2CacheMapLoader(), true, 500);// 第三步,使用缓存获取数据: menuMap.get(1234)写入数据:menuMap.put(1234,Obj)删除数据:menuMap.remove(1234)
9
使用场景
全量缓存
为了提供缓存命中率,很多数据量不大,但访问频率很高的数据可以采用全量缓存,全量缓存的数据在系统启动初始化时,从数据库中全量加载并缓存在Redis中,后续的操作通过后台的自动刷新机制同步到集群中的其他节点。
// autoRefresh设置为truecreateAdvancedMap(String cacheName, L2CacheMapLoader_Inner dataLoader, boolean autoRefresh, int noCheckTime);
按需加载
启动时,系统不会加载全量的缓存数据,当需要读取数据时才通过接口从数据库中读取数据放入缓存中,使用按需加载时修改自己节点的数据不会通过自动刷新机制同步到集群中的其他节点,但是可以通过调用get方法主动对比flag标记位完成数据同步。
// autoRefresh设置为falsecreateAdvancedMap(String cacheName, L2CacheMapLoader_Inner dataLoader, boolean autoRefresh, int noCheckTime);
数据更新
更新数据时,推荐更新完数据库之后,删除Redis缓存中的数据,在下一次加载数据时,如果缓存中没有,会调用数据加载接口从新从数据库加载。尽量不要更新完数据库之后,直接调用put方法往Redis中写入数据。
在涉及高频访问时,尽量以数据库数据为基准来做业务判断,比如验证人员登录密码。
数据同步
目前缓存组件除了用于加速数据访问速度外,还有一个重要的功能是实现各节点之间数据同步,目前缓存中主要的数据同步机制包含以下两种:
flag比对,数据在写入时会变更flag标记位,当本地数据的标记位和Redis中不一致时,组件会同步Redis中数据到本地,数据查询主要查询本地缓存中的数据。(主要机制,主动)
自动刷新机制,组件会在后台以一定的频率批量刷新Redis中的数据到本地缓存,完成各节点之前的数据同步。(次要机制,被动)
高频访问
由于在业务开发中存在循环调用的情况,每次调用即时从Redis中读取数据依旧性能表现不佳,这时可用通过缓存组件中的nocheck机制,在一定时间范围内直接动本地读取数据,减少对Redis访问的频率。调用方式:
// autoRefresh设置为true// noCheckTime设置时间范围createAdvancedMap(String cacheName, L2CacheMapLoader_Inner dataLoader, boolean autoRefresh, int noCheckTime);
这种使用方式依靠自动刷新机制完成各节点之间的数据同步,由于自动刷新机制有一定延迟,且是被动机制,所以在一些低延迟,高数据一致性场景可能会存在一些问题,应该尽量避免使用。如果必须使用可以调用AdvanceCacheMap.get(key,false)穿透到Redis中读取数据。
10
缓存组件对比
写在最后,集群环境缓存组件有bug,尤其是在低版本的时候。大家使用的时候要谨慎多测试。
缓存是个又爱又恨的东西,关于致远OA缓存组件使用问题我们下篇接着聊。
——下期预告——
把AI大模型集成到致远OA中
——往期推荐——
如果您对致远OA集成感兴趣,欢迎联系我们获取更多信息。
转载请注明出处。
领取专属 10元无门槛券
私享最新 技术干货