JAVA

使用SpringBoot 2实现分布式缓存

2017-12-01  本文已影响0人  闲大赋

说明

应用系统需要通过Cache来缓存不经常改变得数据来提高系统性能和增加系统吞吐量,避免直接访问数据库等低速存储系统。缓存的数据通常存放在访问速度更快的内存里或者是低延迟存取的存储器,服务器上。应用系统缓存,通常有如下作用:

Cache 组件和概念

Cache 通常有如下组件构成

Cache的单机应用

单机应用,Cache可以与应用系统位于一个虚拟机,这样的好处是访问速度最快。如果缓存数量大,可以通过一定的策略删除访问较少的缓存项,如 ehcache 就提供了删除访问较少数据,或则将较少访问数据暂存堆外或者硬盘上的功能

image.png

单机应用与缓存系统放在一起的缺点是缓存系统会占用大量内存,因此,对有些缓存项,如伪静态页面等,也可以使用分布式缓存,用来缓存较多的数据。应用通过高速局域网来访问,架构图如下

使用专有的Cache服务器

应用系统一般都是分布式应用,如果缓存位于虚拟机内,需要解决的问题是,当数据改变的时候,需要通知其他应用系统的缓存系统让其缓存失效或者重新加载缓存,ehcache就是通过Terracotta来组件一个缓存集群 。使用reids作为缓存,reids作为单独的缓存服务器,分布式应用系统通过局域网访问redis,如下图


image.png

使用一二级缓存服务器

使用Reids缓存,通过网络访问还是不如从内存中获取性能好,所以通常称为二级缓存,从内存中取得缓存数据称之为一级缓存。当应用系统需要查询缓存的时候,先从一级缓存里查找,如果有,则返回,如果没有查找到,则再查询二级缓存,架构图如下


image.png

Spring Boot 2 自带了前面俩种缓存的实现方式,本问将简单实现第三种,高速一二级缓存实现

高速一二级缓存实现

实现 TowLevelCacheManager

首先,创建创建一个新的缓存管理器,命名为TowLevelCacheManager,继承了Spring Boot的RedisCacheManager,重载decorateCache方法。返回的是我们新创建的LocalAndRedisCache 缓存实现。


    class TowLevelCacheManager extends RedisCacheManager {
        RedisTemplate redisTemplate;
        public TowLevelCacheManager(RedisTemplate redisTemplate,RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
            super(cacheWriter,defaultCacheConfiguration);
            this.redisTemplate = redisTemplate;
        }
        //使用RedisAndLocalCache代替Spring Boot自带的RedisCache
        @Override
        protected Cache decorateCache(Cache cache) {
            return new RedisAndLocalCache(this, (RedisCache) cache);
        }
    
      public void publishMessage(String cacheName) {
        this.redisTemplate.convertAndSend(topicName, cacheName);
      }
      // 接受一个消息清空本地缓存
      public void receiver(String name) {
        RedisAndLocalCache cache = ((RedisAndLocalCache) this.getCache(name));
        if(cache!=null){
          cache.clearLocal();
        }
      }
    
    }

在Spring Cache中,在缓存管理器创建好每个缓存后,都会调用decorateCache方法,这样缓存管理器子类有机会实现自己的扩展,在这段代码,返回了自定义的RedisAndLocalCache实现。 publishMessage方法提供个给Cache,用于当缓存更新的时候,使用Redis的消息机制通知其他分布式节点的一级别缓存。receiver方法对应于publishMessage方法,当收到消息后,会清空一节缓存。

创建RedisAndLocalCache

RedisAndLocalCache 是我们系统的核心,他实现了Cache接口,类,会实现如下操作。

RedisAndLocalCache 的构造如下


    class RedisAndLocalCache implements Cache {
      // 本地缓存提供
      ConcurrentHashMap<Object, Object> local = new ConcurrentHashMap<Object, Object>();
      RedisCache redisCache;
      TowLevelCacheManager cacheManager;
    
      public RedisAndLocalCache(TowLevelCacheManager cacheManager, RedisCache redisCache) {
        this.redisCache = redisCache;
        this.cacheManager = cacheManager;
      }
    
      @Override
      public String getName() {
        return redisCache.getName();
      }
    
      @Override
      public Object getNativeCache() {
        return redisCache.getNativeCache();
      }
    
      //其他get put evict方法参考后面代码到吗片段说明
    }

