前言
最近在項(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))
可以看到 在配置類中,主要分這幾個(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
采取 stringRedisTemplate 的結(jié)果 30 bytes
并且我們可以看到,基于 默認(rèn)序列化的處理之后,可讀性也較差。我們?cè)谶x取 RedisTemplate
之前,需要去指定序列化方式,并且也可以實(shí)現(xiàn)自己的序列化方式,如 protobuf 等
目前實(shí)序列化如下
如果項(xiàng)目中都是string 類型的存儲(chǔ)結(jié)構(gòu),則直接選取 stringRedisTemplate 就可以。
spring-data-redis 的 封裝實(shí)現(xiàn)
我們所有的入口,都是基于 redisTemplate 來進(jìn)行交互操作的,我們看下 redisTemplate 的實(shí)現(xiàn) 放一張類圖
可以看到,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)以及 做了哪些事情
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);
}
可以很明顯的看到,做的操作有這樣幾步
- key ,value 轉(zhuǎn)換為 字節(jié)數(shù)組
- 通過 匿名內(nèi)部類的ValueDeserializingRedisCallback 的實(shí)現(xiàn) 來進(jìn)行具體的調(diào)用,doInRedis
- 通過 connection 來執(zhí)行的redis 原生命令
RedisConnection 接口的類圖
可以看到,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 的操作
具體可以看下面這張類圖
這樣的話,進(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)用鏈路分析圖