• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Spring Boot实现Redis同数据源动态切换DB | Spring Cloud 31

武飞扬头像
gmHappy
帮助6

一、前言

在某些业务场景下,需要多访问同一Redis数据源下的不同DB

Redis中默认提供了16个数据库(序号0-15),默认Redis使用的是db 0

此章节基于spring-boot-starter-data-redis模块,实现了Redis同数据源动态切换DB,具体功能如下:

  • 突破一个项目只能连接Redis一个DB的限制
  • 提供多种操作切换Redis DB的方式(@Autowired方式 和RedisSelectSupport方式)
  • 提供完善的代码使用示例

二、项目结构

学新通

  • RedisSelect:自定义注解,用于切换同一redis数据源下的不同DB Index

  • RedisSelectSupport:自定义切换DB操作,用于线程间传递DB Index

  • RedisIndexSelectAspect:自定义AOP切面,对RedisSelect注解方法进行拦截,调用RedisSelectSupport,利用RedisSelectSupport设置DB Index

  • RedisSelectTemplate:对原RedisTemplate进行封装,利用RedisSelectSupport获取DB Index值,在对Redis操作前实现DB的实际切换

  • RedisIndexSelectConfig:实现RedisSelectTemplate定义,修改Lettuce连接池属性支持动态切换DB

下文对各类的功能描述不进行重复介绍,请详细了解本章节。

本示例源码位于项目的redis/redis-multi-index模块下。

三、动态切换Redis DB实现

3.1 所需依赖

redis/redis-multi-index/pom.xml

<?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">
    <parent>
        <artifactId>redis</artifactId>
        <groupId>com.gm</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>redis-multi-index</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>
    </dependencies>

</project>

3.2 配置文件

src/main/resources/application.yml

server:
  port: 6008

spring:
  application:
    name: @artifactId@
  redis:
    database: 1
    host: 
    password: 
    port: 6379
    timeout: 60000
    lettuce:
      pool:
        max-active: -1
        max-idle: -1
        max-wait: -1
        min-idle: -1

logging:
  level:
    org:
      springframework:
        boot:
          autoconfigure:
            logging: info

3.3 自定义注解类@RedisSelect

com/gm/multi/redis/config/select/annotation/RedisSelect.java

import java.lang.annotation.*;

/**
 * 注解,用于切换同一redis数据源下的不同db index
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisSelect {
    /**
     * redis库   0 - 15  库
     *
     * @return
     */
    int index() default 0;
}

3.4 自定义切换支持类RedisSelectSupport

com/gm/multi/redis/config/select/RedisSelectSupport.java

/**
 * Redis 切换DB操作
 */
public class RedisSelectSupport {

    /**
     * 定义一个静态变量,用于线程间传递值
     */
    private static final ThreadLocal<Integer> DB_SELECT_CONTEXT = new ThreadLocal<>();

    public static void selectIndex(int db) {
        DB_SELECT_CONTEXT.set(db);
    }

    public static Integer getSelectIndex() {
        return DB_SELECT_CONTEXT.get();
    }
}

3.5 自定义切面类RedisIndexSelectAspect

com/gm/multi/redis/config/select/aspect/RedisIndexSelectAspect.java

import com.gm.multi.redis.config.select.RedisSelectSupport;
import com.gm.multi.redis.config.select.annotation.RedisSelect;
import com.gm.multi.redis.config.select.RedisSelectTemplate;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;

@Slf4j
@Aspect
@Component
public class RedisIndexSelectAspect {

    @Value("${spring.redis.database:0}")
    private int defaultIndex;

