分布式的内存对象缓存系统 Sapphire

jopen 12年前

    Sapphire是一个高并发、高缓存吞吐性、高性能的分布式的内存对象缓存系统,其具有简单易学、方便实用等特点。它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。简单的说就是将数据源中的数据临时存储于内存中,然后从内存中读取,从而大大提高读取速度。

 

Sapphire目前最新版本为1.1.7-beta,主要特性包含:

1.敏捷快速;

2.多种缓存管理容器实现;

3.多种缓存策略(LRU、LUF、RDM);

4.支持缓存注解服务驱动(Annotation方式直接缓存方法);

5.支持缓存持久化及加载虚拟机运行期数据;

6.单个缓存最大缓存容量为1gByte;

7.支持缓存容量单位设置(byte、kByte、mByte、gByte)

8.支持TCP单播集群、P2P多播、组播集群、RMI多播集群; 

 

1.Sapphire Framework包目录结构:
org.sapphire.cache:sapphire基础包;
org.sapphire.cache.annotation:sapphire注解服务包;
org.sapphire.cache.bean.config:sapphire配置DTO包;
org.sapphire.cache.cleanup:sapphire缓存回收包;
org.sapphire.cache.exception:sapphire异常包;
org.sapphire.cache.log:sapphire:日志记录包;
org.sapphire.cache.distributed.tcp:基于tcp分布式缓存包;
org.sapphire.cache.distributed.rmi:基于rmi分布式缓存包;
org.sapphire.cache.distributed.p2p:基于p2p分布式缓存包;

2.Sapphire Framework类型结构:
基于org.sapphire.cache包:
-- Cache
-- CacheManager
    -- CacheManagerImpl
        -- SapphireCacheManager
        -- CacheManagerForSafety
-- Element
-- CacheSerializable
-- AnalyticSapphireXml
-- LoadOverflowDate
-- OverflowDate
-- PersistenceCacheManager

基于org.sapphire.cache.annotation包:
-- CacheService
测试中未发布…

基于org.sapphire.cache.bean.config包:
-- CacheAnnotationBean
-- CacheBean
-- CacheDataBuringBean
-- DiskStoreBean
-- DistributedCacheBean
-- TcpUnicastBean

基于org.sapphire.cache.cleanup包:
-- CacheCleanupPolicy
    -- CacheCleanupPolicyImpl
        -- CacheForLFU
        -- CacheForLRU
        -- CacheForRDM

基于org.sapphire.cache.exception包:
-- CacheException
    -- CacheFileNotFoundException
    -- CacheServiceException
    -- CacheElementsException
    -- CacheDistributedException
        -- TcpCacheDistributedException
-- CacheRangeError
-- OutOfCacheError

基于org.sapphire.cache.log包:
-- CacheLog
    -- CacheLogImpl

基于org.sapphire.cache.distributed.tcp包:
-- TcpDistributedCacheConnection
-- TcpDistributedCacheManagerUnicast
-- TcpDistributedCacheManagerFactory
-- TcpDistributedCachePeer
-- TcpDistributedCacheReplicate

基于org.sapphire.cache.distributed.rmi包:
测试中未发布…

基于org.sapphire.cache.distributed.p2p包:
测试中未发布…

3.使用Sapphire构建基础缓存架构
在使用Sapphire为您完成缓存架构之前,应该先建立一个属于Sapphire的配置文件。配置文件的格式以XML的方式为主。
配置文件需要导入Sapphire的DTD标签:
<!DOCTYPE sapphire PUBLIC
"-//Sapphire Cache//DTD Sapphire Configuration 1.0//CN"
"sapphire-cache-1.1.dtd"

Sapphire的缺省配置如下:
<sapphire>
<!-- 缓存注解服务驱动 -->
<service:annotation-driven auto="true" />
<!-- 缓存持久化全局配置 -->
<diskStore path="java.io.tmpdir" diskEternal="true"
timeToRemoveSeconds="5" />
<!-- 缺省缓存配置 -->
<cache name="defaultCache" eternal="false" maxElementsInSize="100"
maxCacheInMemory="1" capacityUnit="kByte" overflowToDisk="true"
diskPersistent="false" timeToLiveSeconds="1" cacheCleanupPolicy="LRU" />
</sapphire>

