Java透明化緩存實現 - SimpleCache
SimpleCache 是一個簡單易用的java緩存工具,用來簡化緩存代碼的編寫,讓你擺脫單調乏味的重復工作!
1. 完全透明的緩存支持,對業務代碼零侵入
2. 支持使用Redis和Memcached作為后端緩存。
3. 支持緩存數據分區規則的定義
4. 使用redis作緩存時,支持list類型的高級數據結構,更適合論壇帖子列表這種類型的數據
5. 支持混合使用redis緩存和memcached緩存。可以將列表數據緩存到redis中,其他kv結構數據繼續緩存到memcached
6. 支持redis的主從集群,可以做讀寫分離。緩存讀取自redis的slave節點,寫入到redis的master節點
使用方式如下: ` @Cache(key = @CacheKey(template = KEY_LIKECOUNT, els = {"#p[0]", "#p[1]"})) public LikeCount getCount(String appId, String contentId) {} ` #p0表示取方法的第一個參數 整個: `@CacheKey(template = "LIKECOUNT/%s/%s", els = {"#p[0]","#p[1]"})` 這一段其實就是 `String.format("LIKECOUNT/%s/%s", appId, contentId)` expire參數用來控制緩存的時間,cacheNull參數用來控制,如果返回的結果為null時,是否緩存改結果。 如果一個方法的緩存key非常簡單,不需要從參數生成,可以簡單的使用: ` @SimpleCache(key = "TEST", expire = 3000, cacheNull = true)` 如果該方法返回的是一個List類型的數據,則可以使用: `@ListedCache(key = @CacheKey(template = "ACTIVITY/%s", els = {"#p[0]"}), offsetIndex = 1, limitIndex = 2)` 其中offsetIdex和limitIndex對應查詢是的分頁參數。 比如查詢 offset=10, limit=10的數據,將首先嘗試從緩存中加載,如果緩存中僅有18條數據,則首先從緩存中加載 offset=10, limit=8的數據,然后將修改傳遞給方法調用的參數為 offset=18, limit =2 ,將剩余的數據補充上。 還支持另外兩個標注: /*** 寫入緩存,writeReutun表示將方法的返回結果寫入緩存。writeParameter表示將parameterIndex 制定的參數寫入緩存 ***/ @WriteCache(key = @CacheKey(template = "ACTIVITY/%s", els = {"#p[0]"}), writeReturn=true) @WriteCache(key = @CacheKey(template = "ACTIVITY/%s", els = {"#p[0]"}), writeParameter=true, writeReturn = false, parameterIndex = 0) /*** 將緩存寫入list類型的緩存 ***/ @WriteListCache(key = @CacheKey(template = "ACTIVITY/%s", els = {"#p[0]"}),writeParameter = true, writeReturn = false, parameterIndex = 0) 如果需要刪除緩存,則可以使用: @RemoveCache ## redis主從支持 ## redis proxy增加主從支持,所有寫操作通過主進行,讀操作通過從進行。配置如下: <bean id="masterRedisClient" class="com.skymobi.sns.cache.redis.RedisClient"> <constructor-arg value="${redis.url}"/> </bean> <bean id="slaveRedisClient" class="com.skymobi.sns.cache.redis.RedisClient"> <constructor-arg value="${redis-slave.url}"/> </bean> <bean id="layerCachePactory" class="com.skymobi.sns.cache.layer.LayerInterceptorFactory"> <constructor-arg ref="masterRedisClient"/> <constructor-arg ref="slaveRedisClient"/> </bean> 同時cacheproxy配置中增加緩存類型設定,可以為不同的場景指定使用不同類型的緩存,比如在有列表類型數據時使用redis,普通數據直接使用memcached。配置如下: <bean id="cacheProxy" class="com.skymobi.sns.cache.EasyCacheProxy"> <constructor-arg ref="layerCachePactory"/> <property name="factories"> <map> <entry key="default" value-ref="layerCachePactory"></entry> <entry key="memcached" value-ref="memcachedFactory"></entry> </map> </property> </bean> 此時如果在service類中直接使用@CacheProxy注解,默認將采用 default指定的緩存。 如果使用 @CacheProxy(type = "memcached") public class SpecialUserServiceImpl implements SpecialUserService {} 則在該service中將使用memcached作為緩存 ## 數據切分支持 ## 增加對一個根據key的規則,將數據劃分到不同服務器的功能。 同時提供一個工具,在規則重劃分時進行數據遷移。 如下: KeyRouter keyRouter = new DefaultKeyRouter("172.16.3.214:6379", ImmutableMap.of("SNS/TEST1/.*", "172.16.3.214:6379", "SNS/TEST2/.*", "172.16.3.214:6389" )); RedisClient client = new RedisClient(keyRouter); client.zadd("SNS/TEST1/1", "t3", 3.0); client.zadd("SNS/TEST2/2", "t1", 10.0); RedisClient client1 = new RedisClient("172.16.3.214:6379"); RedisClient client2 = new RedisClient("172.16.3.214:6389"); assertFalse(client1.exists("SNS/TEST2/2")); //"SNS/TEST2/2" 只會出現在172.16.3.214:6389上 assertFalse(client2.exists("SNS/TEST1/1")); //"SNS/TEST1/1" 只會出現在172.16.3.214:6379上 遷移數據: RedisReplicationTool.copy("172.16.3.214:6379", "172.16.3.214:6389", "SNS/TEST1/*"); RedisReplicationTool.copy("172.16.3.214:6389", "172.16.3.214:6379", "SNS/TEST2/*"); ## 代碼重構 ## 代碼做了大的重構,簡單清晰了很多。 移除不需要的factory類 增加一個混合緩存類型,現在可以在Cache注解中為每一個cache指定要采用的緩存類型 移除Cache注解中的layer屬性。 暫時放棄對本地cache的支持 現在最完整的使用方式下,配置文件應該是這樣的: <bean id="memcachedClient" class="net.spy.memcached.spring.MemcachedClientFactoryBean"> <property name="servers" value="${cache.server}"/> <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="1000"/> <property name="timeoutExceptionThreshold" value="1998"/> <property name="hashAlg" value="KETAMA_HASH"/> <property name="locatorType" value="CONSISTENT"/> <property name="failureMode" value="Redistribute"/> <property name="useNagleAlgorithm" value="false"/> </bean> <bean id="masterRedisClient" class="com.skymobi.sns.cache.redis.RedisClient"> <constructor-arg value="${redis.url}"/> </bean> <bean id="slaveRedisClient" class="com.skymobi.sns.cache.redis.RedisClient"> <constructor-arg value="${redis-slave.url}"/> </bean> <bean id="redisInterceptor" class="com.skymobi.sns.cache.redis.RedisInterceptor"> <constructor-arg ref="masterRedisClient"/> <constructor-arg ref="slaveRedisClient"/> </bean> <bean id="memcachedInterceptor" class="com.skymobi.sns.cache.memcached.MemcachedInterceptor"> <constructor-arg ref="memcachedClient"/> </bean> <bean id="hybridInterceptor" class="com.skymobi.sns.cache.hybrid.HybridInterceptor"> <property name="defaultInterceptor" ref="redisInterceptor"/> <property name="interceptors"> <map> <entry key="default" value-ref="redisInterceptor"></entry> <entry key="memcached" value-ref="memcachedInterceptor"></entry> </map> </property> </bean> <bean id="cacheProxy" class="com.skymobi.sns.cache.EasyCacheProxy"> <constructor-arg ref="hybridInterceptor"/> <property name="interceptors"> <map> <entry key="default" value-ref="hybridInterceptor"></entry> <entry key="memcached" value-ref="memcachedInterceptor"></entry> </map> </property> </bean> 如果使用了HybridInterceptor類型,則可以為每個cache指定不同的緩存類型: @WriteCache(key = @CacheKey(template = KEY_LIKECOUNT, els = {"#p[0].appId", "#p[0].contentId"}), type = "memcached") ## 切分功能改進 ## Cache代碼進行了修改,升級到1.1.0. 1. 增加了兩個redis緩存清理和遷移的groovy腳本 2. KeyRoute增加了一個FunctionRoute的實現。現在可以自定義函數來對key進行處理。默認提供了取模的實現。常見的場景是按照skyid取模,將不同用戶的數據存儲到不同的服務器上。 比如 有服務器s1,s2,s3,s4 4臺 ,按skyid 123456 % 4 = 0 ,則skyid=123456的用戶數據應該保存到 s1 這臺server上。 使用方式如下: Function function = new ModFunction(4); FunctionRouter keyRouter = new FunctionRouter( ImmutableMap.of( "SNS/TEST1/(\\d+)", function )); keyRouter.setHosts(Lists.newArrayList( "172.16.3.214:6379", "172.16.3.215:6379" ,"172.16.3.216:6379" ,"172.16.3.217:6379" )); keyRouter.setDefaultHost("172.16.3.214:6379"); String host = keyRouter.getHost("SNS/TEST1/123456"); assertEquals("172.16.3.214:6379", host); host = keyRouter.getHost("SNS/TEST1/123457"); assertEquals("172.16.3.215:6379", host); host = keyRouter.getHost("SNS/TEST1/123458"); assertEquals("172.16.3.216:6379", host); host = keyRouter.getHost("SNS/TEST1/123459"); assertEquals("172.16.3.217:6379", host); host = keyRouter.getHost("SNS/TEST1/123460"); assertEquals("172.16.3.214:6379", host); RedisClient client = new RedisClient(keyRouter); client.set("SNS/TEST1/123456", 1); int v = client.get("SNS/TEST1/123456", Integer.class); assertEquals(1, v); try{ client.set("SNS/TEST1/123457", 1); }catch (Exception e){ assertEquals(JedisConnectionException.class,e.getClass()); } ## key編寫方式改進 ## 1.1.1版本。 參照Spring3.1的實現,改進了CacheKey的定義,現在可以這樣: @Cache(key=@CacheKey(template="USER/${p0}")) expire現在支持用字符串表達式來指定,如下: exireTime = "1h" 可以使用的方式有 1d ---> 1天 1h ---> 1小時 1mn ---> 1分鐘 1s --> 1秒
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!