天天看點

SpringBoot——RedisTemplate淺析(一)

RedisTemplate 了解分析

前言

最近在使用springboot整合redis的時候遇到了一些問題,問題大緻可以分為

  1. redisTemplate如何去得到連接配接然後操作redis,每次調用是否使用了不同的連接配接
  2. redisTemplate支援開啟事務,但是開啟了事務之後它如何保證都是使用一個連接配接,因為我們在redisTemplate中的操作是每次都使用

    redisTemplate.opsForXxx.xxx()

由于是第一次使用,對裡面的實作比較有興趣是以就翻了翻源碼。而且下面就先來看rediTemplate中的操作:

redisTemplate.opsForXxx.xxx()

  • 例如通過redisTemplate對redis中的hash進行put操作的過程中我們會調用redisTemplate.opsForHash.put()方法,這個opsForHash()方法實際上為我們傳回了一個DefaultHashOperations對象(工廠方法),然後通過這個Operations對象再去進行各種redis操作。

Operations對象

  • 工廠方法會預設為我們生成一個對應redis資料結構的例如DefaultHashOperations的一個執行個體,DefaultHashOperations類繼承自AbstractOperations,這個抽象類中有一個excute(),他會在具體的Operations對象中被調用來執行各種不同的操作。例如DefaultHashOperations中的put方法:
    @Override
    public void put(K key, HK hashKey, HV value) {
    
        byte[] rawKey = rawKey(key);
        byte[] rawHashKey = rawHashKey(hashKey);
        byte[] rawHashValue = rawHashValue(value);
    
        execute(connection -> {
            connection.hSet(rawKey, rawHashKey, rawHashValue);
            return null;
        }, true);
    }
               
    這裡調用的是Operations中的excute()方法。方法的參數是一個RedisCallback對象還有一個布爾值,但是這裡使用了一個lambda表達式,而且傳回值是null,有點看不懂是以選擇debug一下看看這個參數運作後的值是什麼。
    @Nullable
    	<T> T execute(RedisCallback<T> callback, boolean exposeConnection) {
    		return template.execute(callback, exposeConnection);
    	}
               
    發現運作的時候這個callback其實就是[email protected]對象,excute将lambda表達式整個作為參數當作一個RedisCallback對象傳進execute中。在lambda表達式中就書寫了我們對應需要的redis操作,這裡就是hSet,後面拿到連接配接connection之後會由connection對象再去調用redisHashCommond的hSet方法。而這個lambda表達式在execute方法中形參名稱也被改成action。下面具體看一下RedisTemplate中execute方法的源碼。

RedisTemplate中execute方法

@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 {
		// 判斷是否處于事務狀态,這個boolean值可以在配置RedisTemplate時設定開啟事務
        if (enableTransactionSupport) {
            // 這個方法會将連接配接綁定到目前這個事務中,後面再分析
            conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
        } else {
            conn = RedisConnectionUtils.getConnection(factory);
        }

        //這邊又是一些判斷,沒有仔細去看,推測就是判斷這個事務的連接配接是否正确有效之類的
        boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);

        RedisConnection connToUse = preProcessConnection(conn, existingConnection);

        //判斷是否是管道操作,這個變量再connection中可以拿到
        boolean pipelineStatus = connToUse.isPipelined();
        if (pipeline && !pipelineStatus) {
            connToUse.openPipeline();
        }
		
        // 這裡判斷是否是暴露連接配接,如果不是就建立代理什麼的,這個布爾值預設是穿了true,也沒有再仔細去看,等有機會再看看
        RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));
        
        // 這裡就是調用到lambda表達式了,在這一步會通過connection去操作redis然後傳回結果
        T result = action.doInRedis(connToExpose);

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

        // TODO: any other connection processing?
        return postProcessResult(result, connToUse, existingConnection);
    } finally {
        // 釋放連接配接 releaseConnection 後面會再看這個操作
        RedisConnectionUtils.releaseConnection(conn, factory, enableTransactionSupport);
    }
}
           
  • 這段代碼中重點就是這一句

    T result = action.doInRedis(connToExpose);

    這裡對傳進來的lambda表達式做了調用。(lambda函數不會再傳進去的時候馬上做調用)。而lambda是非常靈活的,對應任何操作都可以在Operations裡面編寫不同的lambda表達式,然後都來調用相同的接口,實作不同的操作。這裡可以來複習(預習)一下函數式接口。

