Redis基礎知識(二)

  • 2020 年 3 月 30 日
  • 筆記

先找出你的能力所在,然後再決定你是誰 《你當像鳥飛往你的山》

Redis事務

redis中的事務是一組命令的集合,事務中的命令要麼全部執行,要麼都不執行,Redis 通過 MULTI 、DISCARD 、EXEC 和 WATCH 四個命令來實現事務功能,multi表示事物的開啟,exec表示事物的執行,exec執行後返回事務執行的結果,discard表示放棄事務執行,清空事務隊列中已有的所有命令並退出隊列,watch用於監視給定的鍵,如果鍵被其他客戶端修改,將不會執行事務。

127.0.0.1:6379> multi  OK  127.0.0.1:6379> set key 1  QUEUED  127.0.0.1:6379> get key  QUEUED  127.0.0.1:6379> exec  1) OK  2) "1"    127.0.0.1:6379> multi  OK  127.0.0.1:6379> set key1 1  QUEUED  127.0.0.1:6379> discard  OK  127.0.0.1:6379> get key1  (nil)

這裡我在另一個客戶端修改了被監視的key,導致在這個客戶端事務沒有執行

127.0.0.1:6379> set key 1  OK  127.0.0.1:6379> watch key  OK  127.0.0.1:6379> multi  OK  127.0.0.1:6379> incr key  QUEUED  127.0.0.1:6379> incr key  #客戶端2  (integer) 2  127.0.0.1:6379> exec  (nil)  127.0.0.1:6379> get key  "2"

由於事務在執行時會獨佔伺服器,所以盡量避免在事務中執行過多命令,以免伺服器阻塞

Redis Pipline

redis是一個cs模式的tcp server,使用和http類似的請求響應協議。一個client可以通過一個socket連接發起多個請求命令。每個請求命令發出後client通常會阻塞並等待redis服務處理,redis處理完後請求命令後會將結果通過響應報文返回給client。如果網路延遲較大,那將會花費太多的時間,redis提供了pipline可以解決這個問題,redis可以在pipline中發送多個消息而無需等待每個消息的答覆的過程。這裡我使用python的redis庫寫了個demo來演示使用pipline的效果

from redis import Redis  import time  conn=Redis(host="60.205.177.100",port="6379")    def usepipline():      start_time=time.time()      pipline=conn.pipeline()      for i in range(300):          pipline.incr("key")      pipline.execute()      print("usepipline:",time.time()-start_time)  def withoutpipline():      start_time=time.time()      for i in range(300):          conn.incr("key1")      print("withoutpipline:",time.time()-start_time)  usepipline()  withoutpipline()

響應結果

usepipline: 1.2412519454956055  withoutpipline: 7.2261717319488525

可以看到使用pipline效果是很明顯的

Redis發布訂閱模式

Redis通過PUBLISH 、SUBSCRIBE 等命令實現了訂閱與發布模式,發布者可以向多個頻道發布消息,訂閱者可以訂閱多個頻道,當然一個頻道也可以有多個訂閱者,發布者和訂閱者的這種分離可以允許更大的可伸縮性和更動態的網路拓撲。

命令

向頻道發送消息

publish channel message

例如

返回的是接收到消息的訂閱者數量

127.0.0.1:6379> publish CCTV1 worldnews  (integer) 0  127.0.0.1:6379> publish CCTV1 chinanews  (integer) 0

訂閱頻道

subscribe channel [channel ...]

例如

127.0.0.1:6379> subscribe CCTV1  Reading messages... (press Ctrl-C to quit)  1) "subscribe"  2) "CCTV1"  3) (integer) 1  1) "message"  2) "CCTV1"  3) "chinanews"

退訂頻道

UNSUBSCRIBE [channel [channel ...]]

例如

127.0.0.1:6379> UNSUBSCRIBE CCTV1 CCTV2  1) "unsubscribe"  2) "CCTV1"  3) (integer) 0  4) "unsubscribe"  5) "CCTV2"  6) (integer) 0

訂閱模式

redis支援使用glob的方式來一次訂閱多個頻道

PSUBSCRIBE pattern [pattern ...]

例如

127.0.0.1:6379> publish CCTV2 chinanew  (integer) 1  127.0.0.1:6379> publish CCTV1 worldnews  (integer) 1  127.0.0.1:6379> PSUBSCRIBE CCTV*  Reading messages... (press Ctrl-C to quit)  1) "psubscribe"  2) "CCTV*"  3) (integer) 1  1) "pmessage"  2) "CCTV*"  3) "CCTV2"  4) "chinanews"  1) "pmessage"  2) "CCTV*"  3) "CCTV2"  4) "chinanew"

退訂模式

PUNSUBSCRIBE [pattern [pattern ...]]

例如

127.0.0.1:6379> PUNSUBSCRIBE CCTV*  1) "punsubscribe"  2) "CCTV*"  3) (integer) 0

python demo

redis_pub.py

from redis import Redis  import time  conn=Redis(host="60.205.177.100",port="6379")  def publish():      while True:          conn.publish("CCTV3","test")

redis_sub.py

from redis import Redis  import time  conn=Redis(host="60.205.177.100",port="6379")  def subscribe():      subscribe=conn.pubsub()      subscribe.subscribe('CCTV3')      message=subscribe.parse_response()      print(message)

Redis複製

雖然已經有了aof和rdb做持久化了,但是為了防止單點故障,這就需要複製多個數據副本來保證數據安全

redis複製的基本特徵

  • Redis使用非同步複製,並以每秒一次的頻率向主伺服器確認複製進度。
  • 一個主伺服器可以有多個從伺服器。從伺服器也可以有多個從伺服器,從伺服器還可以以類似級聯的結構連接到其他從伺服器。
  • Redis複製在主伺服器端無阻塞。這意味著當一個或多個副本執行初始同步或部分重新同步時,主伺服器將繼續處理查詢。
  • 複製在從伺服器基本上也沒有阻塞。當副本執行初始同步時,如果配置了replica-serve-stale-data參數為yes,則從伺服器可以使用數據集的舊版本處理查詢。或者配置replica-serve-stale-data參數為no,在複製連接斷開時,向客戶端發送一個錯誤,但是,在初始同步之後,從伺服器刪除舊版本數據集並載入新版本數據集的那段時間內, 連接請求會被阻塞。
  • 複製功能可以單純地用於數據冗餘(data redundancy), 也可以通過讓多個從伺服器處理只讀命令請求來提升擴展性(scalability)
  • 可以使用複製來避免讓主資料庫執行持久化,而是複製到從伺服器之後由從伺服器進行持久化,但是最好還是開啟主伺服器的持久化,因為重新啟動的主伺服器將以空數據集開始,如果從伺服器嘗試與其同步,導致從伺服器的數據也將被清空。
  • 缺點也很明顯,Redis主從模式仍具有單點風險,一旦主伺服器掛掉將無法進行寫數據操作

redis複製原理

完全同步

主伺服器後台發送RDB文件給從伺服器。從伺服器接收rdb數據期間,主伺服器會將新數據保存到複製客戶端緩衝區,當從伺服器接收完rdb文件後,將其保存在磁碟上,然後將其載入到記憶體中。載入完rdb文件後,如果開啟aof,從伺服器會進行重寫操作。主伺服器會把緩衝區的新數據發送給從伺服器

部分同步

當主從連接中斷後,從伺服器使用psync命令向主伺服器發送上次複製的偏移量,以及記錄的masterID,如果上次複製的偏移量仍存在主伺服器的緩衝區中,並且masterID與主伺服器的masterID一致,將會從緩衝區中上次斷開的位置開始增量複製,否則將會發生完全同步

psync命令

psync masterID offset

當從伺服器與主伺服器建立連接時,會判斷是不是初次複製,如果是的話,將會發送psync ? -1進行完全同步,如果不是的話,會發送psync masterID offset嘗試部分同步,如果發送的masterID與主伺服器一致且offset存在於主伺服器的複製緩衝區中,將進行部分同步,否則將會進行完全同步「首先我們查看主伺服器的masterID和offset」

「然後將masterID和offset複製到從伺服器,期間我在主伺服器寫了兩條數據」

「可以看到從伺服器已經有了主伺服器的數據」

redis複製配置

由於主伺服器設置了密碼,需要在從伺服器中指定主伺服器的密碼 vim /etc/redis/6379.conf

masterauth 123456

由於我的redis版本是5.0,所以同步的命令較以前的命令不太一樣,但也兼容以前的同步命令

第一種方法:

配置文件修改 vim /etc/redis/6379.conf

replicaof masterip port   #5.0版本支援的命令  或者  slaveof masterip port

第二種方法:

在redis客戶端執行命令,在客戶端執行的同步命令將在重啟後失效

replicaof masterip port   #5.0版本支援的命令  或者  slaveof masterip port

配置好之後,主從的masterID資訊是一樣的,這是主伺服器的資訊

這是從伺服器的資訊

主從同步後,從伺服器將不能寫入數據

當我們斷開當前資料庫的連接,與另一台主伺服器建立主從同步後,masterid也發生變化,將重新進行完全同步

Redis哨兵

