apache common JCS的使用

冯德宇
2023-12-01

前言

对于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.DCjcs.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.ElementAttributes

元素配置

  • 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。我比较倾向于使用这种方式。

由于这部分涉及一些具体业务和两个项目,等我写好了之后以新的贴子进行更新吧。本文就到这里了。

 类似资料: