1. jedis對應(yīng)redis的四種工作模式
image.png
??????? 圖1-1是Jedis的主要模塊膝藕,Jedis,JedisCluster,JedisSentinel和ShardedJedis對應(yīng)了Redis的四種工作模式:Redis Standalone(單節(jié)點模式),Redis Cluster(集群模式),Redis Sentinel(哨兵模式)和Redis Sharding(分片模式)焕蹄。
2. jedis的三種請求模式
每個jedis實例對應(yīng)一個Redis節(jié)點,我們對jedis實例的每個操作寒屯,都相當(dāng)于redis-cli啟動客戶端的直接操作,無論是集群模式魄缚,哨兵模式,還是分片模式,內(nèi)部均為對Jedis實例的操作雕沿,所以了解jedis類的內(nèi)部結(jié)構(gòu)以及jedis實例的請求模式是掌握jedis框架的基礎(chǔ)。
? ?????? Jedis實例有3種請求模式猴仑,Pipeline审轮,Transaction和Client。
image.png
jedis實例通過Socket建立客戶端與服務(wù)端的長連接辽俗,往outputStream發(fā)送命令疾渣,從inputStream讀取回復(fù)。
Client模式就是常用的所見即所得崖飘,客戶端發(fā)送一個命令榴捡,阻塞等待服務(wù)端執(zhí)行,然后讀取返回結(jié)果朱浴。
Pipeline模式是一次性發(fā)送多個命令吊圾,最后一次取回所有的返回結(jié)果,這種模式通過減少網(wǎng)絡(luò)的往返時間和io讀寫次數(shù)翰蠢,大幅度提高通信性能街夭,但pipeline不支持原子性,要想保證原子性躏筏,可同時開啟事物模式。
Transaction模式即開啟Redis的事務(wù)管理呈枉,事務(wù)模式開啟后趁尼,所有的命令(除了exec,discard猖辫,multi和watch)到達(dá)服務(wù)端以后不會立即執(zhí)行酥泞,會進(jìn)入一個等待隊列,等待收到下述四個命令時執(zhí)行不同操作:
EXEC命令執(zhí)行時啃憎,服務(wù)器以先進(jìn)先出FIFO的方式執(zhí)行事務(wù)隊列中的命令芝囤,當(dāng)事務(wù)隊列的所有命令被執(zhí)行完之后,將回復(fù)隊列作為自己的執(zhí)行結(jié)果返回給客戶端辛萍,客戶端從事務(wù)狀態(tài)返回到非事務(wù)狀態(tài)悯姊。
DISCARD命令用于取消一個事務(wù),它清空客戶端的整個事務(wù)隊列贩毕,然后將客戶端從事務(wù)狀態(tài)調(diào)整到非事務(wù)狀態(tài)悯许。最后返回字符串 OK 給客戶端, 說明事務(wù)已被取消辉阶。
Redis 的事務(wù)是不可嵌套的先壕, 當(dāng)客戶端已經(jīng)處于事務(wù)狀態(tài)瘩扼, 而客戶端又再向服務(wù)器發(fā)送MULTI時, 服務(wù)器只是簡單地向客戶端發(fā)送一個錯誤垃僚, 然后繼續(xù)等待其他命令的入隊集绰。 MULTI命令的發(fā)送不會造成整個事務(wù)失敗, 也不會修改事務(wù)隊列中已有的數(shù)據(jù)谆棺。
WATCH只能在客戶端進(jìn)入事務(wù)狀態(tài)之前執(zhí)行栽燕, 在事務(wù)狀態(tài)下發(fā)送 WATCH命令會引發(fā)一個錯誤, 但它不會造成整個事務(wù)失敗包券, 也不會修改事務(wù)隊列中已有的數(shù)據(jù)(和前面處理 MULTI的情況一樣)纫谅。
2 jedis的類結(jié)構(gòu)
image.png
jedis以輸入的命令參數(shù)是否為二進(jìn)制,將處理請求的具體實現(xiàn)部署在兩個類中溅固,Jedis和BinaryJedis付秕, Client和BinaryClient。與Redis服務(wù)器的連接信息(Socket侍郭,host, port)封裝在Client的基類Connection中询吴,BinaryJedis類中提供了Client,Pipeline和Transaction變量亮元,對應(yīng)三種請求模式猛计。
3. jedis的初始化流程
Jedis jedis =newJedis("localhost",6379,15000);Transaction t = jedis.multi();Pipeline pipeline = jedis.pipelined();
? Jedis通過傳入Redis服務(wù)器地址(host,port)開始初始化,然后在BinaryJedis里實例化Client爆捞。Client通過Socket維持客戶端與Redis服務(wù)器的連接與溝通奉瘤。
Transaction和Pipeline很相似,他們繼承同一個基類MultiKeyPipelineBase煮甥。區(qū)別在于Transaction在實例化的時候盗温,會自動發(fā)送MULTI命令,開啟事務(wù)模式成肘,而Pipeline則按情況手動開啟卖局,他們都是依靠Client發(fā)送命令,下面通過發(fā)送一個get key的命令,看看這三種模式是如何運(yùn)轉(zhuǎn)的双霍。
//BinaryJedis類publicTransactionmulti(){client.multi();transaction =newTransaction(client);returntransaction;}publicPipelinepipelined(){pipeline =newPipeline();pipeline.setClient(client);returnpipeline;}
4. jedis工作模式的調(diào)用流程
4.1 Client模式的調(diào)用流程:
image.png
image.png
從上圖可以看出砚偶,在每次發(fā)送命令之前,會先通過connect()方法判斷是否已經(jīng)連接洒闸,如果未連接則:
實例化Socket染坯,并配置
連接Socket,獲取OutputStream和InputStream
如果是SSL連接丘逸,通過SSLSocketFactory創(chuàng)建socket連接
Protocol是一個通訊工具類酒请,將Redis的各類執(zhí)行關(guān)鍵字存儲為靜態(tài)變量,比如Protocol.Command.GET, 同時將命令包裝成符合Redis的統(tǒng)一請求協(xié)議鸣个,回復(fù)消息的處理也是在這個類進(jìn)行羞反,它先通過通訊協(xié)議提取出當(dāng)次請求的回復(fù)消息布朦,將Object類型的消息轉(zhuǎn)化為String,List 等具體類型,如果回復(fù)消息有Error則以異常的形式拋出昼窗。
4.2 pipeline調(diào)用模式
4.2.1 為什么會出現(xiàn)Pipeline是趴?
??????Redis本身是基于Request/Response協(xié)議的,正常情況下澄惊,客戶端發(fā)送一個命令唆途,等待Redis應(yīng)答,Redis在接收到命令掸驱,處理后應(yīng)答肛搬。在這種情況下,如果同時需要執(zhí)行大量的命令毕贼,那就是等待上一條命令應(yīng)答后再執(zhí)行温赔,這中間不僅僅多了RTT(Round Time Trip),而且還頻繁的調(diào)用系統(tǒng)IO鬼癣,發(fā)送網(wǎng)絡(luò)請求陶贼。
image.png
??????為了提升效率,這時候Pipeline出現(xiàn)了待秃,它允許客戶端可以一次發(fā)送多條命令拜秧,而不等待上一條命令執(zhí)行的結(jié)果,這和網(wǎng)絡(luò)的Nagel算法有點像(TCP_NODELAY選項)章郁。不僅減少了RTT枉氮,同時也減少了IO調(diào)用次數(shù)(IO調(diào)用涉及到用戶態(tài)到內(nèi)核態(tài)之間的切換)。
image.png
4.2.2 pipeline詳解
image.png
????上圖為Transaction和Pipeline兩個類的類結(jié)構(gòu)暖庄,可以看到Pipeline和Transaction都繼承MultikeyPipelineBase聊替,其中,MultiKeyPipelineBase和PipelineBase的區(qū)別在于處理的命令不同雄驹,內(nèi)部均調(diào)用Client發(fā)送命令,Pipeline有一個內(nèi)部類對象MultiResponseBuilder,當(dāng)Pipeline開啟事務(wù)后淹辞,其用于存儲所有返回結(jié)果医舆。Queable用一個LinkedList裝入每個命令的返回結(jié)果,Response<T>是一個泛型象缀,set(Object data)方法傳入格式化之前的結(jié)果蔬将,get()方法返回格式化之后的結(jié)果。
image.png
????上圖顯示了Pipeline從發(fā)送請求到讀取回復(fù)的具體實現(xiàn)央星,Pipeline通過Client發(fā)送命令(這時并未真正地發(fā)送命令霞怀,只是將命令放入了緩沖區(qū),緩沖區(qū)大小為8192byte莉给, 超過這個大小會自動將緩沖區(qū)的命令輸出到服務(wù)端)毙石,Client在sendCommand時廉沮,會同時執(zhí)行pipelinedCommands++,記錄發(fā)送命令的條數(shù)。之后徐矩,返回一個Response<T>實例滞时,并將這個實例塞入了pipelinedResponses隊列中。Response<T>主要有3個屬性:
格式化前的回復(fù)消息data,
格式化后的回復(fù)消息response,
格式化方式builder滤灯。
剛發(fā)送消息后坪稽,Response里面的data和response是空值,只有格式化的方式builder鳞骤。Sync()用于一次性讀取所有回復(fù)窒百,首先調(diào)用client的getAll()方法,getAll()方法根據(jù)之前記錄的pipelinedCommands和Redis通訊協(xié)議豫尽,getAll()之前先調(diào)用flush()刷新此輸出流并強(qiáng)制寫出所有緩沖的輸出字節(jié)(這個時候才真正地發(fā)送命令), 然后讀取相同條數(shù)的回復(fù)消息到一個List篙梢,并返回給Pipeline。隨后遍歷這個List,逐個將回復(fù)消息賦給pipelinedResponses中每個Response的data拂募。在執(zhí)行Response.get()命令時庭猩,Response里面data已經(jīng)有值了,但是是Object類型的陈症,因而還要調(diào)用build()方法蔼水,做一次數(shù)據(jù)轉(zhuǎn)換,返回格式化之后的數(shù)據(jù)录肯。