`
sundoctor
  • 浏览: 324315 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

扩展spring boot cache实现redis一二级分布式缓存

 
阅读更多

 

系统为了提高数据访问速度,先将数据加载到redis缓存中,但是每次从缓存获取数据,要通过网络访问才能获取,效率还是不够逆天快。如果访问量很大,并发很高,性能不够快不说,还容易造成reids负载过高,redis的主机出现各种物理故障。因此,可以在redis前增加本地一级缓存,本地一级缓存和系统应用在同一个JVM内,这样速度最快,redis退居二线当作二级缓存。每次请求先从一级缓存读取数据,一级缓存没有数据,再从二级缓存读取,并同步到一级缓存里,通过redis的消息发布订阅通知其他client机器更新缓存。这同CPU的一级缓存,二级缓存是一个道理。

 

本文并不涉及spring boot cache的详细使用介绍,需要熟悉spring boot cache基本使用。对于spring boot cache详细使用介绍请上度娘。扩展比较简单,闲话少说,直接上代码。

 

pom.xml,加入spring boot cache依赖,本地一级缓存使用ehcache3,二级缓存使用redis

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.M7</version>
    </parent>
    <groupId>springboot</groupId>
    <artifactId>springboot-2level-cache</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.cache</groupId>
            <artifactId>cache-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
        </dependency>

        <!-- Only used to expose cache metrics -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/libs-milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>

 

appliaction.properties里配置属性

 

spring.cache.type=redis
spring.ext.cache.name=countries
spring.ext.cache.redis.topic=cache

 

本地一级缓存使用ehcache3ehcache3.xml配置

 

<config xmlns='http://www.ehcache.org/v3'
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:jsr107="http://www.ehcache.org/v3/jsr107"
		xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
							http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">
	<cache alias="countries">
		<expiry>
			<ttl unit="seconds">600</ttl>
		</expiry>
		<heap unit="entries">200</heap>
		<jsr107:mbeans enable-statistics="true"/>
	</cache>

</config>

 

创建LocalRedisCache 继承RedisCache,这里是实现redis一二级分布式缓存的核心,重载RedisCachegetputevictclean等方法,增加本地一级缓存的读写,增加pub方法,通过redis发布缓存更新消息通知其他 redis clent 更新缓存,增加sub方法,获取缓存更新消息更新缓存。

 

package org.springframework.data.redis.cache;

import org.springframework.cache.Cache;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.util.Assert;

import java.util.concurrent.Callable;

/**
 * extends RedisCache,增加本地一级缓存,redis作为二级缓存
 */
public class LocalRedisCache extends RedisCache {

    private final Cache localCache;//本地一级缓存
    private final RedisOperations redisOperations;//配合topicName,发布缓存更新消息
    private final String topicName;//redis topic ,发布缓存更新消息通知其他client更新缓存

    /**
     * Create new {@link RedisCache}.
     *
     * @param name            must not be {@literal null}.
     * @param cacheWriter     must not be {@literal null}.
     * @param cacheConfig     must not be {@literal null}.
     * @param localCache      must not be {@literal null}.
     * @param redisOperations must not be {@literal null}.
     * @param topicName       must not be {@literal null}.
     */
    protected LocalRedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig,
                              Cache localCache, RedisOperations redisOperations, String topicName) {

        super(name, cacheWriter, cacheConfig);

        Assert.notNull(localCache, "localCache must not be null!");
        Assert.notNull(redisOperations, "redisOperations must not be null!");
        Assert.hasText(topicName, "topicName must not be empty!");

        this.localCache = localCache;
        this.redisOperations = redisOperations;
        this.topicName = topicName;
    }

    @Override
    public synchronized <T> T get(Object key, Callable<T> valueLoader) {
        //先读取本地一级缓存
        T value = localCache.get(key, valueLoader);
        if (value == null) {
            //本地一级缓存不存在,读取redis二级缓存
            value = super.get(key, valueLoader);
            if (value != null) {
                //redis二级缓存存在,存入本地一级缓存
                localCache.put(key, value);
                //发布缓存更新消息通知其他client更新缓存
                pub(new UpdateMessage(key, value, UpdateMessage.Type.PUT));
            }
        }
        return value;
    }

    @Override
    public void put(Object key, Object value) {
        super.put(key, value);
        localCache.put(key, value);
        pub(new UpdateMessage(key, value, UpdateMessage.Type.PUT));
    }

    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        ValueWrapper vw1 = localCache.putIfAbsent(key, value);
        ValueWrapper vw2 = super.putIfAbsent(key, value);

        pub(new UpdateMessage(key, value, UpdateMessage.Type.PUTIFABSENT));

        return vw1 == null ? vw2 : vw1;
    }

    @Override
    public void evict(Object key) {
        localCache.evict(key);
        super.evict(key);
        pub(new UpdateMessage(key, UpdateMessage.Type.REMOVE));
    }

    @Override
    public void clear() {
        localCache.clear();
        super.clear();
        pub(new UpdateMessage(UpdateMessage.Type.CLEAN));
    }

    @Override
    public ValueWrapper get(Object key) {
        ValueWrapper valueWrapper = localCache.get(key);
        if (valueWrapper == null) {
            valueWrapper = super.get(key);
            if (valueWrapper != null) {
                localCache.put(key, valueWrapper.get());
                pub(new UpdateMessage(key, valueWrapper.get(), UpdateMessage.Type.PUT));
            }
        }
        return valueWrapper;
    }

    @Override
    public <T> T get(Object key, Class<T> type) {
        T value = localCache.get(key, type);
        if (value == null) {
            value = super.get(key, type);
            if (value != null) {
                localCache.put(key, value);
                pub(new UpdateMessage(key, value, UpdateMessage.Type.PUT));
            }
        }
        return value;
    }

    /**
     * 更新缓存
     *
     * @param updateMessage
     */
    public void sub(final UpdateMessage updateMessage) {

        if (updateMessage.getType() == UpdateMessage.Type.CLEAN) {
            //清除所有缓存
            localCache.clear();
            super.clear();
        } else if (updateMessage.getType() == UpdateMessage.Type.PUT) {
            //更新缓存
            localCache.put(updateMessage.getKey(), updateMessage.getValue());
            super.put(updateMessage.getKey(), updateMessage.getValue());
        } else if (updateMessage.getType() == UpdateMessage.Type.PUTIFABSENT) {
            //更新缓存
            localCache.putIfAbsent(updateMessage.getKey(), updateMessage.getValue());
            super.putIfAbsent(updateMessage.getKey(), updateMessage.getValue());
        } else if (updateMessage.getType() == UpdateMessage.Type.REMOVE) {
            //删除缓存
            localCache.evict(updateMessage.getKey());
            super.evict(updateMessage.getKey());
        }
    }

    /**
     * 通知其他 redis clent 更新缓存
     *
     * @param message
     */
    private void pub(final UpdateMessage message) {
        this.redisOperations.convertAndSend(topicName, message);
    }

}

 

创建新的缓存管理器,命名为LocalRedisCacheManager继承了Spring BootRedisCacheManager,重载createRedisCache方法。

 

package org.springframework.data.redis.cache;

import org.springframework.cache.Cache;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.util.Assert;

import java.util.Map;

public class LocalRedisCacheManager extends RedisCacheManager {

    private final RedisConnectionFactory connectionFactory;
    private final Cache localCache;
    private final RedisOperations redisOperations;
    private final String topicName;

    public LocalRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
                                  RedisConnectionFactory connectionFactory, Cache localCache, RedisOperations redisOperations,
                                  String topicName) {
        super(cacheWriter, defaultCacheConfiguration);


        this.connectionFactory = connectionFactory;
        this.localCache = localCache;
        this.redisOperations = redisOperations;
        this.topicName = topicName;

        check();
    }

    public LocalRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
                                  RedisConnectionFactory connectionFactory, Cache localCache,
                                  RedisOperations redisOperations, String topicName, String... initialCacheNames) {
        super(cacheWriter, defaultCacheConfiguration, initialCacheNames);


        this.connectionFactory = connectionFactory;
        this.localCache = localCache;
        this.redisOperations = redisOperations;
        this.topicName = topicName;

        check();
    }

    public LocalRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
                                  Map<String, RedisCacheConfiguration> initialCacheConfigurations,
                                  RedisConnectionFactory connectionFactory, Cache localCache,
                                  RedisOperations redisOperations, String topicName) {
        super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations);

        this.connectionFactory = connectionFactory;
        this.localCache = localCache;
        this.redisOperations = redisOperations;
        this.topicName = topicName;

        check();
    }

    @Override
    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
        return new LocalRedisCache(name, new DefaultRedisCacheWriter(connectionFactory),
                cacheConfig != null ? cacheConfig : RedisCacheConfiguration.defaultCacheConfig(),
                localCache, redisOperations, topicName);
    }

    public static LocalRedisCacheManager create(RedisConnectionFactory connectionFactory, Cache localCache, RedisOperations redisOperations, String topicName) {

        Assert.notNull(localCache, "localCache must not be null");
        Assert.notNull(connectionFactory, "connectionFactory must not be null");
        Assert.notNull(redisOperations, "redisOperations must not be null");
        Assert.notNull(topicName, "topicName must not be null");

        return new LocalRedisCacheManager(new DefaultRedisCacheWriter(connectionFactory),
                RedisCacheConfiguration.defaultCacheConfig(), connectionFactory, localCache, redisOperations, topicName);
    }


    private void check() {
        Assert.notNull(localCache, "localCache must not be null");
        Assert.notNull(connectionFactory, "connectionFactory must not be null");
        Assert.notNull(redisOperations, "redisOperations must not be null");
        Assert.notNull(topicName, "topicName must not be null");
    }


}

 

 

缓存配置实现,需要使用注解 @EnableCaching 打开缓存功能

package org.springframework.data.redis.cache;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.jcache.JCacheCacheManager;
import org.springframework.cache.jcache.JCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
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;

import java.io.IOException;

@Configuration
@EnableCaching
public class CacheConfig {

    @Value("${spring.ext.cache.name:countries}")
    private String localCacheName;

    @Value("${spring.ext.cache.redis.topic:cache}")
    private String topicName;

    @Bean
    public CacheManager jCacheCacheManager() {
        return new JCacheCacheManager(jCacheManagerFactoryBean().getObject());
    }

    @Bean
    public JCacheManagerFactoryBean jCacheManagerFactoryBean() {
        JCacheManagerFactoryBean jCacheManagerFactoryBean = new JCacheManagerFactoryBean();
        Resource resource = new ClassPathResource("ehcache3.xml");
        try {
            jCacheManagerFactoryBean.setCacheManagerUri(resource.getURI());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return jCacheManagerFactoryBean;
    }

    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory factory = new JedisConnectionFactory();
        return factory;
    }

    @Bean
    public RedisTemplate redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(jedisConnectionFactory);
        return redisTemplate;
    }

    @Primary
    @Bean
    public RedisCacheManager redisCacheManager(JedisConnectionFactory jedisConnectionFactory,
                                               RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = LocalRedisCacheManager.create(jedisConnectionFactory,
                jCacheCacheManager().getCache(localCacheName), redisTemplate, topicName);
        return cacheManager;
    }


    @Bean
    public RedisMessageListenerContainer container(JedisConnectionFactory jedisConnectionFactory,
                                                   MessageListenerAdapter listenerAdapter) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(jedisConnectionFactory);
        container.addMessageListener(listenerAdapter, new PatternTopic(topicName));

        return container;
    }

    @Bean
    public MessageListenerAdapter listenerAdapter(LocalRedisCacheManager cacheManager, RedisTemplate redisTemplate) {
        return new MessageListenerAdapter(new MessageListener() {
            @Override
            public void onMessage(Message message, byte[] pattern) {
                byte[] channel = message.getChannel();
                byte[] body = message.getBody();

                String cacheName = (String) redisTemplate.getStringSerializer().deserialize(channel);
                LocalRedisCache cache = (LocalRedisCache) cacheManager.getCache(cacheName);
                if (cache == null) {
                    return;
                }

                UpdateMessage updateMessage = (UpdateMessage) redisTemplate.getValueSerializer().deserialize(body);
                cache.sub( updateMessage);
            }
        });
    }

}

 

 

到些,实现基本完成,其他相关简单代码,参考附件。

分享到:
评论
1 楼 无忧何往 2019-01-18  
实际用了,发现有点问题;
1.redis发布的message应该带上cache name,这样方便获取;如果是按照文中用topic,也就是channel来获取每次都是一样的
2.构造RedisCacheManager时候应该传入EcacheManager
3.应该是自己实现一个cachemanager然后持有二级缓存(redisCacheManager)和一级缓存(EcacheManager)的引用;这样耦合不紧密

相关推荐

    spring boot+spring cache实现两级缓存(redis+caffeine)

    "Spring Boot+Spring Cache实现两级缓存(Redis+Caffeine)" 知识点一:缓存与两级缓存 缓存是将数据从读取较慢的介质上读取出来放到读取较快的介质上,如磁盘--&gt;内存。平时我们会将数据存储到磁盘上,如:数据库。...

    spring boot cache 整合 redis demo (redis windows 安装包,和redis desktop管理工具)

    spring boot cache 整合 redis demo (内包含 redis windows 安装包,和redis desktop 桌面 管理工具)

    spring boot整合redis实现shiro的分布式session共享的方法

    Spring Boot 整合 Redis 实现 Shiro 的分布式 Session 共享 Shiro 是一个优秀的 Java 安全框架,提供了强大的身份验证、授权和会话管理功能。然而,在分布式架构中,Shiro 的会话管理机制需要进行特殊处理,以便...

    Spring Boot框架集成Redis教程.doc版

    4. **使用 Redis**:在 Spring Boot 应用中,可以使用 `@Cacheable`、`@CacheEvict` 和 `@CachePut` 等注解来实现方法级别的缓存操作。例如,为一个方法添加缓存: ```java @Service public class UserService { ...

    Spring Boot项目利用Redis实现集中式缓存实例

    Spring Boot项目利用Redis实现集中式缓存实例是实现高性能、可扩展、高可用性的关键技术之一。本文将介绍如何在Spring Boot项目中利用Redis实现集中式缓存实例,提高系统性能和可扩展性。 1. Spring Boot项目初始化...

    springboot +shiro+redis实现session共享(方案二)1

    "Spring Boot + Shiro + Redis 实现 Session 共享方案二" 1. 概述 本文档旨在介绍如何使用 Spring Boot、Shiro 和 Redis 实现分布式 session 共享,以解决 Web 应用程序的登录 session 统一问题。 2. 相关依赖 ...

    Spring Boot整合Redis的完整步骤

    本文将详细介绍Spring Boot整合Redis的完整步骤,包括Spring Boot对Redis的支持、添加依赖、配置RedisTemplate和StringRedisTemplate、使用Redis缓存等。 一、Spring Boot对Redis的支持 Spring Boot对Redis的支持...

    Spring Cache手动清理Redis缓存

    Spring Cache是Spring框架中的一种缓存机制,它可以将缓存数据存储在Redis中。然而,在某些情况下,我们需要手动清理Redis缓存,以便释放内存空间或更新缓存数据。在本文中,我们将介绍如何使用Spring Cache手动清理...

    Spring Boot整合Spring Cache及Redis过程解析

    通过Spring Boot整合Spring Cache及Redis,我们可以实现一个高效、灵活的缓存机制,提高应用程序的性能和效率。 知识点: 1. 如何安装Redis在Windows平台上 2. 如何在Spring Boot工程中配置Redis 3. 如何使用...

    spring boot集成redis做为通用缓存的实战demo,帮助大家彻底掌握s-cache-practice.zip

    spring boot集成redis做为通用缓存的实战demo,帮助大家彻底掌握s-cache-practice

    Spring Cache整合Redis实现方法详解

    在Spring Boot中,RedisCacheManager是Spring Cache中的一种CacheManager实现,负责缓存数据的存储和读取。RedisCacheManager可以自动提供一个Bean,例如: ``` @Configuration @ConditionalOnClass...

    从零开始学Spring Boot

    1.38 Spring Boot集成Redis实现缓存机制 1.39 Spring Boot Cache理论篇 1.40 Spring Boot集成EHCache实现缓存机制 1.41 Spring Boot分布式Session状态保存Redis 1.42 Spring Boot Shiro权限管理 1.43 Spring Boot ...

    Spring Boot 2精髓带书签目录高清版

    包括使用Spring实现RESTful架构,在Spring Boot框架下使用Redis、MongoDB、ZooKeeper、Elasticsearch等流行技术,使用Spring Session实现系统水平扩展,使用Spring Cache提高系统性能。 2.面对系统模块增加,性能和...

    spring Boot 2 精髓

    另一方面,当系统模块增加,性能和吞吐量要求增加时,如何平滑地用Spring Boot实现分布式架构,也会在本书后半部分介绍,包括使用Spring实现RESTful架构,在Spring Boot框架下使用Redis、MongoDB、ZooKeeper、...

    Spring Cache使用RedisCache案例解析

    在Spring Boot项目中,使用Redis作为缓存的存储媒介,可以充分发挥Redis的高性能和高可扩展性。 在本案例中,我们将使用Spring Cache和RedisCache来实现缓存机制。首先,需要在pom.xml文件中添加相应的依赖项,包括...

    springboot+mybatisplus+redis+spring cache缓存

    使用redis和springcache实现数据缓存

    详解Spring boot使用Redis集群替换mybatis二级缓存

    Spring Boot 使用 Redis 集群替换 MyBatis 二级缓存 Spring Boot 作为当前流行的 Java Web 框架,提供了许多便捷的功能来快速开发 Web 应用程序。其中,缓存机制是 Spring Boot 中一个非常重要的组件,缓存机制...

    mybatis plus使用redis作为二级缓存的方法

    MyBatis Plus 是一个基于 MyBatis 的增强工具,提供了许多实用的功能,其中之一就是支持使用 Redis 作为二级缓存。本文将详细介绍如何使用 MyBatis Plus 将 Redis 作为二级缓存。 为什么使用 Redis 作为二级缓存 ...

    从0到1项目搭建-集成 Redis 配置MyBatis二级缓存

    基于 SpringBoot 从0搭建一个企业级开发项目,基于SpringBoot 的项目,并集成MyBatis-Plus、Redis、Druid、Logback ,并使用 Redis 配置 MyBatis 二级缓存。

Global site tag (gtag.js) - Google Analytics