<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Redis:我是如何與客戶端進(jìn)行通信的

          共 5141字,需瀏覽 11分鐘

           ·

          2021-09-17 18:13

          點(diǎn)擊上方“服務(wù)端思維”,選擇“設(shè)為星標(biāo)

          回復(fù)”669“獲取獨(dú)家整理的精選資料集

          回復(fù)”加群“加入全國(guó)服務(wù)端高端社群「后端圈」


          作者 | Dr Hydra
          出品 | 碼農(nóng)參上


          江湖上說,天下武功,無堅(jiān)不摧,唯快不破,這句話簡(jiǎn)直是為我量身定制。

          我是一個(gè)Redis服務(wù),最引以為傲的就是我的速度,我的 QPS 能達(dá)到10萬(wàn)級(jí)別。

          在我的手下有數(shù)不清的小弟,他們會(huì)時(shí)不時(shí)到我這來存放或者取走一些數(shù)據(jù),我管他們叫做客戶端,還給他們起了英文名叫 Redis-client。

          有時(shí)候一個(gè)小弟會(huì)來的非常頻繁,有時(shí)候一堆小弟會(huì)同時(shí)過來,但是,即使再多的小弟我也能管理的井井有條。

          有一天,小弟們問我。

          想當(dāng)年,為了不讓小弟們拖垮我傲人的速度,在設(shè)計(jì)和他們的通信協(xié)議時(shí),我絞盡腦汁,制定了下面的三條原則:

          • 實(shí)現(xiàn)簡(jiǎn)單
          • 針對(duì)計(jì)算機(jī)來說,解析速度快
          • 針對(duì)人類來說,可讀性強(qiáng)

          為什么這么設(shè)計(jì)呢?先來看看一條指令發(fā)出的過程,首先在客戶端需要對(duì)指令操作進(jìn)行封裝,使用網(wǎng)絡(luò)進(jìn)行傳輸,最后在服務(wù)端進(jìn)行相應(yīng)的解析、執(zhí)行。

          這一過程如果設(shè)計(jì)成一種非常復(fù)雜的協(xié)議,那么封裝、解析、傳輸?shù)倪^程都將非常耗時(shí),無疑會(huì)降低我的速度。什么,你問我為什么要遵循最后一條規(guī)則?算是對(duì)于程序員們的饋贈(zèng)吧,我真是太善良了。

          我把創(chuàng)造出來的這種協(xié)議稱為 RESP (REdis Serialization Protocol)協(xié)議,它工作在 TCP 協(xié)議的上層,作為我和客戶端之間進(jìn)行通訊的標(biāo)準(zhǔn)形式。

          說到這,我已經(jīng)有點(diǎn)迫不及待想讓你們看看我設(shè)計(jì)出來的杰作了,但我好歹也是個(gè)大哥,得擺點(diǎn)架子,不能我主動(dòng)拿來給你們看。

          所以我建議你直接使用客戶端發(fā)出一條向服務(wù)器的命令,然后取出這條命令對(duì)應(yīng)的報(bào)文來直觀的看一下。話雖如此,不過我已經(jīng)被封裝的很嚴(yán)實(shí)了,正常情況下你是看不到我內(nèi)部進(jìn)行通訊的具體報(bào)文的,所以,你可以偽裝成一個(gè)Redis的服務(wù)端,來截獲小弟們發(fā)給我的消息。

          實(shí)現(xiàn)起來也很簡(jiǎn)單,我和小弟之間是基于 Socket 進(jìn)行通訊,所以在本地先啟動(dòng)一個(gè)ServerSocket,用來監(jiān)聽Redis服務(wù)的6379端口:

          public static void server() throws IOException {
              ServerSocket serverSocket = new ServerSocket(6379);
              Socket socket = serverSocket.accept();
              byte[] bytes = new byte[1024];
              InputStream input = socket.getInputStream();
              while(input.read(bytes)!=0){
                  System.out.println(new String(bytes));
              }
          }

          然后啟動(dòng)redis-cli客戶端,發(fā)送一條命令:

          set key1 value1

          這時(shí),偽裝的服務(wù)端就會(huì)收到報(bào)文了,在控制臺(tái)打印了:

          *3
          $3
          set
          $4
          key1
          $6
          value1

          看到這里,隱隱約約看到了剛才輸入的幾個(gè)關(guān)鍵字,但是還有一些其他的字符,要怎么解釋呢,是時(shí)候讓我對(duì)協(xié)議報(bào)文中的格式進(jìn)行一下揭秘了。

          我對(duì)小弟們說了,對(duì)大哥說話的時(shí)候得按規(guī)矩來,這樣吧,你們?cè)谡?qǐng)求的時(shí)候要遵循下面的規(guī)則:

          *<參數(shù)數(shù)量> CRLF
          $<參數(shù)1的字節(jié)長(zhǎng)度> CRLF
          <參數(shù)1的數(shù)據(jù)> CRLF
          $<參數(shù)2的字節(jié)長(zhǎng)度> CRLF
          <參數(shù)2的數(shù)據(jù)> CRLF
          ...
          $<參數(shù)N的字節(jié)長(zhǎng)度> CRLF
          <參數(shù)N的數(shù)據(jù)> CRLF

          首先解釋一下每行末尾的CRLF,轉(zhuǎn)換成程序語(yǔ)言就是\r\n,也就是回車加換行。看到這里,你也就能夠明白為什么控制臺(tái)打印出的指令是豎向排列了吧。

          在命令的解析過程中,setkey1value1會(huì)被認(rèn)為是3個(gè)參數(shù),因此參數(shù)數(shù)量為3,對(duì)應(yīng)第一行的*3

          第一個(gè)參數(shù)set,長(zhǎng)度為3對(duì)應(yīng)$3;第二個(gè)參數(shù)key1,長(zhǎng)度為4對(duì)應(yīng)$4;第三個(gè)參數(shù)value1,長(zhǎng)度為6對(duì)應(yīng)$6。在每個(gè)參數(shù)長(zhǎng)度的下一行對(duì)應(yīng)真正的參數(shù)數(shù)據(jù)。

          看到這,一條指令被轉(zhuǎn)換為協(xié)議報(bào)文的過程是不是就很好理解了?

          當(dāng)小弟對(duì)我發(fā)送完請(qǐng)求后,作為大哥,我就要對(duì)小弟的請(qǐng)求進(jìn)行指令回復(fù)了,而且我得根據(jù)回復(fù)內(nèi)容進(jìn)行一下分類,要不然小弟該搞不清我的指示了。

          簡(jiǎn)單字符串

          簡(jiǎn)單字符串回復(fù)只有一行回復(fù),回復(fù)的內(nèi)容以+作為開頭,不允許換行,并以\r\n結(jié)束。有很多指令在執(zhí)行成功后只會(huì)回復(fù)一個(gè)OK,使用的就是這種格式,能夠有效的將傳輸、解析的開銷降到最低。

          錯(cuò)誤回復(fù)

          在RESP協(xié)議中,錯(cuò)誤回復(fù)可以當(dāng)做簡(jiǎn)單字符串回復(fù)的變種形式,它們之間的格式也非常類似,區(qū)別只有第一個(gè)字符是以-作為開頭,錯(cuò)誤回復(fù)的內(nèi)容通常是錯(cuò)誤類型及對(duì)錯(cuò)誤描述的字符串。

          錯(cuò)誤回復(fù)出現(xiàn)在一些異常的場(chǎng)景,例如當(dāng)發(fā)送了錯(cuò)誤的指令、操作數(shù)的數(shù)量不對(duì)時(shí),都會(huì)進(jìn)行錯(cuò)誤回復(fù)。在客戶端收到錯(cuò)誤回復(fù)后,會(huì)將它與簡(jiǎn)單字符串回復(fù)進(jìn)行區(qū)分,視為異常。

          整數(shù)回復(fù)

          整數(shù)回復(fù)的應(yīng)用也非常廣泛,它以:作為開頭,以\r\n結(jié)束,用于返回一個(gè)整數(shù)。例如當(dāng)執(zhí)行incr后返回自增后的值,執(zhí)行llen返回?cái)?shù)組的長(zhǎng)度,或者使用exists命令返回的0或1作為判斷一個(gè)key是否存在的依據(jù),這些都使用了整數(shù)回復(fù)。

          批量回復(fù)

          批量回復(fù),就是多行字符串的回復(fù)。它以$作為開頭,后面是發(fā)送的字節(jié)長(zhǎng)度,然后是\r\n,然后發(fā)送實(shí)際的數(shù)據(jù),最終以\r\n結(jié)束。如果要回復(fù)的數(shù)據(jù)不存在,那么回復(fù)長(zhǎng)度為-1。

          多條批量回復(fù)

          當(dāng)服務(wù)端要返回多個(gè)值時(shí),例如返回一些元素的集合時(shí),就會(huì)使用多條批量回復(fù)。它以*作為開頭,后面是返回元素的個(gè)數(shù),之后再跟隨多個(gè)上面講到過的批量回復(fù)。

          到這里,基本上我和小弟之間的通訊協(xié)議就介紹完了。剛才你嘗試了偽裝成一個(gè)服務(wù)端,這會(huì)再來試一試直接寫一個(gè)客戶端來直接和我進(jìn)行交互吧。

          private static void client() throws IOException {
              String CRLF="\r\n";

              Socket socket=new Socket("localhost"6379);
              try (OutputStream out = socket.getOutputStream()) {
                  StringBuffer sb=new StringBuffer();
                  sb.append("*3").append(CRLF)
                          .append("$3").append(CRLF).append("set").append(CRLF)
                          .append("$4").append(CRLF).append("key1").append(CRLF)
                          .append("$6").append(CRLF).append("value1").append(CRLF);
                  out.write(sb.toString().getBytes());
                  out.flush();

                  try (InputStream inputStream = socket.getInputStream()) {
                      byte[] buff = new byte[1024];
                      int len = inputStream.read(buff);
                      if (len > 0) {
                          String ret = new String(buff, 0, len);
                          System.out.println("Recv:" + ret);
                      }
                  }
              }
          }

          運(yùn)行上面的代碼,控制臺(tái)輸出:

          Recv:+OK

          上面模仿了客戶端發(fā)出set命令的過程,并收到了回復(fù)。依此類推,你也可以自己封裝其他的命令,來實(shí)現(xiàn)一個(gè)自己的Redis客戶端,作為小弟,來和我進(jìn)行通信。

          不過記住,要叫我大哥。

          — 本文結(jié)束 —


          ● 漫談設(shè)計(jì)模式在 Spring 框架中的良好實(shí)踐

          ● 顛覆微服務(wù)認(rèn)知:深入思考微服務(wù)的七個(gè)主流觀點(diǎn)

          ● 人人都是 API 設(shè)計(jì)者

          ● 一文講透微服務(wù)下如何保證事務(wù)的一致性

          ● 要黑盒測(cè)試微服務(wù)內(nèi)部服務(wù)間調(diào)用,我該如何實(shí)現(xiàn)?



          關(guān)注我,回復(fù) 「加群」 加入各種主題討論群。



          對(duì)「服務(wù)端思維」有期待,請(qǐng)?jiān)谖哪c(diǎn)個(gè)在看

          喜歡這篇文章,歡迎轉(zhuǎn)發(fā)、分享朋友圈


          在看點(diǎn)這里
          瀏覽 25
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产黄色视频大全 | 高清无码理论 | 亚洲欧美天堂 | 在线一区 | 国产99精品 |