Redis基礎(chǔ)知識(二)

微信公眾號:運(yùn)維開發(fā)故事,作者:wanger

Redis事務(wù)

redis中的事務(wù)是一組命令的集合,事務(wù)中的命令要么全部執(zhí)行,要么都不執(zhí)行,Redis 通過 MULTI 、DISCARD 、EXEC 和 WATCH
四個命令來實現(xiàn)事務(wù)功能,multi表示事物的開啟,exec表示事物的執(zhí)行,exec執(zhí)行后返回事務(wù)執(zhí)行的結(jié)果,discard表示放棄事務(wù)執(zhí)行,清空事務(wù)隊列中已有的所有命令并退出隊列,watch用于監(jiān)視給定的鍵,如果鍵被其他客戶端修改,將不會執(zhí)行事務(wù)。

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)

這里我在另一個客戶端修改了被監(jiān)視的key,導(dǎo)致在這個客戶端事務(wù)沒有執(zhí)行

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"

由于事務(wù)在執(zhí)行時會獨占服務(wù)器,所以盡量避免在事務(wù)中執(zhí)行過多命令,以免服務(wù)器阻塞

Redis Pipline

redis是一個cs模式的tcp server,使用和http類似的請求響應(yīng)協(xié)議。一個client可以通過一個socket連接發(fā)起多個請求命令。每個請求命令發(fā)出后client通常會阻塞并等待redis服務(wù)處理,redis處理完后請求命令后會將結(jié)果通過響應(yīng)報文返回給client。如果網(wǎng)絡(luò)延遲較大,那將會花費太多的時間,redis提供了pipline可以解決這個問題,redis可以在pipline中發(fā)送多個消息而無需等待每個消息的答復(fù)的過程。
這里我使用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()

響應(yīng)結(jié)果

usepipline: 1.2412519454956055
withoutpipline: 7.2261717319488525

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

Redis發(fā)布訂閱模式

Redis通過PUBLISH 、SUBSCRIBE 等命令實現(xiàn)了訂閱與發(fā)布模式,發(fā)布者可以向多個頻道發(fā)布消息,訂閱者可以訂閱多個頻道,當(dāng)然一個頻道也可以有多個訂閱者,發(fā)布者和訂閱者的這種分離可以允許更大的可伸縮性和更動態(tài)的網(wǎng)絡(luò)拓?fù)洹?/p>

圖片

命令

向頻道發(fā)送消息

publish channel message

例如

返回的是接收到消息的訂閱者數(shù)量

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復(fù)制

雖然已經(jīng)有了aof和rdb做持久化了,但是為了防止單點故障,這就需要復(fù)制多個數(shù)據(jù)副本來保證數(shù)據(jù)安全

redis復(fù)制的基本特征

  • Redis使用異步復(fù)制,并以每秒一次的頻率向主服務(wù)器確認(rèn)復(fù)制進(jìn)度。

  • 一個主服務(wù)器可以有多個從服務(wù)器。

    從服務(wù)器也可以有多個從服務(wù)器,從服務(wù)器還可以以類似級聯(lián)的結(jié)構(gòu)連接到其他從服務(wù)器。

  • Redis復(fù)制在主服務(wù)器端無阻塞。

    這意味著當(dāng)一個或多個副本執(zhí)行初始同步或部分重新同步時,主服務(wù)器將繼續(xù)處理查詢。

  • 復(fù)制在從服務(wù)器基本上也沒有阻塞。

    當(dāng)副本執(zhí)行初始同步時,如果配置了replica-serve-stale-data參數(shù)為yes,則從服務(wù)器可以使用數(shù)據(jù)集的舊版本處理查詢。

    或者配置replica-serve-stale-data參數(shù)為no,在復(fù)制連接斷開時,向客戶端發(fā)送一個錯誤,但是,在初始同步之后,從服務(wù)器刪除舊版本數(shù)據(jù)集并載入新版本數(shù)據(jù)集的那段時間內(nèi), 連接請求會被阻塞。

  • 復(fù)制功能可以單純地用于數(shù)據(jù)冗余(data redundancy), 也可以通過讓多個從服務(wù)器處理只讀命令請求來提升擴(kuò)展性(scalability)

  • 可以使用復(fù)制來避免讓主數(shù)據(jù)庫執(zhí)行持久化,而是復(fù)制到從服務(wù)器之后由從服務(wù)器進(jìn)行持久化,但是最好還是開啟主服務(wù)器的持久化,因為重新啟動的主服務(wù)器將以空數(shù)據(jù)集開始,如果從服務(wù)器嘗試與其同步,導(dǎo)致從服務(wù)器的數(shù)據(jù)也將被清空。

  • 缺點也很明顯,Redis主從模式仍具有單點風(fēng)險,一旦主服務(wù)器掛掉將無法進(jìn)行寫數(shù)據(jù)操作

