From 2efeab6b2863c9268ed970a867849fd6ff8bcaf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B1=E5=AE=B6=E6=A6=86?= Date: Mon, 14 Nov 2016 19:01:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=B9JedisCluster?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 ++-- doc/ConcurrentHashMap.md | 2 +- doc/JRedis.md | 2 +- doc/Memcache.md | 2 +- doc/changesLog.md | 74 +++++++------- doc/deleteCache.md | 6 +- doc/idea.md | 10 +- doc/script.md | 10 +- doc/use.md | 16 +-- doc/warning.md | 20 ++-- .../java/com/jarvis/cache/ICacheManager.java | 2 +- .../cache/redis/JedisClusterCacheManager.java | 99 +++++++++++++++---- 12 files changed, 160 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 1d6c6a35..aa4ae29f 100644 --- a/README.md +++ b/README.md @@ -8,30 +8,30 @@ 现在使用的缓存技术很多,比如*Redis*、 *Memcache* 、 *EhCache*等,甚至还有使用*ConcurrentHashMap* 或 *HashTable* 来实现缓存。但在缓存的使用上,每个人都有自己的实现方式,大部分是直接与业务代码绑定,随着业务的变化,要更换缓存方案时,非常麻烦。接下来我们就使用**AOP + Annotation** 来解决这个问题,同时使用**自动加载机制** 来实现数据“**常驻内存**”。 -###[设计思想及原理](./doc/idea.md) +### [设计思想及原理](./doc/idea.md) -###[使用方法](./doc/use.md) +### [使用方法](./doc/use.md) -###[注解(Annotation)说明](./doc/annotations.md) +### [注解(Annotation)说明](./doc/annotations.md) -###[表达式的应用](./doc/script.md) +### [表达式的应用](./doc/script.md) -###[缓存删除](./doc/deleteCache.md) +### [缓存删除](./doc/deleteCache.md) -###[注意事项](./doc/warning.md) +### [注意事项](./doc/warning.md) -###[缓存管理页面](./doc/admin.md) +### [缓存管理页面](./doc/admin.md) -###[与Spring Cache的区别](./doc/SpringCache.md) +### [与Spring Cache的区别](./doc/SpringCache.md) -###源码阅读 +### 源码阅读 已经实现基于aspectj,代码在com.jarvis.cache.aop.aspectj.AspectjAopInterceptor。想通过阅读代码了解详细细节,可以以此为入口。 ### [更新日志](./doc/changesLog.md) -###未来计划: +### 未来计划: 希望未来能更好适应高并发的环境,更方便运维,欢迎有这方面经验的人能参与进来,让更多的人受益。 在异步刷新缓存时,增加尝试多次去数据层加载数据,以适应解决有多个数据源,而其中部分数据源出问题情况。通过这种尝试机制,也许能获取到新的数据。 diff --git a/doc/ConcurrentHashMap.md b/doc/ConcurrentHashMap.md index 1fca8b13..bed36f59 100644 --- a/doc/ConcurrentHashMap.md +++ b/doc/ConcurrentHashMap.md @@ -1,4 +1,4 @@ -###ConcurrentHashMap配置 +### ConcurrentHashMap配置 diff --git a/doc/JRedis.md b/doc/JRedis.md index baa37f3c..57c57b7e 100644 --- a/doc/JRedis.md +++ b/doc/JRedis.md @@ -1,4 +1,4 @@ -####JRedis配置 +#### JRedis配置 Redis配置 diff --git a/doc/Memcache.md b/doc/Memcache.md index 2622d326..6e54a535 100644 --- a/doc/Memcache.md +++ b/doc/Memcache.md @@ -1,4 +1,4 @@ -###Memcache配置 +### Memcache配置 diff --git a/doc/changesLog.md b/doc/changesLog.md index d0127279..5ce2e46b 100644 --- a/doc/changesLog.md +++ b/doc/changesLog.md @@ -1,11 +1,11 @@ -##更新日志 +## 更新日志 -* ####4.14 修改说明 +* #### 4.14 修改说明 * 使用Lombok来缩减代码,Lombok能帮助我们减少写get,set,hashCode,toString等没有技术含量的工作,而且在修改增加或删除java bean中的属性时,不需要去改hashCode,toString等方法,减少因为忘记修改而造成的错误。 * 增加OGNL表达式引擎支持,经过测试它的性能要比SpringEL表达式还要优秀。测试代码在:com.test.script.ScriptTest -* ####4.13 修改说明 +* #### 4.13 修改说明 * com.jarvis.cache.clone.ICloner中的Object deepClone(Object obj) 改为 Object deepClone(Object obj, final Type type),在能获得Type的情况下使用,能更好保证数据准备和效率 @@ -17,7 +17,7 @@ * 修复DataLoader中,等待第一个请求获取数据后,又再去数据层获取数据的BUG,这个BUG是在4.11版本产生的,建议使用4.11和4.12的用户升级一下。 -* ####4.12 修改说明: +* #### 4.12 修改说明: * fastjson deepClone 数组中有Map或Collection时,转换失败的问题 * Fastjson深度复制优化:针对深度复制Method的arguments进行优化,通过method.getGenericParameterTypes() 来方便深度复制泛型参数。 @@ -26,7 +26,7 @@ * ICacheManager中的get和set方法中,增加Method method和 Object args[]两个参数,方便使用者根据情况进行扩展,比如:增加一些自定义的注解,来处理特殊业务逻辑(比如:增加本地缓存功能) -* ####4.11 修改说明: +* #### 4.11 修改说明: * 增加刷新缓存数据功能,如果不使用自动加载,则在缓存即将过期时,开启线程进行异步刷新数据,可以减少用户的等待,避免因缓存失效失效造成系统压力过大; * 在刷新缓存和自动加载缓存中,如果从数据层加载数据时,发生异常,则使用旧数据进行续租(如果有旧数据的话),达到提前预防缓存失效后,系统压力过大问题; @@ -34,32 +34,32 @@ * 增加使用cloning进行深度复制,性能要比fastjson提高50%。相关的测试代码在:com.test.fastjson.CloningTest和com.test.fastjson.DeepCloneTest两个类中; * 将数据加载功能从AbstractCacheManager中移到DataLoader中,使得代码更加清晰易懂; -* ####4.10 增加使用Fastjson进行序列化的支持,同时也增加了对比较大的数据进行压缩的工具。 +* #### 4.10 增加使用Fastjson进行序列化的支持,同时也增加了对比较大的数据进行压缩的工具。 -* ####4.9 去除默认的serializer和scriptParser,以便于与Spring 进行强解耦。 +* #### 4.9 去除默认的serializer和scriptParser,以便于与Spring 进行强解耦。 -* ####4.8 优化JavaScriptParser 中的代码,并根据JDK版本,使用javascript 还是nashorn 引擎。 +* #### 4.8 优化JavaScriptParser 中的代码,并根据JDK版本,使用javascript 还是nashorn 引擎。 -* ####4.7 修改说明: +* #### 4.7 修改说明: * 修改缓存预警时间值,加入-10~10秒随机数,避免缓存集中失效 * 实现表达式引擎的可扩展性,默认还是使用SpringEL表达式引擎(com.jarvis.cache.script.SpringELParser),但可以自己实际情况进行扩展。扩展方法:实现com.jarvis.cache.script.IScriptParser,然后通过配置把实现的实例注入到AbstractCacheManager中的scriptParser属性即可。使用不同表达式引擎时,注意表达式的语言的区别。 * 增加了JavaScript 表达式引擎实现:com.jarvis.cache.script.JavaScriptParser。如果项目使用JDK1.8的,想用JavaScript的话,建议使用Nashorn 来实现,性能会比较好。 -* ####4.6 修改说明: +* #### 4.6 修改说明: * @Cache中增加 expireExpression,实现通过表达式来动态获取expire 值; * @Cache 中增加 alarmTime。当缓存在alarmTime 时间内即将过期的话,会自动刷新缓存内容; * 解决 hessian2 无法序列化SoftReference 的bug; -* ####4.5 修改说明: +* #### 4.5 修改说明: * 调整CacheTask 中 Thread.sleep(0); 的位置; * 增加CacheChangeListener 接口,优化代码结构; * 使用SoftReference 管理Map中的缓存数据; -* ####4.4 修改说明: +* #### 4.4 修改说明: 当遍历次数达到2000时,执行Thread.sleep(0); 触发操作系统立刻重新进行一次CPU竞争, 让其它线程获得CPU控制权的权力。 @@ -68,18 +68,18 @@ * 增加属性:int unpersistMaxSize,允许不持久化变更数(当缓存变更数量超过此值才做持久化操作) * 增加属性: boolean copyValue;是否拷贝缓存中的值:true时,是拷贝缓存值,可以避免外界修改缓存值;false,不拷贝缓存值,缓存中的数据可能被外界修改,但效率比较高。 -* ####4.3 对 ConcurrentHashMap 缓存增加持久化功能。重启时会从本地磁盘加载缓存数据,避免因刚启动没有缓存数据,造成压力过大。 +* #### 4.3 对 ConcurrentHashMap 缓存增加持久化功能。重启时会从本地磁盘加载缓存数据,避免因刚启动没有缓存数据,造成压力过大。 -* ####4.2 改用JDK1.6进行编译;将isAutoload中的cache.expire() > 120 改为 cache.expire() >= 120; +* #### 4.2 改用JDK1.6进行编译;将isAutoload中的cache.expire() > 120 改为 cache.expire() >= 120; AutoLoadHandler中排序线程增加sleep,以节约系统资源 -* ####4.1 提升缓存管理页的扩展性 +* #### 4.1 提升缓存管理页的扩展性 将获取AOP配置信息功能从 AdminServlet 中抽取出来,并使用CacheManagerConfig接口来获取。 -* ####4.0 实现AOP的可扩展 +* #### 4.0 实现AOP的可扩展 受网友Rekoe 将AutoLoadCache 和 nutz整合的启发([https://github.com/Rekoe/AutoLoadCache](https://github.com/Rekoe/AutoLoadCache)),将AutoLoadCache 中的AOP相关功能进行抽取,以达到可扩展 @@ -87,39 +87,39 @@ * 实现了使用Aspectj进行AOP拦截:com.jarvis.cache.aop.aspectj.AspectjAopInterceptor * 升级时一定要注意配置文件的变化,可以参考[cache-example](https://github.com/qiujiayu/cache-example) 中的配置 -* ####3.7 细节优化: +* #### 3.7 细节优化: * 调整 写缓存(writeCache)代码结构。 * 将ShardedCachePointCut中hashExpire默认值设置为-1; * 解析SpEL表达式时,增加判断是否有返回值,避免在不能使用#retVal的地方,使用了#retVal * 将com.jarvis.cache.map.CachePointCut.shutDown() 改成 destroy() -* ####3.6 对@ExCache相关代码进行调整,自动加载时也对@ExCache进行处理。对一些变量增加volatile 修饰符,以便于其它线程的读取到最新的值。 +* #### 3.6 对@ExCache相关代码进行调整,自动加载时也对@ExCache进行处理。对一些变量增加volatile 修饰符,以便于其它线程的读取到最新的值。 -* ####3.5 增加如下几个功能: +* #### 3.5 增加如下几个功能: * 注册自定义Spring EL表达式函数 * 如果ShardedCachePointCut 中的 hashExpire 小于0则使用@Cache中设置的expire值,替换hashExpire值。 * 增加@ExCache,用于增强@Cache 设置缓存功能。应用场景举例:我们使用getUserByName时,返回User的对象,其实也可以设置给getUserById的缓存中的,反过也是同样的道理,通过getUserById方法返回的数据,也可以设置给getUserByName的缓存中​,这样可以减少与DAO的交互了。 -* ####3.4 使用管道来操作Redis 的哈希表,减少与Redis的交互次数。 +* #### 3.4 使用管道来操作Redis 的哈希表,减少与Redis的交互次数。 -* ####3.3 增加 Spring EL 表达式(Expression)缓存。 +* #### 3.3 增加 Spring EL 表达式(Expression)缓存。 -* ####3.2 进一步优化“拿来主义”机制 +* #### 3.2 进一步优化“拿来主义”机制 使用当前的ProcessingTO来做同步锁,把锁粒度降到了最低,以提升并发性能;删除并发线程的计数器;如果第一个线程执行时出现异常,那等待中的线程,也直接抛出异常,因为此时去执行DAO的出错的可能性非常大,会造成服务器压力过大。 -* ####3.1 优化“拿来主义”机制 +* #### 3.1 优化“拿来主义”机制 “拿来主义”机制指的是,当有多个请求去获取同一个数据时,我们先让其中一个请求先去DAO中获取数据,并放到缓存中,其它请求则等它完成后,直接去缓存中获取数据,通过这种方式减轻DAO中的并发。 但经测试发现,刚往Reids中放的数据,立即去取是获取不到数据的(无法命中),测试代码已经放到[cache-example](https://github.com/qiujiayu/cache-example) 中。优化后的方案是,不从远程服务器获取,而是从本地内存中获取第一个请求返回的数据。减少并发的同时,还能减轻网络压力。 -* ####3.0 此版本做了大调整,有些功能已经不兼容老版本 +* #### 3.0 此版本做了大调整,有些功能已经不兼容老版本 不再使用默认缓存Key,所有的缓存都必须自定义缓存Key;原来使用$hash()来调用hash函数,改为使用#hash()进行调用。 @@ -129,17 +129,17 @@ 如果需要在MyBatis Mapper中使用@Cache和@CacheDelete,则需要使用com.jarvis.cache.mybatis.CachePointCutProxy 来处理。 -* ####2.13 优化多线程并发等机制, 代码调整如下: +* #### 2.13 优化多线程并发等机制, 代码调整如下: ![Alt 优化多线程并发等机制](wait.png "优化多线程并发等机制") 由于我们这里要实现的功能是,当前的线程要等待前一个正在运行线程的结果,但我们不知道前一个线程的执行到哪一步。有可能在我们要执行wait时,已经完成notifyAll了。通过调整逻辑变得更加严谨。 -* ####2.12 解决jdk1.8之前中 java.lang.NoSuchMethodError: java.util.Map.putIfAbsent 错误。 +* #### 2.12 解决jdk1.8之前中 java.lang.NoSuchMethodError: java.util.Map.putIfAbsent 错误。 -* ####2.11 @CacheDeleteKey中keyType 设置default,以实现向下兼容。 +* #### 2.11 @CacheDeleteKey中keyType 设置default,以实现向下兼容。 -* ####2.10 修改记录: +* #### 2.10 修改记录: * 优化ConcurrentHashMap 使用,将以下代码: @@ -159,7 +159,7 @@ * 放弃使用 @CacheDeleteKey中keyType, 直接使用它的value值来判断是自定义缓存Key,还是默认生成的缓存Key。所以keyType 变得多余了。 -* ####2.9 修复以下几个问题 +* #### 2.9 修复以下几个问题 * @Cache(expire=0, waitTimeOut=500),当expire=0时,将设置为永久缓存;waitTimeOut 用于设置并发等待时间(毫秒)。 @@ -172,21 +172,21 @@ * 优化AbstractCacheManager类的loadData方法中线程同步锁。 -* ####2.8 com.jarvis.lib.util.BeanUtil.toString()方法中增加反射缓存,提升反射效率 +* #### 2.8 com.jarvis.lib.util.BeanUtil.toString()方法中增加反射缓存,提升反射效率 -* ####2.7 当参数类型为 Class,自动生成的缓存Key会出问题。(感谢zhaopeng 提供的代码) +* #### 2.7 当参数类型为 Class,自动生成的缓存Key会出问题。(感谢zhaopeng 提供的代码) -* ####2.5 2.6 当autoload=true,缓存Key中没有加上命名空间,对1.9及以上版本有影响 +* #### 2.5 2.6 当autoload=true,缓存Key中没有加上命名空间,对1.9及以上版本有影响 -* ####2.4 Jedis更新到2.8 +* #### 2.4 Jedis更新到2.8 -* ####2.3 AdminServlet 增加登录用户名和密码; +* #### 2.3 AdminServlet 增加登录用户名和密码; * ####2.2 解决Hessian不能正确序列化BigDecimal问题 -* ####2.1 对Kryo进行测试,发现问题问题比较多,所以删除Kryo 支持,用户可以根据自己的情况实现ISerializer接口。优化HessianSerializer,提升性能,并将HessianSerializer作为默认的序列化和反序列化工具。 +* #### 2.1 对Kryo进行测试,发现问题问题比较多,所以删除Kryo 支持,用户可以根据自己的情况实现ISerializer接口。优化HessianSerializer,提升性能,并将HessianSerializer作为默认的序列化和反序列化工具。 -* ####2.0 增加了Hessian 和 Kryo 序列化支持,还是使用JDK自带的处理方法。修改方法如下: +* #### 2.0 增加了Hessian 和 Kryo 序列化支持,还是使用JDK自带的处理方法。修改方法如下: @@ -199,4 +199,4 @@ 虽然Kryo效率比较高,但使用Kryo会出现的问题比较多,所以还是慎重使用,系统经常维护的就不太适合使用,经过测试,改变属性名称,或删除中间的属性等情况都可能反序列出错误的值,所以如果遇到有减少或修改的情况要及时清里缓存。如果是增加属性则会反序列化失败,这正符合我们的要求。 -* ####1.9 增加了命名空间,避免不同的系统之支缓存冲突 \ No newline at end of file +* #### 1.9 增加了命名空间,避免不同的系统之支缓存冲突 \ No newline at end of file diff --git a/doc/deleteCache.md b/doc/deleteCache.md index 8f12e7a9..b9578d5a 100644 --- a/doc/deleteCache.md +++ b/doc/deleteCache.md @@ -1,6 +1,6 @@ -##缓存删除 +## 缓存删除 -###通过删除缓存,实现数据实时性 +### 通过删除缓存,实现数据实时性 下面商品评论的例子中,如果用户发表了评论,要所有涉及到的前端页面立即显示该如何来处理? @@ -26,7 +26,7 @@ } } -###批量删除缓存 +### 批量删除缓存 在一些用户交互比较多的系统中,使用程序删除缓存是非常常见的事情,当我们遇到一些查询条件比较复杂的查询时,我们没有办法通过程序,反向生成缓存key,也就无法精确定位需要删除的缓存,但我们又不希望把不相关的缓存给误删除时。这时就可以使用下面介绍的批量删除缓存功能。 diff --git a/doc/idea.md b/doc/idea.md index 4a326d67..d65d08e8 100644 --- a/doc/idea.md +++ b/doc/idea.md @@ -1,4 +1,4 @@ -###设计思想及原理 +### 设计思想及原理 如下图所示: ![Alt 缓存框架](FlowChart.png "缓存框架") @@ -24,7 +24,7 @@ AutoLoadHandler(自动加载处理器)主要做的事情:当缓存即将 如果将应用部署在多台服务器上,理论上可以认为自动加载队列是由这几台服务器共同完成自动加载任务。比如应用部署在A,B两台服务器上,A服务器自动加载了数据D,(因为两台服务器的自动加载队列是独立的,所以加载的顺序也是一样的),接着有用户从B服务器请求数据D,这时会把数据D的最后加载时间更新给B服务器,这样B服务器就不会重复加载数据D。 -##为什么要使用自动加载机制? +## 为什么要使用自动加载机制? 首先我们想一下系统的瓶颈在哪里? @@ -35,14 +35,14 @@ AutoLoadHandler(自动加载处理器)主要做的事情:当缓存即将 注:上面提到的两种异步刷新数据机制,如果从数据层获取数据时,发生异常,则会使用旧数据进行续租。 -##如何减少DAO层并发 +## 如何减少DAO层并发 1. 使用缓存; 2. 使用自动加载机制,因“写”数据往往比读数据性能要差,使用自动加载也能减少写缓存的并发。 3. 从DAO层加载数据时,**增加等待机制**(拿来主义) -##使用规范 +## 使用规范 1. 将调接口或数据库中取数据,**封装在DAO层**,不能什么地方都有调接口的方法。 2. 自动加载缓存时,**不能**在缓存方法内**叠加(或减)**查询条件值,但允许设置值。 @@ -50,7 +50,7 @@ AutoLoadHandler(自动加载处理器)主要做的事情:当缓存即将 4. 对于比较大的系统,要进行**模块化设计**,这样可以将自动加载,均分到各个模块中。 -##为了能让更多人使用此项目,一直在为具有更好的扩展性而努力,在以下几个方法已实现可扩展: +## 为了能让更多人使用此项目,一直在为具有更好的扩展性而努力,在以下几个方法已实现可扩展: 1. 缓存:现在已经支持Memcache、Redis、ConcurrentHashMap三种缓存,用户也可以自己增加扩展; 2. AOP:项目中已经实现了Spring AOP的拦截。在Nutz官方项目中也实现了相关的插件:https://github.com/nutzam/nutzmore/tree/master/nutz-integration-autoloadcache diff --git a/doc/script.md b/doc/script.md index 4a66d8c1..f9efc459 100644 --- a/doc/script.md +++ b/doc/script.md @@ -1,8 +1,8 @@ -##表达式的应用 +## 表达式的应用 以下是以Spring EL表达式为例子,如果使用其它表达式,需要注意语法的不同。 -###缓存Key的生成 +### 缓存Key的生成 在@Cache中设置key,可以是字符串或Spring EL表达式: @@ -20,21 +20,21 @@ 在拼缓存Key时,各项数据最好都用特殊字符进行分隔,否则缓存的Key有可能会乱的。比如:a,b 两个变量a=1,b=11,如果a=11,b=1,两个变量中间不加特殊字符,拼在一块,值是一样的。 Spring EL表达式支持调整类的static 变量和方法,比如:"T(java.lang.Math).PI"。 -###提供的SpEL上下文数据 +### 提供的SpEL上下文数据 | 名字 | 描述 | 示例 | | ------------- | ------------- | ------------- | | args | 当前被调用的方法的参数列表 | #args[0] | | retVal | 方法执行后的返回值(仅当方法执行之后才有效,如@Cache(opType=CacheOpType.WRITE),expireExpression,autoloadCondition,@ExCache() | #retVal | -###提供的SpEL函数 +### 提供的SpEL函数 | 名字 | 描述 | 示例 | | ------------- | ------------- | ------------- | | hash | 将Object 对象转换为唯一的Hash字符串 | #hash(#args) | | empty | 判断Object对象是否为空 | #empty(#args[0]) | -###自定义SpEL函数 +### 自定义SpEL函数 通过AutoLoadConfig 的functions 注册自定义函数,例如: diff --git a/doc/use.md b/doc/use.md index 432f7d00..d15b66c0 100644 --- a/doc/use.md +++ b/doc/use.md @@ -1,7 +1,7 @@ -##使用方法 +## 使用方法 -###1. Maven dependency: +### 1. Maven dependency: com.github.qiujiayu @@ -9,9 +9,9 @@ ${version} -###2. [AutoLoadConfig 配置说明](AutoLoadConfig.md) +### 2. [AutoLoadConfig 配置说明](AutoLoadConfig.md) -###3. 序列化工具: +### 3. 序列化工具: 序列化工具主要用于深度复杂,以及缓存中数据与Java对象的转换。框架中已经实现如下几种序列化工具: @@ -31,13 +31,13 @@ -###4. 表达式解析器 +### 4. 表达式解析器 缓存Key及一些条件表达式,都是通过表达式与Java对象进行交互的,框架中已经内置了使用Spring El和Javascript两种表达的解析器,分别的:com.jarvis.cache.script.SpringELParser 和 com.jarvis.cache.script.JavaScriptParser,如果需要扩展,需要继承com.jarvis.cache.script.AbstractScriptParser 这个抽象类。 -###5.缓存配置 +### 5.缓存配置 框架已经支持 Redis、Memcache以及ConcurrentHashMap 三种缓存: @@ -46,7 +46,7 @@ * [ConcurrentHashMap 配置](ConcurrentHashMap.md) -###6.AOP 配置: +### 6.AOP 配置: @@ -66,7 +66,7 @@ 如果不同的数据,要使用不同的缓存的话,可以通过配置多个AOP来进行共区分。 -###7. 在需要使用缓存操作的方法前增加 @Cache和 @CacheDelete注解 +### 7. 在需要使用缓存操作的方法前增加 @Cache和 @CacheDelete注解 更多的配置可以参照[实例代码](https://github.com/qiujiayu/cache-example) diff --git a/doc/warning.md b/doc/warning.md index 6a41c435..37150614 100644 --- a/doc/warning.md +++ b/doc/warning.md @@ -1,9 +1,9 @@ -##注意事项 +## 注意事项 -###1. 当@Cache中 **autoload** 设置为 **ture** 时,对应方法的参数必须都是Serializable的。 +### 1. 当@Cache中 **autoload** 设置为 **ture** 时,对应方法的参数必须都是Serializable的。 AutoLoadHandler中需要缓存通过**深度复制**后的参数。 -###2. 参数中只设置必要的属性值,在DAO中用不到的属性值尽量不要设置,这样能避免生成不同的缓存Key,降低缓存的使用率。 +### 2. 参数中只设置必要的属性值,在DAO中用不到的属性值尽量不要设置,这样能避免生成不同的缓存Key,降低缓存的使用率。 例如: public CollectionTO getAccountByCriteria(AccountCriteriaTO criteria) { @@ -24,7 +24,7 @@ AutoLoadHandler中需要缓存通过**深度复制**后的参数。 } } -###3. 注意AOP失效的情况; +### 3. 注意AOP失效的情况; 例如: TempDAO { @@ -41,7 +41,7 @@ AutoLoadHandler中需要缓存通过**深度复制**后的参数。 通过 new TempDAO().a() 调用b方法时,AOP失效,也无法进行缓存相关操作。 -###4. 自动加载缓存时,不能在缓存方法内叠加查询参数值; +### 4. 自动加载缓存时,不能在缓存方法内叠加查询参数值; 例如: @Cache(expire=600, autoload=true, key="'myKey'+#hash(#args[0])") @@ -56,14 +56,14 @@ AutoLoadHandler中需要缓存通过**深度复制**后的参数。 因为自动加载时,AutoLoadHandler 缓存了查询参数,执行自动加载时,每次执行时 threshold 都会乘以10,这样threshold的值就会越来越大。 -###5. 对于一些比较耗时的方法尽量使用自动加载。 +### 5. 对于一些比较耗时的方法尽量使用自动加载。 -###6. 对于查询条件变化比较剧烈的,不要使用自动加载机制。 +### 6. 对于查询条件变化比较剧烈的,不要使用自动加载机制。 比如,根据用户输入的关键字进行搜索数据的方法,不建议使用自动加载。 -###7. 如果DAO方法中需要从ThreadLocal 获取数据时,不能使用自动加载机制(@Cache的autoload值不能设置为true)。自动加载是用新的线程中模拟用户请求的,这时ThreadLocal的数据都是空的。 +### 7. 如果DAO方法中需要从ThreadLocal 获取数据时,不能使用自动加载机制(@Cache的autoload值不能设置为true)。自动加载是用新的线程中模拟用户请求的,这时ThreadLocal的数据都是空的。 -###8. 使用 @Cache(opType=CacheOpType.WRITE)的坑 +### 8. 使用 @Cache(opType=CacheOpType.WRITE)的坑 因为AutoloadCache是不支持事务回滚的,所以在如下情况时,会出现缓存中的数据不正确的情况: public class UserDAO { @@ -77,7 +77,7 @@ AutoLoadHandler中需要缓存通过**深度复制**后的参数。 如果事务提交失败时,此时缓存中的数据无法回滚,所以使用时要注意。 -##在事务环境中,如何减少“脏读” +## 在事务环境中,如何减少“脏读” 1. 不要从缓存中取数据,然后应用到修改数据的SQL语句中 diff --git a/src/main/java/com/jarvis/cache/ICacheManager.java b/src/main/java/com/jarvis/cache/ICacheManager.java index e67fcec9..9ecf113d 100644 --- a/src/main/java/com/jarvis/cache/ICacheManager.java +++ b/src/main/java/com/jarvis/cache/ICacheManager.java @@ -18,7 +18,7 @@ public interface ICacheManager { * @param result 缓存数据 * @param method Method * @param args args - * @throws CacheCenterConnectionException + * @throws CacheCenterConnectionException 缓存异常 */ void setCache(final CacheKeyTO cacheKey, final CacheWrapper result, final Method method, final Object args[]) throws CacheCenterConnectionException; diff --git a/src/main/java/com/jarvis/cache/redis/JedisClusterCacheManager.java b/src/main/java/com/jarvis/cache/redis/JedisClusterCacheManager.java index 0adab81d..8c9c699a 100644 --- a/src/main/java/com/jarvis/cache/redis/JedisClusterCacheManager.java +++ b/src/main/java/com/jarvis/cache/redis/JedisClusterCacheManager.java @@ -2,8 +2,12 @@ import java.lang.reflect.Method; import java.lang.reflect.Type; +import java.util.HashSet; +import java.util.Set; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.apache.log4j.Logger; +import org.springframework.beans.factory.InitializingBean; import com.jarvis.cache.AbstractCacheManager; import com.jarvis.cache.exception.CacheCenterConnectionException; @@ -14,14 +18,14 @@ import com.jarvis.cache.to.CacheKeyTO; import com.jarvis.cache.to.CacheWrapper; +import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.ShardedJedis; /** * Redis缓存管理 * @author jiayu.qiu */ -public class JedisClusterCacheManager extends AbstractCacheManager { +public class JedisClusterCacheManager extends AbstractCacheManager implements InitializingBean { private static final Logger logger=Logger.getLogger(JedisClusterCacheManager.class); @@ -29,22 +33,58 @@ public class JedisClusterCacheManager extends AbstractCacheManager { private JedisCluster jedisCluster; + private Integer timeout; + + private Integer maxRedirections; + + private String redisUrls; + + private GenericObjectPoolConfig genericObjectPoolConfig; + /** * Hash的缓存时长:等于0时永久缓存;大于0时,主要是为了防止一些已经不用的缓存占用内存;hashExpire小于0时,则使用@Cache中设置的expire值(默认值为-1)。 */ private int hashExpire=-1; - /** - * 是否通过脚本来设置 Hash的缓存时长 - */ - private boolean hashExpireByScript=false; - public JedisClusterCacheManager(AutoLoadConfig config, ISerializer serializer, AbstractScriptParser scriptParser) { super(config, serializer, scriptParser); } - private void returnResource(ShardedJedis shardedJedis) { - shardedJedis.close(); + private Set parseHostAndPort() throws Exception { + if(null == redisUrls || redisUrls.length() == 0) { + return null; + } + try { + String reids[]=redisUrls.split(";"); + Set haps=new HashSet(); + for(String redis: reids) { + + String[] ipAndPort=redis.split(":"); + + try { + HostAndPort hap=new HostAndPort(ipAndPort[0], Integer.parseInt(ipAndPort[1])); + haps.add(hap); + } catch(Exception ex) { + logger.error(ex); + } + } + + return haps; + } catch(IllegalArgumentException ex) { + throw ex; + } catch(Exception ex) { + throw new Exception("解析 jedis 配置失败", ex); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + Set haps=this.parseHostAndPort(); + if(null == haps || null == timeout || null == maxRedirections || null == genericObjectPoolConfig) { + return; + } + logger.debug("new JedisCluster"); + jedisCluster=new JedisCluster(haps, timeout, maxRedirections, genericObjectPoolConfig); } @Override @@ -56,7 +96,6 @@ public void setCache(final CacheKeyTO cacheKeyTO, final CacheWrapper res if(null == cacheKey || cacheKey.length() == 0) { return; } - ShardedJedis shardedJedis=null; try { int expire=result.getExpire(); String hfield=cacheKeyTO.getHfield(); @@ -73,7 +112,6 @@ public void setCache(final CacheKeyTO cacheKeyTO, final CacheWrapper res } catch(Exception ex) { logger.error(ex.getMessage(), ex); } finally { - returnResource(shardedJedis); } } @@ -106,7 +144,6 @@ public CacheWrapper get(final CacheKeyTO cacheKeyTO, final Method method return null; } CacheWrapper res=null; - ShardedJedis shardedJedis=null; try { byte bytes[]=null; String hfield=cacheKeyTO.getHfield(); @@ -120,7 +157,6 @@ public CacheWrapper get(final CacheKeyTO cacheKeyTO, final Method method } catch(Exception ex) { logger.error(ex.getMessage(), ex); } finally { - returnResource(shardedJedis); } return res; } @@ -157,10 +193,6 @@ public JedisCluster getJedisCluster() { return jedisCluster; } - public void setJedisCluster(JedisCluster jedisCluster) { - this.jedisCluster=jedisCluster; - } - public int getHashExpire() { return hashExpire; } @@ -172,11 +204,36 @@ public void setHashExpire(int hashExpire) { this.hashExpire=hashExpire; } - public boolean isHashExpireByScript() { - return hashExpireByScript; + public Integer getTimeout() { + return timeout; + } + + public void setTimeout(Integer timeout) { + this.timeout=timeout; + } + + public Integer getMaxRedirections() { + return maxRedirections; } - public void setHashExpireByScript(boolean hashExpireByScript) { - this.hashExpireByScript=hashExpireByScript; + public void setMaxRedirections(Integer maxRedirections) { + this.maxRedirections=maxRedirections; } + + public String getRedisUrls() { + return redisUrls; + } + + public void setRedisUrls(String redisUrls) { + this.redisUrls=redisUrls; + } + + public GenericObjectPoolConfig getGenericObjectPoolConfig() { + return genericObjectPoolConfig; + } + + public void setGenericObjectPoolConfig(GenericObjectPoolConfig genericObjectPoolConfig) { + this.genericObjectPoolConfig=genericObjectPoolConfig; + } + }