spring-data-redis -- 一次執(zhí)行鏈路的分析

前言

最近在項(xiàng)目中,使用到了 spring-data-redis ,基于他的一些實(shí)現(xiàn)原理與細(xì)節(jié),做一次學(xué)習(xí)

spring-redis 基于配置項(xiàng)的自動(dòng)裝載
redis 基于 bean 初始化的裝載
spring-data-redis 的基本用法,事務(wù)的事項(xiàng)方式,等

難免會(huì)有疏漏,不對(duì)之處,歡迎指出,一起探討

簡(jiǎn)介

Spring Data Redis, part of the larger Spring Data family, provides easy configuration and access to Redis from Spring applications. It offers both low-level and high-level abstractions for interacting with the store, freeing the user from infrastructural concerns

? --- 官網(wǎng)介紹

可以看出,提供統(tǒng)一的底層抽象封裝,簡(jiǎn)化操作邏輯,提供高低版本之間存儲(chǔ)庫之間的交互方式

spring-data-redis 的初始化

spring-data-redis 的初始化

先看下配置文件

只展示部分,就不一一列舉了

spring:
  redis:
    host: 127.0.0.1
    password: xxxxx
    jedis:
      pool:
        max-wait:
    ....

看下關(guān)于redis 配置類內(nèi)都有哪些元素 (yml 中的配置和配置類一一對(duì)應(yīng))

image

可以看到 在配置類中,主要分這幾個(gè)部分

1. 基本鏈接配置 database,url,host,passwrod 等
2. 客戶端配置 jedis,Lettuce
3. 鏈接池配置 Pool   
4. 集群配置 (本文暫不做過多,后期加上)

這一部分?jǐn)?shù)據(jù)在spirng 初始化的時(shí)候,會(huì)自動(dòng)進(jìn)行bean的裝載與注入 RedisAutoConfiguration,并根據(jù)當(dāng)前配置的redis 客戶端,進(jìn)行客戶端的統(tǒng)一封裝。并會(huì)初始化2個(gè)bean RedisTemplate 和 StringRedisTemplate

后續(xù)我們?cè)谑褂玫臅r(shí)候,只需要通過注入的方式,來進(jìn)行調(diào)用

大致流程如下

[圖片上傳失敗...(image-d19ed0-1584797731464)]

具體內(nèi)容,可查看 RedisAutoConfiguration 類

spring-data-redis序列化

在看了RedisAutoConfiguration 類之后,我們會(huì)發(fā)現(xiàn)初始化了2個(gè)bean RedisTemplate StringRedisTemplate

為什么會(huì)初始化 2個(gè)bean ?這兩者之間,有什么區(qū)別呢?

看代碼可以知道,做了如下操作

public class StringRedisTemplate extends RedisTemplate<String, String> {

    /**
     * Constructs a new <code>StringRedisTemplate</code> instance. {@link #setConnectionFactory(RedisConnectionFactory)}
     * and {@link #afterPropertiesSet()} still need to be called.
     */
    public StringRedisTemplate() {
        setKeySerializer(RedisSerializer.string());
        setValueSerializer(RedisSerializer.string());
        setHashKeySerializer(RedisSerializer.string());
        setHashValueSerializer(RedisSerializer.string());
    }

  .... 其余內(nèi)容省略
    }


==========================================================
  
  RedisTemplate 內(nèi)的 初始化操作
  
  @Override
    public void afterPropertiesSet() {

        super.afterPropertiesSet();

        boolean defaultUsed = false;

        if (defaultSerializer == null) {

            defaultSerializer = new JdkSerializationRedisSerializer(
                    classLoader != null ? classLoader : this.getClass().getClassLoader());
        }
  }

其實(shí)可以看到,這兩之間的唯一區(qū)別,是在于 序列化的設(shè)置,StringRedisTemplate 采取的是 string 序列化 Charset UTF_8 = Charset.forName("UTF-8");

而 RedisTemplate 采取的是 默認(rèn)的 jdk 序列化 默認(rèn)的jdk 序列化。我們可以看下對(duì)比

循環(huán)1000 次 ,寫入一個(gè)list

  for (long i = 0; i < 1000; i++) {

            GoodsCache goodsCache = GoodsCache.builder().goodsId(i)
                    .storeId(1001L)
                    .build();

            stringRedisTemplate.opsForList().leftPush("goods:jdk", JSON.toJSONString(goodsCache));
        }

采取 redisTemplate 的結(jié)果 37 bytes

image

采取 stringRedisTemplate 的結(jié)果 30 bytes

image

并且我們可以看到,基于 默認(rèn)序列化的處理之后,可讀性也較差。我們?cè)谶x取 RedisTemplate

之前,需要去指定序列化方式,并且也可以實(shí)現(xiàn)自己的序列化方式,如 protobuf 等

目前實(shí)序列化如下

image

如果項(xiàng)目中都是string 類型的存儲(chǔ)結(jié)構(gòu),則直接選取 stringRedisTemplate 就可以。

spring-data-redis 的 封裝實(shí)現(xiàn)

我們所有的入口,都是基于 redisTemplate 來進(jìn)行交互操作的,我們看下 redisTemplate 的實(shí)現(xiàn) 放一張類圖

image

可以看到,RedisTemplate 主要是有3部分

  • 繼承自RedisAccessor 實(shí)現(xiàn)了 InitializingBean 來去做一些 bean的初始化時(shí)候操作,如 序列化方式的設(shè)置等
  • 實(shí)現(xiàn)了BeanClassLoaderAware 接口 來去設(shè)置 類加載器 setBeanClassLoader
  • 實(shí)現(xiàn)了 RedisOperations 接口,該接口定義了一些列的redis 交互操作,所有的redis 交互,都由該接口的實(shí)現(xiàn)來完成