    /**
     * 创建RedisSelect对应的切面,来对标有注解的方法拦截
     *
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("@annotation(com.gm.multi.redis.config.select.annotation.RedisSelect)")
    @ConditionalOnBean(RedisSelectTemplate.class)
    public Object configRedis(ProceedingJoinPoint point) throws Throwable {
        int index = defaultIndex;
        try {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            RedisSelect config = method.getAnnotation(RedisSelect.class);
            if (config != null) {
                index = config.index();
            }
            RedisSelectSupport.selectIndex(index);
            return point.proceed();
        } finally {
            RedisSelectSupport.selectIndex(defaultIndex);
            log.debug("redis index reset {} to {}", index, defaultIndex);
        }
    }
}

3.6 封装支持切换DB的RedisSelectTemplate

com/gm/multi/redis/config/select/RedisSelectTemplate.java:.

import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;

public class RedisSelectTemplate<K, V> extends RedisTemplate<K, V> {

    @Override
    protected RedisConnection createRedisConnectionProxy(RedisConnection pm) {
        return super.createRedisConnectionProxy(pm);
    }

    /**
     * 在连接Redis之前做一些配置
     *
     * @param connection
     * @param existingConnection
     * @return
     */
    @Override
    protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
        Integer index = RedisSelectSupport.getSelectIndex();
        if (index != null) {
            //切换 redis db 到 其他的库
            connection.select(index);
        }
        return super.preProcessConnection(connection, existingConnection);
    }
}

3.7 自定义配置类RedisIndexSelectConfig

com/gm/multi/redis/config/select/index/RedisIndexSelectConfig.java

import com.gm.multi.redis.config.select.RedisSelectTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Slf4j
@Configuration(proxyBeanMethods = false)
public class RedisIndexSelectConfig {

    @Autowired(required = false)
    private LettuceConnectionFactory factory;

    @Bean
    @ConditionalOnMissingBean
    public RedisSelectTemplate<String, Object> redisIndexSelectTemplate() {

        /**
         * 使用默认注入的RedisConnectionFactory factory时,切换db时出现以下异常:
         *
         * java.lang.UnsupportedOperationException:Selecting a new database not supported due to shared connection.
         * Use separate ConnectionFactorys to work with multiple databases
         * 从默认RedisConnectionFactory factory注入,改为LettuceConnectionFactory factory,
         * 并通过factory.setShareNativeConnection(false)关闭共享链接
         */
        RedisSelectTemplate<String, Object> redisTemplate = new RedisSelectTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericToStringSerializer(Object.class));
        redisTemplate.setValueSerializer(new StringRedisSerializer());

        // 关闭共享链接
        factory.setShareNativeConnection(false);
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.afterPropertiesSet();
        log.info("实例化 SelectableRedisTemplate 对象完成");
        return redisTemplate;
    }
}

LettuceConnectionFactory默认创建的LettuceConnection共享同一个线程安全的本地连接处理非阻塞和非事物的操作。
shareNativeConnection 设置为 false可以让LettuceConnectionFactory每次创建一个专用连接。

四、测试业务搭建

4.1 多种操作切换Redis DB的方式

通过以下方式获取支持Redis同数据源可切换DB的操作类RedisSelectTemplate

@Autowired
private RedisSelectTemplate<String, Object> redisTemplate;

4.1.1 注解@RedisSelect方式

com/gm/multi/redis/controller/RedisSelectIndexontroller.java

    @RequestMapping("/one")
    @RedisSelect(index = 1)         //选择db1库
    public String selectOne() {
        redisTemplate.opsForValue().set("one", "one_"   System.currentTimeMillis());
        String one = (String) redisTemplate.opsForValue().get("one");
        return one;
    }

    @RequestMapping("/two")
    @RedisSelect(index = 2)         //选择db2库
    public String selectTwo() {
        redisTemplate.opsForValue().set("two", "two_"   System.currentTimeMillis());
        String two = (String) redisTemplate.opsForValue().get("two");
        return two;
    }

4.1.2 设置RedisSelectSupport方式

com/gm/multi/redis/controller/RedisSelectIndexontroller.java

    /**
     * 同一个方法中切换不同的redis库
     *
     * @return
     */
    @RequestMapping("/three")
    @RedisSelect(index = 2)         //选择db2库
    public String selectThree() {
        redisTemplate.opsForValue().set("two", "two_"   System.currentTimeMillis());
        String two = (String) redisTemplate.opsForValue().get("two");
        log.info(two);
        RedisSelectSupport.selectIndex(3);//此处切换到db3库
        redisTemplate.opsForValue().set("three", "three_"   System.currentTimeMillis());
        String three = (String) redisTemplate.opsForValue().get("three");
        log.info(three);
        return three;
    }

