编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

基于EhCache搭建分布式缓存服务(ehcache是分布式缓存吗)

wxchong 2024-08-31 04:08:18 开源技术 8 ℃ 0 评论

在实际开发中,缓存是一定要考虑的问题。这里的缓存是指对系统中大的且不经常修改的数据进行缓存,下次使用时不需要再从数据库中查询而是直接使用缓存中的数据即可,这样可以提高查询效率。比较成熟的缓存容器包括memcache或者redis,或者mongodb也可以做缓存容器。

如果我们的系统架构比较简单,使用上述的缓存容器我觉得有点小题大作,通常在一个小型系统中,多数程序员系统使用Map做缓存容器,因为key和value的概念比较容易理解,但使用map也会有不少问题,例如在高并发下的数据安全问题,在分布式架构中的缓存共享问题,以及数据的有效性以及容量问题等。今天我们要讲的是一款轻量级的缓存容器EhCahce。EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。

有人知道hibernate中的默认二级缓存不就是ehcache吗,是的,今天我们并不是讲解hibernate的缓存,而是就其缓存的实现ehcahce,单独拿出来讲解。相比于Map和其他缓存,ehcache有以下优点:

1、API易于使用,轻量级,依赖少。

2、缓存在内存和磁盘存储可以伸缩到数G。

3、动态、运行时缓存配置,存活时间、空闲时间、内存和磁盘存放缓存的最大数目都是可以在运行时修改的。

4、Ehcache提供了对JSR107 JCACHE API最完整的实现。

5、分布式缓存,支持插件开发。

6、Ehcache是第一个引入缓存数据持久化存储的开源Java缓存框架。

7、提供了许多对缓存事件发生后的处理机制:notifyElementRemoved/Put/Updated/Expired。

8、Ehcache的JMX功能是默认开启的,你可以监控和管理如下的MBean:

CacheManager、Cache、CacheConfiguration、CacheStatistics 。

等等。

当然,我们今天所讲解的ehcache主要还是讲解它的使用,不会过多的涉及上述的高级内容,但会讲解如何实现集群配置。

其实,对于缓存的实现可能没有太多可以讲解的,就好比map,就需要知道两个方法put和get即可,但是如果现在让我们基于ehcache来搭建一个分布式的缓存服务,用以存储系统中固定的或者后期生成的一些数据,我们该如何设计呢?

1、服务架构简介

在讲解之前我们需要考虑几个问题。系统中的缓存一般分两类,1、是系统中固定的数据,例如菜单,大数据;2、后期动态产生的数据,例如用户登录后要缓存其session等信息。不管是前者还是后者,我们缓存的都是对象,或者说都是有结构的数据,即我们事先已经设计好了我们要缓存的内容。所以总结下来就是:

第一,我是基于对象缓存的,即我缓存的对象不是json(当然你可以设置value为json),不是无结构的数据,而是有结构的数据体。比如user,我需要定义它的ID和name。第二我希望这个缓存服务可以使用多种容器,不一定非得用ehcache,有些场景我希望用mongodb缓存,所以我希望系统支持配置,可以让开发者自己选择缓存容器。先看下结构图:

2、需要考虑的问题

讲解之前,我们需要中间插一笔讲讲经常会被问的几个问题。我讲的不一定全,但都是我在设计中考虑的。

(1)key和value

我们把缓存作为一个服务的目的就是,让服务具有规范和约束,当缓存可以被用户随便使用的时候,用户的使用是个人的,是不被大家所熟知的,容易造成缓存数据的冗余或浪费,也不利于缓存内容的管理,而作为服务,我们有必要定义缓存的key和value,即标识和内容。举例子,我们想缓存座位信息,我们缓存的方式有N种,可以一个个的缓存,也可以按区缓存,也可以按楼缓存。如果我们不制约约束,但缓存的用户就会随便缓存,而使用的人并不知道怎么取,但如果我们可以指定缓存的key就是seat_id_xxx,按区域缓存就是bound_id_xxx,楼层缓存是floor_id_xxx,那使用的人就可以根据各自的key来取内容了。同时,缓存的内容我们也需要有一个约束,例如我们可以支持缓存字符串,list,数组等。

(2)缓存接口

有人会说,你使用ehcache或者redis,他们本身不就提供了接口了吗,这里的接口是什么意思。的确他们都提供了很好的接口,但有一个问题就是并不是每个人都熟悉这些接口,即使熟悉,这些接口也并不一定能完全适合我们的业务场景。所以我们有必要在他们的接口之上再封装一层,定义属于我们自己的业务接口。

(3)缓存实例

缓存实例的意思就好比一个房间,我们的容器里要指定不同的房间缓存不同的内容,seat_cache就缓存关于座位的,而user_cache就负责缓存用户。互不干涉。如果我们不指定,那就会造成数据混乱,设置会导致实例臃肿等问题。

(4)缓存工厂

为什么会有缓存工厂的概念,原因是因为实例。我们通常在一个缓存容器里的实例有且只有一个(一台服务器),例如user_cache的创建只允许被实例一次,然后不断的往里存储数据,如果其他人想往里面存,首先先获得这个实例,而不是自己创建,否则自己创建的实例有可能将把原实例覆盖,这里我们就需要提供一个工厂对外提供一个获得实例的接口以便保证实例的唯一性。

(5)缓存配置

