目录
4、适配公司sedis客户端-未使用Springboot 的项目配置
jetcache官方源码:
github:GitHub - alibaba/jetcache: JetCache is a Java cache framework.
官方文档:Home · alibaba/jetcache Wiki · GitHub
一、JetCache介绍
1.1 诞生
2013年 | JetCache诞生于 [ 阿里彩票 ],作者是 [ huangli ] 凭借得天独厚的Tair支持和丰富的Spring生态注解支持,赢得了大家的喜爱。 |
2015年 | 随着SpringBoot的大热和集团内PandoraBoot的彻底铺开,JetCache以Starter的形式实现了扩展,优化了配置项,在架构设计和性能上更上一层楼。 |
2015年同年 | JetCache开源至Github,作为alibaba的开源缓存框架,其易用性和设计先进性吸引了大批国内外用户,截止当前在github上累计3.7k star,870 fork。 |
2018年 | JetCache最大版本更新,对整体的设计进行了调整,修改了默认的序列化方式,集成支持了SpringData,RedisLettuce,Redisson等更加高效以及功能更加灵活且高级的三方SDK。 |
1.2 整合-开源界大放异彩
支持范围方面 | JetCache原生支持的远程缓存是Tair,但是Tair在集团外并不可用。JetCache为了拥抱开源,实现了时下主流的GuavaCache, CaffeineCache, Redis,MemCache基本覆盖了国内的主流缓存中间件 |
在功能性方面 | JetCache满足了用户一行注解解决Method缓存的刚需,同时也能通过叠加注解的方式非常高效的处理缓存穿透,缓存击穿,缓存雪崩,缓存失效等经典分布式缓存的问题,这让用户充分体验到了缓存框架的效率优势和设计先进性。 |
在扩展性方面 | JetCache满足了用户一行注解解决Method缓存的刚需,也提供了优秀的扩展能力。想要实现一个新的Cache类型,只需要实现AbstractEmbeddedCache或者AbstractExternalCache就可以以非常低廉的成本实现一个新的缓存框架。 |
1.3 挑战-SpringCache江湖地位
对比 | SpringCache | JetCache |
---|---|---|
出身 | 在2015年最火的框架是SpringBoot,SpringBoot提供了非常丰富的组件支持以及模块化的组件管理,其中就包括基于JSR-107–JCacheAPI实现的SpringCache框架。 | JetCache是阿里推出的一套替代springcache的缓存方案。JetCache是对SpringCache进行了封装。在阿里实际业务应用实战后开源的缓存框架 |
依赖性 | SpringCache 是 Spring 框架自带的缓存框架 | JetCache 是独立的缓存框架,不依赖于任何框架。 |
使用方式 | SpringCache 集成于 Spring 框架中,使用起来比较简单,只需要在代码中添加注解即可 | JetCache 需要手动引入依赖并配置相关参数,使用起来相对复杂一些 |
分布式 | SpringCache框架很好的实现了JCacheAPI,在当时占据了非常有力的位置,几乎所有的SpringBoot初创项目,都选择了使用SpringCache来作为他们的第一个缓存框架。但随着软件工程的规模越来越大,分布式场景的经典问题也接踵而至,显然SpringCache在应对分布式环境的经典问题时显得太过于稚嫩。 对于分布式场景, 分布式锁,缓存穿透,缓存击穿,缓存雪崩 等经典问题,缺少足够成熟的方案。 | 在SpringCache基础上实现了多级缓存、缓存统计、自动刷新(防止某个缓存失效,突然访问量增大,导致数据库挂掉的缓存雪崩)、异步调用、数据报表等功能。 支持分布式锁,JetCache提供了分布式锁和分布式计数器等功能,以支持分布式环境下的并发控制。 |
缓存层级 | 仅支持一级缓存,与提供了基本的缓存功能 | 支持多级缓存滑动窗口,缓存序列化,异步API支持等实际工作场景经常会需要用到的核心能力 支持多种缓存策略,包括基于时间的过期策略、基于LRU算法的自动清理策略、基于手动清理的策略等。此外, |
扩展性方面 | 对于扩展性上,设计的不够开放和正交,很难低成本的完成一些高级功能的扩展。 | 在迁移缓存方面基本上可以做到换注解平替,所以一旦工程规模达到一定量级,很多架构师会选择从SpringCache的方式切换到JetCache上。 |
总体来说,如果需要更高级的缓存特性,或者需要更好的性能表现,可以选择 JetCache。如果只需要基本的缓存功能,或者已经使用了 Spring 框架,可以选择 SpringCache。 |
1.4 JetCache核心特性和优势
etCache设定了本地缓存与远程缓存的多级缓存方案:
提供统一的,类似jsr-107风格的API访问Cache,并可通过注解创建并配置Cache实例
支持多种缓存类型 | JetCache支持多种缓存类型,包括本地缓存、远程缓存和分布式缓存,可以根据业务需求选择合适的缓存类型。 |
支持多种缓存协议 | JetCache支持多种缓存协议,包括Redis、Memcached、Couchbase等,可以与多种缓存系统进行集成。可选择内存缓存、分布式缓存,或者同时存在,同时存在时优先访问内存 |
支持多种缓存策略 | JetCache支持多种缓存策略,包括FIFO、LRU、LFU等,可以根据业务需求选择合适的缓存策略。 |
支持注解和编程式缓存 | JetCache支持注解和编程式缓存,可以根据业务需求选择合适的缓存方式。 |
支持分布式事务 | JetCache支持分布式事务,可以保证缓存和数据库的一致性。分布式缓存自动刷新,分布式锁 (2.2+) |
支持缓存监控和统计 | JetCache支持缓存监控和统计,可以实时查看缓存的使用情况和性能指标 |
易于使用和扩展 | JetCache具有简单易用的API和灵活的扩展机制,可以方便地集成到各种应用中。Key的生成策略和Value的序列化策略是可以定制的、支持异步Cache API |
2.1 使用要求
JetCache需要JDK1.8、Spring Framework4.0.8以上版本。Spring Boot为可选,需要1.1.9以上版本。如果不使用注解(仅使用jetcache-core),Spring Framework也是可选的,此时使用方式与Guava/Caffeine cache类似。
2.2 项目代码介绍
JSR-107--缓存JCache标准抽象实
Java在2012的JSR-107协议中新增了关于缓存的抽象设计标准–JCache。
JetCache2.0的核心是com.alicp.jetcache.Cache
接口(以下简写为Cache
),它提供了部分类似于javax.cache.Cache
(JSR107)的API操作。没有完整实现JSR107的原因包括:
- 希望维持API的简单易用。
- 对于特定的远程缓存系统来说,
javax.cache.Cache
中定义的有些操作无法高效率的实现,比如一些原子操作方法和类似removeAll()
这样的方法。 - JSR107比较复杂,完整实现要做的工作很多。
丰富注解-无侵入抽象设计
二、JetCache核心注解
JetCache方法缓存和SpringCache比较类似,它原生提供了TTL支持,以保证最终一致,并且支持二级缓存。JetCache2.4以后支持基于注解的缓存更新和删除。
在spring环境下,使用@Cached注解可以为一个方法添加缓存,@CacheUpdate用于更新缓存,@CacheInvalidate用于移除缓存元素。注解可以加在接口上也可以加在类上,加注解的类必须是一个spring bean,例如:
public interface UserService {
@Cached(name="userCache.", key="#userId", expire = 3600)
User getUserById(long userId);
@CacheUpdate(name="userCache.", key="#user.userId", value="#user")
void updateUser(User user);
@CacheInvalidate(name="userCache.", key="#userId")
void deleteUser(long userId);
}
@Cached注解说明
-
在方法上添加 @Cached 注解,指定缓存的 key 和过期时间等参数。
-
当方法被调用时,JetCache 会先从缓存中查找对应的数据。
-
如果缓存中存在数据,则直接返回缓存中的数据,不再执行方法体。
-
如果缓存中不存在数据,则执行方法体,并将方法的返回值存入缓存中。
-
后续调用该方法时,将直接从缓存中获取数据,不再执行方法体。
-
缓存的过期时间到达后,缓存数据将被自动清除,下次调用该方法将重新执行方法体并更新缓存数据。
JetCache 支持多种缓存类型,包括本地内存缓存、Redis 缓存、Caffeine 缓存等,可以根据实际需求进行配置。同时,JetCache 还提供了缓存预热、缓存穿透、缓存雪崩等解决方案,以提高缓存的效率和稳定性。
@Cached注解和@CreateCache的属性非常类似,但是多几个:
属性 | 默认值 | 备注 |
area | “default” | 如果在配置中配置了多个缓存area,在这里指定使用哪个area |
name | 未定义 | 指定缓存的唯一名称,不是必须的,如果没有指定,会使用类名+方法名。name会被用于远程缓存的key前缀。另外在统计中,一个简短有意义的名字会提高可读性。 |
key | 未定义 | 使用SpEL指定key,如果没有指定会根据所有参数自动生成。 |
expire | 未定义 | 超时时间。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为无穷大 |
timeUnit | TimeUnit.SECONDS | 指定expire的单位 |
cacheType | CacheType.REMOTE | 缓存的类型,包括CacheType.REMOTE、CacheType.LOCAL、CacheType.BOTH。如果定义为BOTH,会使用LOCAL和REMOTE组合成两级缓存 |
localLimit | 未定义 | 如果cacheType为LOCAL或BOTH,这个参数指定本地缓存的最大元素数量,以控制内存占用。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为100,参数 localLimit 表示本地缓存的最大条目限制。当使用 JetCache 的本地缓存功能时,localLimit 参数可以控制本地缓存中最多缓存的条目数量,如果超过了这个数量,将会按照 LRU(Least Recently Used)算法删除最近最少使用的缓存条目,以保持缓存的大小在限制范围内。localLimit 默认值为 100条,可以通过设置该值来优化缓存性能和内存使用。 备注:当type为LOCAL时,配置localLimit,当key值超出限制条件,会删除最少使用的条目,重新请求缓存过的,会走到db再缓存一次。 当type为BOTH时,虽然配置了localLimit 当key值超出限制条件,本地会删除使用最少得条目,但是远程还有缓存,查询缓存时是优先查询本地,没有再去远程,都没有再走db,将查询结果进行一次缓存。如果配置BOTH时,可以配置该项减少本地内存使用,但是不影响缓存整体使用因为有远程兜底。 |
localExpire | 未定义 | 仅当cacheType为BOTH时适用,为内存中的Cache指定一个不一样的超时时间,通常应该小于expire |
serialPolicy | 未定义 | 指定远程缓存的序列化方式。可选值为SerialPolicy.JAVA和SerialPolicy.KRYO。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为SerialPolicy.JAVA |
keyConvertor | 未定义 | 指定KEY的转换方式,用于将复杂的KEY类型转换为缓存实现可以接受的类型,当前支持KeyConvertor.FASTJSON和KeyConvertor.NONE。NONE表示不转换,FASTJSON可以将复杂对象KEY转换成String。如果注解上没有定义,会使用全局配置。 |
enabled | true | 是否激活缓存。例如某个dao方法上加缓存注解,由于某些调用场景下不能有缓存,所以可以设置enabled为false,正常调用不会使用缓存,在需要的地方可使用CacheContext.enableCache在回调中激活缓存,缓存激活的标记在ThreadLocal上,该标记被设置后,所有enable=false的缓存都被激活 |
cacheNullValue | false | 当方法返回值为null的时候是否要缓存 |
condition | 未定义 | 使用SpEL指定条件,如果表达式返回true的时候才去缓存中查询 |
postCondition | 未定义 | 使用SpEL指定条件,如果表达式返回true的时候才更新缓存,该评估在方法执行后进行,因此可以访问到#result |
@CacheRefresh注解说明:
(强制刷新即使缓存结果未到时效时间)
属性 | 默认值 | 说明 |
---|---|---|
refresh | 未定义 | 刷新间隔 |
timeUnit | TimeUnit.SECONDS | 时间单位 |
stopRefreshAfterLastAccess | 未定义 | 指定该key多长时间没有访问就停止刷新,如果不指定会一直刷新 |
refreshLockTimeout | 60秒 | 类型为BOTH/REMOTE的缓存刷新时,同时只会有一台服务器在刷新,这台服务器会在远程缓存放置一个分布式锁,此配置指定该锁的超时时间 |
@CacheInvalidate注解说明:
属性 | 默认值 | 说明 |
---|---|---|
area | “default” | 如果在配置中配置了多个缓存area,在这里指定使用哪个area,指向对应的@Cached定义。 |
name | 未定义 | 指定缓存的唯一名称,指向对应的@Cached定义。 |
key | 未定义 | 使用SpEL指定key |
condition | 未定义 | 使用SpEL指定条件,如果表达式返回true才执行删除,可访问方法结果#result |
@CacheUpdate注解说明:
属性 | 默认值 | 说明 |
---|---|---|
area | “default” | 如果在配置中配置了多个缓存area,在这里指定使用哪个area,指向对应的@Cached定义。 |
name | 未定义 | 指定缓存的唯一名称,指向对应的@Cached定义。 |
key | 未定义 | 使用SpEL指定key |
value | 未定义 | 使用SpEL指定value |
condition | 未定义 | 使用SpEL指定条件,如果表达式返回true才执行更新,可访问方法结果#result |
使用@CacheUpdate和@CacheInvalidate的时候,相关的缓存操作可能会失败(比如网络IO错误),所以指定缓存的超时时间是非常重要的。
@CachePenetrationProtect注解:
当缓存访问未命中的情况下,对并发进行的加载行为进行保护。 当前版本实现的是单JVM内的保护,即同一个JVM中同一个key只有一个线程去加载,其它线程等待结果。
对于以上未定义默认值的参数,如果没有指定,将使用yml中指定的全局配置,全局配置请参考配置说明。
三、JetCache接入配置
1、依赖哪个jar?
jetcache-anno-api:定义jetcache的注解和常量,不传递依赖。如果你想把Cached注解加到接口上,又不希望你的接口jar传递太多依赖,可以让接口jar依赖jetcache-anno-api。
jetcache-core:核心api,完全通过编程来配置操作Cache
,不依赖Spring。两个内存中的缓存实现LinkedHashMapCache
和CaffeineCache
也由它提供。
jetcache-anno:基于Spring提供@Cached和@CreateCache注解支持。
jetcache-redis:使用jedis提供Redis支持。
jetcache-redis-lettuce(需要JetCache2.3以上版本):使用lettuce提供Redis支持,实现了JetCache异步访问缓存的的接口。
jetcache-starter-redis:Spring Boot方式的Starter,基于Jedis。
jetcache-starter-redis-lettuce(需要JetCache2.3以上版本):Spring Boot方式的Starter,基于Lettuce。
项目需要使用二级缓存(远程使用redis),用注解加到接口的方式实现,选用jetcache-anno-api、jetcache-redis 两个jar包
2、官方-Springboot 项目配置
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>${jetcache.latest.version}</version>
</dependency>
配置一个spring boot风格的application.yml文件,把他放到资源目录中
jetcache:
statIntervalMinutes: 15
areaInCacheName: false
local:
default:
type: linkedhashmap
keyConvertor: fastjson
remote:
default:
type: redis
keyConvertor: fastjson2
broadcastChannel: projectA
valueEncoder: java
valueDecoder: java
poolConfig:
minIdle: 5
maxIdle: 20
maxTotal: 50
host: 127.0.0.1
port: 6379
然后创建一个App类放在业务包的根下,EnableMethodCache,EnableCreateCacheAnnotation这两个注解分别激活Cached和CreateCache注解,其他和标准的Spring Boot程序是一样的。这个类可以直接main方法运行。
package com.company.mypackage;
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableMethodCache(basePackages = "com.company.mypackage")
@EnableCreateCacheAnnotation
public class MySpringBootApp {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApp.class);
}
}
3、官方-未使用Springboot 的项目配置
如果没有使用spring boot,可以按下面的方式配置(这里使用jedis客户端连接redis为例)。
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-anno</artifactId>
<version>${jetcache.latest.version}</version>
</dependency>
官方配置文档
package com.company.mypackage;
import java.util.HashMap;
import java.util.Map;
import com.alicp.jetcache.anno.CacheConsts;
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import com.alicp.jetcache.anno.support.GlobalCacheConfig;
import com.alicp.jetcache.anno.support.SpringConfigProvider;
import com.alicp.jetcache.embedded.EmbeddedCacheBuilder;
import com.alicp.jetcache.embedded.LinkedHashMapCacheBuilder;
import com.alicp.jetcache.redis.RedisCacheBuilder;
import com.alicp.jetcache.support.Fastjson2KeyConvertor;
import com.alicp.jetcache.support.JavaValueDecoder;
import com.alicp.jetcache.support.JavaValueEncoder;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.util.Pool;
@Configuration
@EnableMethodCache(basePackages = "com.company.mypackage")
@EnableCreateCacheAnnotation // deprecated in jetcache 2.7, 如果不用@CreateCache注解可以删除
@Import(JetCacheBaseBeans.class) //need since jetcache 2.7+
public class JetCacheConfig {
@Bean
public Pool<Jedis> pool(){
GenericObjectPoolConfig pc = new GenericObjectPoolConfig();
pc.setMinIdle(2);
pc.setMaxIdle(10);
pc.setMaxTotal(10);
return new JedisPool(pc, "localhost", 6379);
}
//@Bean for jetcache <=2.6
//public SpringConfigProvider springConfigProvider() {
// return new SpringConfigProvider();
//}
@Bean
public GlobalCacheConfig config(Pool<Jedis> pool){
// public GlobalCacheConfig config(SpringConfigProvider configProvider, Pool<Jedis> pool){ // for jetcache <=2.5
Map localBuilders = new HashMap();
EmbeddedCacheBuilder localBuilder = LinkedHashMapCacheBuilder
.createLinkedHashMapCacheBuilder()
.keyConvertor(FastjsonKeyConvertor.INSTANCE);
localBuilders.put(CacheConsts.DEFAULT_AREA, localBuilder);
Map remoteBuilders = new HashMap();
RedisCacheBuilder remoteCacheBuilder = RedisCacheBuilder.createRedisCacheBuilder()
.keyConvertor(Fastjson2KeyConvertor.INSTANCE)
.valueEncoder(JavaValueEncoder.INSTANCE)
.valueDecoder(JavaValueDecoder.INSTANCE)
.broadcastChannel("projectA")
.jedisPool(pool);
remoteBuilders.put(CacheConsts.DEFAULT_AREA, remoteCacheBuilder);
GlobalCacheConfig globalCacheConfig = new GlobalCacheConfig();
// globalCacheConfig.setConfigProvider(configProvider); // for jetcache <= 2.5
globalCacheConfig.setLocalCacheBuilders(localBuilders);
globalCacheConfig.setRemoteCacheBuilders(remoteBuilders);
globalCacheConfig.setStatIntervalMinutes(15);
//globalCacheConfig.setAreaInCacheName(false); for jetcache <=2.6
return globalCacheConfig;
}
}
本地缓存-LinkedHashMapCache
本地缓存当前有两个实现。如果自己用jetcache-core的Cache API,可以不指定keyConvertor,此时本地缓存使用equals方法来比较key。 如果使用jetcache-anno中的@Cached、@CreateCache等注解,必须指定keyConvertor。
LinkedHashMapCache是JetCache中实现的一个最简单的Cache,使用LinkedHashMap做LRU方式淘汰。
Cache<Long, OrderDO> cache = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder()
.limit(100)
.expireAfterWrite(200, TimeUnit.SECONDS)
.buildCache();
本地缓存-caffeine
caffeine cache的介绍看这里,它是guava cache的后续作品。
Cache<Long, OrderDO> cache = CaffeineCacheBuilder.createCaffeineCacheBuilder()
.limit(100)
.expireAfterWrite(200, TimeUnit.SECONDS)
.buildCache();
4、统计
官网信息
jetcache/Stat.md at master · alibaba/jetcache · GitHub
当yml中的jetcache.statIntervalMinutes大于0时,通过@CreateCache和@Cached配置出来的Cache自带监控。JetCache会按指定的时间定期通过logger输出统计信息。默认输出信息类似如下:
只有使用computeIfAbsent方法或者@Cached注解才会统计loadTime。用get方法取缓存,没有命中的话自己去数据库load,显然是无法统计到的。
官方配置
如果需要定制输出,可以这样做:,按下文配置会在hotdog.debug.log文件中展示统计图,如果要制定输出到某日志需要配置logback,配置信息下文有
@Bean
public Consumer<StatInfo> statCallback() {
return new StatInfoLogger(false);
// ... 或实现自己的Consumer<StatInfo>
}
JetCache按statIntervalMinutes指定的周期,定期调用statCallback返回着这个Consumer,传入的StatInfo是已经统计好的数据。这个方法默认的实现是:
return new StatInfoLogger(false);
StatInfoLogger的构造参数设置为true会有更详细的统计信息,包括put等操作的统计。StatInfoLogger输出的是给人读的信息,你也可以自定义logger将日志输出成特定格式,然后通过日志系统统一收集和统计。
自定义统计示例: jetCache会自动回调这个方法,回调周期按照setStatIntervalMinutes(2);配置的时间
logback日志输出配置
如果想要让jetcache的日志输出到独立的文件中,在使用logback的情况下可以这样配置:(如果需要独立打印,不要配置上述自定义统计埋点)如果需要打印日志+埋点可以自定义类,具体请看文章最后
<appender name="JETCACHE_LOGFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>jetcache.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>jetcache.log.%d{yyyy-MM-dd}</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.alicp.jetcache" level="INFO" additivity="false">
<appender-ref ref="JETCACHE_LOGFILE" />
</logger>
统计日志:
5、适配公司sedis客户端-未使用Springboot 的项目配置
@Configuration
@EnableMethodCache(basePackages = "应用范围包名路径")
@Import(JetCacheBaseBeans.class)
public class JetCacheConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(JetCacheConfig.class);
protected int maxNameLength = 65;
@Bean
public Consumer<StatInfo> statCallback() {
//自定义Logger,实现自定义埋点+统计日志打印
return new JetCacheLogger();
}
@Bean
public GlobalCacheConfig config(@Qualifier("jetCacheSedis") AdvancedSedis3 jetCacheSedis) {
//这里参数的 jetCacheSedis 为本项目自己封装的redis,使用自己项目封装好的redis即可,
//或者照着官网示例代码的redis pool 配置也可以
Map localBuilders = new HashMap();
CaffeineCacheBuilder.CaffeineCacheBuilderImpl caffeineCacheBuilder = CaffeineCacheBuilder.createCaffeineCacheBuilder()
.keyConvertor(Fastjson2KeyConvertor.INSTANCE);
localBuilders.put(CacheConsts.DEFAULT_AREA, caffeineCacheBuilder);
Map remoteBuilders = new HashMap();
SedisJetCacheBuilder.SedisDataCacheBuilderImpl builder = SedisJetCacheBuilder.createBuilder()
.keyConvertor(Fastjson2KeyConvertor.INSTANCE)
.valueEncoder(Kryo5ValueEncoder.INSTANCE)
.valueDecoder(Kryo5ValueDecoder.INSTANCE)
.broadcastChannel("t_desert_feed")
.keyPrefix("jetCache-")
.sedisClient(jetCacheSedis);//放入redis
remoteBuilders.put(CacheConsts.DEFAULT_AREA, builder);
GlobalCacheConfig globalCacheConfig = new GlobalCacheConfig();
globalCacheConfig.setRemoteCacheBuilders(remoteBuilders);
globalCacheConfig.setLocalCacheBuilders(localBuilders);
globalCacheConfig.setStatIntervalMinutes(1);//设置每次间隔多久统计
return globalCacheConfig;
}
}
自定义Logger处理埋点和统计日志打印
public class JetCacheLogger implements Consumer<StatInfo> {
private static final String JETCACHE_MONITOR_PREFIX = "jetcache.moniter.";
private static Logger logger = LoggerFactory.getLogger(StatInfoLogger.class);//此处logger类需要StatInfoLogger这个
protected int maxNameLength = 65;
@Override
public void accept(StatInfo statInfo) {
//埋点
List<CacheStat> stats = statInfo.getStats();
if (stats != null && stats.size() > 0) {
for (CacheStat c : stats) {
String monitorKey = JETCACHE_MONITOR_PREFIX + c.getCacheName();
long get = c.getGetCount();
long hit = c.getGetHitCount();
long fail = c.getGetFailCount();
long expire = c.getGetExpireCount();
double avgLoadTime = c.avgLoadTime() * 100;
double maxLoadTime = c.getMaxLoadTime() * 100;
//此处为项目封装的埋点方法,需要替换为自己项目埋点监控方法
Monitor.recordMany(monitorKey + ".get", get, 0);
Monitor.recordMany(monitorKey + ".hit", hit, 0);
Monitor.recordMany(monitorKey + ".fail", fail, 0);
Monitor.recordMany(monitorKey + ".expire", expire, 0);
Monitor.recordQuantile(monitorKey + ".avgLoadTime", (long) avgLoadTime);
Monitor.recordQuantile(monitorKey + ".maxLoadTime", (long) maxLoadTime);
}
}
//日志,此处为复制StatInfoLogger 中的方法
Collections.sort(stats, (o1, o2) -> {
if (o1.getCacheName() == null) {
return -1;
} else if (o2.getCacheName() == null) {
return 1;
} else {
return o1.getCacheName().compareTo(o2.getCacheName());
}
});
StringBuilder sb;
sb = logStatSummary(statInfo);//只使用总结统计,明细统计使用logVerbose这个方法且需要复制logVerbose方法的实现
logger.info(sb.toString());
}
private StringBuilder logTitle(int initSize, StatInfo statInfo) {
StringBuilder sb = new StringBuilder(initSize);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
sb.append("jetcache stat from ").append(sdf.format(new Date(statInfo.getStartTime())))
.append(" to ").append(sdf.format(statInfo.getEndTime())).append("\n");
return sb;
}
private void printSepLine(StringBuilder sb, String title) {
title.chars().forEach((c) -> {
if (c == '|') {
sb.append('+');
} else {
sb.append('-');
}
});
sb.append('\n');
}
private StringBuilder logStatSummary(StatInfo statInfo) {
StringBuilder sb = logTitle(2048, statInfo);
List<CacheStat> stats = statInfo.getStats();
OptionalInt maxCacheNameLength = stats.stream().mapToInt((s) -> getName(s.getCacheName()).length()).max();
int len = Math.max(5, maxCacheNameLength.orElse(0));
String title = String.format("%-" + len + "s|%10s|%7s|%14s|%14s|%14s|%14s|%11s|%11s", "cache", "qps", "rate", "get", "hit", "fail", "expire", "avgLoadTime", "maxLoadTime");
sb.append(title).append('\n');
printSepLine(sb, title);
for (CacheStat s : stats) {
sb.append(String.format("%-" + len + "s", getName(s.getCacheName()))).append('|');
sb.append(String.format("%,10.2f", s.qps())).append('|');
sb.append(String.format("%6.2f%%", s.hitRate() * 100)).append('|');
sb.append(String.format("%,14d", s.getGetCount())).append('|');
sb.append(String.format("%,14d", s.getGetHitCount())).append('|');
sb.append(String.format("%,14d", s.getGetFailCount())).append('|');
sb.append(String.format("%,14d", s.getGetExpireCount())).append('|');
sb.append(String.format("%,11.1f", s.avgLoadTime())).append('|');
sb.append(String.format("%,11d", s.getMaxLoadTime())).append('\n');
}
printSepLine(sb, title);
return sb;
}
private String getName(String name) {
if (name == null) {
return null;
}
if (name.length() > maxNameLength) {
return "..." + name.substring(name.length() - maxNameLength + 3);
} else {
return name;
}
}
}
还需要配置logback,看上文
6、指标的解读
以下是 JetCache 的各项统计指标的解读:
-
缓存命中率(Hit Rate):表示缓存中已经存在的数据占总请求次数的比例。命中率越高,说明缓存效果越好,反之则说明缓存效果较差。
-
缓存请求次数(Request Count):表示系统对缓存的请求总次数,包括读取和写入操作。
-
缓存命中次数(Hit Count):表示系统从缓存中读取数据的次数。
-
缓存未命中次数(Miss Count):表示系统从缓存中读取数据失败的次数,需要从数据库或其他数据源中获取数据。
-
缓存写入次数(Put Count):表示系统向缓存中写入数据的次数。
-
缓存删除次数(Remove Count):表示系统从缓存中删除数据的次数。
-
缓存平均读取时间(Average Read Time):表示系统从缓存中读取数据的平均时间。
-
缓存平均写入时间(Average Write Time):表示系统向缓存中写入数据的平均时间。
-
缓存平均删除时间(Average Remove Time):表示系统从缓存中删除数据的平均时间。