在redis主從模式下,一旦主伺服器宕機,需要人工進行干預將某個從伺服器轉換為主伺服器,還需要去監視redis的狀態,費事費力,還會造成一段時間內服務不可用,對於某些應用場景,這種處理方法並不可取,redis在2.8版本開始提供了RedisSentinel工具,通過心跳檢測的方式監視多個主伺服器以及它們屬下的所有從伺服器,並在某個主伺服器下線時自動對其實施故障轉移,Sentinel將選擇一個從伺服器並將其提升為主伺服器,其他剩餘的從伺服器實例將自動重新配置為使用新的主伺服器。

哨兵功能

  • 監控。Sentinel會不斷檢查主從節點是否按預期工作。
  • 通知。Sentinel通過API通知客戶端出現問題的節點
  • 自動故障轉移。當主伺服器不能正常工作時,Sentinel可以進行故障轉移操作,在該操作中將從伺服器升級為主伺服器,將其他從伺服器改為複製新主伺服器。
  • 配置提供程式。客戶端在初始化時,通過連接到Sentinels,獲取當前主伺服器的節點地址。

哨兵搭建

環境準備

192.168.179.131:6379 master  192.168.179.132:6379 slave  192.168.179.134:6379 slave  192.168.179.131:26379 sentinel  192.168.179.132:26379 sentinel  192.168.179.134:26379 sentinel

主從節點配置

#192.168.179.131  port 6379  daemonize yes  logfile /var/log/redis_6379.log  dbfilename dump.rdb    #192.168.179.132  port 6379  daemonize yes  logfile /var/log/redis_6379.log  dbfilename dump.rdb  replicaof 192.168.179.131 6379    #192.168.179.134  port 6379  daemonize yes  logfile /var/log/redis_6379.log  dbfilename dump.rdb  replicaof 192.168.179.131 6379

測試主從複製是否配置成功

哨兵節點配置

三個哨兵節點相同配置,最後一句配置的意思是監控192.168.179.131這個主節點,節點名稱是mymaster,並且至少需要兩個哨兵節點同意才能判定主節點故障並進行自動遷移

port 26379  daemonize yes  sentinel monitor mymaster 192.168.179.131 6379 2

啟動哨兵節點

以下兩條命令啟動都可以,可以看到哨兵已經啟動成功

./src/redis-sentinel sentinel.conf  ./src/redis-server sentinel.conf --sentinel

故障轉移測試

這裡我將192.168.179.131的redis給關掉,查看從節點的複製資訊,可以看到redis的主節點已經切換為192.168.179.132

將192.168.179.131上面的redis再次啟動,查看節點狀態已經變為從節點了

哨兵管理命令

  • info sentinel:獲取監控的所有主節點的基本資訊
  • sentinel masters:獲取所有被監視主節點的資訊
  • sentinel master:獲取指定主節點的詳細資訊
  • sentinel slaves:獲取指定主節點的從節點的詳細資訊
  • sentinel sentinels:獲取指定主節點的哨兵節點的詳細資訊
  • sentinel get-master-addr-by-name mymaster:獲取主節點的IP地址和埠
  • sentinel is-master-down-by-addr:哨兵節點之間可以通過該命令詢問主節點是否下線,從而對是否客觀下線做出判斷
  • sentinel failover: 強制對主節點進行故障轉移
  • sentinel ckquorum: 檢查可用的哨兵數量
  • sentinel flushconfig: 強制寫入配置文件
  • sentinel remove:取消對指定主節點的監視

Redis集群模式

上面也說到,Redis主從模式中如果主伺服器宕掉將無法進行寫操作,即使哨兵模式提供了Redis的高可用,但面對數據量比較大的場景,Redis單點就不太能滿足這個要求了 Redis 集群是一個分散式、容錯的 Redis 實現, 集群可以使用的功能是普通單機 Redis 所能使用的功能的一個子集。Redis 集群是Redis的分散式實現,集群中不存在中心節點或者代理節點, 集群的其中一個主要設計目標是達到線性可擴展性。集群的容錯功能是通過使用主節點和從節點兩種角色的節點來實現的:主節點和從節點使用完全相同的伺服器實現, 它們的功能也完全一樣, 但從節點通常僅用於替換失效的主節點。如果不需要保證「先寫入,後讀取」操作的一致性, 那麼可以使用從節點來執行只讀查詢。Redis 集群不像單機 Redis 那樣支援多資料庫功能, 集群只使用默認的 0 號資料庫, 並且不能使用 SELECT index 命令。

Redis集群中節點的工作內容

  • 保存鍵值對數據。
  • 記錄集群的狀態,包括鍵到正確節點的映射。
  • 自動發現其他節點,識別工作不正常的節點,並從節點中選舉出新的主節點。

鍵分布模型