至于缓存配置,其实就是对缓存服务的一些配置内容,例如,我想user使用ehcache进行缓存,而seat使用mongodb缓存。例如我希望ehcache的缓存时间为2个小时过期等,这些配置大多数他们都是支持的,少数是需要我们自己来写代码实现的。

3、实际案例

(1)缓存接口层定义

这里我们先做一个简单的缓存,我们要缓存的就是一个字符串,但key是由使用者指定的。首先我们需要指定这个实体的缓存接口。

接口很简单,事务层接口也一样。

(2)缓存接口层实现

我们的缓存事务使用spring管理,所以直接写了transactional注解即可。但有个问题就是,我们要把这个key和value存储在哪里,我们可以存储在mongodb里作为一个文档,有两个字段,key和value,也同样可以存储在ehcache中。我希望是可以动态配置的,所以我在basicCacheDao中写了类似的注解:

它的意思就是在实例化dao的时候通过读取配置文件,获取容器中已经存在的实例,这个实例的key就是basicCacheDao,但是value不一样,有可能是ehcache,也有可能是mongdob。看一眼配置文件就明白了:

(3)缓存持久层实现

Mongo和ehcache对basic对象的缓存实现是不一样的,所以她们肯定是两套实现方式,我们一一来看,首先是ehcache:

具体来说,在BasicEhcacheDaoImpl中,我们得到了一个ehcache实例,是通过我们刚才说的缓存工厂获取的。当然这个cacheManger是内置的。我们传递的参数就是这个实例的名字。

接下来再看mongdob如何保存:

Springdata提供了关于mongdob的api接口,这里使用的方法是upsert,意味如果存在就更新,如果不存在就插入。当然具体的业务我们要区分来看,有些场景是这样的,有些就是单纯的插入或者更新了。

(4)Ehcache集群

Ehcache提供了配置式集群,可以将数据快速同步不同的机器实例,配置如下:





关于xml中这些配置参数的解释:

★ maxElementsInMemory :cache 中最多可以存放的元素的数量。如果放入cache中的元素超过这个数值,有两种情况:若overflowToDisk的属性值为true,会将cache中多出的元素放入磁盘文件中;若overflowToDisk的属性值为false,会根据memoryStoreEvictionPolicy的策略替换cache中原有的元素。

★ eternal :意思是是否永驻内存。如果值是true,cache中的元素将一直保存在内存中,不会因为时间超时而丢失,所以在这个值为true的时候,timeToIdleSeconds和timeToLiveSeconds两个属性的值就不起作用了。

★ timeToIdleSeconds :就是访问这个cache中元素的最大间隔时间。如果超过这个时间没有访问这个cache中的某个元素,那么这个元素将被从cache中清除。

★ timeToLiveSeconds : 这是cache中元素的生存时间。意思是从cache中的某个元素从创建到消亡的时间,从创建开始计时,当超过这个时间,这个元素将被从cache中清除。

★ 在ehcache中,缓存有2个失效相关的配置即 timeToLiveSeconds和timeToIdleSeconds,分别简称为ttl和tti。 在通常的解释中,前者表示一条缓存自创建时间起多少秒后失效,而后者表示一条缓存自最后读取或更新起多少秒失效。在2个同时配置时可能时间计算就不那么简单了。 简单说来 任何一方为0,则以另一方时间为准。否则就以最短时间为准。 ehcache是这样计算失效时间的:如果ttl不为0并且tti为0, 如果缓存未被读过,失效时间=ttl;如果tti不为0,失效时间=tti+读取时间否则 失效时间=min(ttl, tti+读取时间)。

★ timeToIdleSeconds :就是访问这个cache中元素的最大间隔时间。如果超过这个时间没有访问这个cache中的某个元素,那么这个元素将被从cache中清除。

★ timeToLiveSeconds : 这是cache中元素的生存时间。意思是从cache中的某个元素从创建到消亡的时间,从创建开始计时,当超过这个时间,这个元素将被从cache中清除。

★ memoryStoreEvictionPolicy :内存存储与释放策略。有三个值:LRU -least recently used;LFU -least frequently used;FIFO-first in first out, the oldest element by creation time

★ diskPersistent : 是否持久化磁盘缓存。当这个属性的值为true时,系统在初始化的时候会在磁盘中查找文件名为cache名称,后缀名为index的的文件,如CACHE_FUNC.index 。这个文件中存放了已经持久化在磁盘中的cache的index,找到后把cache加载到内存。要想把cache真正持久化到磁盘,写程序时必须注意,在是用net.sf.ehcache.Cache的void put (Element element)方法后要使用void flush()方法。

★ overflowToDisk :溢出是否写入磁盘。系统会根据标签<diskStore path="java.io.tmpdir"/> 中path的值查找对应的属性值,如果系统的java.io.tmpdir的值是 D:/temp,写入磁盘的文件就会放在这个文件夹下。文件的名称是cache的名称,后缀名的data。如:CACHE_FUNC.data。这个属性在解释maxElementsInMemory的时候也已经说过了。

★ 如果有多台缓存服务器,按照如下格式配置即可:

上述配置已经经过实践证明是没问题的,我费了好大功夫呢。

小结:

其实对于ehcache的使用上还是比较简单的,今天我们主要是讲解了如何设计一个缓存服务是基于ehcache的,也讲解了如何集成mongodb进去。大家可以多尝试下使用redis或者其他缓存容器。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表