Sapphire配置文件各个元素及属性描述:
<!-- service:annotation-driven -->
<!-- auto: 自动缓存需指定方法 -->
<!-- diskStore -->
<!-- path: 缓存持久化临时目录 -->
<!-- diskEternal: 缓存持久化是否永久有效 -->
<!-- timeToRemoveSeconds: 缓存持久化回收时间/秒 -->
<!-- cache -->
<!-- name: 缓存名称 -->
<!-- eternal: 缓存是否永久有效 -->
<!-- maxElementsInSize: 缓存最大元素数量 -->
<!-- maxCacheInMemory: 缓存最大容量 -->
<!-- capacityUnit: 缓存容量单位(byte|kByte|mByte|gByte) -->
<!-- overflowToDisk: 缓存持久化 -->
<!-- diskPersistent: 加载虚拟机重启期数据 -->
<!-- timeToLiveSeconds: 设置缓存中元素在失效前允许存活时间/秒 -->
<!-- cacheCleanupPolicy: 当缓存中元素失效后,所采用的缓存回收策略 (LRU(最少使用, Least Recently  Used)、LFU(较少使用, Least Frequently Used)、 RDM(随机, Random)) -->

注意:
在使用Sapphire为您完成缓存架构之前,需要将log4j.jar导入至您的项目中。

基于Sapphire的第一个缓存应用程序:
/* 初始化Sapphire容器 */
CacheManager cacheManager = new SapphireCacheManager();

/* 获取缓存实例 */
Cache cache = cacheManager.getCache("defaultCache");

/* 缓存数据元素 */
cache.put("key", "value");
System.out.println(cache.get("key"));

在您的程序中使用Sapphire来帮助您搭建缓存架构是一件极其简单且轻松的事情。首先我们只需初始化Sapphire缓存管理容器(上述程序示例中使用到的Sapphire管理容器为SapphireCacheManager),该容器会负责初始化一系列的缓存前期准备工作。紧接着我们需要通过 SapphireCacheManager的getCache(String cacheName)方法取得缓存实例并得到一个具体的Cache对象,最后通过Cache对象的put(Object key, Object value)方法添加您所需缓存的数据即可。
CacheManager为Sapphire的顶层缓存管理容器,其派生类有:
-- CacheManagerImpl
    -- SapphireCacheManager
    -- CacheManagerForSafety

提供给您使用的主要是SapphireCacheManager与CacheManagerForSafety这2个缓存管理实现。其中 SapphireCacheManager为非线程安全,而CacheManagerForSafety则基于线程安全模型。就性能比较而言 SapphireCacheManager显得更为高效,CacheManagerForSafety则更为安全,当然您可以根据您的具体需求选择合适您的缓存管理实现。

CacheManager提供的常用方法如下:
getCache(String cacheName) throws Exception: Cache 返回缓存实例
getCacheInMemoryAll() throws Exception:long 获取所有缓存已用缓存容量

当然不管您是使用任何一种缓存管理容器,Sapphire都为您提供有2种构造实现。假设您在项目中使用SapphireCacheManager作为缓存管理实现,您可以根据您具体的配置需求来达到满足您具体的实现。通常情况下,我们建议您使用缺省构造,因为这样不仅仅可以为您减少一定的代码量,且显得更为直观,但使用带参构造则会使您的应用更为灵活和高效。
使用SapphireCacheManager作为管理容器2种实现:
/* 第一种初始化Sapphire容器方式 */
CacheManager cacheManager = new SapphireCacheManager();

或者:

/* 第二种初始化Sapphire容器方式 */
CacheManager cacheManager = new SapphireCacheManager("sapphire.xml");

使用CacheManagerForSafety作为管理容器2种实现:
/* 第一种初始化Sapphire容器方式 */
CacheManager cacheManager = new CacheManagerForSafety();

或者:

/* 第二种初始化Sapphire容器方式 */
CacheManager cacheManager = new CacheManagerForSafety("sapphire.xml");


注意:
如果使用第一种缓存构造实现,Sapphire的缓存管理容器会自动在工程目录查找名为sapphire.xml的缓存配置文件。

