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
|