spring redis序列化_spring-data-redis -- 一次执行链路的分析

c3927303219748bd9c3b999197eaf99e.png
  • 前言
  • 简介
  • spring-data-redis 的初始化
  • spring-data-redis序列化
  • spring-data-redis 的 封装实现

前言

最近在项目中,使用到了 spring-data-redis ,基于他的一些实现原理与细节,做一次学习

spring-redis 基于配置项的自动装载
redis 基于 bean 初始化的装载
spring-data-redis 的基本用法,事务的事项方式,等

难免会有疏漏,不对之处,欢迎指出,一起探讨

简介

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
​ --- 官网介绍

可以看出,提供统一的底层抽象封装,简化操作逻辑,提供高低版本之间存储库之间的交互方式

spring-data-redis 的初始化

spring-data-redis 的初始化

先看下配置文件

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

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

看下关于redis 配置类内都有哪些元素 (yml 中的配置和配置类一一对应)

68b8dfb0454d599db2bfd8d9c703253d.png

可以看到 在配置类中,主要分这几个部分

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

这一部分数据在spirng 初始化的时候,会自动进行bean的装载与注入 RedisAutoConfiguration,并根据当前配置的redis 客户端,进行客户端的统一封装。并会初始化2个bean RedisTemplate 和 StringRedisTemplate

后续我们在使用的时候,只需要通过注入的方式,来进行调用

大致流程如下

67ca5f9c38bba6695ee004c7266d09e2.png
具体内容,可查看 RedisAutoConfiguration 类

spring-data-redis序列化

在看了RedisAutoConfiguration 类之后,我们会发现初始化了2个bean RedisTemplate StringRedisTemplate

为什么会初始化 2个bean ?这两者之间,有什么区别呢?

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

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());
	}

  .... 其余内容省略
	}


==========================================================
  
  RedisTemplate 内的 初始化操作
  
  @Override
	public void afterPropertiesSet() {

		super.afterPropertiesSet();

		boolean defaultUsed = false;

		if (defaultSerializer == null) {

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

其实可以看到,这两之间的唯一区别,是在于 序列化的设置,StringRedisTemplate 采取的是 string 序列化 Charset UTF_8 = Charset.forName("UTF-8");

而 RedisTemplate 采取的是 默认的 jdk 序列化 默认的jdk 序列化。我们可以看下对比

循环1000 次 ,写入一个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 的结果 37 bytes

6aeebe0220f42019a9c2cb6c41b2fe57.png

采取 stringRedisTemplate 的结果 30 bytes

5f556c86e808a3f9c4193a43c25c6395.png

并且我们可以看到,基于 默认序列化的处理之后,可读性也较差。我们在选取 RedisTemplate

之前,需要去指定序列化方式,并且也可以实现自己的序列化方式,如 protobuf 等

目前实序列化如下

c13594640b09903a780acb021ee11f14.png

如果项目中都是string 类型的存储结构,则直接选取 stringRedisTemplate 就可以。

spring-data-redis 的 封装实现

我们所有的入口,都是基于 redisTemplate 来进行交互操作的,我们看下 redisTemplate 的实现 放一张类图

fddbaa9be6942d916cf907bf212874b2.png

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

  • 继承自RedisAccessor 实现了 InitializingBean 来去做一些 bean的初始化时候操作,如 序列化方式的设置等
  • 实现了BeanClassLoaderAware 接口 来去设置 类加载器 setBeanClassLoader
  • 实现了 RedisOperations 接口,该接口定义了一些列的redis 交互操作,所有的redis 交互,都由该接口的实现来完成

RedisOperations

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

execute 系列操作 ,pipline,批量,事务等
公有redis 抽象命令,如 deletd,keys 等
opsForXXX 基于 redis 数据结构的封装 一些数据结构独特的命令用法,需要 先 转换为该对象之后,才可以进行执行
eg: (java doc) Returns the operations performed on list values.

抽其中的 ListOperations 接口,来看下他的实现以及 做了哪些事情

c2ef456f16953bea3eb4d8834fdd16ee.png

AbstractOperations

AbstractOperations 抽象类中,主要用来做一些序列化,和反序列化 以及 一个内部抽象类 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 的实现类中,主要就是进行一系列的原生命令的封装和调用,具体看个栗子

@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 转换为 字节数组
  2. 通过 匿名内部类的ValueDeserializingRedisCallback 的实现 来进行具体的调用,doInRedis
  3. 通过 connection 来执行的redis 原生命令

RedisConnection 接口的类图

67d7c3b407db342008bfcc409586618a.png

可以看到,RedisConnection 集成 RedisCommands 集成 一系列的 数据结构相关的 commands 接口

不同客户端的操作执行,都会按照数据结构的纬度,去实现对这一系列命令 commands 接口,实现对redis 的操作

具体可以看下面这张类图

61cc08a3730a813e5d64a50e228041e2.png

这样的话,进过层层的抽象继承,最终实现了一个入口,多个不同客户端的实现逻辑

看下Jedis 对 rpush 的实现逻辑

@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 客户端的封装实现中,都提供了两种模式的操作 **pipline & transaction **

支持管道操作 和 事物处理

经过上边的一路跟踪,我们知道了,相关的底层操作,是如何进行封装和处理,那么在来看下,关于 前文提到的

最终的execute 是如何进行实现的

最终的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");
	
    // 获取当前 的工厂实例,jedis or Lettuce等
		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);

      // pipeline 相关处理
			boolean pipelineStatus = connToUse.isPipelined();
			if (pipeline && !pipelineStatus) {
				connToUse.openPipeline();
			}

			RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));
      
      // 具体的进行redis 操作执行,交由 最底层的 客户端封装层去执行
			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);
		}
	}

最后附一张调用链路分析图

a254e10ea27604d0cbf6437692135a59.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值