4.Cache类型
Sapphire 为您提供的Cache类型为具体的缓存对象,该类型主要负责为您缓存您所需缓存的数据,以及管理这些缓存进Sapphire管理容器中的数据。Cache 实现自JDK中java.util.concurrent包的ConcurrentHashMap高并发容器,这样便能够为您带来更高效的缓存吞吐性。

Cache提供的常用方法如下:
put(Object key, Object value):Object 添加缓存数据
get(Object key):Object 获取缓存数据
remove(Object key):Object 清除指定缓存数据
clear():void 清除所有缓存数据
getElementsInSize():long 获取当前缓存已用元素数量
getSurplusElementsInSize(): long 获取当前缓存剩余元素数量
getMaxElementsInSize(): long 获取当前缓存最大元素数量
getCacheInMemory(): long 获取当前缓存已用缓存容量
getMaxCacheInMemory(): long 获取当前缓存最大缓存容量
getSurplusCacheInMemory():long 获取当前缓存剩余缓存容量
cacheUseRecords(Object key): void 计算缓存使用记录
getCacheUseRecords():HashMap 获取缓存使用记录
getMaxCacheInMemoryForByte(long maxCacheInMemory,
String capacityUnit): long 计算当前缓存的最大字节缓存容量

5.使用Sapphire缓存缺省数据类型
缺省情况下Sapphire可以为您缓存的数据类型为如下3类:
基本数据类型;
数组类型;
String类型;

使用Sapphire缓存缺省数据类型实现:
/* 缓存基本数据类型 */
cache.put("byte", 1);
cache.put("short", 10);
cache.put("int", 100);
cache.put("long", 1000L);
cache.put("float", 1.5);
cache.put("double", 10.5D);
cache.put("char", 'A');
cache.put("boolean", true);

/* 缓存数组数据类型 */
cache.put("byte[]", new byte[100]);

/* 缓存String数据类型 */
cache.put("String", "Sapphire Cache");

6.使用Sapphire缓存复合数据类型
缺省情况下Sapphire并没有提供对复合数据类型的支持,但您可以将您需要缓存的复合对象实现Sapphire为您提供的 CacheSerializable接口(该接口为标记接口,无需任何实现)。一旦您的复合对象实现了该接口,Sapphire便会允许缓存您所指定的复合对象,除了可以实现CacheSerializable接口,Sapphire仍然允许您实现JDK原生的Serializable接口。
至于复合数据类型为什么需要实现CacheSerializable接口或者实现Serializable接口Sapphire才允许将其缓存呢?其实 Sapphire在对您的数据进行缓存之前需要将所有数据进行序列化处理,以便计算当前所需缓存的数据所占缓存容量的内存大小。
使用Sapphire缓存复合数据类型实现:
class SaveObject implements CacheSerializable
{
//...
}

/* 初始化Sapphire容器方式 */
CacheManager cacheManager = new SapphireCacheManager();

/* 获取缓存实例 */
Cache cache = cacheManager.getCache("defaultCache");

/* 缓存复合数据类型 */
cache.put("object", new SaveObject());
System.out.println(cache.get("object") instanceof CacheSerializable);

注意:
如果您在使用Sapphire为您缓存复合对象时并没有将所需缓存的数据实现CacheSerializable接口或者Serializable接口,Sapphire将会抛出java.io.NotSerializableException异常信息。

7.启动缓存注解服务驱动
通过Sapphire的配置文件我们可以观察到<service:annotation-driven>标签。该标签主要用作于开启缓存注解服务驱动,其中包含有一个auto属性,该属性所允许的参数范围为“true”与“false”。如果您将auto属性设置为“true”,则意味着您将成功开启Sapphire的缓存注解服务驱动服务,否则意味着该服务的状态为关闭状态。
至于开启Sapphire的缓存注解服务驱动有何用处,想必这是您最为关心的问题。其实不难发现在很多复杂应用的情况下,我们经常需要缓存具体的方法返回结果(比如服务层与持久层),面对这种情况虽然我们可以使用Cache对象的put方法进行数据缓存,但这并不灵活。所以Sapphire将为您提供了一个基于注解方式的方法返回值缓存技术,这便是刚才我们所提到的缓存注解服务。
位于org.sapphire.cache.annotation包下的@CacheService类型正是我们即将使用到的方法返回值缓存技术的实现。
缓存注解服务驱动实例:
此处省略导包过程…
public class SapphireCacheTest extends TestCase
{
/**
* @param args
*/
public static void main(String[] args) throws Exception
{
/* 初始化Sapphire容器方式 */
CacheManager cacheManager = new SapphireCacheManager();

/* 获取缓存实例 */
Cache cache = cacheManager.getCache("defaultCache");
Element element1 = new Element(SaveObject.class.getName(),
"testService1", cache);
element1.put(26);
System.out.println("age: " + cache.get("age"));
Element element2 = new Element(SaveObject.class.getName(),
"testService2", cache);
element2.put(1, "JohnGao");
System.out.println("UserBean: " + cache.get("info"));
}
}

