spring-data-redis -- 一次执行链路的分析

用户头像
PCMD
关注
发布于: 2020 年 05 月 27 日
spring-data-redis -- 一次执行链路的分析



[TOC]



前言



最近在项目中,使用到了 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 中的配置和配置类一一对应)





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



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



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



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



大致流程如下





具体内容,可查看 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





采取 stringRedisTemplate 的结果 30 bytes





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



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



目前实序列化如下





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



spring-data-redis 的 封装实现



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





可以看到,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 接口,来看下他的实现以及 做了哪些事情





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 接口的类图





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



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



具体可以看下面这张类图





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



看下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);
}
}



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





发布于: 2020 年 05 月 27 日 阅读数: 51
用户头像

PCMD

关注

我看青山多妩媚 2020.02.07 加入

还未添加个人简介

评论

发布
暂无评论
spring-data-redis -- 一次执行链路的分析