4.2 多线程测试

com/gm/multi/redis/controller/RedisSelectIndexontroller.java

    @Autowired
    RedisMultiIndexService redisMultiIndexService;

    @RequestMapping("/testMultiIndex")
    public String testMultiIndex() {
        Thread thread[] = new Thread[500];
        AtomicBoolean result = new AtomicBoolean(true);
        for (int i = 0; i < thread.length; i  ) {
            int finalI = i;
            thread[i] = new Thread(() -> {
                try {
                    redisMultiIndexService.testMultiIndex("Thread-"   finalI);
                } catch (Exception e) {
                    e.printStackTrace();
                    result.set(false);
                }
            });
            thread[i].setName("Thread-"   i);

        }
        for (int i = 0; i < thread.length; i  ) {
            thread[i].start();
        }
        return "";
    }

com/gm/multi/redis/service/RedisMultiIndexService.java

public interface RedisMultiIndexService {
    void testMultiIndex(String suffix);
}

com/gm/multi/redis/service/impl/RedisMultiIndexServiceImpl.java

import com.gm.multi.redis.config.select.RedisSelectSupport;
import com.gm.multi.redis.config.select.RedisSelectTemplate;
import com.gm.multi.redis.service.RedisMultiIndexService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class RedisMultiIndexServiceImpl implements RedisMultiIndexService {

    @Autowired
    private RedisSelectTemplate<String, Object> redisSelectTemplate;

    public void testMultiIndex(String suffix) {
        String defaultkv = "default-"   suffix   "-index";
        String twokv = "two-"   suffix   "-index";
        String threekv = "three-"   suffix   "-index";
        String fourkv = "two-"   suffix   "-index";
        //使用认连接
        RedisSelectSupport.selectIndex(10);
        redisSelectTemplate.opsForValue().set(defaultkv, defaultkv);

        //使用 two 连接
        RedisSelectSupport.selectIndex(11);
        redisSelectTemplate.opsForValue().set(twokv, twokv);

        //使用 three 连接
        RedisSelectSupport.selectIndex(12);
        redisSelectTemplate.opsForValue().set(threekv, threekv);

        //使用 four 连接
        RedisSelectSupport.selectIndex(13);
        redisSelectTemplate.opsForValue().set(fourkv, fourkv);

        //使用默认连接
        RedisSelectSupport.selectIndex(10);
        String value1 = String.valueOf(redisSelectTemplate.opsForValue().get(defaultkv));

        //使用 two 连接
        RedisSelectSupport.selectIndex(11);
        String value2 = String.valueOf(redisSelectTemplate.opsForValue().get(twokv));

        //使用 three 连接
        RedisSelectSupport.selectIndex(12);
        String value3 = String.valueOf(redisSelectTemplate.opsForValue().get(threekv));

        //使用 four 连接
        RedisSelectSupport.selectIndex(13);
        String value4 = String.valueOf(redisSelectTemplate.opsForValue().get(fourkv));

        log.info("suffix:{}     default={}    two={}   three={}    four={}", suffix, (defaultkv).equals(value1),
                (twokv).equals(value2), (threekv).equals(value3), (fourkv).equals(value4));
    }
}

4.3 测试效果

浏览器访问:http://127.0.0.1:6008/redis/index/one

学新通
浏览器访问:http://127.0.0.1:6008/redis/index/two

学新通
浏览器访问:http://127.0.0.1:6008/redis/index/three

学新通
浏览器访问:http://127.0.0.1:6008/redis/index/testMultiIndex
学新通

查看控制台输出暂未发现在切换Redis DB在多线程下,存在线程安全问题。

查看Redisclient信息:

学新通

五、源码

源码地址:https://gitee.com/gm19900510/springboot-cloud-example.git 中模块redis/redis-multi-index

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhkaaj
系列文章
更多 icon
同类精品
更多 icon
继续加载