RedisOperations

在該接口中,主要包含以下

execute 系列操作 ,pipline,批量,事務(wù)等
公有redis 抽象命令,如 deletd,keys 等
opsForXXX 基于 redis 數(shù)據(jù)結(jié)構(gòu)的封裝 一些數(shù)據(jù)結(jié)構(gòu)獨(dú)特的命令用法,需要 先 轉(zhuǎn)換為該對(duì)象之后,才可以進(jìn)行執(zhí)行
eg: (java doc) Returns the operations performed on list values.

抽其中的 ListOperations 接口,來看下他的實(shí)現(xiàn)以及 做了哪些事情

image

AbstractOperations

AbstractOperations 抽象類中,主要用來做一些序列化,和反序列化 以及 一個(gè)內(nèi)部抽象類 ValueDeserializingRedisCallback

abstract class ValueDeserializingRedisCallback implements RedisCallback<V> {
        private Object key;

        public ValueDeserializingRedisCallback(Object key) {
            this.key = key;
        }

        public final V doInRedis(RedisConnection connection) {
            byte[] result = inRedis(rawKey(key), connection);
            return deserializeValue(result);
        }

        @Nullable
        protected abstract byte[] inRedis(byte[] rawKey, RedisConnection connection);
    }

DefaultListOperations ListOperations 的實(shí)現(xiàn)類中,主要就是進(jìn)行一系列的原生命令的封裝和調(diào)用,具體看個(gè)栗子

    @Override
    public Long leftPush(K key, V value) {

        byte[] rawKey = rawKey(key);
        byte[] rawValue = rawValue(value);
        return execute(connection -> connection.lPush(rawKey, rawValue), true);
    }

     */
    @Override
    public V rightPop(K key) {

        return execute(new ValueDeserializingRedisCallback(key) {

            @Override
            protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
                return connection.rPop(rawKey);
            }
        }, true);
    }

可以很明顯的看到,做的操作有這樣幾步

  1. key ,value 轉(zhuǎn)換為 字節(jié)數(shù)組
  2. 通過 匿名內(nèi)部類的ValueDeserializingRedisCallback 的實(shí)現(xiàn) 來進(jìn)行具體的調(diào)用,doInRedis
  3. 通過 connection 來執(zhí)行的redis 原生命令

RedisConnection 接口的類圖

image

可以看到,RedisConnection 集成 RedisCommands 集成 一系列的 數(shù)據(jù)結(jié)構(gòu)相關(guān)的 commands 接口

不同客戶端的操作執(zhí)行,都會(huì)按照數(shù)據(jù)結(jié)構(gòu)的緯度,去實(shí)現(xiàn)對(duì)這一系列命令 commands 接口,實(shí)現(xiàn)對(duì)redis 的操作

具體可以看下面這張類圖

image

這樣的話,進(jìn)過層層的抽象繼承,最終實(shí)現(xiàn)了一個(gè)入口,多個(gè)不同客戶端的實(shí)現(xiàn)邏輯

看下Jedis 對(duì) rpush 的實(shí)現(xiàn)邏輯

@Override
    public Long rPush(byte[] key, byte[]... values) {

        Assert.notNull(key, "Key must not be null!");

        try {
            if (isPipelined()) {
                pipeline(connection.newJedisResult(connection.getRequiredPipeline().rpush(key, values)));
                return null;
            }
            if (isQueueing()) {
                transaction(connection.newJedisResult(connection.getRequiredTransaction().rpush(key, values)));
                return null;
            }
            return connection.getJedis().rpush(key, values);
        } catch (Exception ex) {
            throw convertJedisAccessException(ex);
        }
    }

在Jedis 以及 Lettuce 客戶端的封裝實(shí)現(xiàn)中,都提供了兩種模式的操作 **pipline & transaction **

支持管道操作 和 事物處理

經(jīng)過上邊的一路跟蹤,我們知道了,相關(guān)的底層操作,是如何進(jìn)行封裝和處理,那么在來看下,關(guān)于 前文提到的

最終的execute 是如何進(jìn)行實(shí)現(xiàn)的

最終的execute 方法的實(shí)現(xiàn),是在 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");
    
    // 獲取當(dāng)前 的工廠實(shí)例,jedis or Lettuce等
        RedisConnectionFactory factory = getRequiredConnectionFactory();
        RedisConnection conn = null;
        try {
         
      // 進(jìn)行是否開啟事務(wù)的傳播
            if (enableTransactionSupport) {
                // only bind resources in case of potential transaction synchronization
                conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
            } else {
                conn = RedisConnectionUtils.getConnection(factory);
            }
        
      // 事務(wù)相關(guān)處理
            boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);

      // 獲取鏈接
            RedisConnection connToUse = preProcessConnection(conn, existingConnection);

      // pipeline 相關(guān)處理
            boolean pipelineStatus = connToUse.isPipelined();
            if (pipeline && !pipelineStatus) {
                connToUse.openPipeline();
            }

            RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));
      
      // 具體的進(jìn)行redis 操作執(zhí)行,交由 最底層的 客戶端封裝層去執(zhí)行
            T result = action.doInRedis(connToExpose);

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

            // TODO: any other connection processing?
            return postProcessResult(result, connToUse, existingConnection);
        } finally {
      // 釋放當(dāng)前的鏈接資源
            RedisConnectionUtils.releaseConnection(conn, factory);
        }
    }

最后附一張調(diào)用鏈路分析圖

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容