此处省略导包过程…
public class SaveObject implements CacheSerializable
{
@CacheService(cacheElementKey = "age")
public int testService1(int age)
{
return age;
}

@CacheService(cacheElementKey = "info")
public Object testService2(int userId, String userName)
{
class InfoBean implements CacheSerializable
{
int userId;
String userName;
}
InfoBean info = new InfoBean();
info.userId = userId;
info.userName = userName;
return info;
}
}

在您使用Sapphire为您提供的缓存注解服务器时,您务必先找到Sapphire的配置文件中的<service:annotation- driven>标签,并将该标签的auto属性设置为“true”则意味着注解服务成功开启。接着您便可以通过 @CacheService(cacheElementKey = "age")的注解方式对所需缓存的方法进行标注。这里需要提醒一下@CacheService中需要设置一个cacheElementKey的属性,该属性所代表的是缓存Key。
当您成功的在方法前添加@CacheService标注后,仍然还需要使用到一个类型,那便是Element类型。该类型作为Sapphire的缓存元素类型,在使用缓存注解服务的时候您必须使用它才能够对您的方法返回值进行动态缓存。
Element提供的常用方法如下:
Element(String serviceClass, String serviceMethod,
Cache cache) 构造函数(缓存类型名称、缓存方法名称、Cache实例)
put(Object... params):void 添加服务参数
cacheParam(Object... params):void 缓存服务参数

注意:
如果您并没有开启Sapphire的缓存注解服务便使用时,Sapphire将会抛出org.sapphire.cache.
exception.CacheServiceException异常。

8.设置缓存所能存储的最大元素数量
Sapphire在缓存管理上为您提供了更为灵活的缓存管理方式,但首先您应该先了解Sapphire到底是如何对缓存进行管理的。
通过Sapphire的配置文件我们可以在<cache/>标签中找到一个名为maxElementsInSize的属性,该属性用于定义单个缓存所允许存放的最大缓存元素数,假设您将maxElementsInSize属性设置为10,那么您的缓存实例将只允许缓存10个数据元素。
一旦您所定义的maxElementsInSize溢出,Sapphire将会抛org.sapphire.cache.exception.
CacheElementsException异常。

9.设置缓存所能存储的最大缓存容量
Sapphire 作为一个高效的缓存Framework,单个缓存允许您存储高达1Gbyte的缓存数据。当然Sapphire的缓存容量与实际的物理内存与VM内存精密相关,也就是说如果您希望将Sapphire的单个缓存容量设置为1GByte,那么你需要观察您的实际物理内存及VM内存是否允许,否则 Sapphire将会抛出java.lang.OutOfMemoryError异常。
通过Sapphire的配置文件我们可以在<cache/>标签中找到2个名为maxCacheInMemory和capacityUnit的属性,其中 maxCacheInMemory属性用于设置您所定义的缓存容量大小,而maxCacheInMemory则用于定义您的缓存容量单位,假设 maxCacheInMemory定义为“10”,capacityUnit定义为“mByte”,那么也就是说您则定义了一个缓存容量为10mByte 的单个缓存实例。一旦您所定义的maxCacheInMemory溢出,Sapphire将会抛出 org.sapphire.cache.exception.
OutOfCacheError异常。
在程序中您可以通过Cache类型提供的一些缓存容量及元素检测方法来观测您具体的缓存容量开销。
观测缓存容量开销实例:
/* 初始化Sapphire缓存管理容器 */
CacheManager cacheManager = new SapphireCacheManager();

