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

Stella981 等级 804 0 0

     缓存系统的用来代替直接访问数据库,用来提升系统性能,减小数据库复杂。早期缓存跟系统在一个虚拟机里,这样内存访问,速度最快。 后来应用系统水平扩展,缓存作为一个独立系统存在,如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
收藏
评论区

相关推荐

springBoot集成redis
Redis作为一个Java后端面试中的一个常问考点,并且在项目中越来越常用,所以自己动手搭建了一个基于springboot集成redis做为数据缓存的demo(springboot集成mybatis、redis,并具有增删改查询接口)。关注微信公众号【菜鸟阿都】并回复:redis,可获得源码。后面也会继续深入研究redis相关知识,期待与大家一起学习交流。r
2.在spring boot中配置Redis
在java的 spring boot项目中使用Redis做缓存。 操作是 1.首先在POM文件中加入redis依赖, 2.在application.yml中添加redis的路径、端口和密码 spring: redis: host: xxx.xxx.xxx.xxx port: xxxxx password: xxxx 3.在confi
Spring Boot demo系列(十):Redis缓存
1 概述 ==== 本文演示了如何在`Spring Boot`中将`Redis`作为缓存使用,具体的内容包括: * 环境搭建 * 项目搭建 * 测试 2 环境 ==== * `Redis` * `MySQL` * `MyBatis Plus` 3 `Redis`安装 =========== `Redis`安装非常简单,以笔
Spring Boot 与 Kotlin 使用Redis数据库
Spring Boot中除了对常用的关系型数据库提供了优秀的自动化支持之外,对于很多NoSQL数据库一样提供了自动化配置的支持,包括:Redis, MongoDB, Elasticsearch, Solr和Cassandra。 使用Redis ------- Redis是一个开源的使用`ANSI C`语言编写、支持网络、可基于内存亦可持久化的日志型、`K
Spring Boot 整合 Spring Cache + Redis
1.安装redis   a.由于官方是没有Windows版的,所以我们需要下载微软开发的redis,网址:https://github.com/MicrosoftArchive/redis/releases   b.解压后,在redis根目录打开cmd界面,输入:redis-server.exe redis.windows.conf,启动redis(关闭
Spring Boot整合redis
一、添加依赖 ------ <!--SpringBoot整合redis的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-
Spring Boot集成Redis,这个坑把我害惨了!
![](https://oscimg.oschina.net/oscnet/836578aa-a315-4dfd-81af-46af9176a5d5.jpg) 最近项目中使用SpringBoot集成Redis,踩到了一个坑:从Redis中获取数据为null,但实际上Redis中是存在对应的数据的。是什么原因导致此坑的呢? 本文就带大家从SpringBoo
Spring Cloud + Redis 是如何实现点赞、取消点赞的?
* **文章结尾有彩蛋** * 一、Redis 缓存设计及实现 * 1.1 Redis 安装及运行 * 1.2 Redis 与 SpringBoot 项目的整合 * 1.3 Redis 的数据结构类型 * 1.4 点赞数据在 Re
Spring Data Redis 详解及实战一文搞定
SDR - Spring Data Redis的简称。 Spring Data Redis提供了从Spring应用程序轻松配置和访问Redis的功能。它提供了与商店互动的低级别和高级别抽象,使用户免受基础设施问题的困扰。 ### Spring Boot 实战 #### 引用依赖 <dependency> <groupId>or
SpringBoot集成redis + spring cache
Spring Cache集成redis的运行原理: Spring缓存抽象模块通过CacheManager来创建、管理实际缓存组件,当SpringBoot应用程序引入spring-boot-starter-data-redi依赖后吗,容器中将注册的是CacheManager实例RedisCacheManager对象,RedisCacheManager来负责创
SpringBoot集成redis + spring cache
Spring Cache集成redis的运行原理: Spring缓存抽象模块通过CacheManager来创建、管理实际缓存组件,当SpringBoot应用程序引入spring-boot-starter-data-redi依赖后吗,容器中将注册的是CacheManager实例RedisCacheManager对象,RedisCacheManager来负责创
springboot+session(redis)(另附上:ip地址无法访问Redis)
springboot的版本:<version>2.1.6.RELEASE</version> 搭建springboot框架涉及到session管理,交给springboot框架管理,同时为了以后分布式或集群等的扩展,故将session存储到Redis数据库中。 理解:: session交给spring管理, spring将session信息存储到
springboot之Redis的使用
spring boot对常用的数据库支持外,对nosql 数据库也进行了封装自动化。 redis介绍 ------- Redis是目前业界使用最广泛的内存数据存储。相比memcached,Redis支持更丰富的数据结构,例如hashes, lists, sets等,同时支持数据持久化。除此之外,Redis还提供一些类数据库的特性,比如事务,HA,主从库。
springboot使用redis缓存
### Redis 优势 > 本文部分步骤继承于springboot使用cache缓存,如果有不清楚的,请移驾[springboot使用cache缓存](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.jianshu.com%2Fp%2Fe9b40acb2993) > * 性
springboot操作redis基础说明
软件环境:springboot 1.5.2,redis 3.2.1 配置 == 在application.properties,增加redis的配置,主要配置项包括redis的ip,端口,密码等,具体如下: \# redis # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring