Redis - RedisTemplate不太一样的切换库实现

RedisTemplate不太一样的切换库实现

一丶缘由

  一个Redis实例有[0-15]共16个database, 默认情况下, redisTemplate只能配置一个database, 当服务应用需要使用另外配置来配置另外的redisTemplate. 由于配置多, 容易出错.这时就出现了"选库"的需求.

二丶RedisTemaplte的执行逻辑

    /**
     * Executes the given action object within a connection that can be exposed or not. Additionally, the connection can
     * be pipelined. Note the results of the pipeline are discarded (making it suitable for write-only scenarios).
     *
     * @param <T> return type
     * @param action callback object to execute
     * @param exposeConnection whether to enforce exposure of the native Redis Connection to callback code
     * @param pipeline whether to pipeline or not the connection for the execution
     * @return object returned by the action
     */
    @Nullable
    public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {

        Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
        Assert.notNull(action, "Callback object must not be null");

        RedisConnectionFactory factory = getRequiredConnectionFactory();
        RedisConnection conn = null;
        try {

            if (enableTransactionSupport) {
                // only bind resources in case of potential transaction synchronization
                conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
            } else {
                conn = RedisConnectionUtils.getConnection(factory);
            }

            boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);

            RedisConnection connToUse = preProcessConnection(conn, existingConnection);

            boolean pipelineStatus = connToUse.isPipelined();
            if (pipeline && !pipelineStatus) {
                connToUse.openPipeline();
            }

            RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));
            T result = action.doInRedis(connToExpose);

            // close pipeline
            if (pipeline && !pipelineStatus) {
                connToUse.closePipeline();
            }

            // TODO: any other connection processing?
            return postProcessResult(result, connToUse, existingConnection);
        } finally {
            RedisConnectionUtils.releaseConnection(conn, factory, enableTransactionSupport);
        }
    }

   主要分3步:

  1. 获取连接

扫描二维码关注公众号,回复: 8417338 查看本文章

    a. 如果开启了事务, 则从事务管理器根据connectionFatory和当前线程获取连接, 如果没有, 则用connectionFactory获取连接

    b. 没有开启事务, 直接从connectionFactory获取连接

  2. 用connection执行命令

    a. preProcessConnection  connection前置处理, 一般用于初始化

    b. action.doInRedis(connToExpose) 执行当前action

    c. postProcessResult(result, connToUse, existingConnection);  后置处理结果

  3. 释放连接

    RedisConnectionUtils.releaseConnection(conn, factory, enableTransactionSupport);

  可以看出,redisTemplate执行主要是围绕connection进行的,  主要是使用RedisConnectionFactory#getConnection()获取连接,  如果每次调用该方法获取到的都是不同的连接, 使用同一个RedisTemplate并不会出现线程安全问题, 然而接口定义并没有指出不同, LettuceConnectionFactory可以创建共享连接, 所以使用同一个RedisTemaplte进行选库操作有可能出现并发问题.LettuceConnection在同步状态下禁止选库操作(会抛出异常).

二丶实现一: 使用connection进行选库

  

   上图中的配置, 每次进行一次不同的库的操作, 就需要选择一次库, 因为操作一次前就移除了dbIndex

   注意: spring boot 2.0 默认使用lettuce连接, 在同步状态下不能进行select(dbIndex)操作

三丶实现二: 创建不同的redisTemplate

  直接使用配置创建不同的redisTemplate, 会出现配置过多的问题, 可以利用反射, BeanUtils进行属性复制, 达到相同配置的目的. 需要注意的是, 在RedisTemplate, ConnectionFactory等对象属性, 需要创建不同的对象实例, 以避免并发问题, 因为BeanUtils属性复制, 仅仅进行了引用复制, 还是可能会出现并发问题. 即, 可能出现并发问题的对象, 需要重新创建一份.

  

/**
 * 其他选库实现 https://www.jianshu.com/p/9d78cd1c46b6
 *
 *
 * 本类用于创建restTemplate相同配置,但dbIndex不同的RestTemplate, 可以理解为选库
 *
 * @author TimFruit
 * @date 20-1-2 下午2:31
 */
public class RedisDbSelectFactory {