redis復(fù)制原理

完全同步

主服務(wù)器后臺發(fā)送RDB文件給從服務(wù)器。從服務(wù)器接收rdb數(shù)據(jù)期間,主服務(wù)器會將新數(shù)據(jù)保存到復(fù)制客戶端緩沖區(qū),當(dāng)從服務(wù)器接收完rdb文件后,將其保存在磁盤上,然后將其加載到內(nèi)存中。加載完rdb文件后,如果開啟aof,從服務(wù)器會進(jìn)行重寫操作。主服務(wù)器會把緩沖區(qū)的新數(shù)據(jù)發(fā)送給從服務(wù)器

部分同步

當(dāng)主從連接中斷后,從服務(wù)器使用psync命令向主服務(wù)器發(fā)送上次復(fù)制的偏移量,以及記錄的masterID,如果上次復(fù)制的偏移量仍存在主服務(wù)器的緩沖區(qū)中,并且masterID與主服務(wù)器的masterID一致,將會從緩沖區(qū)中上次斷開的位置開始增量復(fù)制,否則將會發(fā)生完全同步

psync命令

psync masterID offset

當(dāng)從服務(wù)器與主服務(wù)器建立連接時,會判斷是不是初次復(fù)制,如果是的話,將會發(fā)送psync ? -1進(jìn)行完全同步,如果不是的話,會發(fā)送psync masterID offset嘗試部分同步,如果發(fā)送的masterID與主服務(wù)器一致且offset存在于主服務(wù)器的復(fù)制緩沖區(qū)中,將進(jìn)行部分同步,否則將會進(jìn)行完全同步
首先我們查看主服務(wù)器的masterID和offset

圖片

然后將masterID和offset復(fù)制到從服務(wù)器,期間我在主服務(wù)器寫了兩條數(shù)據(jù)

圖片

可以看到從服務(wù)器已經(jīng)有了主服務(wù)器的數(shù)據(jù)

redis復(fù)制配置

由于主服務(wù)器設(shè)置了密碼,需要在從服務(wù)器中指定主服務(wù)器的密碼
vim /etc/redis/6379.conf

masterauth 123456

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

第一種方法:

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

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

第二種方法:

在redis客戶端執(zhí)行命令,在客戶端執(zhí)行的同步命令將在重啟后失效

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





配置好之后,主從的masterID信息是一樣的,這是主服務(wù)器的信息

圖片

這是從服務(wù)器的信息

圖片

主從同步后,從服務(wù)器將不能寫入數(shù)據(jù)

圖片

當(dāng)我們斷開當(dāng)前數(shù)據(jù)庫的連接,與另一臺主服務(wù)器建立主從同步后,masterid也發(fā)生變化,將重新進(jìn)行完全同步

圖片

Redis哨兵

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

圖片

哨兵功能

  • 監(jiān)控。

    Sentinel會不斷檢查主從節(jié)點是否按預(yù)期工作。

  • 通知。

    Sentinel通過API通知客戶端出現(xiàn)問題的節(jié)點

  • 自動故障轉(zhuǎn)移。

    當(dāng)主服務(wù)器不能正常工作時,Sentinel可以進(jìn)行故障轉(zhuǎn)移操作,在該操作中將從服務(wù)器升級為主服務(wù)器,將其他從服務(wù)器改為復(fù)制新主服務(wù)器。

  • 配置提供程序。

    客戶端在初始化時,通過連接到Sentinels,獲取當(dāng)前主服務(wù)器的節(jié)點地址。

哨兵搭建

環(huán)境準(zhǔn)備

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

主從節(jié)點配置

#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

測試主從復(fù)制是否配置成功

圖片

哨兵節(jié)點配置

三個哨兵節(jié)點相同配置,最后一句配置的意思是監(jiān)控192.168.179.131這個主節(jié)點,節(jié)點名稱是mymaster,并且至少需要兩個哨兵節(jié)點同意才能判定主節(jié)點故障并進(jìn)行自動遷移

port 26379
daemonize yes
sentinel monitor mymaster 192.168.179.131 6379 2

啟動哨兵節(jié)點

以下兩條命令啟動都可以,可以看到哨兵已經(jīng)啟動成功

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

圖片

故障轉(zhuǎn)移測試

