前言
对于JCS研究仍然需要回到过去redis的场景。主要是redis大量数据可以作为分布式缓存集中在内存中。但是,过程和redis通信最终是过程之间的通信,所有数据都需要序列化和反序列化。在高频访问场景中,中间的费用实际上非常大。最简单的就是我在顶级的时候参加的性能测试。当时单压登录时,瓶颈就显示了redis反序列化过程更准确。 无论压力测试方法是否合理,这个瓶颈在很多情况下都不容忽视,比如会话验证。无论是否需要会话验证,每个接口都需要遵循相关的逻辑。如果逻辑中使用的某些配置值(例如不需要验证)uri有哪些)在redis其实这个费用挺大的,平时不显,压力大了就显了。当时我用了一个HashMap临时缓存。结果有两个问题:
- 有内存泄漏
- 缓存不能定期移除
事实上,我不知道过程中有缓存框架。这也是前段时间刚刚遇到的。最近忙了一段时间回来,发现还没整理好,就先用了。 当时选型还是选了很久,现在时间过得有点久了,细节记不清了。总之,我终于选择了JCS。当时仔细对比过Spring的cache机制,发现它不是缓存,而是一组方便我们进行缓存操作的类别spring和核心。我们暂时不在乎这里,先说吧。JCS怎么用吧。
Common JCS官网地址
目标
我对JCS定位是过程中的缓存,基本目标是将其纳入我未来建设项目的工具集中。这里有几个基本功能需要实现,即本研究的目标是:
- 缓存数据的基本操作
- 管理缓存池
- 多过程之间的缓存同步
引入
官网的Getting Started中,使用jar引入包的形式jcs3.这不符合我常用的项目依赖管理模式。maven介绍方法如下:
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-jcs3-jcache</artifactId> <version>3.0</version> </dependency>
核心概念
在使用jcs在此之前,我们应该首先了解它的一些核心概念,否则以后的许多配置和操作将无法讨论。
元素elements
元素,是jcs最小管理单元,即缓存数据对象。每个元素都可以独立设置。
区域regions
jcs使用该区域来管理元素分区。这样,我们就可以使用这种配置的一部分元素,而另一部分元素则使用另一种配置。
辅助功能auxiliaries
对于核心功能以外的扩展功能,如使用jdbc持久缓存、多过程同步等,详见官网相关章节。
配置
在官方网站的介绍中,我们需要配置根目录resource目录下创建一个cache.ccf文件用于配置缓存。即使这个文件什么都没有,缓存也可以使用。然而,当我使用缓存时,我可以控制它的记忆需求,我们仍然需要知道它是如何配置的。
总体配置规则
一般来说,缓存包括三种配置:
- 默认及系统配置
- 指定区域配置
- 辅助缓存定义
以下是各部分配置结构的简配置结构,后面详细整理具体配置内容。
默认配置
在使用JCS使用任何未指定配置的区域default区域配置。以下是正确的default区域的一点样例配置。
jcs.default=DC,RFailover jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes jcs.default.cacheattributes.MaxObjects=1000
最重要的是jcs.default=DC,RFailover
。这将告诉我们使用了什么辅助缓存。然而,这些辅助缓存将在后面独立定义。我们可以根据需要添加任何数量的辅助缓存,并用英文逗号将其分开。远程和水平辅助缓存可能会发生冲突,需要注意。
区域配置
以下是区域配置的示例
jcs.region.testCache=DC,RFailover jcs.region.testCache.cacheattributes= org.apache.commons.jcs3.engine.CompositeCacheAttributes jcs.region.testCache.cacheattributes.MaxObjects=1000
我们注意到默认配置是基于jcs.default以上配置为开头jcs.region.testCache开头testCache该区域的配置default对应,jcs.region.testCache=DC,RFailover也是辅助缓存的配置。
辅助缓存配置
以下是两个辅助缓存配置的例子
jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.DiskCacheFactory jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.DiskCacheAttributes jcs.auxiliary.DC.attributes.DiskPath=c:/dev/cache/raf jcs.auxiliary.RFailover=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory jcs.auxiliary.RFailover.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes jcs.auxiliary.RFailover.attributes.RemoteTypeName=LOCAL jcs.auxiliary.RFailover.attributes.FailoverServers=localhost:1102,localhost:1101
以上定义了元素和区域配置中使用的两个辅助缓存。我们可以看到它们配置的开始是jcs.auxiliary.DC
和jcs.auxiliary.RFailover
辅助缓存似乎主要用于远程同步和本地持久化。
具体配置参数
官方网站上只有配置参数的例子,没有详细的配置参数说明。然而,它所有的配置都是特定的类别,所以我们可以去类别查看具体的配置说明,以及可以配置的参数。
org.apache.commons.jcs3.engine.CompositeCacheAttributes
在default中的jcs.default.cacheattributes属性使用此类配置。该类别定义了缓存区域的默认配置。如果根本没有在配置文件中进行配置,则该类别中的硬编码将用于配置缓存区域。属性不是特别多。让我列出它:
- useLateral:默认值true。横向缓存是否允许
- useRemote:默认值true。是否允许远程缓存
- useMemoryShrinker:默认值false。内存收缩线程是否运行
- maxObjs:默认值100。允许同时存在的最大缓存对象。需要注意的是,这里必须限制最大数量。
- maxMemoryIdleTimeSeconds:默认值60*120.默认缓存最大闲置秒。该配置仅在启用收缩后有效,如果闲置超时,将收缩并储存在磁盘上。
- shrinkerIntervalSeconds:默认值30。默认收缩间隔秒数
- maxSpoolPerRun:默认值2。每轮存储到磁盘的内存块数。
- cacheName:区域名称,无默认值
- memoryCacheName:实现缓存类名称,无默认值
- diskUsagePattern:默认情况下,磁盘使用模式DiskUsagePattern.SWAP
- spoolChunkSize:默认值-1。存储在磁盘中的最大轮数。
org.apache.commons.jcs3.engine.ElementAtributes
元素配置
- IS_SPOOL:默认值true。该元素是否可以被存储到磁盘
- IS_LATERAL:默认值true。该元素是否可被横向扩展。
- IS_REMOTE:默认值true。该元素是否可以被发送到远程缓存
- IS_ETERNAL:默认值true。该元素是否永恒。该配置为true时,可以绕开最大生命周期和最大闲置时间的限制。
- maxLife:默认值-1。最大生命时长(秒数),-1为永不过期。
- maxIdleTime:默认值-1。入口(entry)可以被最大闲置的时间。
- size:缓存的字节数,默认为0,必须被手动设置。
- createTime:创建时间,用来控制最大生命时长的。
- lastAccessTime:最后一次访问时间,用来控制最大闲置时间的
使用
首先获取cache对象
CacheAccess<String, String> cache = JCS.getInstance( "default" );
泛型里指定的就是缓存的key和value的类型。getInstance中传入的是region的名称,没有配置的名称也是可以传的,会按照默认配置返回。接下来我们就可以拿着这个对象对缓存进行操作了。
下面是一段典型的对缓存赋值的操作。
try
{
cache.put(key,value);
}
catch ( CacheException e )
{
logger.error(String.format( "Problem putting value %s in the cache, for key %s%n%s",
value, key, e.getMessage() ) );
throw e;
}
可以看到,操作异常是可以使用缓存捕捉的。 常用的操作也就put,get和remove,具体就不详细说明了,很简单。 这样日常使用也就够了。
一些进阶的使用
上面的操作,作为使用缓存进行增删改查已经没有什么问题了。但是,我们有些时候可能会需要使用缓存做一些进阶的事情,就需要对其进行更加深入的控制和监听了。
订阅事件
可用事件
在jcs3中,可以订阅缓存元素的一些事件,方便我们在元素相关的生命周期,或者发生相应的事情时获得通知,及时处理。
- ELEMENT_EVENT_EXCEEDED_MAXLIFE_BACKGROUND:该元件超过了其最大寿命。这是在后台清理中检测到的。 ELEMENT_EVENT_EXCEEDED_MAXLIFE_ONREQUEST:该元件超过了其最大寿命。这是根据请求检测到的。 ELEMENT_EVENT_EXCEEDED_IDLETIME_BACKGROUND:元素已超出其最大空闲状态。这是在后台清理中检测到的。 ELEMENT_EVENT_EXCEEDED_IDLETIME_ONREQUEST:元素已超出其最大空闲时间。这是根据请求检测到的。 ELEMENT_EVENT_SPOOLED_DISK_AVAILABLE:该元素已推出内存存储区,有一个磁盘存储可用于该区域,并且该元素被标记为可假脱机。 ELEMENT_EVENT_SPOOLED_DISK_NOT_AVAILABLE:该元素已推出内存存储区,并且没有可用于该区域的磁盘存储区。 ELEMENT_EVENT_SPOOLED_NOT_ALLOWED:该元素已推出内存存储区,该区域有一个磁盘存储区可用,但该元素被标记为不可假脱机。
编码调用
具体的事件处理需要实现接口:org.apache.commons.jcs3.engine.control.event.behavior.IElementEventHandler 事件处理的实例可以直接添加到元素里,也可以放到区域的默认配置里,但是好像只能通过代码进行添加。以下是官网的一些样例代码,由于我这里还没有实际的业务点使用,就没有对其进行测试了。记录在这里,以后就不用老去翻官网了。 通过元素添加
CacheAccess<String, String> jcs = JCS.getInstance( "myregion" );
. . .
MyEventHandler meh = new MyEventHandler();
// jcs.getDefaultElementAttributes returns a copy not a reference
IElementAttributes attributes = jcs.getDefaultElementAttributes();
attributes.addElementEventHandler( meh );
jcs.put( "key", "data", attributes );
通过区域的默认配置进行设置
CacheAccess<String, String> jcs = JCS.getInstance( "myregion" );
. . .
MyEventHandler meh = new MyEventHandler();
// this should add the event handler to all items as
//they are created.
// jcs.getDefaultElementAttributes returns a copy not a reference
IElementAttributes attributes = jcs.getDefaultElementAttributes();
attributes.addElementEventHandler( meh );
jcs.setDefaultElementAttributes( attributes );
多进程同步
在我的实际使用场景中,我希望将数据字典以及一些常用的配置放到缓存里,在使用的时候直接通过缓存加载而不是联表查询,以降低表之间的耦合,减少sql所承载的职责。但是,数据字典中数据的维护是在后台的,使用的时候大多实在业务进程,它们一般是以springboot服务的形式承载的。所以,我们就需要后台服务进程可以更新业务进程的缓存。 在jcs3中,有三种方式可以解决这个问题:
- remote cache:远程缓存。其实际的模型是缓存的Server/Client模型这个东西。也就是,缓存只有一份,客户端都去服务端去读取缓存。换句话说,在这中间还是存在远程传输和序列化反序列化的开销的。
- JGroup:在官方文档中说jcs支持这种方式对缓存进行扩展,但是它的速度比TCP横向扩展要慢很多,并不推荐使用。
- 横向扩展:横向扩展是通过TCP建立通道,维护不同进程中的缓存更新。相互之间的发现方式有TCP和UDP。我比较倾向于使用这种方式。
由于这部分涉及一些具体业务和两个项目,等我写好了之后以新的贴子进行更新吧。本文就到这里了。