/* 获取缓存实例 */
Cache cache = cacheManager.getCache("defaultCache");

/* 添加缓存元素 */
cache.put("key", new byte[100]);
System.out.println("最大缓存元素长度:" + cache.getMaxElementsInSize());
System.out.println("当前缓存元素长度:" + cache.getElementsInSize());
System.out.println("剩余缓存元素长度:" + cache.getSurplusElementsInSize());
System.out.println("最大缓存容量:" + cache.getMaxCacheInMemory());
System.out.println("当前缓存容量:" + cache.getCacheInMemory());
System.out.println("剩余缓存容量:" + cache.getSurplusCacheInMemory());

注意:
如果最大缓存元素长度<=当前缓存元素长度时,剩余缓存元素长度则为“-1”。如果当前缓存容量>最大缓存容量时,剩余缓存容量则为“-1”。

10.缓存持久化
在您使用Sapphire缓存数据时,极有可能会出现maxElementsInSize溢出及maxCacheInMemory溢出。但这些溢出的数据可能恰恰是较为重要且不希望丢失的数据,这个时候该怎么办呢?值得庆幸的是Sapphire为您带来了高效的缓存持久化技术,也就是说您可以通过 Sapphire的配置文件找到<cache/>标签的overflowToDisk属性,并设置为“true”时,那么一旦 maxElementsInSize或者maxCacheInMemory溢出时,其溢出的数据并不会直接丢失,Sapphire则将会为您将这一部分溢出的数据持久化于本地进行存储,以便于您继续使用。
如果您希望使用Sapphire为您提供的缓存持久化技术,那么您不仅仅需要将 Sapphire配置文件中得overflowToDisk属性设置为“true”,您还需要设置<diskStore>标签中得path、 diskEternal、timeToRemoveSeconds等3个属性。path属性缺省为“java.io.tmpdir”,也就是说一旦出现 maxElementsInSize或者maxCacheInMemory溢出时,Sapphire会将溢出数据缓存于操作系统的临时目录(不同的操作系统临时目录不同)中进行存储,存储格式为SAPPHIRE_CACHE.data。
除了path属性外,<diskStore>标签中的另外2个属性diskEternal和timeToRemoveSeconds则将满足您更多的需求。如果您希望您所持久化的缓存数据是永远有效的,则需要将diskEternal属性设置为“true”,这样一来持久化于本地的缓存将永不失效。如果您将其设置为“false”时,则意味着 timeToRemoveSeconds属性所定义的内容将影响到持久化缓存的失效周期,假设您将timeToRemoveSeconds属性定义为 “10”,当10秒以后,Sapphire的回收器将会对持久化缓存进行回收。

11.加载虚拟机重启期数据
当您成功将缓存进行持久化后,Sapphire为您提供了一种持久化读取机制来加载虚拟机重启期数据。当然您需要将Sapphire配置文件中的diskPersistent属性设置为“true”
这里有一点您需要稍加注意,如果您在Sapphire的配置文件中设置的maxElementsInSize或者maxCacheInMemory属性不再重启期数据的范围内,也就是说Sapphire仍然会将其认为溢出,导致重复缓存持久化。

12.缓存回收策略
Sapphire为您提供有3种缓存回收策略,分别为:LRU、LFU、RMD。
LRU(最少使用, Least Recently Used)将会根据您对缓存元素的实际使用来动态收回缓存元素,使用次数最少的缓存元素则优先被Sapphire回收器进行回收,当然您需要通过 Sapphire的配置文件找到<cache>标签并设置timeToLiveSeconds属性,该属性主要用于设置缓存的失效周期,单位为秒。假设您将timeToLiveSeconds属性设置为“10”,则意味着10秒以后Sapphire回收器将会按照 cacheCleanupPolicy属性所定义的回收策略对缓存进行动态回收。LFU和RMD等回收策略也是经常较为适用的,但一般来说我们推荐您使用 LRU缓存回收策略。

注意:
如果您在<cache>标签中将eternal属性设置为“true”时,则意味着您的缓存为永不失效,同样cacheCleanupPolicy则也就意味着失效。