Redis 集群的鍵空間被分割為 16384 個槽(slot), 集群的最大節點數量也是 16384 個。當一個集群處於「穩定」狀態時, 集群每個哈希槽都不會進行移動,當需要添加一個節點的時候,只需要將其他節點的某些哈希槽轉移到新節點上,當需要刪除一個節點的時候,就把此節點的哈希槽轉移到其他節點上就可以了。一個主節點可以有任意多個從節點, 這些從節點用於在主節點發生網路斷線或者節點失效時, 對主節點進行替換。

集群節點屬性

每個節點在集群中都有一個獨一無二的 ID , 該 ID 是一個十六進位表示的 160 位隨機數, 在節點第一次啟動時由 /dev/urandom 生成。節點會將它的 ID 保存到配置文件, 只要這個配置文件不被刪除, 節點就會一直沿用這個 ID 。節點 ID 用於標識集群中的每個節點。一個節點可以改變它的 IP 和埠號, 而不改變節點 ID 。集群可以自動識別出 IP/埠號的變化, 並將這一資訊通過 Gossip 協議廣播給其他節點知道。以下是每個節點都有的關聯資訊, 並且節點會將這些資訊發送給其他節點:

  • 節點所使用的 IP 地址和 TCP 埠號。
  • 節點的標誌(flags)。
  • 節點負責處理的哈希槽。
  • 節點最近一次使用集群連接發送 PING 數據包(packet)的時間。
  • 節點最近一次在回復中接收到 PONG 數據包的時間。
  • 集群將該節點標記為下線的時間。
  • 該節點的從節點數量。
  • 如果該節點是從節點的話,那麼它會記錄主節點的節點 ID 。如果這是一個主節點的話,那麼主節點 ID 這一欄的值為 0000000 。

以上資訊的其中一部分可以通過向集群中的任意節點(主節點或者從節點都可以)發送 CLUSTER NODES 命令來獲得。

集群數據一致性

Redis集群是無法保證數據的強一致性的

  1. 原因之一是因為Redis集群是非同步複製的,當客戶端向服務端的某個主節點寫數據時,主伺服器會先響應客戶端,之後再把寫操作請求發送給從節點,在這過程之中,如果主節點發生了宕機,而從節點還沒有收到寫操作的請求,那麼這條數據將會永久丟失,當然可以通過強制資料庫在回復客戶端以前刷新數據到磁碟,但這樣會導致性能降低。
  2. 還有一種情況是網路分區(network partition)帶來的,當Redis集群出現網路分區時,客戶端仍對處於小分區的主節點進行寫操作,當達到集群的node timeout的時間限制後,處於大分區的那個從節點將會取代處於小分區的主節點稱為新的主節點,而在node timeout這段時間裡客戶端向主節點寫入的數據將會丟失

節點失效檢測

  1. 當一個節點向另一個節點發送 PING 命令時, 目標節點未能在node timeout內返回 PING 命令的回復時, 那麼發送命令的節點會將目標節點標記為 PFAIL (可能已失效)。
  2. 每次當節點對其他節點發送 PING 命令的時候, 它都會隨機地廣播三個它所知道的節點的資訊, 這些資訊裡面的其中一項就是說明節點是否已經被標記為 PFAIL 或者 FAIL 。
  3. 如果節點已經將某個節點標記為 PFAIL , 並且集群中的大部分其他主節點也認為那個節點進入了失效狀態, 那麼節點會將那個失效節點的狀態標記為 FAIL 。
  4. 一旦某個節點被標記為 FAIL , 關於這個節點已失效的資訊就會被廣播到整個集群, 所有接收到這條資訊的節點都會將失效節點標記為 FAIL 。

集群搭建

編輯配置文件啟動集群節點

每個集群實例都要開啟,之後重啟redis實例

cluster-enabled yes  cluster-config-file nodes-6379.conf  cluster-node-timeout 15000

連接集群節點並分配槽

./redis-cli --cluster create 192.168.179.131:6379 192.168.179.131:6380 192.168.179.134:6379 192.168.179.134:6380 192.168.179.132:6379 192.168.179.132:6380 --cluster-replicas 1

可以查看集群的節點資訊,輸入集群內任意節點地址即可

./redis-cli --cluster check 192.168.179.132:6379

集群驗證

當我們set一個key 時,會用CRC16演算法來取模得到所屬的slot,然後將這個key 分到哈希槽區間的節點上,當get數據的時候,也是通過這種方法去對應的節點上獲取數據

./redis-cli -c -h 192.168.179.132 -p 6380  192.168.179.132:6380> set key 1  -> Redirected to slot [12539] located at 192.168.179.132:6379  OK  ./redis-cli -c -h 192.168.179.134 -p 6379  192.168.179.134:6379> get key  -> Redirected to slot [12539] located at 192.168.179.132:6379  "1"

Tags: