SpringBoot,用200行代碼完成一個一二級分布式緩存


緩存系統的用來代替直接訪問數據庫,用來提升系統性能,減小數據庫復雜。早期緩存跟系統在一個虛擬機里,這樣內存訪問,速度最快。 后來應用系統水平擴展,緩存作為一個獨立系統存在,如redis,但是每次從緩存獲取數據,都還是要通過網絡訪問才能獲取,效率相對於早先從內存里獲取,還是差了點。如果一個應用,比如傳統的企業應用,一次頁面顯示,要訪問數次redis,那效果就不是特別好,因此,現在有人提出了一二級緩存。即一級緩存跟系統在一個虛擬機內,這樣速度最快。二級緩存位於redis里,當一級緩存沒有數據的時候,再從redis里獲取,並同步到一級緩存里。

現在實現這種一二級緩存的也挺多的,比如 hazelcast,新版的Ehcache..不過,實際上,如果你用spring boot,手里又一個Redis,則不需要搞hazelcastEhcache,只需要200行代碼,就能在spring boot基礎上,提供一個一二級緩存,代碼如下:


import java.io.UnsupportedEncodingException; import java.util.concurrent.ConcurrentHashMap; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.data.redis.cache.RedisCache; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCachePrefix; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; @Configuration @Conditional(StarterCacheCondition.class) public class CacheConfig { @Value("${springext.cache.redis.topic:cache}") String topicName ; @Bean public MyRedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) { MyRedisCacheManager cacheManager = new MyRedisCacheManager(redisTemplate); cacheManager.setUsePrefix(true); return cacheManager; } @Bean RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.addMessageListener(listenerAdapter, new PatternTopic(topicName)); return container; } @Bean MessageListenerAdapter listenerAdapter(MyRedisCacheManager cacheManager ) { return new MessageListenerAdapter(new MessageListener(){ @Override public void onMessage(Message message, byte[] pattern) { byte[] bs = message.getChannel(); try { String type = new String(bs,"UTF-8"); cacheManager.receiver(type); } catch (UnsupportedEncodingException e) { e.printStackTrace(); // 不可能出錯 } } }); } class MyRedisCacheManager extends RedisCacheManager{ public MyRedisCacheManager(RedisOperations redisOperations) { super(redisOperations); } @SuppressWarnings("unchecked") @Override protected RedisCache createCache(String cacheName) { long expiration = computeExpiration(cacheName); return new MyRedisCache(this,cacheName, (this.isUsePrefix()? this.getCachePrefix().prefix(cacheName) : null), this.getRedisOperations(), expiration); } /** * get a messsage for update cache * @param cacheName */ public void receiver(String cacheName){ MyRedisCache cache = (MyRedisCache)this.getCache(cacheName); if(cache==null){ return ; } cache.cacheUpdate(); } //notify other redis clent to update cache( clear local cache in fact) public void publishMessage(String cacheName){ this.getRedisOperations().convertAndSend(topicName, cacheName); } } class MyRedisCache extends RedisCache{ //local cache for performace ConcurrentHashMap<Object,ValueWrapper> local = new ConcurrentHashMap<>(); MyRedisCacheManager cacheManager; public MyRedisCache(MyRedisCacheManager cacheManager,String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration) { super(name, prefix, redisOperations, expiration); this.cacheManager = cacheManager; } @Override public ValueWrapper get(Object key) { ValueWrapper wrapper = local.get(key); if(wrapper!=null){ return wrapper; }else{ wrapper = super.get(key); if(wrapper!=null){ local.put(key, wrapper); } return wrapper; } } @Override public void put(final Object key, final Object value) { super.put(key, value); cacheManager.publishMessage(super.getName()); } @Override public void evict(Object key) { super.evict(key); cacheManager.publishMessage(super.getName()); } @Override public ValueWrapper putIfAbsent(Object key, final Object value){ ValueWrapper wrapper = super.putIfAbsent(key, value); cacheManager.publishMessage(super.getName()); return wrapper; } public void cacheUpdate(){ //clear all cache for simplification local.clear(); } } } class StarterCacheCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( context.getEnvironment(), "springext.cache."); String env = resolver.getProperty("type"); if(env==null){ return false; } return "local2redis".equalsIgnoreCase(env.toLowerCase()); } }

代碼的核心在於spring boot提供一個概念CacheManager&Cache用來表示緩存,並提供了多達8種實現,但由於缺少一二級緩存,因此,需要在Redis基礎上擴展,因此實現了MyRedisCacheManger,以及MyRedisCache,增加一個本地緩存。

一二級緩存需要解決的的一個問題是緩存更新的時候,必須通知其他節點的springboot應用緩存更新。這里可以用Redis的 Pub/Sub 功能來實現,具體可以參考listenerAdapter方法實現。

使用的時候,需要配置如下,這樣,就可以使用緩存了,性能杠杠的好

 

springext.cache.type=local2redis

# Redis服務器連接端口 spring.redis.host=172.16.86.56 spring.redis.port=6379 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM