RedisTemplate设计理解

本文详细介绍了Spring集成Redis的RedisTemplate设计,包括资源管理、接口回调和思想总结。RedisTemplate采用ThreadLocal绑定线程与Redis连接,利用回调接口实现操作。文中还探讨了TransactionSynchronizationManager在资源管理中的作用,以及如何通过RedisCallback接口实现Redis操作。最后总结了RedisTemplate设计的两大理念:线程资源副本和回调接口的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. Spring对于redis的集成很大程度上方便了开发人员操作redis的各种数据。屏蔽了对于底层redis连接的获取,释放。Spring-data-redis主要集成了jedis第三方客户端的操作。
  2. redis中主要有5种数据类型,分别为String,List,Set,Map,sort set.每一种数据结构在spring中都封装成一个操作接口。类图如下:

    这里写图片描述

  3. 上述5个接口定义了redis操作行为,主要是面向上层应用操作,程序开发人员直接通过这些接口的实例进行操作redis。每一种数据接口都有一个默认的实现类,都是以Defalut开头,同时都继承了AbstractOperations抽象类。该父类中定义了RedisTemplate的应用,同时通过定义一个内部类来实现对回调接口RedisCall的引用。核心源码如下:

abstract class AbstractOperations<K, V> {

    // utility methods for the template internal methods
    // 内部类实现回调接口
    abstract class ValueDeserializingRedisCallback implements RedisCallback<V> {
        private Object key;

        public ValueDeserializingRedisCallback(Object key) {
            this.key = key;
        }
    // 采用模板方法设计,doInRedis中定义算法骨架,具体的子类实现inRedis功能
        public final V doInRedis(RedisConnection connection) {
            byte[] result = inRedis(rawKey(key), connection);
            return deserializeValue(result);
        }
  //   具体子类实现inRedis功能,作为模板方法的具体实现功能部分。
        protected abstract byte[] inRedis(byte[] rawKey, RedisConnection connection);
    }

    RedisTemplate<K, V> template;
}
  1. 抽象类AsbtractOperations中的抽象内部类ValueDeserializingRedisCallback 实现了回调接口RedisCall,该接口中定义了一个非常重要的方法就是doInRedis(RedisConnection connection)。这是spring-data-redis中非常核心的方法,后续会讲述。该抽象内部类ValueDeserializingRedisCallback 采用了模板方法思想,doInRedis方法为业务逻辑的主要执行入口,抽象方法inRedis,主要是具体的实现类去完成最终的redis操作。redis是一个Key-Value数据库,所以inRedis中方法参数都可以定义为key和RedisConnection两个参数。
  2. RedisConnection顾名思义,就是封装了redis连接,借鉴java.sql.connection思想,主要封装了底层操作redis的操作。
  3. 接口 RedisConnection继承了RedisCommand接口,而RedisCommand接口继承了一堆封装了redis操作命令的接口。RedisConnection是Redis操作的核心接口。
    这里写图片描述

  4. 通过UML类图可以发现RedisConnection接口具体实现主要有JedisConnection和JedisClusterConnection两个实现类。分别封装了redis操作,redis哨兵和RedisCluster操作。

  5. RedisTemplate中最核心的设计思想主要是两部分,第一是采用ThreadLocal将redis连接与线程请求绑定,即每个线程都会操作单独的redis连接。第二是采用回调的设计思想,将回调接口RedisCallback中具体的redis操作行为最终采用匿名内部类实现。即实现上述中ValueDeserializingRedisCallback 类中的抽象方法InRedis。这两个部分的设计完美的体现了Spring封装redis的操作核心理念。
  6. TransactionSynchronizationManager事物同步资源管理器,这个工具类是spring事物模块中的一个核心操作类。在spring-tx.jar中,可见这个工具类的重要性。这个工具类中有如下几个属性:
// map存放资源的信息,以key-value形式作为一个整体存储在ThreadLocal中
private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<Map<Object, Object>>("Transactional resources");

    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
            new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");

    private static final ThreadLocal<String> currentTransactionName =
            new NamedThreadLocal<String>("Current transaction name");

    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
            new NamedThreadLocal<Boolean>("Current transaction read-only status");

    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
            new NamedThreadLocal<Integer>("Current transaction isolation level");

    private static final ThreadLocal<Boolean> actualTransactionActive =
            new NamedThreadLocal<Boolean>("Actual transaction active");
  1. TransactionSynchronizationManager中的属性都是ThreadLocal类型变量,其中ThreadLocal中的存储对象类型有map息),String(当前事物名称)等。其中,事物资源又以map形式存储,即一个连接对应一个key。
  2. 该类中主要的方法有getResource(key)和bindResource(key,value)。这两个方法底层都是采用ThreadLocal进行存储。因为ThreadL储,数据结构为。对象类型的有map,set,String,boolean等。
  3. bindResource源码如下:
public static void bindResource(Object key, Object value) throws IllegalStateException {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        Assert.notNull(value, "Value must not be null");
        // 获取当前线程绑定的map对象,判断是否为空
        Map<Object, Object> map = resources.get();
        // set ThreadLocal Map if none found
        if (map == null) {
            map = new HashMap<Object, Object>();
            resources.set(map);
        }
        // 将应用的key,value存放在map中。
        Object oldValue = map.put(actualKey, value);
        // Transparently suppress a ResourceHolder that was marked as void...
        if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
            oldValue = null;
        }
        if (oldValue != null) {
            throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
                    actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
                    Thread.currentThread().getName() + "]");
        }
    }
  1. 通过源码可以看出ThreadLocal类型的resources获取当前线程的变量副本对象map,将当前key,value以map的形式存储在threadLocal中。
  2. getResource(key)源码如下:
public static Object getResource(Object key) {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        // 根据key真正获得资源的方法
        Object value = doGetResource(actualKey);
        if (value != null && logger.isTraceEnabled()) {
            logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
                    Thread.currentThread().getName() + "]");
        }
        return value;
    }

其中,doGetResource(key)源码如下:

private static Object doGetResource(Object actualKey) {
        // 1. 获取当前线程的map变量副本
        Map<Object, Object> map = resources.get();
        if (map == null) {
            return null;
        }
        //2. 根据key从map中获取value对象
        Object value = map.get(actualKey);
        // Transparently remove ResourceHolder that was marked as void...
        if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
            map.remove(actualKey);
            // Remove entire ThreadLocal if empty...
            if (map.isEmpty()) {
                resources.remove();
            }
            value = null;
        }
        return value;
    }

其中,resources.get()是从ThreadLocal中获取当前线程绑定的map对象,map.get(actualKey)才是根据当前actualKey获取真正的应用资源 。从整个最底层来说,就是map中嵌套一个map数据结构。

资源管理器具体使用

  1. 在RedisTemplate具体的代码中,主要是提现如下方法中使用资源绑定类:
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
// 1. 正常redis操作,俩boolean的值都为false
        Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
        Assert.notNull(action, "Callback object must not be null");
// 2. 根据工厂获取RedisConnectionFactory
        RedisConnectionFactory factory = getConnectionFactory();
        RedisConnection conn = null;
        try {
// 3. 事物支持,redis支持事物操作,没有事物则不执行此行代码
            if (enableTransactionSupport) {
                // only bind resources in case of potential transaction synchronization
                conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
            } else {
// 4. 获取redisConnection的工具类,获取redis连接的时候,就决定了是否支持事物,
//是否将factory与当前connection作为key-value绑定到threadLocal中。
                conn = RedisConnectionUtils.getConnection(factory);
            }
// 5. 检查当前获取的连接是否已经与当前线程绑定
            boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);
// 6. 不用管,默认返回当前连接
            RedisConnection connToUse = preProcessConnection(conn, existingConnection);
// 7. 是否管道支持
            boolean pipelineStatus = connToUse.isPipelined();
            if (pipeline && !pipelineStatus) {
                connToUse.openPipeline();
            }
// 8 目前默认返回当前连接,spring没有什么实现
            RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));
/**
**  真正实现redis操作的代码,回调接口中具体实现在spring中是由不同的匿名内部类实
*   现的。这个也是精华所在
*/          
            T result = action.doInRedis(connToExpose);

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

            // TODO: any other connection processing?
            return postProcessResult(result, connToUse, existingConnection);
        } finally {
    // 9 .关闭当前连接
            RedisConnectionUtils.releaseConnection(conn, factory);
        }
    }

上述源码中,由于数据库连接属于局部变量,在获取过程已经判断是否进行资源绑定。对于普通的redis操作其实是没有必要的,但是对于redis的事物操作是非常有必要进行判断的。

接口回调使用

对于spring中操作redis来说,都是采用RedisTemplate调用的String,set,map等对应的方法来获取ValueOperations,SetOperations,MapOperations等类型的对象(一些公共的redis操作可以直接使用redisTemplate),然后再通过他们进行redis的操作。即RedsiTemplate是Spring中操作redis的入口。

// 获取valueOperations具体类型,然后进行具体的操作
public ValueOperations<K, V> opsForValue() {
        if (valueOps == null) {
            valueOps = new DefaultValueOperations<K, V>(this);
        }
        return valueOps;
    }
// 获取ListOperations具体类型,然后进行具体的操作
    public ListOperations<K, V> opsForList() {
        if (listOps == null) {
            listOps = new DefaultListOperations<K, V>(this);
        }
        return listOps;
    }

RedisTemplate中接口回调内部实现主要体现在5中数据结构操作上。现在以DefaultValueOperations为例进行分析,该类中根据最开始的类图可以发现,它是继承AbstractOperations同时实现ValueOperations接口。同时这个父类中持有RedisTemplate的引用,同时还有一个抽象内部类实现了回调接口,可以认为是变异的实现回调接口。

abstract class AbstractOperations<K, V> {

    // utility methods for the template internal methods
    // 内部类实现了回调接口,可以认为变异的实现回调接口
    abstract class ValueDeserializingRedisCallback implements RedisCallback<V> {
        private Object key;

        public ValueDeserializingRedisCallback(Object key) {
            this.key = key;
        }
      // 采用模板方法设计,具体的实现类去完成inRedis中的算法功能
        public final V doInRedis(RedisConnection connection) {
            byte[] result = inRedis(rawKey(key), connection);
            return deserializeValue(result);
        }
       // 模板方法中的需要子类去完成的功能
        protected abstract byte[] inRedis(byte[] rawKey, RedisConnection connection);
    }
    // 持有RedisTemplate的引用
    RedisTemplate<K, V> template;

    AbstractOperations(RedisTemplate<K, V> template) {
        this.template = template;
    }
}

DefaultValueOperations中的每一个redis的方法操作,都需要去调用RedisTemplate中的execute方法,而execute方法中的参数为RedisCallback类型,通常是传递一个该接口的具体实现类实例就可以。现在可以通过匿名内部类去实现回调接口RedisCallback,实现inRedis方法。通过上面分析可以知道,每个匿名内部类都是根据模板方法思想设计实现的。可以通过时序图分析一下,使用RedisTemplate流程:
这里写图片描述
上述时序图中,ValueOperation中定义了RedisCallback内部抽象实现类。

思想总结

RedisTemplate的设计上总结出了两个重要的设计理念。
第一:对于多线程的共享的资源,可以采用ThreadLocal进行线程副本的拷贝,即每一个请求线程都拥有自己独特的资源副本。这种以空间换时间的方式能够更好的容纳更高的并发请求。
第二:回调的运用。通过分析源码可以看出,回调接口声明的方法一般都是直接声明业务逻辑的方法,设计回调接口可以让其他类或对象去调用该接口声明的方法。回调接口可以具体的实现类或者采用匿名内部类去实现。采用匿名内部类实现类时,最大的好处就是减少大量的实现类代码,使整个代码看起来整洁,优雅。通常回调接口作为方法中的参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值