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

     緩存系統的用來代替直接訪問數據庫,用來提高系統性能,減少數據庫複雜。早期緩存跟系統在一個虛擬機裏,這樣內存訪問,速度最快。 後來應用系統水平擴展,緩存做爲一個獨立系統存在,如redis,可是每次從緩存獲取數據,都仍是要經過網絡訪問才能獲取,效率相對於早先從內存裏獲取,仍是差了點。若是一個應用,好比傳統的企業應用,一次頁面顯示,要訪問數次redis,那效果就不是特別好,所以,如今有人提出了一二級緩存。即一級緩存跟系統在一個虛擬機內,這樣速度最快。二級緩存位於redis裏,當一級緩存沒有數據的時候,再從redis裏獲取,並同步到一級緩存裏。java

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

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,增長一個本地緩存。spring

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

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

 

springext.cache.type=local2redis

# Redis服務器鏈接端口
spring.redis.host=172.16.86.56
spring.redis.port=6379
相關文章
相關標籤/搜索