Jedis源碼分析(二)-Jedis的內部實現(Client,Pipeline,Transaction)

Jedis源碼分析共有四個章節,如下爲各章連接:html

  1. Jedis源碼分析(一)-Jedis介紹
  2. Jedis源碼分析(二)-Jedis類結構及實現
  3. Jedis源碼分析(三)- JedisCluster類結構及實現
  4. Jedis源碼分析(四)-JedisSentinel與ShardedJedis介紹

1 Jedis的類結構

​ 首先看Jedis的內部結構,圖2-1中用橘色框標出了主要支架, 爲突出主要架構,或有稍許內容沒有標出。java

圖片描述

圖1-1 Jedis的類結構

​ Jedis以輸入的命令參數是否爲二進制,將處理請求的具體實現部署在兩個類中,例如JedisBinaryJedisClientBinaryClient。與Redis服務器的鏈接信息(Socket,host,port……)封裝在Client的基類Connection中。BinaryJedis類中提供了Client,Pipeline和Transcation變量,對應3種請求模式。redis

2 Jedis的初始化流程

Jedis jedis = new Jedis("localhost", 6379, 15000);
Transaction t = jedis.multi();
Pipeline pipeline = jedis.pipelined();

​ Jedis經過傳入Redis服務器地址(host,port)開始初始化,而後在BinaryJedis裏實例化Client。Client經過Socket維持客戶端與Redis服務器的鏈接與溝通。
前文提到Transaction和Pipeline很類似,它們繼承同一個基類MultiKeyPipelineBase。區別在於Transaction在實例化的時候,就自動發送MULTI命令,開啓事務模式,而Pipeline則按狀況手動開啓,它們均依靠Client發送命令。如下是Transaction和Pipeline初始化的具體實現:segmentfault

//BinaryJedis類
public Transaction multi() {
client.multi();
transaction = new Transaction(client);
return transaction;
}
public Pipeline pipelined() {
pipeline = new Pipeline();
pipeline.setClient(client);
return pipeline;
}

​ 下面經過發送一個get key 的命令,看看,這三種模式是如何運轉的。服務器

3 Jedis工做模式的調用流程

3.1 client請求模式

​ 以get key 爲例,爲突出主要步驟,部分代碼略有縮減。
用法:架構

jedis.get("foo");

圖片描述
圖3-1 Clinet模式的時序圖 socket

圖片描述
圖3-2 Client模式的調用流程工具

​ 圖3-1和3-2是client模式下,發送請求,讀取回復的具體實現。能夠清楚看到,在每次發送命令前,會先經過connect()方法判斷是否已經鏈接,若未鏈接則進行以下操做:源碼分析

  1. 實例化Socket,並配置,
  2. 鏈接Socket,獲取OutputStream和InputStream
  3. 若是是SSL鏈接,則會經過SSLSocketFactory建立socket鏈接

Protocol是一個通信工具類,將Redis的各種執行關鍵字存儲爲靜態變量,能夠直觀調用命令,例如Protocol.Command.GET。同時,將命令包裝成符合Redis的統一請求協議,回覆消息的處理也是在這個類進行,先經過通信協提取出當次請求的回覆消息,將Object類型的消息,格式化爲String,List等具體類型,若是回覆消息有Error則以異常的形式拋出。ui

3.2 Pipeline和Transaction模式

圖片描述
圖3-3 Transaction和Pipeline的類結構

​ 圖3-3 是Transaction和Pipeline兩個類的的類結構。能夠看到Pipeline和Transaction都繼承自MultiKeyPipelineBase,其中,MultiKeyPipelineBasePipelineBase的區別在於處理的命令不一樣,內部均調用Client發送命令。從如下用例也能夠看出二者的操做也十分相似。Pipeline有一個內部類對象MultiResponseBuilder,前文提到,當Redis事務結束時,會以List的形式,一次性返回全部命令的執行結果。MultiResponseBuilder對象就是用於,當Pipeline開始其實模式後,在事務結束時,存儲全部返回結果。
Queable用一個LinkedList裝入每一個命令的返回結果,Response<T>是一個泛型,set(Object data)方法傳入格式化以前的結果,get()方法返回格式化以後的結果。
Pipeline的使用方法:

