1. 程式人生 > >Redis伺服器和客戶端的通訊

Redis伺服器和客戶端的通訊

Redis客戶端使用RESP(Redis序列化協議)與Redis伺服器進行通訊,RESP在位於TCP之上,而網路模型上客戶端和伺服器是保持的雙工的連線。如圖1 ![1587974695972](https://img2020.cnblogs.com/blog/1261088/202005/1261088-20200507165623485-637600838.png) 而一個簡單的請求/響應的序列通訊模型如下圖: ![1587975373862](https://img2020.cnblogs.com/blog/1261088/202005/1261088-20200507165622945-1862448462.png) ## 序列化通訊 序列化通訊比較簡單,上面那張圖就很表面的反應出來這種通訊方式,同一個Connction需要在等上一個命令執行完成之後在執行下一個命令,我們在前面文章講Redis各種型別的時候做的測試,就是用這種方式。客戶端傳送一個指令到Redis例項,Redis例項處理完成之後將結果返回給客戶端。 前面文章說Redis為什麼要用多執行緒中有說過,Redis處理請求的速度特別快,我們一個請求的瓶頸主要是在I/O上面,而對於序列化通訊,每一個請求的傳送都要等到上一個請求的響應介紹,因此在序列模式下,**單連線的大部分時間都浪費在網路等待上,沒有充分的利用伺服器的處理能力**。 ## 管道技術 Redis在很早的時候就支援管道技術了,簡單來說,就是可以完全無需等待服務端應答地傳送多條指令給服務端,並最終一次性讀取所有應答。管道技術最顯著的優勢是提高了redis服務的效能,通過管道技術來進行大批量的操作的時候,可以節省很多在網路延遲上的時間。 在.net core 的Redis客戶端StackExchange.Redis則是基於Task來實現管道技術,而StackExchangeRedis本身的非同步也都是通過管道技術來實現。 ## 事務 在菜鳥教程中是這麼介紹的 > Redis 事務可以一次執行多個命令, 並且帶有以下三個重要的保證: > > - 批量操作在傳送 EXEC 命令前被放入佇列快取。 > - 收到 EXEC 命令後進入事務執行,事務中任意命令執行失敗,其餘的命令依然被執行。 > - 在事務執行過程,其他客戶端提交的命令請求不會插入到事務執行命令序列中。 > > 一個事務從開始到執行會經歷以下三個階段: > > - 開始事務。 > - 命令入隊。 > - 執行事務 > - 放棄事務 原理很簡單,客戶端傳送命令MULTI,伺服器會將後續的命令都放入佇列快取,直到收到EXEC命令才會依次執行命令。單個Redis的命令是原子性的,但是Redis並沒有在事務上增加任何的維持原子性的機制,當中間某條命令失敗並不會導致其他命令的回滾,這個跟我們在關係型資料庫的理解不一樣,更多的像一個打包的批處理指令碼。 菜鳥中有這麼一句話 > 在事務執行過程,其他客戶端提交的命令請求不會插入到事務執行命令序列中。 粗略一看我還理解為事務開啟會阻塞其他客戶端的命令,嚇得我馬上做了一下測試 在客戶端1中開啟事務multi,併發送一個set 和 get 的命令,能看到都是QUEUED的狀態,表明是正確的入隊了 ![1588074772802](https://img2020.cnblogs.com/blog/1261088/202005/1261088-20200507165622460-402956051.png) 接著在客戶端2中獲取key1發現值是null,說明客戶端1的命令還沒有真正執行,接著設定key1的值為value2,接著取得key1的值,在客戶端1中開啟事務後,在客戶端2是可以順利執行命令的,菜鳥中的話的意思其實客戶端的命令不會進入開啟事務那個客戶端的命令佇列中。 ![1588081337699](https://img2020.cnblogs.com/blog/1261088/202005/1261088-20200507165622085-1392119352.png) 我們接著在客戶端1提交命令,key1的值變為value1,客戶端2中設定的value2被更改為value1了。 我們將Redis事務與資料庫事務的四大特徵對比下 | | | | | ------ | ------ | :----------------------------------------------------------- | | 原子性 | 不支援 | Redis單個指令是具有原子性的,但是事務沒有 | | 一致性 | 不支援 | 在上面的例子就可以看見,在客戶端1的事務開啟的時候,我仍然能修改key1的值,在關係型資料庫中我們有悲觀鎖和樂觀鎖來解決這種併發問題,Redis也通過Watch可以實現樂觀鎖的效果,但是我還是沒有體會出來有什麼用處。在關係型資料中的事務,我們可能會先取出來值,在進行修改,最後提交事務,如果沒有鎖來保證,那麼我們最後的資料就沒有一致性了,但是對於Redis我還是沒想出來什麼場景下會需要用樂觀鎖來控制併發,知道的小夥伴麻煩告知一聲。 | | 隔離性 | 支援 | Redis本身是沒有隔離性這個說法的,之所以我覺得是支援隔離性,因為我覺得Redis的事務都是在最後才執行,而本身命令又是原子性的,所以隔離性對Redis是無意義的。 | | 永續性 | 不支援 | Redis有持久化方案,但是最高資料安全性的方式-AOF中的修改同步,仍然會在異常情況下導致資料丟失。 | 其實這個對比不太恰當,Redis的事務只是頂著事務這個名字,做的還是批量處理的事情,它的關注點不應該在正真的事務上 ## 指令碼 在說事務的時候有說事務更像是批處理的感覺,而指令碼也是批處理,不同的是,我們可以根據上一個指令的結果作為我們下個指令的引數,這是處理邏輯問題的時候特別有用。 Redis指令碼是通過Eval命令實現,當客戶都安使用Eval命令的時候,Redis例項會通過lua直譯器來執行指令碼,我們這裡的指令碼也是lua指令碼,用Abp中清除快取的的原始碼作為示例 ``` EVAL "local keys = redis.call('keys', ARGV[1]) for i=1,#keys,5000 do redis.call('del', unpack(keys, i, math.min(i+4999, #keys))) end" 0 'Test_*' ``` 這個指令碼第一步將以Test做為字首的key全部取出來存入變數keys,接著從1開始,以keys的長度為最大值,步長為5000進行遍歷,每一步都是刪除5000個key。為什麼要用每次5000遍歷來執行呢?因為unpack函式在數量太多的時候會出現 'too many results to unpack' 的錯誤,我們來實際操作下,往例項中新增10個用Test_為字首的值,然後執行上面的指令碼 ![1588151071491](https://img2020.cnblogs.com/blog/1261088/202005/1261088-20200507165621337-690052851.png) ![1588151130571](https://img2020.cnblogs.com/blog/1261088/202005/1261088-20200507165620425-1155144686.png) 可以看到我們以Test_做為字首的Key都被刪除了 ## 釋出/訂閱模式 前面有講到過,Redis例項和客戶都之間是雙工連線的,但是前面所說的不管是簡單的命令還是事務指令碼都是客戶端主動發起請求,Redis例項被動迴應的,而釋出/訂閱模式則是可以由Redis例項主動給客戶端傳送訊息,在下一節會詳細說這種