前言
公司部门老我的项目要迁徙降级java版本,须要进行缓存相干操作,原框架未反对这部分,通过调研java相干缓存计划大抵分为ehcache和redis两种,redis的value最大值为500mb且超过1mb会对存取有性能影响,业务零碎须要反对列表查问缓存就不可避免的波及到大量的数据存取过滤,ehcache反对内存+磁盘缓存不必放心缓存容量问题,所以框架初步版本决定集成ehcache3,设计流程构造如下图所示
缓存配置
maven援用
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency>
个性化配置
#缓存配置 cache: ehcache: heap: 1000 offheap: 100 disk: 500 diskDir: tempfiles/cache/
@Component@ConfigurationProperties("frmae.cache.ehcache")public class EhcacheConfiguration { /** * ehcache heap大小 * jvm内存中缓存的key数量 */ private int heap; /** * ehcache offheap大小 * 堆外内存大小, 单位: MB */ private int offheap; /** * 磁盘长久化目录 */ private String diskDir; /** * ehcache disk * 长久化到磁盘的大小, 单位: MB * diskDir无效时才失效 */ private int disk; public EhcacheConfiguration(){ heap = 1000; offheap = 100; disk = 500; diskDir = "tempfiles/cache/"; }}
代码注入配置
因为springboot默认缓存优先注入redis配置,所以须要手动申明bean进行注入,同时ehcache的value值必须反对序列化接口,不能应用Object代替,这里申明一个缓存基类,所有缓存value对象必须继承该类
public class BaseSystemObject implements Serializable { }
@Configuration@EnableCachingpublic class EhcacheConfig { @Autowired private EhcacheConfiguration ehcacheConfiguration; @Autowired private ApplicationContext context; @Bean(name = "ehCacheManager") public CacheManager getCacheManager() { //资源池生成器配置长久化 ResourcePoolsBuilder resourcePoolsBuilder = ResourcePoolsBuilder.newResourcePoolsBuilder() // 堆内缓存大小 .heap(ehcacheConfiguration.getHeap(), EntryUnit.ENTRIES) // 堆外缓存大小 .offheap(ehcacheConfiguration.getOffheap(), MemoryUnit.MB) // 文件缓存大小 .disk(ehcacheConfiguration.getDisk(), MemoryUnit.MB); //生成配置 ExpiryPolicy expiryPolicy = ExpiryPolicyBuilder.noExpiration(); CacheConfiguration config = CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, BaseSystemObject.class, resourcePoolsBuilder) //设置永不过期 .withExpiry(expiryPolicy) .build(); CacheManagerBuilder cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder() .with(CacheManagerBuilder.persistence(ehcacheConfiguration.getDiskDir())); return cacheManagerBuilder.build(true); }}
缓存操作
缓存预热
针对缓存框架抉择的双写策略,即数据库和缓存同时写入,所以在系统启动时须要事后将数据库数据加载到缓存中
针对单表申明自定义注解,个性化缓存定义自定义接口
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface HPCache {}
public interface IHPCacheInitService { String getCacheName(); void initCache();}
零碎初始化时同步进行缓存初始化,扫描注解实体类与接口实现Bean
@Async public void initCache(Class runtimeClass, List<String> extraPackageNameList) { List<Class<?>> cacheEntityList = new ArrayList<>(); if (!runtimeClass.getPackage().getName().equals(Application.class.getPackage().getName())) { cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(runtimeClass.getPackage(), HPCache.class)); } for (String packageName : extraPackageNameList) { cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(packageName, HPCache.class)); } for (Class clazz : cacheEntityList) { TableName tableName = (TableName) clazz.getAnnotation(TableName.class); List<LinkedHashMap<String, Object>> resultList = commonDTO.selectList(tableName.value(), "*", "1=1", "", new HashMap<>(), false); for (LinkedHashMap<String, Object> map : resultList) { Cache cache = cacheManager.getCache(clazz.getName(), String.class, BaseSystemObject.class); String unitguid = ConvertOp.convert2String(map.get("UnitGuid")); try { Object obj = clazz.newInstance(); obj = ConvertOp.convertLinkHashMapToBean(map, obj); cache.put(unitguid, obj); } catch (Exception e) { e.printStackTrace(); } } } //自定义缓存 Map<String, IHPCacheInitService> res = context.getBeansOfType(IHPCacheInitService.class); for (Map.Entry en : res.entrySet()) { IHPCacheInitService service = (IHPCacheInitService) en.getValue(); service.initCache(); } System.out.println("缓存初始化结束"); }
须要留神,在EhcacheConfig配置类中须要进行缓存名称的提前注册,否则会导致操作缓存时空指针异样
Map<String, Object> annotatedBeans = context.getBeansWithAnnotation(SpringBootApplication.class); Class runtimeClass = annotatedBeans.values().toArray()[0].getClass(); //do,dao扫描 List<String> extraPackageNameList = new ArrayList<String>(); extraPackageNameList.add(Application.class.getPackage().getName()); List<Class<?>> cacheEntityList = new ArrayList<>(); if (!runtimeClass.getPackage().getName().equals(Application.class.getPackage().getName())) { cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(runtimeClass.getPackage(), HPCache.class)); } for (String packageName : extraPackageNameList) { cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(packageName, HPCache.class)); } for (Class clazz : cacheEntityList) { cacheManagerBuilder = cacheManagerBuilder.withCache(clazz.getName(), config); } //自定义缓存 Map<String, IHPCacheInitService> res = context.getBeansOfType(IHPCacheInitService.class); for (Map.Entry en :res.entrySet()) { IHPCacheInitService service = (IHPCacheInitService)en.getValue(); cacheManagerBuilder = cacheManagerBuilder.withCache(service.getCacheName(), config); }
更新操作
手动获取ehcache的bean对象,调用put,repalce,delete办法进行操作
private CacheManager cacheManager = (CacheManager) SpringBootBeanUtil.getBean("ehCacheManager"); public void executeUpdateOperation(String cacheName, String key, BaseSystemObject value) { Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class); if (cache.containsKey(key)) { cache.replace(key, value); } else { cache.put(key, value); } } public void executeDeleteOperation(String cacheName, String key) { Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class); cache.remove(key); }
查问操作
缓存存储单表以主键—object模式存储,个性化缓存为key-object模式存储,单条记录能够通过getCache办法查问,列表查问须要取出整个缓存按条件进行过滤
public Object getCache(String cacheName, String key){ Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class); return cache.get(key); } public List<Object> getAllCache(String cacheName){ List result = new ArrayList<>(); Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class); Iterator iter = cache.iterator(); while (iter.hasNext()) { Cache.Entry entry = (Cache.Entry) iter.next(); result.add(entry.getValue()); } return result; }
缓存与数据库数据一致性
数据库数据操作与缓存操作程序为先操作数据后操作缓存,在开启数据库事务的状况下针对单条数据单次操作是没有问题的,如果是组合操作一旦数据库操作产生异样回滚,缓存并没有回滚就会导致数据的不统一,比方执行程序为dbop1=》cacheop1=》dbop2=》cacheop2,dbop2异样,cacheop1的操作曾经更改了缓存
这里抉择的计划是在数据库全副执行结束后对立操作缓存,这个计划有一个毛病是如果缓存操作产生异样还是会呈现上述问题,理论过程中缓存只是对内存的操作异样概率较小,对缓存操作持乐观状态,同时咱们提供手动重置缓存的性能,算是一个折中计划,上面概述该计划的一个实现
申明自定义缓存事务注解
@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface CacheTransactional {}
申明切面监听,在标记了CacheTransactional注解的办法执行前进行Redis标识,对立执行完办法体后执行缓存操作
@Aspect@Component@Order(value = 101)public class CacheExecuteAspect { @Autowired private CacheExecuteUtil cacheExecuteUtil; /** * 切面点 指定注解 */ @Pointcut("@annotation(com.haopan.frame.common.annotation.CacheTransactional) " + "|| @within(com.haopan.frame.common.annotation.CacheTransactional)") public void cacheExecuteAspect() { } /** * 拦挡办法指定为 repeatSubmitAspect */ @Around("cacheExecuteAspect()") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); CacheTransactional cacheTransactional = method.getAnnotation(CacheTransactional.class); if (cacheTransactional != null) { cacheExecuteUtil.putCacheIntoTransition(); try{ Object obj = point.proceed(); cacheExecuteUtil.executeOperation(); return obj; }catch (Exception e){ e.printStackTrace(); throw e; } } else { return point.proceed(); } }}
将缓存操作以线程id辨别放入待执行队列中序列化到redis,提供办法对立操作
public class CacheExecuteModel implements Serializable { private String obejctClazzName; private String cacheName; private String key; private BaseSystemObject value; private String executeType;}
private CacheManager cacheManager = (CacheManager) SpringBootBeanUtil.getBean("ehCacheManager"); @Autowired private RedisUtil redisUtil; public void putCacheIntoTransition(){ String threadID = Thread.currentThread().getName(); System.out.println("init threadid:"+threadID); CacheExecuteModel cacheExecuteModel = new CacheExecuteModel(); cacheExecuteModel.setExecuteType("option"); redisUtil.redisTemplateSetForCollection(threadID,cacheExecuteModel, GlobalEnum.RedisDBNum.Cache.get_value()); redisUtil.setExpire(threadID,5, TimeUnit.MINUTES, GlobalEnum.RedisDBNum.Cache.get_value()); } public void putCache(String cacheName, String key, BaseSystemObject value) { if(checkCacheOptinionInTransition()){ String threadID = Thread.currentThread().getName(); CacheExecuteModel cacheExecuteModel = new CacheExecuteModel("update", cacheName, key, value.getClass().getName(),value); redisUtil.redisTemplateSetForCollection(threadID,cacheExecuteModel, GlobalEnum.RedisDBNum.Cache.get_value()); redisUtil.setExpire(threadID,5, TimeUnit.MINUTES, GlobalEnum.RedisDBNum.Cache.get_value()); }else{ executeUpdateOperation(cacheName,key,value); } } public void deleteCache(String cacheName, String key) { if(checkCacheOptinionInTransition()){ String threadID = Thread.currentThread().getName(); CacheExecuteModel cacheExecuteModel = new CacheExecuteModel("delete", cacheName, key); redisUtil.redisTemplateSetForCollection(threadID,cacheExecuteModel, GlobalEnum.RedisDBNum.Cache.get_value()); redisUtil.setExpire(threadID,5, TimeUnit.MINUTES, GlobalEnum.RedisDBNum.Cache.get_value()); }else{ executeDeleteOperation(cacheName,key); } } public void executeOperation(){ String threadID = Thread.currentThread().getName(); if(checkCacheOptinionInTransition()){ List<LinkedHashMap> executeList = redisUtil.redisTemplateGetForCollectionAll(threadID, GlobalEnum.RedisDBNum.Cache.get_value()); for (LinkedHashMap obj:executeList) { String executeType = ConvertOp.convert2String(obj.get("executeType")); if(executeType.contains("option")){ continue; } String obejctClazzName = ConvertOp.convert2String(obj.get("obejctClazzName")); String cacheName = ConvertOp.convert2String(obj.get("cacheName")); String key = ConvertOp.convert2String(obj.get("key")); LinkedHashMap valueMap = (LinkedHashMap)obj.get("value"); String valueMapJson = JSON.toJSONString(valueMap); try{ Object valueInstance = JSON.parseObject(valueMapJson,Class.forName(obejctClazzName)); if(executeType.equals("update")){ executeUpdateOperation(cacheName,key,(BaseSystemObject)valueInstance); }else if(executeType.equals("delete")){ executeDeleteOperation(cacheName,key); } }catch (Exception e){ e.printStackTrace(); } } redisUtil.redisTemplateRemove(threadID,GlobalEnum.RedisDBNum.Cache.get_value()); } } public boolean checkCacheOptinionInTransition(){ String threadID = Thread.currentThread().getName(); System.out.println("check threadid:"+threadID); return redisUtil.isValid(threadID, GlobalEnum.RedisDBNum.Cache.get_value()); }