zoukankan      html  css  js  c++  java
  • 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 
  • 相关阅读:
    KooTeam
    nopCommerce架构分析系列(一)nopCommerce简介
    NServiceBus最流行的开源企业服务总线 for .Net资源学习篇
    How to become a software architect?
    DotNetMQ: A Complete Message Queue System for .NET
    CSLA.Net专注电子商务 – Focus on eCommerce
    .net framework从1.0说到4.0
    ERP/SCM
    泛型接口的协变和逆变
    HTML5学习
  • 原文地址:https://www.cnblogs.com/telwanggs/p/10809557.html
Copyright © 2011-2022 走看看