函數式接口(參考 java 核心技術 卷I)

  • 對于隻有一個抽象方法的接口,需要使用這種接口的對象時,就可以提供一個lambda表達式。這種皆空稱為函數式接口。為了展示如何轉化為函數式接口,下面考慮Array.sort 方法。它的第二個參數需要一個comparator 執行個體, Comparator就是隻有一個抽象方法的函數式接口,是以可以提供一個lambda表達式
    Arrays.sort(words,
               (first,second)->first.length()-second.length());
               
    在底層,Arrays.sort方法會接收實作了Compatator的某個對象。在這個對象上調用compare方法會執行這個lambda表達式的體。
  • 看到這裡大概解答了自己的疑惑,對應RedisTemplate中就成了:redisTemplate.execute()方法會接收一個實作了RedisCallback的某個對象。在這個對象上調用doInRedis方法會執行lambda表達式的體。
    public interface RedisCallback<T> {
    	/**
    	 * Gets called by {@link RedisTemplate} with an active Redis connection. Does not need to care about activating or
    	 * closing the connection or handling exceptions.
    	 *
    	 * @param connection active Redis connection
    	 * @return a result object or {@code null} if none
    	 * @throws DataAccessException
    	 */
    	@Nullable
    	T doInRedis(RedisConnection connection) throws DataAccessException;
    }
               
  • 是以,

    T result = action.doInRedis(connToExpose);

    就會得到表達式運作之後的值,我們調用的式set方法,它就傳回一個null給了result。

redisTemplate事務

  • 最後來看下redisTemplate是怎麼支援事務的,要支援事務,在我們進入multi()到exec()送出事務之間,肯定要使用一個連接配接操作。在execute()中剛才看過這段代碼塊
    // 判斷是否處于事務狀态,這個boolean值可以在配置RedisTemplate時設定開啟事務
    if (enableTransactionSupport) {
        conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
    } else {
        conn = RedisConnectionUtils.getConnection(factory);
    }
               

    顯然,bindConnection()就是他做的一個綁定操作了。再看下它内部是怎麼實作的。

    RedisConnectionUtils.bindConnection()會調用一個方法是doGetConnection(), 部分代碼如下:

    public static RedisConnection bindConnection(RedisConnectionFactory factory, boolean enableTranactionSupport) {
    		return doGetConnection(factory, true, true, enableTranactionSupport);
    	}
    
    public static RedisConnection doGetConnection(RedisConnectionFactory factory, boolean allowCreate, boolean bind, boolean enableTransactionSupport) {
    
    		Assert.notNull(factory, "No RedisConnectionFactory specified");
    
    		RedisConnectionHolder connHolder = (RedisConnectionHolder) TransactionSynchronizationManager.getResource(factory);
    
    		if (connHolder != null) {
    			if (enableTransactionSupport) {
    				potentiallyRegisterTransactionSynchronisation(connHolder, factory);
    			}
    			return connHolder.getConnection();
    		}
               
    可以看到這裡就是使用了

    TransactionSynchronizationManager.getResource(factory)

    ,這個TransactionSynchronizationManager是spring中對事務的支援。再點進去,會看到調用了這個方法:
    @Nullable
    private static Object doGetResource(Object actualKey) {
        Map<Object, Object> map = (Map)resources.get();
        if (map == null) {
            return null;
        } else {
            Object value = map.get(actualKey);
            if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
                map.remove(actualKey);
                if (map.isEmpty()) {
                    resources.remove();
                }
                value = null;
            }
            return value;
        }
    }
               
    這裡由一個map,這個map肯定就是能将我們的事務跟這個連接配接綁定映射,再點進去看其實就是用了ThreadLocal将連接配接綁定到線程中:
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
               

後言

在使用過程中也查閱了一下部落格,看到有個部落客中說當redisTemplate開啟了事務支援後,會導緻使用這個redisTemplate中調用非事務操作無法解綁和關閉連接配接。看了下源碼好像确實是如此,而查閱的部落格中也建議将事務跟非事務的redisTemplate分開。而且在exec()之後也還是不會解綁連接配接,由于我是開啟了連接配接池的,是以隻要在事務操作後是用

RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());

接觸綁定就好。

剛才說的部落格:https://blog.csdn.net/qq_34021712/article/details/79606551

看這部分源碼的時候也是感覺很複雜,這裡面它每個方法的調用棧都比較深,debug也不太能整,而且一開始對lambda并不了解,一開始也看不懂。後續有時間可以再了解下函數程式設計以及redis的設計與實作。

如果有大佬發現錯誤,希望可以幫忙指出~

繼續閱讀