13.分布式缓存——TCP集群配置模式
Sapphire为您提供了3种类型的分布式缓存机制,但Sapphire推荐您使用TCP集群为首选。因为该分布式缓存的实现方式为TCP单播机制,无论是安全性、正确性、稳定性,哪怕是效率上都是较为优秀的。
如果您希望使用TCP集群配置模式的方式来实现缓存共享,那么首先您需要在Sapphire的配置文件中添加TCP集群配置。
您可以使用Sapphire为您提供的<distributedCacheManagerFactory>标签,该标签中一共包含了3个属性,分别是:class、serverHost以及serverPort。其中class用于指定集群类型,serverHost属性用于指定集群地址,serverPort属性用于指定集群端口。
假设您已经成功配置TCP集群后,您还需在<cache>标签中添加<distributedCacheListener>子标签,该标签用于启动缓存复制监听,并包含一个名为replicateCache 的属性,如果您将其设置为“true”则意味着您允许缓存实例实现缓存共享,否则将意味着拒绝缓存共享。
TCP集群配置示例:
<!-- 缓存注解服务驱动 -->
<service:annotation-driven auto="true" />

<!-- 缓存持久化全局配置 -->
<diskStore path="java.io.tmpdir" diskEternal="false"
timeToRemoveSeconds="60" />

<!-- TCP集群配置模式 -->
<distributedCacheManagerFactory
class="org.sapphire.cache.distributed.tcp.TcpDistributedCacheManagerFactory" serverHost="127.0.0.1" serverPort="30051" />

<!-- 缺省缓存配置 -->
<cache name="defaultCache" eternal="false" maxElementsInSize="100"
maxCacheInMemory="1" capacityUnit="mByte" overflowToDisk="true"
diskPersistent="false" timeToLiveSeconds="60" cacheCleanupPolicy="LRU">
<!-- 启动缓存复制监听 -->
<distributedCacheListener replicateCache="true" />
</cache>

通过上述示例,您可以发现< distributedCacheManagerFactory >标签中的class属性其值为一个TcpDistributedCacheManagerFactory类型(位于 org.sapphire.cache.distributed.tcp包下)。该类型作用于搭建一个稳定且高性能的TCP分布式缓存服务器。
使用Sapphire来为您实现分布式缓存是一件及其简单的事情,只要您在Sapphire的配置文件中启用了TCP集群配置模式,并成功开启了缓存复制监听,那么就无需您在做任何操作,便可实现分布式缓存共享。接下来您可以通过一段简单的测试代码来实现Sapphire为您提供的基于TCP集群配置模式的分布式缓存。
分布式缓存示例:
/* 初始化Sapphire缓存管理容器 */
CacheManager cacheManager = new SapphireCacheManager();

/* 获取缓存实例 */
Cache cache = cacheManager.getCache("defaultCache");
while(true)
{
cache.put("cacheKey", "cacheValue");
System.out.println(cache.get("key"));
Thread.sleep(1000);
}

上述程序一旦运行后,Sapphire便会成功开启TCP缓存服务器,启动缓存服务,并成功的实现缓存共享。但这个时候您可能会觉得奇怪,因为您并没有使用客户端对TCP缓存服务器进行连接,为什么您却可以获取共享后的缓存?其实这是因为在缺省环境下Sapphire的TCP缓存服务器既是服务端又是客户端,能够自身实现缓存共享。
如果您的网络中有其余的节点需要共享缓存信息,那么您便可以将< distributedCacheManagerFactory >标签中的class属性的值更改为org.sapphire.cache.distributed.tcp. TcpDistributedCacheConnection即可,该类型为TCP集群连接类型。

注意:
如果程序中,您并没有成功启动TCP集群配置模式,而直接为class属性去配置TcpDistributedCacheConnection类型,这个时候Sapphire则会抛出org.sapphire.cache.exception.
TcpCacheDistributedException异常。
还有一点您需要注意,假设您程序中有多个缓存实例都需要实现缓存共享时,您可以在Sapphire配置文件中为需要实现缓存共享的缓存类型开启缓存复制监听即可。

项目主页:http://www.open-open.com/lib/view/home/1342138177062