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 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!