Pipeline p = jedis.pipelined();
//只發送命令,不讀取結果,此時的Response<T>沒有數據
Response<String> string = p.get("string");
Response<String> list = p.lpop("list");
Response<String> hash = p.hget("hash", "foo");
Response<Set<String>> zset = p.zrange("zset", 0, -1);
Response<String> set = p.spop("set");
//一次讀取全部response.此時的Response<T>有數據
p.sync();

assertEquals("foo", string.get());
assertEquals("foo", list.get());
assertEquals("bar", hash.get());
assertEquals("foo", zset.get().iterator().next());
assertEquals("foo", set.get());

Transactions使用方法:
//開啓事務
Transaction t = jedis.multi();
//命令進入服務端的待執行隊列
Response<String> string = t.get("string");
Response<String> list = t.lpop("list");
Response<String> hash = t.hget("hash", "foo");
Response<Set<String>> zset = t.zrange("zset", 0, -1);
Response<String> set = t.spop("set");
//發送EXEC指令,執行全部命令,並返回結果
t.exec();

assertEquals("foo", string.get());
assertEquals("foo", list.get());
assertEquals("bar", hash.get());
assertEquals("foo", zset.get().iterator().next());
assertEquals("foo", set.get());

圖片描述
圖3-4 Pipeline的調用時序圖
圖片描述
圖3-5 Pipeline的調用流程

​ 圖3-4,3-5 顯示了Pipeline從發送請求到讀取回復的具體實現,Transaction的實現與其相似,於是沒有另外作圖說明。由上圖可見,Pipeline經過Client發送命令,Client在sendCommand時,會同時執行pipelinedCommands++,記錄發送命令的條數(參見圖3-5)。以後,返回一個Response<T>實例,並將這個實例塞入了pipelinedResponses隊列中。Response<T>主要有3個屬性:

  1. 格式化前的回覆消息data,
  2. 格式化後的回覆消息response,
  3. 格式化方式builder。

​ 剛發送消息後,Response<T>裏面的dataresponse是空值,只有格式化的方式builderSync()用於一次性讀取全部回覆,首先調用client的getAll()方法,getAll()方法根據以前記錄的pipelinedCommands和Redis通信協議,讀取相同條數的回覆消息到一個List,並返回給Pipeline。隨後遍歷這個List,逐個將回復消息賦給pipelinedResponses中每一個Response<T>data

​ 在執行Response<T>.get()命令時,Response<T>裏面data已經有值了,可是是Object類型的,於是還要調用build()方法,作一次數據轉換,返回格式化以後的數據。

以上就是Pipeline的主要工做流程。Transaction的exec()方法和sync()很類似,下文爲exec()的具體實現。

public List<Object> exec() {
  // 清空inputstream裏面的全部數據,忽略QUEUED or ERROR回覆
  client.getMany(getPipelinedResponseLength());
  //發送EXEC指令,讓服務端執行全部命令
  client.exec();
  //事務結束
  inTransaction = false;
  //從inputStream中讀取全部回覆
  List<Object> unformatted = client.getObjectMultiBulkReply();
  if (unformatted == null) {
    return null;
  }
  //和sync()同樣
  List<Object> formatted = new ArrayList<Object>();
  for (Object o : unformatted) {
    try {
     formatted.add(generateResponse(o).get());
    } catch (JedisDataException e) {
    formatted.add(e);
    }
  }
  return formatted;
}

本節雖未將Pipeline和Transaction的方法實現盡述,但也大同小異。關鍵點在於理解第一章介紹的3類請求邏輯。

相關文章
相關標籤/搜索