這里我將192.168.179.131的redis給關(guān)掉,查看從節(jié)點的復(fù)制信息,可以看到redis的主節(jié)點已經(jīng)切換為192.168.179.132

圖片

將192.168.179.131上面的redis再次啟動,查看節(jié)點狀態(tài)已經(jīng)變?yōu)閺墓?jié)點了






圖片

哨兵管理命令

  • info sentinel:

    獲取監(jiān)控的所有主節(jié)點的基本信息

  • sentinel masters:

    獲取所有被監(jiān)視主節(jié)點的信息

  • sentinel master

    :獲取指定主節(jié)點的詳細(xì)信息

  • sentinel slaves

    :獲取指定主節(jié)點的從節(jié)點的詳細(xì)信息

  • sentinel sentinels

    :獲取指定主節(jié)點的哨兵節(jié)點的詳細(xì)信息

  • sentinel get-master-addr-by-name mymaster:

    獲取主節(jié)點的IP地址和端口

  • sentinel is-master-down-by-addr:

    哨兵節(jié)點之間可以通過該命令詢問主節(jié)點是否下線,從而對是否客觀下線做出判斷

  • sentinel failover
    強(qiáng)制對主節(jié)點進(jìn)行故障轉(zhuǎn)移
  • sentinel ckquorum: 檢查可用的哨兵數(shù)量

  • sentinel flushconfig: 強(qiáng)制寫入配置文件

  • sentinel remove

    :取消對指定主節(jié)點的監(jiān)視

Redis集群模式

上面也說到,Redis主從模式中如果主服務(wù)器宕掉將無法進(jìn)行寫操作,即使哨兵模式提供了Redis的高可用,但面對數(shù)據(jù)量比較大的場景,Redis單點就不太能滿足這個要求了
Redis 集群是一個分布式、容錯的 Redis 實現(xiàn), 集群可以使用的功能是普通單機(jī) Redis 所能使用的功能的一個子集。
Redis 集群是Redis的分布式實現(xiàn),集群中不存在中心節(jié)點或者代理節(jié)點, 集群的其中一個主要設(shè)計目標(biāo)是達(dá)到線性可擴(kuò)展性。集群的容錯功能是通過使用主節(jié)點和從節(jié)點兩種角色的節(jié)點來實現(xiàn)的:主節(jié)點和從節(jié)點使用完全相同的服務(wù)器實現(xiàn), 它們的功能也完全一樣, 但從節(jié)點通常僅用于替換失效的主節(jié)點。如果不需要保證“先寫入,后讀取”操作的一致性, 那么可以使用從節(jié)點來執(zhí)行只讀查詢。
Redis 集群不像單機(jī) Redis 那樣支持多數(shù)據(jù)庫功能, 集群只使用默認(rèn)的 0 號數(shù)據(jù)庫, 并且不能使用 SELECT index 命令。

Redis集群中節(jié)點的工作內(nèi)容

  • 保存鍵值對數(shù)據(jù)。

  • 記錄集群的狀態(tài),包括鍵到正確節(jié)點的映射。

  • 自動發(fā)現(xiàn)其他節(jié)點,識別工作不正常的節(jié)點,并從節(jié)點中選舉出新的主節(jié)點。

鍵分布模型

Redis 集群的鍵空間被分割為 16384 個槽(slot), 集群的最大節(jié)點數(shù)量也是 16384 個。當(dāng)一個集群處于“穩(wěn)定”狀態(tài)時, 集群每個哈希槽都不會進(jìn)行移動,當(dāng)需要添加一個節(jié)點的時候,只需要將其他節(jié)點的某些哈希槽轉(zhuǎn)移到新節(jié)點上,當(dāng)需要刪除一個節(jié)點的時候,就把此節(jié)點的哈希槽轉(zhuǎn)移到其他節(jié)點上就可以了。一個主節(jié)點可以有任意多個從節(jié)點, 這些從節(jié)點用于在主節(jié)點發(fā)生網(wǎng)絡(luò)斷線或者節(jié)點失效時, 對主節(jié)點進(jìn)行替換。

集群節(jié)點屬性

