• 【开发篇】十二、缓存框架JetCache



    上篇完成了Spring Cache底层技术的各种切换,但各个技术有各自的优缺点,因此阿里的Jetcache框架出现。 注意Jetcache是一个框架,做的是一个整合和封装,自身不是像redis一样的缓存实现技术,Jetcache框架底层还得依靠具体的缓存实现技术,它是和Spring Cache同等级的,用来替换Spring Cache的。

    0、介绍

    JetCache对SpringCache进行了封装,在原有功能基础上实现了多级缓存、缓存统计、自动刷新、异步调用、数据报表等功能

    JetCache设定了本地缓存与远程缓存的多级缓存解决方案,其中本地缓存(local)支持:

    • LinkedHashMap
    • Caffeine

    远程缓存(remote)支持:

    • Redis
    • Tair

    1、JetCache远程缓存

    先引入依赖

    <dependency>    
    	<groupId>com.alicp.jetcachegroupId>   
    	<artifactId>jetcache-starter-redisartifactId>    
    	<version>2.6.2version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    远程缓存的属性配置

    jetcache:  
      remote:    
        default:      
          type: redis      
          host: localhost      
          port: 6379   
          password: admin1234   
          poolConfig:        
            maxTotal: 50
        # 再定义一个缓存区域也行
        myArea:
          type: redis
          host: .......
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    @EnableCreateCacheAnnotation启动用注解来创建缓存,开启后就可以用@CreateCache来创建一个缓存对象。

    @SpringBootApplication
    
    @EnableCreateCacheAnnotation
    
    public class CacheApplication {    
    
    	public static void main(String[] args) {   
    	    
    		SpringApplication.run(CacheApplication.class, args);    
    	}
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    使用@CreateCache注解声明一个缓存对象:

    @Service
    public class SMSCodeServiceImpl implements SMSCodeService {
    
        @CreateCache(area = "default", name = "smsCache::", expire = 3600, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.REMOTE)  
        private Cache<String, String> jetSMSCache;
        //...
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意@CreateCache注解的属性:

    • area:用哪块存储空间,默认值为default,即配置文件里的jetcache.remote.default及其下面的配置,可以再自己定义区域
    • name:key的分组前缀,常用str::或者str_
    • expire:过期时间,单位默认秒
    • timeUnit:时间单位,默认值秒
    • cacheType:缓存类型,local即本地缓存,remote即远程缓存,all即两个都写,默认远程缓存

    操作缓存:使用缓存对象.put和get

    @Service
    public class SMSCodeServiceImpl implements SMSCodeService {    
    
    	@Override   
    	public String sendCodeToSMS(String tele) {     
    	   
    		String code = this.codeUtils.generator(tele);  
    		      
    		jetSMSCache.put(tele,code);    //put
    		    
    		return code;    
    	}   
    	
    	@Override    
    	public boolean checkCode(String tele,String code) {  
    	      
    		String value = jetSMSCache.get(tele);     //get
    		   
    		return code.equals(value);
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    重启服务,调用之前的缓存接口查看效果。出现了一个莫名奇妙的循环依赖,先临时解决下,跳过了:

    spring:
    	main:
        	allow-circular-references: true
    
    
    • 1
    • 2
    • 3
    • 4

    发现接口调用正常,启动日志也输出了相关信息:

    在这里插入图片描述

    2、JetCache本地缓存

    和远程一样,引入依赖后,配置文件不同:

    jetcache:  
      local:    
        default:     
          type: linkedhashmap      
          keyConvertor: fastjson # 即key不是String时,用哪个转换器转为String
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    再后面一样的操作,开启缓存注解支持、声明缓存对象、put和get都一样,需要注意的是上面的CacheType属性改为Local

    @CreateCache(name = "smsCache::", expire = 3600, cacheType = CacheType.LOCAL)  
    private Cache<String, String> jetSMSCache;
    
    • 1
    • 2

    3、标准配置文件

    JetCache能同时支持本地和远程缓存,所以标准的配置文件写一起就行:

    jetcache:  
      statIntervalMinutes: 15  
      areaInCacheName: false    # 即key里加不加area的名字,true时key形如default_myname::key
      
      local:    
        default:      
          type: linkedhashmap      
          keyConvertor: fastjson      
          limit: 100  
          
      remote:
        default:      
          host: localhost      
          port: 6379      
          type: redis      
          keyConvertor: fastjson      
          valueEncoder: java      
          valueDecoder: java      
          poolConfig:        
            minIdle: 5        
            maxIdle: 20       
            maxTotal: 50
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    关于配置的含义:

    在这里插入图片描述

    4、JetCache方法缓存注解–@Cached

    上面写缓存得创建JetCache缓存对象后put和get,如何像@Cacheable注解一样,对整个方法的结果通过加注解来自动写查缓存?

    请添加图片描述

    答案是在@EnableCreateCacheAnnotation的基础上,再加注解@EnableMethodCache(basePackages = "com.mydemo"),开启对方法缓存注解的支持,basePackeages属性写需要用到方法缓存注解的包路径。

    @SpringBootApplication
    @EnableCreateCacheAnnotation  ####!
    @EnableMethodCache(basePackages = "com.mydemo") ####!
    public class JetCacheApplication {  
      
    	public static void main(String[] args) { 
    	       
    		SpringApplication.run(JetCacheApplication.class, args);    
    		
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    @Cached注解加需要缓存的方法上,如此查到的Book对象就被缓存到default的Area,且是远程缓存

    @Service
    public class BookServiceImpl implements BookService {    
    
    	@Autowired    
    	private BookDao bookDao;   
    	 
    	@Cached(name = "smsCache_", key = "#id", expire = 3600) 
    	@Override      
    	public Book getById(Integer id) {
    		return bookDao.selectById(id);    
    	}
    	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    上面@Cache注解的cacheType属性没写,那就是默认的远程缓存。此时,调用接口返回正常,但接口日志有异常:

    在这里插入图片描述

    需要加一个配置,指定key的转换器给本地和远程缓存:

    在这里插入图片描述

    再重启,接口正常,但日志仍有报错NotSerializableException,这个就很明确了,Java对象存进缓存时,要转为json,而要存的对象的实体类没去实现序列化接口,实现下Serializable接口就行。

    在这里插入图片描述
    实现序列化接口后,别忘了要在配置中加:

    valueEncoder: java      
    valueDecoder: java    
    
    • 1
    • 2

    即,编码时的是java对象,解码时,也要转汇Java对象。

    5、@Cached

    重新整理下@Cached注解,它的属性和@CreateCache的一样,但多了几个,放一起整理了:

    属性默认值描述
    areadefault如果在配置中配置了多个缓存area,在这里指定使用哪个area
    name未定义(undefined指定缓存的唯一名称,不是必须的,如果没有指定,会使用类名+方法名。name会被用于远程缓存的key前缀。另外在统计中,一个简短有意义的名字会提高可读性。
    key未定义使用SpEL来指定缓存的key值,如#id 、#p0
    expire-2147483648过期时间
    timeUnitTimeUnit.SECONDS过期时间的单位
    cacheTypeCacheType.REMOTE缓存的类型,可选CacheType.REMOTE、CacheType.LOCAL、CacheType.BOTH。如果定义为BOTH,会使用LOCAL和REMOTE组合成两级缓存
    localLimit-2147483648如果cacheType为LOCAL或BOTH,这个参数指定本地缓存的最大元素数量,以控制内存占用。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为100
    localExpire-2147483648仅当cacheType为BOTH时适用,为内存中的Cache指定一个不一样的超时时间,通常应该小于expire
    serialPolicy未定义指定远程缓存的序列化方式。可选值为SerialPolicy.JAVA和SerialPolicy.KRYO。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为SerialPolicy.JAVA
    keyConvertor未定义指定KEY的转换方式,用于将复杂的KEY类型转换为缓存实现可以接受的类型,当前支持KeyConvertor.FASTJSON和KeyConvertor.NONE。NONE表示不转换,FASTJSON可以将复杂对象KEY转换成String。如果注解上没有定义,会使用全局配置
    enabledtrue是否激活缓存。例如某个dao方法上加缓存注解,由于某些调用场景下不能有缓存,所以可以设置enabled为false,正常调用不会使用缓存,在需要的地方可使用CacheContext.enableCache在回调中激活缓存,缓存激活的标记在ThreadLocal上,该标记被设置后,所有enable=false的缓存都被激活
    cacheNullValuefalse当方法返回值为null的时候是否要缓存
    condition未定义使用SpEL指定条件,如果表达式返回true的时候才去缓存中查询
    postCondition未定义使用SpEL指定条件,如果表达式返回true的时候才更新缓存,该评估在方法执行后进行,因此可以访问到#result

    整体有点像@Cacheable注解。重点注意下area,一块一块的缓存区域。

    4、@CacheUpdate

    方法上加了@Cached后,方法返回值被写进缓存,但如果持久层被update,再查数据时。从缓存中拿到的数据就不对了,可通过在更新持久层的方法上加@CacheUpdate注解解决。先看这个注解属性:

    属性默认值描述
    areadefault缓存区域,注意和上面@Cached时所在的区域保持一致
    namekey前缀,注意和上面@Cached时的name一致
    key未定义SpEL表达式,根据当前方法的形参拼出@Cached时的key,如@Cached的方法形参为id,key为#id。而@CacheUpdate所在方法形参为Book对象book,则key为#book.id
    value要更新的值
    condition未定义使用SpEL指定条件,如果表达式返回true才执行更新,可访问方法结果#result
    @Service
    public class BookServiceImpl implements BookService {
    
        @CacheUpdate(name = "smsCache_", key = "#book.id", value = "#book")    
        public boolean update(Book book) {            
        	return bookDao.updateById(book) > 0;       
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    对照上面的@Cached注解的key来看这个@CacheUpdate的key。此时,调用查询接口,返回更新后的数据,且不会查持久层,因为缓存已被更新,到不了持久层。

    5、@CacheInvalidate

    和更新一样,如果持久层数据被删除,如何通知到缓存呢,答案是@CacheInvalidate注解。

    @Service
    public class BookServiceImpl implements BookService {
    
    	@CacheInvalidate(name = "smsCache_", key = "#id")    
    	public boolean delete(Integer id) {        
    		return bookDao.deleteById(id) > 0;    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样,当delete方法执行,持久层数据被删除时,缓存也就被删掉了。此时,调用查询接口,会去查持久层!!查持久层才合理,缓存里都没数据了,不去持久层看看停在缓存层干嘛 。这个注解属性和更新缓存注解一样,少了value,参照上文。

    6、@CacheRefresh

    以上,不管是更新还是删除,总在我的服务中,我自己掌握,我自己加了注解去通知缓存跟着变。但如果持久层更新的操作是在别的部门管理的服务里呢?即在缓存层无感知,如何保证数据最新,答案是@CacheRefresh,每隔一定间隔去查数据库,刷新缓存值。 先看注解属性:

    属性默认值描述
    refresh刷新间隔,谨慎设置,过小会导致查持久层频率太高,给数据库带来压力
    timeUnitTimeUnit.SECONDS时间单位
    stopRefreshAfterLastAccess-2147483648指定该key多长时间没有访问就停止刷新,如果不指定会一直刷新
    refreshLockTimeout-2147483648类型为BOTH/REMOTE的缓存刷新时,同时只会有一台服务器在刷新,这台服务器会在远程缓存放置一个分布式锁,此配置指定该锁的超时时间

    在这里插入图片描述
    以上即,查到的Book对象缓存起来,且没10秒去数据库查一下,然后刷新缓存里的Book值

    7、缓存统计报告

    在服务配置文件application.yaml中加以下配置:

    jetcache:
      statIntervalMinutes: 15
      # statInterval即统计间隔,也就是15分钟一统计
    
    • 1
    • 2
    • 3

    重启服务,控制台每隔15分钟输出一次统计数据,值为0时即不统计,(直接不写就行,不用写了又给个0值),效果如下:
    在这里插入图片描述

    查询26次,命中缓存21次。

  • 相关阅读:
    Python【猜拳游戏】
    kubernetes资源对象介绍及常用命令
    Word控件Spire.Doc 【页面背景】教程(9) ;C#/VB.NET:从 Word 文档中删除文本或图像水印
    【科普向】Jmeter 如何测试接口保姆式教程
    黄石市2022年知识产权质押贷款贴息补贴项目申报条件、材料
    LCD DRM component 框架分析
    有了这45个小技巧,再也不怕女朋友代码写得烂了!!
    牛客刷题——剑指offer(第6期)
    为什么说 Gradle 是 Android 进阶绕不去的坎 —— Gradle 系列(1)
    【原创】生成文件MD5图像,类似于GitHub的像素风格头像
  • 原文地址:https://blog.csdn.net/llg___/article/details/133409739