1. jedis對(duì)應(yīng)redis的四種工作模式
?????? 圖1-1是Jedis的主要模塊,Jedis,JedisCluster,JedisSentinel和ShardedJedis對(duì)應(yīng)了Redis的四種工作模式:Redis Standalone(單節(jié)點(diǎn)模式),Redis Cluster(集群模式),Redis Sentinel(哨兵模式)和Redis Sharding(分片模式)溃斋。
2. jedis的三種請(qǐng)求模式
??????每個(gè)jedis實(shí)例對(duì)應(yīng)一個(gè)Redis節(jié)點(diǎn)陶舞,我們對(duì)jedis實(shí)例的每個(gè)操作,都相當(dāng)于redis-cli啟動(dòng)客戶端的直接操作,無(wú)論是集群模式,哨兵模式,還是分片模式惨险,內(nèi)部均為對(duì)Jedis實(shí)例的操作,所以了解jedis類(lèi)的內(nèi)部結(jié)構(gòu)以及jedis實(shí)例的請(qǐng)求模式是掌握jedis框架的基礎(chǔ)脊髓。
? ?????? Jedis實(shí)例有3種請(qǐng)求模式辫愉,Pipeline,Transaction和Client供炼。
?????? jedis實(shí)例通過(guò)Socket建立客戶端與服務(wù)端的長(zhǎng)連接一屋,往outputStream發(fā)送命令窘疮,從inputStream讀取回復(fù)。
?????? Client模式就是常用的所見(jiàn)即所得冀墨,客戶端發(fā)送一個(gè)命令闸衫,阻塞等待服務(wù)端執(zhí)行,然后讀取返回結(jié)果诽嘉。
?????? Pipeline模式是一次性發(fā)送多個(gè)命令蔚出,最后一次取回所有的返回結(jié)果,這種模式通過(guò)減少網(wǎng)絡(luò)的往返時(shí)間和io讀寫(xiě)次數(shù)虫腋,大幅度提高通信性能骄酗,但pipeline不支持原子性,要想保證原子性悦冀,可同時(shí)開(kāi)啟事物模式趋翻。
?????? Transaction模式即開(kāi)啟Redis的事務(wù)管理,事務(wù)模式開(kāi)啟后盒蟆,所有的命令(除了exec踏烙,discard,multi和watch)到達(dá)服務(wù)端以后不會(huì)立即執(zhí)行历等,會(huì)進(jìn)入一個(gè)等待隊(duì)列讨惩,等待收到下述四個(gè)命令時(shí)執(zhí)行不同操作:
- EXEC命令執(zhí)行時(shí),服務(wù)器以先進(jìn)先出FIFO的方式執(zhí)行事務(wù)隊(duì)列中的命令寒屯,當(dāng)事務(wù)隊(duì)列的所有命令被執(zhí)行完之后荐捻,將回復(fù)隊(duì)列作為自己的執(zhí)行結(jié)果返回給客戶端,客戶端從事務(wù)狀態(tài)返回到非事務(wù)狀態(tài)寡夹。
- DISCARD命令用于取消一個(gè)事務(wù)处面,它清空客戶端的整個(gè)事務(wù)隊(duì)列,然后將客戶端從事務(wù)狀態(tài)調(diào)整到非事務(wù)狀態(tài)要出。最后返回字符串 OK 給客戶端鸳君, 說(shuō)明事務(wù)已被取消农渊。
- Redis 的事務(wù)是不可嵌套的患蹂, 當(dāng)客戶端已經(jīng)處于事務(wù)狀態(tài), 而客戶端又再向服務(wù)器發(fā)送MULTI時(shí)砸紊, 服務(wù)器只是簡(jiǎn)單地向客戶端發(fā)送一個(gè)錯(cuò)誤传于, 然后繼續(xù)等待其他命令的入隊(duì)。 MULTI命令的發(fā)送不會(huì)造成整個(gè)事務(wù)失敗醉顽, 也不會(huì)修改事務(wù)隊(duì)列中已有的數(shù)據(jù)沼溜。
- WATCH只能在客戶端進(jìn)入事務(wù)狀態(tài)之前執(zhí)行, 在事務(wù)狀態(tài)下發(fā)送 WATCH命令會(huì)引發(fā)一個(gè)錯(cuò)誤游添, 但它不會(huì)造成整個(gè)事務(wù)失敗系草, 也不會(huì)修改事務(wù)隊(duì)列中已有的數(shù)據(jù)(和前面處理 MULTI的情況一樣)通熄。
2 jedis的類(lèi)結(jié)構(gòu)
jedis以輸入的命令參數(shù)是否為二進(jìn)制,將處理請(qǐng)求的具體實(shí)現(xiàn)部署在兩個(gè)類(lèi)中找都,Jedis和BinaryJedis唇辨, Client和BinaryClient。與Redis服務(wù)器的連接信息(Socket能耻,host, port)封裝在Client的基類(lèi)Connection中赏枚,BinaryJedis類(lèi)中提供了Client,Pipeline和Transaction變量晓猛,對(duì)應(yīng)三種請(qǐng)求模式饿幅。
3. jedis的初始化流程
Jedis jedis = new Jedis("localhost", 6379, 15000);
Transaction t = jedis.multi();
Pipeline pipeline = jedis.pipelined();
? Jedis通過(guò)傳入Redis服務(wù)器地址(host,port)開(kāi)始初始化,然后在BinaryJedis里實(shí)例化Client戒职。Client通過(guò)Socket維持客戶端與Redis服務(wù)器的連接與溝通栗恩。
Transaction和Pipeline很相似,他們繼承同一個(gè)基類(lèi)MultiKeyPipelineBase洪燥。區(qū)別在于Transaction在實(shí)例化的時(shí)候摄凡,會(huì)自動(dòng)發(fā)送MULTI命令,開(kāi)啟事務(wù)模式蚓曼,而Pipeline則按情況手動(dòng)開(kāi)啟亲澡,他們都是依靠Client發(fā)送命令,下面通過(guò)發(fā)送一個(gè)get key的命令,看看這三種模式是如何運(yùn)轉(zhuǎn)的纫版。
//BinaryJedis類(lèi)
public Transaction multi() {
client.multi();
transaction = new Transaction(client);
return transaction;
}
public Pipeline pipelined() {
pipeline = new Pipeline();
pipeline.setClient(client);
return pipeline;
}
4. jedis工作模式的調(diào)用流程
4.1 Client模式的調(diào)用流程:
從上圖可以看出床绪,在每次發(fā)送命令之前,會(huì)先通過(guò)connect()方法判斷是否已經(jīng)連接其弊,如果未連接則:
- 實(shí)例化Socket癞己,并配置
- 連接Socket,獲取OutputStream和InputStream
- 如果是SSL連接梭伐,通過(guò)SSLSocketFactory創(chuàng)建socket連接
??????Protocol是一個(gè)通訊工具類(lèi)痹雅,將Redis的各類(lèi)執(zhí)行關(guān)鍵字存儲(chǔ)為靜態(tài)變量,比如Protocol.Command.GET, 同時(shí)將命令包裝成符合Redis的統(tǒng)一請(qǐng)求協(xié)議糊识,回復(fù)消息的處理也是在這個(gè)類(lèi)進(jìn)行绩社,它先通過(guò)通訊協(xié)議提取出當(dāng)次請(qǐng)求的回復(fù)消息,將Object類(lèi)型的消息轉(zhuǎn)化為String,List 等具體類(lèi)型赂苗,如果回復(fù)消息有Error則以異常的形式拋出愉耙。
4.2 pipeline調(diào)用模式
4.2.1 為什么會(huì)出現(xiàn)Pipeline?
??????Redis本身是基于Request/Response協(xié)議的拌滋,正常情況下朴沿,客戶端發(fā)送一個(gè)命令,等待Redis應(yīng)答,Redis在接收到命令赌渣,處理后應(yīng)答魏铅。在這種情況下,如果同時(shí)需要執(zhí)行大量的命令坚芜,那就是等待上一條命令應(yīng)答后再執(zhí)行沦零,這中間不僅僅多了RTT(Round Time Trip),而且還頻繁的調(diào)用系統(tǒng)IO货岭,發(fā)送網(wǎng)絡(luò)請(qǐng)求路操。
??????為了提升效率,這時(shí)候Pipeline出現(xiàn)了千贯,它允許客戶端可以一次發(fā)送多條命令屯仗,而不等待上一條命令執(zhí)行的結(jié)果,這和網(wǎng)絡(luò)的Nagel算法有點(diǎn)像(TCP_NODELAY選項(xiàng))搔谴。不僅減少了RTT魁袜,同時(shí)也減少了IO調(diào)用次數(shù)(IO調(diào)用涉及到用戶態(tài)到內(nèi)核態(tài)之間的切換)。
4.2.2 pipeline詳解
????上圖為T(mén)ransaction和Pipeline兩個(gè)類(lèi)的類(lèi)結(jié)構(gòu)敦第,可以看到Pipeline和Transaction都繼承MultikeyPipelineBase峰弹,其中,MultiKeyPipelineBase和PipelineBase的區(qū)別在于處理的命令不同芜果,內(nèi)部均調(diào)用Client發(fā)送命令鞠呈,Pipeline有一個(gè)內(nèi)部類(lèi)對(duì)象MultiResponseBuilder,當(dāng)Pipeline開(kāi)啟事務(wù)后,其用于存儲(chǔ)所有返回結(jié)果右钾。Queable用一個(gè)LinkedList裝入每個(gè)命令的返回結(jié)果蚁吝,Response<T>是一個(gè)泛型,set(Object data)方法傳入格式化之前的結(jié)果舀射,get()方法返回格式化之后的結(jié)果窘茁。
????上圖顯示了Pipeline從發(fā)送請(qǐng)求到讀取回復(fù)的具體實(shí)現(xiàn),Pipeline通過(guò)Client發(fā)送命令(這時(shí)并未真正地發(fā)送命令脆烟,只是將命令放入了緩沖區(qū)山林,緩沖區(qū)大小為8192byte, 超過(guò)這個(gè)大小會(huì)自動(dòng)將緩沖區(qū)的命令輸出到服務(wù)端)邢羔,Client在sendCommand時(shí)驼抹,會(huì)同時(shí)執(zhí)行pipelinedCommands++,記錄發(fā)送命令的條數(shù)。之后张抄,返回一個(gè)Response<T>實(shí)例砂蔽,并將這個(gè)實(shí)例塞入了pipelinedResponses隊(duì)列中洼怔。Response<T>主要有3個(gè)屬性:
- 格式化前的回復(fù)消息data,
- 格式化后的回復(fù)消息response,
- 格式化方式builder署惯。
????剛發(fā)送消息后,Response<T>里面的data和response是空值镣隶,只有格式化的方式builder极谊。Sync()用于一次性讀取所有回復(fù)诡右,首先調(diào)用client的getAll()方法,getAll()方法根據(jù)之前記錄的pipelinedCommands和Redis通訊協(xié)議轻猖,getAll()之前先調(diào)用flush()刷新此輸出流并強(qiáng)制寫(xiě)出所有緩沖的輸出字節(jié)(這個(gè)時(shí)候才真正地發(fā)送命令), 然后讀取相同條數(shù)的回復(fù)消息到一個(gè)List帆吻,并返回給Pipeline。隨后遍歷這個(gè)List,逐個(gè)將回復(fù)消息賦給pipelinedResponses中每個(gè)Response<T>的data咙边。在執(zhí)行Response<T>.get()命令時(shí)猜煮,Response<T>里面data已經(jīng)有值了,但是是Object類(lèi)型的败许,因而還要調(diào)用build()方法王带,做一次數(shù)據(jù)轉(zhuǎn)換,返回格式化之后的數(shù)據(jù)市殷。