Redis+Caffeine两级缓存,让访问速度纵享丝滑
Posted 码农参上
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis+Caffeine两级缓存,让访问速度纵享丝滑相关的知识,希望对你有一定的参考价值。
在高性能的服务架构设计中,缓存是一个不可或缺的环节。在实际的项目中,我们通常会将一些热点数据存储到Redis
或MemCache
这类缓存中间件中,只有当缓存的访问没有命中时再查询数据库。在提升访问速度的同时,也能降低数据库的压力。
随着不断的发展,这一架构也产生了改进,在一些场景下可能单纯使用Redis
类的远程缓存已经不够了,还需要进一步配合本地缓存使用,例如Guava cache
或Caffeine
,从而再次提升程序的响应速度与服务性能。于是,就产生了使用本地缓存作为一级缓存,再加上远程缓存作为二级缓存的两级缓存架构。
在先不考虑并发等复杂问题的情况下,两级缓存的访问流程可以用下面这张图来表示:
优点与问题那么,使用两级缓存相比单纯使用远程缓存,具有什么优势呢?
Redis
类的远程缓存间的数据交互,减少网络I/O开销,降低这一过程中在网络通信上的耗时但是在设计中,还是要考虑一些问题的,例如数据一致性问题。首先,两级缓存与数据库的数据要保持一致,一旦数据发生了修改,在修改数据库的同时,本地缓存、远程缓存应该同步更新。
另外,如果是分布式环境下,一级缓存之间也会存在一致性问题,当一个节点下的本地缓存修改后,需要通知其他节点也刷新本地缓存中的数据,否则会出现读取到过期数据的情况,这一问题可以通过类似于Redis中的发布/订阅功能解决。
此外,缓存的过期时间、过期策略以及多线程访问的问题也都需要考虑进去,不过我们今天暂时先不考虑这些问题,先看一下如何简单高效的在代码中实现两级缓存的管理。
准备工作在简单梳理了一下要面对的问题后,下面开始两级缓存的代码实战,我们整合号称最强本地缓存的Caffeine
作为一级缓存、性能之王的Redis
作为二级缓存。首先建一个springboot项目,引入缓存要用到的相关的依赖:
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.8.1</version>
</dependency>
在application.yml
中配置Redis
的连接信息:
spring:
redis:
host: 127.0.0.1
port: 6379
database: 0
timeout: 10000ms
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
在下面的例子中,我们将使用RedisTemplate
来对redis
进行读写操作,RedisTemplate
使用前需要配置一下ConnectionFactory
和序列化方式,这一过程比较简单就不贴出代码了,有需要本文全部示例代码的可以在文末获取。
下面我们在单机环境下,将按照对业务侵入性的不同程度,分三个版本来实现两级缓存的使用。
V1.0版本我们可以通过手动操作Caffeine
中的Cache
对象来缓存数据,它是一个类似Map
的数据结构,以key
作为索引,value
存储数据。在使用Cache
前,需要先配置一下相关参数:
@Configuration
public class CaffeineConfig
@Bean
public Cache<String,Object> caffeineCache()
return Caffeine.newBuilder()
.initialCapacity(128)//初始大小
.maximumSize(1024)//最大数量
.expireAfterWrite(60, TimeUnit.SECONDS)//过期时间
.build();
简单解释一下Cache
相关的几个参数的意义:
initialCapacity
:初始缓存空大小maximumSize
:缓存的最大数量,设置这个值可以避免出现内存溢出expireAfterWrite
:指定缓存的过期时间,是最后一次写操作后的一个时间,这里此外,缓存的过期策略也可以通过expireAfterAccess
或refreshAfterWrite
指定。
在创建完成Cache
后,我们就可以在业务代码中注入并使用它了。在没有使用任何缓存前,一个只有简单的Service
层代码是下面这样的,只有crud操作:
@Service
@AllArgsConstructor
public class OrderServiceImpl implements OrderService
private final OrderMapper orderMapper;
@Override
public Order getOrderById(Long id)
Order order = orderMapper.selectOne(new LambdaQueryWrapper<Order>()
.eq(Order::getId, id));
return order;
@Override
public void updateOrder(Order order)
orderMapper.updateById(order);
@Override
public void deleteOrder(Long id)
orderMapper.deleteById(id);
接下来,对上面的OrderService
进行改造,在执行正常业务外再加上操作两级缓存的代码,先看改造后的查询操作:
public Order getOrderById(Long id)
String key = CacheConstant.ORDER + id;
Order order = (Order) cache.get(key,
k ->
//先查询 Redis
Object obj = redisTemplate.opsForValue().get(k);
if (Objects.nonNull(obj))
log.info("get data from redis");
return obj;
// Redis没有则查询 DB
log.info("get data from database");
Order myOrder = orderMapper.selectOne(new LambdaQueryWrapper<Order>()
.eq(Order::getId, id));
redisTemplate.opsForValue().set(k, myOrder, 120, TimeUnit.SECONDS);
return myOrder;
);
return order;
在Cache
的get
方法中,会先从缓存中进行查找,如果找到缓存的值那么直接返回。如果没有找到则执行后面的方法,并把结果加入到缓存中。
因此上面的逻辑就是先查找Caffeine
中的缓存,没有的话查找Redis
,Redis
再不命中则查询数据库,写入Redis
缓存的操作需要手动写入,而Caffeine
的写入由get
方法自己完成。
在上面的例子中,设置Caffeine
的过期时间为60秒,而Redis
的过期时间为120秒,下面进行测试,首先看第一次接口调用时,进行了数据库的查询:
而在之后60秒内访问接口时,都没有打印打任何sql或自定义的日志内容,说明接口没有查询Redis
或数据库,直接从Caffeine
中读取了缓存。
等到距离第一次调用接口进行缓存的60秒后,再次调用接口:
可以看到这时从Redis
中读取了数据,因为这时Caffeine
中的缓存已经过期了,但是Redis
中的缓存没有过期仍然可用。
下面再来看一下修改操作,代码在原先的基础上添加了手动修改Redis
和Caffeine
缓存的逻辑:
public void updateOrder(Order order)
log.info("update order data");
String key=CacheConstant.ORDER + order.getId();
orderMapper.updateById(order);
//修改 Redis
redisTemplate.opsForValue().set(key,order,120, TimeUnit.SECONDS);
// 修改本地缓存
cache.put(key,order);
看一下下面图中接口的调用、以及缓存的刷新过程。可以看到在更新数据后,同步刷新了缓存中的内容,再之后的访问接口时不查询数据库,也可以拿到正确的结果:
最后再来看一下删除操作,在删除数据的同时,手动移除Reids
和Caffeine
中的缓存:
public void deleteOrder(Long id)
log.info("delete order");
orderMapper.deleteById(id);
String key= CacheConstant.ORDER + id;
redisTemplate.delete(key);
cache.invalidate(key);
我们在删除某个缓存后,再次调用之前的查询接口时,又会出现重新查询数据库的情况:
简单的演示到此为止,可以看到上面这种使用缓存的方式,虽然看起来没什么大问题,但是对代码的入侵性比较强。在业务处理的过程中要由我们频繁的操作两级缓存,会给开发人员带来很大负担。那么,有什么方法能够简化这一过程呢?我们看看下一个版本。
V2.0版本在spring
项目中,提供了CacheManager
接口和一些注解,允许让我们通过注解的方式来操作缓存。先来看一下常用几个注解说明:
@Cacheable
:根据键从缓存中取值,如果缓存存在,那么获取缓存成功之后,直接返回这个缓存的结果。如果缓存不存在,那么执行方法,并将结果放入缓存中。@CachePut
:不管之前的键对应的缓存是否存在,都执行方法,并将结果强制放入缓存@CacheEvict
:执行完方法后,会移除掉缓存中的数据。如果要使用上面这几个注解管理缓存的话,我们就不需要配置V1版本中的那个类型为Cache
的Bean
了,而是需要配置spring
中的CacheManager
的相关参数,具体参数的配置和之前一样:
@Configuration
public class CacheManagerConfig
@Bean
public CacheManager cacheManager()
CaffeineCacheManager cacheManager=new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(128)
.maximumSize(1024)
.expireAfterWrite(60, TimeUnit.SECONDS));
return cacheManager;
然后在启动类上再添加上@EnableCaching
注解,就可以在项目中基于注解来使用Caffeine
的缓存支持了。下面,再次对Service
层代码进行改造。
首先,还是改造查询方法,在方法上添加@Cacheable
注解:
@Cacheable(value = "order",key = "#id")
//@Cacheable(cacheNames = "order",key = "#p0")
public Order getOrderById(Long id)
String key= CacheConstant.ORDER + id;
//先查询 Redis
Object obj = redisTemplate.opsForValue().get(key);
if (Objects.nonNull(obj))
log.info("get data from redis");
return (Order) obj;
// Redis没有则查询 DB
log.info("get data from database");
Order myOrder = orderMapper.selectOne(new LambdaQueryWrapper<Order>()
.eq(Order::getId, id));
redisTemplate.opsForValue().set(key,myOrder,120, TimeUnit.SECONDS);
return myOrder;
@Cacheable
注解的属性多达9个,好在我们日常使用时只需要配置两个常用的就可以了。其中value
和cacheNames
互为别名关系,表示当前方法的结果会被缓存在哪个Cache
上,应用中通过cacheName
来对Cache
进行隔离,每个cacheName
对应一个Cache
实现。value
和cacheNames
可以是一个数组,绑定多个Cache
。
而另一个重要属性key
,用来指定缓存方法的返回结果时对应的key
,这个属性支持使用SpringEL
表达式。通常情况下,我们可以使用下面几种方式作为key
:
#参数名
#参数对象.属性名
#p参数对应下标
在上面的代码中,我们看到添加了@Cacheable
注解后,在代码中只需要保留原有的业务处理逻辑和操作Redis
部分的代码即可,Caffeine
部分的缓存就交给spring处理了。
下面,我们再来改造一下更新方法,同样,使用@CachePut
注解后移除掉手动更新Cache
的操作:
@CachePut(cacheNames = "order",key = "#order.id")
public Order updateOrder(Order order)
log.info("update order data");
orderMapper.updateById(order);
//修改 Redis
redisTemplate.opsForValue().set(CacheConstant.ORDER + order.getId(),
order, 120, TimeUnit.SECONDS);
return order;
注意,这里和V1版本的代码有一点区别,在之前的更新操作方法中,是没有返回值的void
类型,但是这里需要修改返回值的类型,否则会缓存一个空对象到缓存中对应的key
上。当下次执行查询操作时,会直接返回空对象给调用方,而不会执行方法中查询数据库或Redis
的操作。
最后,删除方法的改造就很简单了,使用@CacheEvict
注解,方法中只需要删除Redis
中的缓存即可:
@CacheEvict(cacheNames = "order",key = "#id")
public void deleteOrder(Long id)
log.info("delete order");
orderMapper.deleteById(id);
redisTemplate.delete(CacheConstant.ORDER + id);
可以看到,借助spring
中的CacheManager
和Cache
相关的注解,对V1版本的代码经过改进后,可以把全手动操作两级缓存的强入侵代码方式,改进为本地缓存交给spring
管理,Redis
缓存手动修改的半入侵方式。那么,还能进一步改造,使之成为对业务代码完全无入侵的方式吗?
模仿spring
通过注解管理缓存的方式,我们也可以选择自定义注解,然后在切面中处理缓存,从而将对业务代码的入侵降到最低。
首先定义一个注解,用于添加在需要操作缓存的方法上:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DoubleCache
String cacheName();
String key(); //支持springEl表达式
long l2TimeOut() default 120;
CacheType type() default CacheType.FULL;
我们使用cacheName + key
作为缓存的真正key
(仅存在一个Cache
中,不做CacheName
隔离),l2TimeOut
为可以设置的二级缓存Redis
的过期时间,type
是一个枚举类型的变量,表示操作缓存的类型,枚举类型定义如下:
public enum CacheType
FULL, //存取
PUT, //只存
DELETE //删除
因为要使key
支持springEl
表达式,所以需要写一个方法,使用表达式解析器解析参数:
public static String parse(String elString, TreeMap<String,Object> map)
elString=String.format("#%s",elString);
//创建表达式解析器
ExpressionParser parser = new SpelExpressionParser();
//通过evaluationContext.setVariable可以在上下文中设定变量。
EvaluationContext context = new StandardEvaluationContext();
map.entrySet().forEach(entry->
context.setVariable(entry.getKey(),entry.getValue())
);
//解析表达式
Expression expression = parser.parseExpression(elString, new TemplateParserContext());
//使用Expression.getValue()获取表达式的值,这里传入了Evaluation上下文
String value = expression.getValue(context, String.class);
return value;
参数中的elString
对应的就是注解中key
的值,map
是将原方法的参数封装后的结果。简单进行一下测试:
public void test()
String elString="#order.money";
String elString2="#user";
String elString3="#p0";
TreeMap<String,Object> map=new TreeMap<>();
Order order = new Order();
order.setId(111L);
order.setMoney(123D);
map.put("order",order);
map.put("user","Hydra");
String val = parse(elString, map);
String val2 = parse(elString2, map);
String val3 = parse(elString3, map);
System.out.println(val);
System.out.println(val2);
System.out.println(val3);
执行结果如下,可以看到支持按照参数名称、参数对象的属性名称读取,但是不支持按照参数下标读取,暂时留个小坑以后再处理。
123.0
Hydra
null
至于Cache
相关参数的配置,我们沿用V1版本中的配置即可。准备工作做完了,下面我们定义切面,在切面中操作Cache
来读写Caffeine
的缓存,操作RedisTemplate
读写Redis
缓存。
@Slf4j @Component @Aspect
@AllArgsConstructor
public class CacheAspect
private final Cache cache;
private final RedisTemplate redisTemplate;
@Pointcut("@annotation(com.cn.dc.annotation.DoubleCache)")
public void cacheAspect()
@Around("cacheAspect()")
public Object doAround(ProceedingJoinPoint point) throws Throwable
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
//拼接解析springEl表达式的map
String[] paramNames = signature.getParameterNames();
Object[] args = point.getArgs();
TreeMap<String, Object> treeMap = new TreeMap<>();
for (int i = 0; i < paramNames.length; i++)
treeMap.put(paramNames[i],args[i]);
DoubleCache annotation = method.getAnnotation(DoubleCache.class);
String elResult = ElParser.parse(annotation.key(), treeMap);
String realKey = annotation.cacheName() + CacheConstant.COLON + elResult;
//强制更新
if (annotation.type()== CacheType.PUT)
Object object = point.proceed();
redisTemplate.opsForValue().set(realKey, object,annotation.l2TimeOut(), TimeUnit.SECONDS);
cache.put(realKey, object);
return object;
//删除
else if (annotation.type()== CacheType.DELETE)
redisTemplate.delete(realKey);
cache.invalidate(realKey);
return point.proceed();
//读写,查询Caffeine
Object caffeineCache = cache.getIfPresent(realKey);
if (Objects.nonNull(caffeineCache))
log.info("get data from caffeine");
return caffeineCache;
//查询Redis
Object redisCache = redisTemplate.opsForValue().get(realKey);
if (Objects.nonNull(redisCache))
log.info("get data from redis");
cache.put(realKey, redisCache);
return redisCache;
log.info("get data from database");
Object object = point.proceed();
if (Objects.nonNull(object))
//写入Redis
redisTemplate.opsForValue().set(realKey, object,annotation.l2TimeOut(), TimeUnit.SECONDS);
//写入Caffeine
cache.put(realKey, object);
return object;
切面中主要做了下面几件工作:
key
的springEl
表达式,组装真正缓存的key
修改Service
层代码,代码中只保留原有业务代码,再添加上我们自定义的注解就可以了:
@DoubleCache(cacheName = "order", key = "#id",
type = CacheType.FULL)
public Order getOrderById(Long id)
Order myOrder = orderMapper.selectOne(new LambdaQueryWrapper<Order>()
.eq(Order::getId, id));
return myOrder;
@DoubleCache(cacheName = "order",key = "#order.id",
type = CacheType.PUT)
public Order updateOrder(Order order)
orderMapper.updateById(order);
return order;
@DoubleCache(cacheName = "order",key = "#id",
type = CacheType.DELETE)
public void deleteOrder(Long id)
orderMapper.deleteById(id);
到这里,基于切面操作缓存的改造就完成了,Service
的代码也瞬间清爽了很多,让我们可以继续专注于业务逻辑处理,而不用费心去操作两级缓存了。
本文按照对业务入侵的递减程度,依次介绍了三种管理两级缓存的方法。至于在项目中是否需要使用二级缓存,需要考虑自身业务情况,如果Redis这种远程缓存已经能够满足你的业务需求,那么就没有必要再使用本地缓存了。毕竟实际使用起来远没有那么简单,本文中只是介绍了最基础的使用,实际中的并发问题、事务的回滚问题都需要考虑,还需要思考什么数据适合放在一级缓存、什么数据适合放在二级缓存等等的其他问题。
本文的全部代码示例已传到了Hydra的Github上,后台回复缓存获取链接
那么,这次的分享就到这里,我是Hydra,下期见。
公众号后台回复
"356"---领取100余本后端书籍
"面试"---领取大厂面试资料
"导图"---领取24张Java后端学习笔记导图
"架构"---领取29本java架构师电子书籍
"实战"---领取springboot实战项目
关注公众号
有趣、深入、直接
与你聊聊技术
觉得有用,一键四连吧~
基于内存和 Redis 的两级 Java 缓存框架
Java 两级缓存框架,可以让应用支持两级缓存框架 ehcache(Caffeine) + redis 。避免完全使用独立缓存系统所带来的网络IO开销问题
基于内存和 Redis 的两级 Java 缓存框架
J2Cache 第一级缓存使用内存(同时支持 Ehcache 2.x、Ehcache 3.x 和 Caffeine),第二级缓存使用 Redis(推荐)/Memcached 。由于大量的缓存读取会导致 L2 的网络成为整个系统的瓶颈,因此 L1 的目标是降低对 L2 的读取次数。该缓存框架主要用于集群环境中。单机也可使用,用于避免应用重启导致的缓存冷启动后对后端业务的冲击。
J2Cache 的两级缓存结构
L1:进程内缓存(caffeineehcache)
L2:Redis/Memcached 集中式缓存
数据读取
-
读取顺序 -> L1 -> L2 -> DB
-
数据更新
1 从数据库中读取最新数据,依次更新 L1 -> L2 ,发送广播清除某个缓存信息
2 接收到广播(手工清除缓存 & 一级缓存自动失效),从 L1 中清除指定的缓存信息
J2Cache 配置
配置文件位于 core/resources 目录下,包含三个文件:
-
j2cache.properties J2Cache 核心配置文件,可配置两级的缓存,Redis 服务器、连接池以及缓存广播的方式
-
caffeine.properties 如果一级缓存选用 Caffeine ,那么该文件用来配置缓存信息
-
ehcache.xml Ehcache 的配置文件,配置说明请参考 Ehcache 文档
-
ehcache3.xml Ehcache3 的配置文件,配置说明请参考 Ehcache 文档
-
network.xml JGroups 网络配置,如果使用 JGroups 组播的话需要这个文件,一般无需修改
实际使用过程需要将所需的配置文件复制到应用类路径中,如 WEB-INF/classes 目录。
J2Cache 运行时所需 jar 包请查看 core/pom.xml
测试方法
-
安装 Redis
-
git clone https://gitee.com/ld/J2Cache
-
修改
core/resource/j2cache.properties
配置使用已安装的 Redis 服务器 -
在命令行中执行
mvn package -DskipTests=true
进行项目编译 -
打开多个命令行窗口,同时运行
runtest.sh
-
在 > 提示符后输入 help 查看命令,并进行测试
使用方法
J2Cache 默认使用 Caffeine 作为一级缓存,使用 Redis 作为二级缓存。你还可以选择 Ehcache2 和 Ehcache3 作为一级缓存。
准备工作
-
安装 Redis
-
新建一个基于 Maven 的 Java 项目
一. 引用 Maven
<dependency> <groupId>net.oschina.j2cache</groupId> <artifactId>j2cache-core</artifactId> <version>xxxxx</version> </dependency>
二. 准备配置
拷贝 j2cache.properties
和 caffeine.properties
到你项目的源码目录,并确保这些文件会被编译到项目的 classpath 中。如果你选择了 ehcache 作为一级缓存,需要拷贝ehcache.xml
或者 ehcache3.xml
到源码目录(后者对应的是 Ehcache 3.x 版本),这些配置文件的模板可以从 https://gitee.com/ld/J2Cache/tree/master/core/resources 这里获取。
使用你喜欢的文本编辑器打开 j2cache.properties
并找到 redis.hosts
项,将其信息改成你的 Redis 服务器所在的地址和端口。
我们建议缓存在使用之前都需要预先设定好缓存大小及有效时间,使用文本编辑器打开 caffeine.properties 进行缓存配置,配置方法请参考文件中的注释内容。
例如:default = 1000,30m #定义缓存名 default ,对象大小 1000,缓存数据有效时间 30 分钟。你可以定义多个不同名称的缓存。
三. 编写代码
Test.java public static void main(String[] args) { CacheChannel cache = J2Cache.getChannel(); //缓存操作 cache.set("default", "1", "Hello J2Cache"); System.out.println(cache.get("default", "1")); cache.evict("default", "1"); System.out.println(cache.get("default", "1")); cache.close(); }
编译并运行查看结果,更多的用法请参考 CacheChannel.java 接口的方法。
请注意 cache.close() 方法只需在程序退出时调用。
四. 动态构建 J2Cache 实例
J2CacheConfig config = new J2CacheConfig(); //填充 config 变量所需的配置信息 J2CacheBuilder builder = J2CacheBuilder.init(config); CacheChannel channel = builder.getChannel(); //进行缓存的操作 channel.close();
五. 集群测试
为了方便测试集群模式下 J2Cache 的运行,我们提供了一个命令行小程序,请参考此页面前面的 “测试方法”。
常见问题
-
J2Cache 的使用场景是什么?
首先你的应用是运行在集群环境,使用 J2Cache 可以有效降低节点间的数据传输量;其次单节点使用 J2Cache 可以避免应用重启后对后端业务系统的冲击 -
为什么不能在程序中设置缓存的有效期
在程序中定义缓存数据的有效期会导致缓存不可控,一旦数据出问题无从查起,因此 J2Cache 的所有缓存的有效期都必须在一级缓存
的配置中预设好再使用 -
如何使用 JGroups 组播方式(无法在云主机中使用)
首先修改j2cache.properties
中的j2cache.broadcast
值为jgroups
,然后在 maven 中引入<dependency> <groupId>org.jgroups</groupId> <artifactId>jgroups</artifactId> <version>3.6.13.Final</version> </dependency>
-
如何使用 ehcache 作为一级缓存
首先修改j2cache.properties
中的j2cache.L1.provider_class
为 ehcache 或者 ehcache3,然后拷贝 ehcache.xml 或者 ehcache3.xml 到类路径,并配置好缓存,需要在项目中引入对 ehcache 的支持:<dependency><!-- Ehcache 2.x //--> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.4</version> </dependency> <dependency><!-- Ehcache 3.x //--> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> <version>3.4.0</version> </dependency>
-
如何使用 RabbitMQ 作为消息通知
首先修改j2cache.properties
中的j2cache.broadcast
为 rabbitmq,然后在 j2cache.properties 中配置 rabbitmq.xxx 相关信息。需要在项目中引入对 rabbitmq 的支持:
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.3.0</version> </dependency>
-
如何使用 RocketMQ 作为消息通知
首先修改j2cache.properties
中的j2cache.broadcast
为 rocketmq,然后在 j2cache.properties 中配置 rocketmq.xxx 相关信息。需要在项目中引入对 rabbitmq 的支持:
<dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-client</artifactId> <version>4.3.0</version> <scope>provided</scope> </dependency>
-
如何使用 memcached 作为二级缓存
首先修改j2cache.properties
中的j2cache.L2.provider_class
为 memcached,然后在 j2cache.properties 中配置 memcached.xxx 相关信息。需要在项目中引入对 memcached 的支持:
<dependency> <groupId>com.googlecode.xmemcached</groupId> <artifactId>xmemcached</artifactId> <version>2.4.5</version> </dependency>
-
为什么 J2Cache 初始化时,连接本机的 Redis 非常慢,要 5 秒以上?
如果出现这种情况,请在系统 hosts 里配置机器名和IP地址的对应关系,例如:
127.0.0.1 localhost 127.0.0.1 winter-notebook.local ::1 localhost ::1 winter-notebook.local
-
使用何种 Redis 的存储模式最佳?generic 还是 hash ?
我们推荐使用 generic 存储模式,这也是 J2Cache 默认的存储模式,hash 模式最大的问题是无法单独对 key 进行 expire 设置。
以上是关于Redis+Caffeine两级缓存,让访问速度纵享丝滑的主要内容,如果未能解决你的问题,请参考以下文章