如上代码所示,RedisAndLocalCache 实现了Cache接口,并使用了真正的RedisCache作为其实现方法。其关键的get和put方法如下

    @Override
    public ValueWrapper get(Object key) {
      // 一级缓存先取
      ValueWrapper wrapper = (ValueWrapper) local.get(key);
      if (wrapper != null) {
        return wrapper;
      } else {
        // 二级缓存取
        wrapper = redisCache.get(key);
        if (wrapper != null) {
          local.put(key, wrapper);
        }
        return wrapper;
      }
    }
    
    @Override
    public void put(Object key, Object value) {
      System.out.println(value.getClass().getClassLoader());
      redisCache.put(key, value);
      //通知其他节点缓存更新
      clearOtherJVM();
    }
    @Override
    public void evict(Object key) {
      redisCache.evict(key);
      //通知其他节点缓存更新
      clearOtherJVM();
    }
    protected void clearOtherJVM() {
        cacheManager.publishMessage(redisCache.getName());
    }
    // 提供给CacheManager清空一节缓存
    public void clearLocal() {
      this.local.clear();
    }

变量local代表了一个简单的缓存实现, 使用了ConcurrentHashMap。其get方法有如下逻辑实现

put方法实现逻辑如下

为了简单起见,一级缓存的同步更新 仅仅是清空一级缓存而并非采用同步更新缓存项。一级缓存将在下一次get方法调用时会再次从Reids里加载最新数据。

一节缓存仅仅简单使用了Map实现,并未实现缓存的多种策略。因此,如果你的一级缓存如果需要各种缓存策略,还需要用一些第三方库或者自行实现,但大部分情况下TowLevelCacheManager都足够使用

缓存同步说明

当缓存发生改变的时候,需要通知分布式系统的TowLevelCacheManager的,清空一级缓存.这里使用Redis实现消息通知,关于Redis消息发布和订阅,参考Redis一章。
为了实现Redis的Pub/Sub 模式,我们需要在CacheConfig里添加一些代码,创建一个消息监听器

    //定义一个redis 的频道,默认叫cache,用于pub/sub
    @Value("${springext.cache.redis.topic:cache}")
    String topicName;
    //Redis message,参考Redis一章
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            MessageListenerAdapter listenerAdapter) {
      RedisMessageListenerContainer container = new RedisMessageListenerContainer();
      container.setConnectionFactory(connectionFactory);
      container.addMessageListener(listenerAdapter, new PatternTopic(topicName));
      return container;
    }

如上所示,需要配置文件配置 springext.cache.redis.topic,指定一个频道的名字,如果没有配置,默认的频道名称是cache。

配置一个监听器很简单,只需要实现MessageListenerAdapter,并注册到RedisMessageListenerContainer即可。

MessageListenerAdapter 需要实现onMessage方法,我们只需要获取消息内容,这里是指要清空的缓存名字,然后交给MyRedisCacheManager 来处理即可

    @Bean
    MessageListenerAdapter listenerAdapter(final TowLevelCacheManager cacheManager) {
      return new MessageListenerAdapter(new MessageListener() {
        public void onMessage(Message message, byte[] pattern) {
          byte[] bs = message.getChannel();
          try {
            //Sub 一个消息,通知缓存管理器,这里的type就是Cache的名字
            String type = new String(bs, "UTF-8");
            cacheManager.receiver(type);
          } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            // 不可能出错,忽略
          }
        }
      });
    }

将代码组合在一起

前三节分别实现了缓存管理器,缓存,还有缓存之间的同步,现在需要将缓存管理器配置为应用的缓存管理器,通过搭配@Configuration和@Bean实现

    @Configuration
    public class CacheConfig {
      @Bean
      public TowLevelCacheManager cacheManager(RedisTemplate redisTemplate) {
        //RedisCache需要一个RedisCacheWriter来实现读写Redis
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(redisTemplate.getConnectionFactory());
        /*SerializationPair用于Java和Redis之间的序列化和反序列化,我们这里使用自带的JdkSerializationRedisSerializer,并在反序列化过程中,使用当前的ClassLoader*/
        SerializationPair pair = SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(this.getClass().getClassLoader()));
        /*构造一个RedisCache的配置,比如是否使用前缀,比如Key和Value的序列化机制(*/
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        /*创建CacheManager,并返回给Spring 容器*/
        TowLevelCacheManager cacheManager = new TowLevelCacheManager(redisTemplate,writer,config);
        return cacheManager;
      }
    }

构造一个TowLevelCacheManager较为复杂,这是因为构造RedisCacheManager复杂导致的,构造RedisCacheManager需要如下俩个参数

如上代码实现了一二级缓存,行数不到200行代码。相对于自带的RedisCache来说,缓存效率更高。相对于专业的一二级缓存服务器来说,如Ehcache+Terracotta组合,更加轻量级

国内第一本讲述Spring Boo 2的书已经由本人编著出版,有兴趣的可以访问
http://item.jd.com/12214143.html 了解详情

上一篇下一篇

猜你喜欢

热点阅读