每個節(jié)點在集群中都有一個獨一無二的 ID , 該 ID 是一個十六進(jìn)制表示的 160 位隨機(jī)數(shù), 在節(jié)點第一次啟動時由 /dev/urandom 生成。
節(jié)點會將它的 ID 保存到配置文件, 只要這個配置文件不被刪除, 節(jié)點就會一直沿用這個 ID 。
節(jié)點 ID 用于標(biāo)識集群中的每個節(jié)點。一個節(jié)點可以改變它的 IP 和端口號, 而不改變節(jié)點 ID 。集群可以自動識別出 IP/端口號的變化, 并將這一信息通過 Gossip 協(xié)議廣播給其他節(jié)點知道。
以下是每個節(jié)點都有的關(guān)聯(lián)信息, 并且節(jié)點會將這些信息發(fā)送給其他節(jié)點:

  • 節(jié)點所使用的 IP 地址和 TCP 端口號。

  • 節(jié)點的標(biāo)志(flags)。

  • 節(jié)點負(fù)責(zé)處理的哈希槽。

  • 節(jié)點最近一次使用集群連接發(fā)送 PING 數(shù)據(jù)包(packet)的時間。

  • 節(jié)點最近一次在回復(fù)中接收到 PONG 數(shù)據(jù)包的時間。

  • 集群將該節(jié)點標(biāo)記為下線的時間。

  • 該節(jié)點的從節(jié)點數(shù)量。

  • 如果該節(jié)點是從節(jié)點的話,那么它會記錄主節(jié)點的節(jié)點 ID 。

    如果這是一個主節(jié)點的話,那么主節(jié)點 ID 這一欄的值為 0000000 。

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

集群數(shù)據(jù)一致性

Redis集群是無法保證數(shù)據(jù)的強(qiáng)一致性的

  1. 原因之一是因為Redis集群是異步復(fù)制的,當(dāng)客戶端向服務(wù)端的某個主節(jié)點寫數(shù)據(jù)時,主服務(wù)器會先響應(yīng)客戶端,之后再把寫操作請求發(fā)送給從節(jié)點,在這過程之中,如果主節(jié)點發(fā)生了宕機(jī),而從節(jié)點還沒有收到寫操作的請求,那么這條數(shù)據(jù)將會永久丟失,當(dāng)然可以通過強(qiáng)制數(shù)據(jù)庫在回復(fù)客戶端以前刷新數(shù)據(jù)到磁盤,但這樣會導(dǎo)致性能降低。

  2. 還有一種情況是網(wǎng)絡(luò)分區(qū)(network partition)帶來的,當(dāng)Redis集群出現(xiàn)網(wǎng)絡(luò)分區(qū)時,客戶端仍對處于小分區(qū)的主節(jié)點進(jìn)行寫操作,當(dāng)達(dá)到集群的node timeout的時間限制后,處于大分區(qū)的那個從節(jié)點將會取代處于小分區(qū)的主節(jié)點稱為新的主節(jié)點,而在node timeout這段時間里客戶端向主節(jié)點寫入的數(shù)據(jù)將會丟失

節(jié)點失效檢測

  1. 當(dāng)一個節(jié)點向另一個節(jié)點發(fā)送 PING 命令時, 目標(biāo)節(jié)點未能在node timeout內(nèi)返回 PING 命令的回復(fù)時, 那么發(fā)送命令的節(jié)點會將目標(biāo)節(jié)點標(biāo)記為 PFAIL (可能已失效)。

  2. 每次當(dāng)節(jié)點對其他節(jié)點發(fā)送 PING 命令的時候, 它都會隨機(jī)地廣播三個它所知道的節(jié)點的信息, 這些信息里面的其中一項就是說明節(jié)點是否已經(jīng)被標(biāo)記為 PFAIL 或者 FAIL 。

  3. 如果節(jié)點已經(jīng)將某個節(jié)點標(biāo)記為 PFAIL , 并且集群中的大部分其他主節(jié)點也認(rèn)為那個節(jié)點進(jìn)入了失效狀態(tài), 那么節(jié)點會將那個失效節(jié)點的狀態(tài)標(biāo)記為 FAIL 。

  4. 一旦某個節(jié)點被標(biāo)記為 FAIL , 關(guān)于這個節(jié)點已失效的信息就會被廣播到整個集群, 所有接收到這條信息的節(jié)點都會將失效節(jié)點標(biāo)記為 FAIL 。

集群搭建

編輯配置文件啟動集群節(jié)點

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

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

連接集群節(jié)點并分配槽

./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

圖片

可以查看集群的節(jié)點信息,輸入集群內(nèi)任意節(jié)點地址即可

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

圖片

集群驗證

當(dāng)我們set一個key 時,會用CRC16算法來取模得到所屬的slot,然后將這個key 分到哈希槽區(qū)間的節(jié)點上,當(dāng)get數(shù)據(jù)的時候,也是通過這種方法去對應(yīng)的節(jié)點上獲取數(shù)據(jù)

./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"


作者:wanger


歡迎關(guān)注:運(yùn)維開發(fā)故事