AutoLoadCache 是使用 Spring AOP 、 Annotation以及Spring EL表达式 来进行管理缓存的解决方案,同时基于AOP实现自动加载机制来达到数据“常驻内存”的目的。
现在使用的缓存技术很多,比如Redis、 Memcache 、 EhCache等,甚至还有使用ConcurrentHashMap 或HashTable 来实现缓存。但在缓存的使用上,每个人都有自己的实现方式,大部分是直接与业务代码绑定,随着业务的变化,要更换缓存方案时,非常麻烦。接下来我们就使用AOP + Annotation 来解决这个问题,同时使用自动加载机制来实现数据“常驻内存”。
Spring AOP这几年非常热门,使用也越来越多,但个人建议AOP只用于处理一些辅助的功能(比如:接下来我们要说的缓存),而不能把业务逻辑使用AOP中实现,尤其是在需要“事务”的环境中。
如下图所示:
AOP拦截到请求后:
根据请求参数生成Key,后面我们会对生成Key的规则,进一步说明;
如果是AutoLoad的,则请求相关参数,封装到AutoLoadTO中,并放到AutoLoadHandler中。
根据Key去缓存服务器中取数据,如果取到数据,则返回数据,如果没有取到数据,则执行DAO中的方法,获取数据,同时将数据放到缓存中。如果是 AutoLoad的,则把最后加载时间,更新到AutoLoadTO中,最后返回数据;如是AutoLoad的请求,每次请求时,都会更新 AutoLoadTO中的 最后请求时间。
为了减少并发,增加等待机制:如果多个用户同时取一个数据,那么先让第一个用户去DAO取数据,其它用户则等待其返回后,去缓存中获取,尝试一定次数后,如果还没获取到,再去DAO中取数据。
AutoLoadHandler(自动加载处理器)主要做的事情:当缓存即将过期时,去执行DAO的方法,获取数据,并将数据放到缓存中。为了防止 自动加载队列过大,设置了容量限制;同时会将超过一定时间没有用户请求的也会从自动加载队列中移除,把服务器资源释放出来,给真正需要的请求。
使用自加载的目的:
避免在请求高峰时,因为缓存失效,而造成数据库压力无法承受;
把一些耗时业务得以实现。
把一些使用非常频繁的数据,使用自动加载,因为这样的数据缓存失效时,最容易造成服务器的压力过大。
分布式自动加载
如果将应用部署在多台服务器上,理论上可以认为自动加载队列是由这几台服务器共同完成自动加载任务。比如应用部署在A,B两台服务器上,A服务器自 动加载了数据D,(因为两台服务器的自动加载队列是独立的,所以加载的顺序也是一样的),接着有用户从B服务器请求数据D,这时会把数据D的最后加载时间 更新给B服务器,这样B服务器就不会重复加载数据D。
1
2
3
4
5
|
<
dependency
>
<
groupId
>com.github.qiujiayu</
groupId
>
<
artifactId
>autoload-cache</
artifactId
>
<
version
>2.2</
version
>
</
dependency
>
|
从0.4版本开始增加了Redis及Memcache的PointCut 的实现,直接在Spring 中用aop:config就可以使用。
Redis 配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
<!-- Jedis 连接池配置 -->
<
bean
id
=
"jedisPoolConfig"
class
=
"redis.clients.jedis.JedisPoolConfig"
>
<
property
name
=
"maxTotal"
value
=
"2000"
/>
<
property
name
=
"maxIdle"
value
=
"100"
/>
<
property
name
=
"minIdle"
value
=
"50"
/>
<
property
name
=
"maxWaitMillis"
value
=
"2000"
/>
<
property
name
=
"testOnBorrow"
value
=
"false"
/>
<
property
name
=
"testOnReturn"
value
=
"false"
/>
<
property
name
=
"testWhileIdle"
value
=
"false"
/>
</
bean
>
<
bean
id
=
"shardedJedisPool"
class
=
"redis.clients.jedis.ShardedJedisPool"
>
<
constructor-arg
ref
=
"jedisPoolConfig"
/>
<
constructor-arg
>
<
list
>
<
bean
class
=
"redis.clients.jedis.JedisShardInfo"
>
<
constructor-arg
value
=
"${redis1.host}"
/>
<
constructor-arg
type
=
"int"
value
=
"${redis1.port}"
/>
<
constructor-arg
value
=
"instance:01"
/>
</
bean
>
<
bean
class
=
"redis.clients.jedis.JedisShardInfo"
>
<
constructor-arg
value
=
"${redis2.host}"
/>
<
constructor-arg
type
=
"int"
value
=
"${redis2.port}"
/>
<
constructor-arg
value
=
"instance:02"
/>
</
bean
>
<
bean
class
=
"redis.clients.jedis.JedisShardInfo"
>
<
constructor-arg
value
=
"${redis3.host}"
/>
<
constructor-arg
type
=
"int"
value
=
"${redis3.port}"
/>
<
constructor-arg
value
=
"instance:03"
/>
</
bean
>
</
list
>
</
constructor-arg
>
</
bean
>
<
bean
id
=
"autoLoadConfig"
class
=
"com.jarvis.cache.to.AutoLoadConfig"
>
<
property
name
=
"threadCnt"
value
=
"10"
/>
<
property
name
=
"maxElement"
value
=
"20000"
/>
<
property
name
=
"printSlowLog"
value
=
"true"
/>
<
property
name
=
"slowLoadTime"
value
=
"500"
/>
<
property
name
=
"sortType"
value
=
"1"
/>
<
property
name
=
"checkFromCacheBeforeLoad"
value
=
"true"
/>
</
bean
>
<
bean
id
=
"hessianSerializer"
class
=
"com.jarvis.cache.serializer.HessianSerializer"
/>
<
bean
id
=
"cachePointCut"
class
=
"com.jarvis.cache.redis.ShardedCachePointCut"
destroy-method
=
"destroy"
>
<
constructor-arg
ref
=
"autoLoadConfig"
/>
<
property
name
=
"serializer"
ref
=
"hessianSerializer"
/>
<
property
name
=
"shardedJedisPool"
ref
=
"shardedJedisPool"
/>
<
property
name
=
"namespace"
value
=
"test_hessian"
/>
</
bean
>
|
Memcache 配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<
bean
id
=
"memcachedClient"
class
=
"net.spy.memcached.spring.MemcachedClientFactoryBean"
>
<
property
name
=
"servers"
value
=
"192.138.11.165:11211,192.138.11.166:11211"
/>
<
property
name
=
"protocol"
value
=
"BINARY"
/>
<
property
name
=
"transcoder"
>
<
bean
class
=
"net.spy.memcached.transcoders.SerializingTranscoder"
>
<
property
name
=
"compressionThreshold"
value
=
"1024"
/>
</
bean
>
</
property
>
<
property
name
=
"opTimeout"
value
=
"2000"
/>
<
property
name
=
"timeoutExceptionThreshold"
value
=
"1998"
/>
<
property
name
=
"hashAlg"
>
<
value
type
=
"net.spy.memcached.DefaultHashAlgorithm"
>KETAMA_HASH</
value
>
</
property
>
<
property
name
=
"locatorType"
value
=
"CONSISTENT"
/>
<
property
name
=
"failureMode"
value
=
"Redistribute"
/>
<
property
name
=
"useNagleAlgorithm"
value
=
"false"
/>
</
bean
>
<
bean
id
=
"hessianSerializer"
class
=
"com.jarvis.cache.serializer.HessianSerializer"
/>
<
bean
id
=
"cachePointCut"
class
=
"com.jarvis.cache.memcache.CachePointCut"
destroy-method
=
"destroy"
>
<
constructor-arg
ref
=
"autoLoadConfig"
/>
<
property
name
=
"serializer"
ref
=
"hessianSerializer"
/>
<
property
name
=
"memcachedClient"
,
ref
=
"memcachedClient"
/>
<
property
name
=
"namespace"
value
=
"test"
/>
</
bean
>
|
AOP 配置:
1
2
3
4
5
6
7
8
9
10
|
<aop:config>
<aop:aspect ref=
"cachePointCut"
>
<aop:pointcut id=
"daoCachePointcut"
expression=
"execution(public !void com.jarvis.cache_example.common.dao..*.*(..)) && @annotation(cache)"
/>
<aop:around pointcut-ref=
"daoCachePointcut"
method=
"proceed"
/>
</aop:aspect>
<aop:aspect ref=
"cachePointCut"
order=
"1000"
><!-- order 参数控制 aop通知的优先级,值越小,优先级越高 ,在事务提交后删除缓存 -->
<aop:pointcut id=
"deleteCachePointcut"
expression=
"execution(* com.jarvis.cache_example.common.dao..*.*(..)) && @annotation(cacheDelete)"
/>
<aop:after-returning pointcut-ref=
"deleteCachePointcut"
method=
"deleteCache"
returning=
"retVal"
/>
</aop:aspect>
</aop:config>
|
通过Spring配置,能更好地支持,不同的数据使用不同的缓存服务器的情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
15
16
17
18
19
20
21
22
23
24
25
26
27
28
17
18
19
20
21
22
23
24
25
26
27
28
20
|