SpringBoot,用200行代码完成一个一二级分布式缓存

Stella981
• 阅读 1450

     缓存系统的用来代替直接访问数据库,用来提升系统性能,减小数据库复杂。早期缓存跟系统在一个虚拟机里,这样内存访问,速度最快。 后来应用系统水平扩展,缓存作为一个独立系统存在,如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
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Stella981 Stella981
3年前
Nginx + lua +[memcached,redis]
精品案例1、Nginxluamemcached,redis实现网站灰度发布2、分库分表/基于Leaf组件实现的全球唯一ID(非UUID)3、Redis独立数据监控,实现订单超时操作/MQ死信操作SelectPollEpollReactor模型4、分布式任务调试Quartz应用
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
SpringBoot 2,用200行代码完成一个一二级分布式缓存
缓存系统的用来代替直接访问数据库,用来提升系统性能,减小数据库负载。早期缓存跟系统在一个虚拟机里,这样内存访问,速度最快。后来应用系统水平扩展,缓存作为一个独立系统存在,如redis,但是每次从缓存获取数据,都还是要通过网络访问才能获取,效率相对于早先从内存里获取,还是不够逆天快。如果一个应用,比如传统的企业应用,一次页面显示,要访问数次redis,那效果
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这