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)一致性的
-
原因之一是因為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)致性能降低。
-
還有一種情況是網(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é)點失效檢測
-
當(dāng)一個節(jié)點向另一個節(jié)點發(fā)送 PING 命令時, 目標(biāo)節(jié)點未能在node timeout內(nèi)返回 PING 命令的回復(fù)時, 那么發(fā)送命令的節(jié)點會將目標(biāo)節(jié)點標(biāo)記為 PFAIL (可能已失效)。
-
每次當(dāng)節(jié)點對其他節(jié)點發(fā)送 PING 命令的時候, 它都會隨機(jī)地廣播三個它所知道的節(jié)點的信息, 這些信息里面的其中一項就是說明節(jié)點是否已經(jīng)被標(biāo)記為 PFAIL 或者 FAIL 。
-
如果節(jié)點已經(jīng)將某個節(jié)點標(biāo)記為 PFAIL , 并且集群中的大部分其他主節(jié)點也認(rèn)為那個節(jié)點進(jìn)入了失效狀態(tài), 那么節(jié)點會將那個失效節(jié)點的狀態(tài)標(biāo)記為 FAIL 。
-
一旦某個節(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ā)故事