    /**
     * 创建restTemplate相同配置,但dbIndex不同的RestTemplate, 可以理解为选库
     *
     * @param redisTemplate
     * @param dbIndex redis库
     * @return
     */
    public static RedisTemplate selectDb(RedisTemplate redisTemplate, int dbIndex){
        try {
            RedisTemplate dbSelectRedisTemplate=redisTemplate.getClass().getConstructor().newInstance();
            BeanUtils.copyProperties(redisTemplate, dbSelectRedisTemplate);


            RedisConnectionFactory connectionFactory=dbSelectRedisTemplate.getConnectionFactory();

            RedisConnectionFactory dbSelectConnectionFactory=null;
            if(connectionFactory instanceof LettuceConnectionFactory){
                dbSelectConnectionFactory= createLettuceDbSelectFactory((LettuceConnectionFactory)connectionFactory, dbIndex);
            }else if(connectionFactory instanceof JedisConnectionFactory){
                dbSelectConnectionFactory= createJedisDbSelectFactory((JedisConnectionFactory) connectionFactory, dbIndex);
            }else {
                throw new RuntimeException("不能识别类型"+connectionFactory.getClass());
            }

            dbSelectRedisTemplate.setConnectionFactory(dbSelectConnectionFactory);

            dbSelectRedisTemplate.afterPropertiesSet();
            return dbSelectRedisTemplate;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

    }


    // --------------------------------------
    // jedisConnectionFactory
    private static JedisConnectionFactory createJedisDbSelectFactory(JedisConnectionFactory connectionFactory, int dbIndex){

        JedisConnectionFactory dbSelectConnectionFactory=new JedisDbSelectConnectionFactory(dbIndex);
        BeanUtils.copyProperties(connectionFactory, dbSelectConnectionFactory);

        dbSelectConnectionFactory.afterPropertiesSet();
        return dbSelectConnectionFactory;
    }


    @Slf4j
    private static class JedisDbSelectConnectionFactory extends JedisConnectionFactory{

        private int pointDbIndex;

        public JedisDbSelectConnectionFactory(int pointDbIndex) {
            this.pointDbIndex = pointDbIndex;
        }

        /**
         * 替换原配置的dbIndex
         * @return
         */
        @Override
        public int getDatabase() {
            log.info("使用redis库{}",pointDbIndex);
            return pointDbIndex;
        }
    }




    // --------------------------------------
    // lettuceConnectionFactory, 创建后的connection在同步状态下不支持选择库 (connection#select),

    private static LettuceConnectionFactory createLettuceDbSelectFactory(LettuceConnectionFactory connectionFactory, int dbIndex){
        LettuceConnectionFactory dbSelectConnectionFactory=new LettuceDbSelectConnectionFactory(dbIndex);
        BeanUtils.copyProperties(connectionFactory, dbSelectConnectionFactory);

        dbSelectConnectionFactory.afterPropertiesSet();
        return dbSelectConnectionFactory;
    }

    @Slf4j
    private static class LettuceDbSelectConnectionFactory extends LettuceConnectionFactory{

        private int pointDbIndex;

        public LettuceDbSelectConnectionFactory(int pointDbIndex) {
            this.pointDbIndex = pointDbIndex;
        }

        /**
         * 替换原配置的dbIndex
         * @return
         */
        @Override
        public int getDatabase() {
            log.info("使用redis库{}",pointDbIndex);
            return pointDbIndex;
        }
    }


}

 配置使用不同的库:

    /**
     * key是String, value是json
     * 使用3号库
     * @param defaultRedisTemplate
     * @return
     */
    @Bean("default3RedisManager")
    public RedisManager<String, Object> default3RedisManager(RedisTemplate<String,Object> defaultRedisTemplate){
        RedisTemplate<String,Object> default3RedisTemplate=RedisDbSelectFactory.selectDb(defaultRedisTemplate, 3);
        return new RedisManager<>(default3RedisTemplate);
    }

 

   上图可以看出, keySerializer是同一个对象, connectionFactory则是不同的对象

  完整源码

四丶后记

  每次实现都有它的适用场景和短板, 需要使用得当, 否则会出现问题.

学习资料:

  spring-data-redis进行选库操作

猜你喜欢

转载自www.cnblogs.com/timfruit/p/12148677.html