可以學(xué)習(xí)一下:https://www.cnblogs.com/demingblog/p/10295236.html
redis單點(diǎn)砾层、redis主從、redis哨兵 sentinel贱案,redis集群cluster配置搭建與使用
目錄
- redis單點(diǎn)肛炮、redis主從、redis哨兵 sentinel宝踪,redis集群cluster配置搭建與使用
redis是如今被互聯(lián)網(wǎng)公司使用最廣泛的一個中間件,我們打開GitHub搜索redis瘩燥,邊可以看到秕重,該項(xiàng)目的介紹是這樣的:
Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Sets, Hashes, HyperLogLogs, Bitmaps.
從這句話中,我們可以提取其特性的關(guān)鍵字:
- in-memory database 厉膀,內(nèi)存數(shù)據(jù)庫
- support:Strings , lists, sets ,hashes ,hyperloglogs, bitmaps
也就是高性能溶耘,支持?jǐn)?shù)據(jù)類型多。本文假設(shè)你已經(jīng)了解redis的基本使用服鹅,進(jìn)而討論redis的單點(diǎn)汰具,高可用,集群菱魔。
1 .redis 安裝及配置
redis的安裝十分簡單留荔,打開redis的官網(wǎng) http://redis.io 。
- 下載一個最新版本的安裝包澜倦,如 redis-version.tar.gz
- 解壓
tar zxvf redis-version.tar.gz
- 執(zhí)行 make (執(zhí)行此命令可能會報錯聚蝶,例如確實(shí)gcc,一個個解決即可)
如果是 mac 電腦藻治,安裝redis將十分簡單執(zhí)行brew install redis
即可碘勉。
安裝好redis之后,我們先不慌使用桩卵,先進(jìn)行一些配置验靡。打開redis.conf
文件,我們主要關(guān)注以下配置:
port 6379 # 指定端口為 6379雏节,也可自行修改
daemonize yes # 指定后臺運(yùn)行
1.1 redis 單點(diǎn)
安裝好redis之后胜嗓,我們來運(yùn)行一下。啟動redis的命令為 :
redishome/bin/redis-server path/to/redis.config
假設(shè)我們沒有配置后臺運(yùn)行(即:daemonize no),那么我們會看到如下啟動日志:
93825:C 20 Jan 2019 11:43:22.640 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
93825:C 20 Jan 2019 11:43:22.640 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=93825, just started
93825:C 20 Jan 2019 11:43:22.640 # Configuration loaded
93825:S 20 Jan 2019 11:43:22.641 * Increased maximum number of open files to 10032 (it was originally set to 256).
無論是否配置了后臺運(yùn)行钩乍,啟動成功之后辞州,我們可以新開一個命令行窗口來操作試試。
1.1.2 在命令窗口操作redis
使用命令:telnet localhost 6379
來連接redis寥粹,或者你可以直接使用代碼來連接測試变过。連接之后埃元,看到如下信息:
Connected to localhost.
Escape character is '^]'.
我們輸入幾個命令試試:
set hello world 設(shè)置key-value
get hello 獲取key值
expire hello 10 設(shè)置10秒過期
ttl hello 查看過期時間
del hello 刪除key
如此,我們便體驗(yàn)了一把redis媚狰,可以說是非常簡單了岛杀。
1.2 redis 主從
上面我們啟動了一臺redis,并對其進(jìn)行操作崭孤。當(dāng)然這只是實(shí)驗(yàn)性的玩玩楞件。假設(shè)我們生產(chǎn)環(huán)境使用了一臺redis,redis掛了怎么辦裳瘪?如果等到運(yùn)維重啟redis,并恢復(fù)好數(shù)據(jù)罪针,可能需要花費(fèi)很長時間彭羹。那么在這期間,我們的服務(wù)是不可用的泪酱,這應(yīng)該是不能容忍的派殷。假設(shè)我們做了主從,主庫掛了之后墓阀,運(yùn)維讓從庫接管毡惜,那么服務(wù)可以繼續(xù)運(yùn)行,正所謂有備無患斯撮。
redis主從配置非常簡單,過程如下(ps 演示情況下主從配置在一臺電腦上):
- 復(fù)制兩個redis配置文件(啟動兩個redis经伙,只需要一份redis程序,兩個不同的redis配置文件即可)
mkdir redis-master-slave
cp path/to/redis/conf/redis.conf path/to/redis-master-slave master.conf
cp path/to/redis/conf/redis.conf path/to/redis-master-slave slave.conf
- 修改配置
-->master.conf
port 6379
-->master.conf
port 6380
slaveof 127.0.0.1 6379
- 分別啟動兩個redis
redis-server path/to/redis-master-slave/master.conf
redis-server path/to/redis-master-slave/slave.conf
啟動之后勿锅,打開兩個命令行窗口帕膜,分別執(zhí)行telnet localhost 6379 telnet localhost 6380
然后分別在兩個窗口中執(zhí)行 info
命令,可以看到
Replication
role:master
Replication
role:slave
master_host:127.0.0.1
master_port:6379
主從配置沒問題。
然后在master 窗口執(zhí)行 set 之后溢十,到slave窗口執(zhí)行g(shù)et垮刹,可以get到,說明主從同步成功张弛。
這時荒典,我們?nèi)绻趕lave窗口執(zhí)行 set ,會報錯:
-READONLY You can't write against a read only replica.
因?yàn)閺墓?jié)點(diǎn)是只讀的吞鸭。
1.3 哨兵sentinel
上面我們介紹了主從寺董,從庫作為一個“傀儡”,可以在需要的時候“頂上來”刻剥,”接盤“螃征。我們配置的主從是為了”有備無患“,在主redis掛了之后透敌,可以立馬切換到從redis上盯滚,可能只需要花幾分鐘的時間踢械,但是仍然是需要人為操作。假設(shè)主redis在晚上23點(diǎn)掛了魄藕,10分鐘之后你接到電話内列,老板讓你趕緊修復(fù),于是你從被窩爬起來整背率,豈不是很頭疼话瞧。假如你關(guān)機(jī)了,又其他人知道服務(wù)器密碼寝姿,那系統(tǒng)豈不是要停機(jī)一晚上交排?太可怕了。
這個時候redis sentinel 就派上用場了饵筑。sentinel 通常翻譯成哨兵埃篓,就是放哨的,這里它就是用來監(jiān)控主從節(jié)點(diǎn)的健康情況根资〖茏ǎ客戶端連接redis主從的時候,先連接 sentinel玄帕,sentinel會告訴客戶端主redis的地址是多少部脚,然后客戶端連接上redis并進(jìn)行后續(xù)的操作。當(dāng)主節(jié)點(diǎn)掛掉的時候裤纹,客戶端就得不到連接了因而報錯了委刘,客戶端重新想sentinel詢問主master的地址,然后客戶端得到了[新選舉出來的主redis]鹰椒,然后又可以愉快的操作了钱雷。
1.3.2 哨兵sentinel配置
為了說明sentinel的用處,我們做個試驗(yàn)吹零。配置3個redis(1主2從)罩抗,1個哨兵。步驟如下:
mkdir redis-sentinel
cd redis-sentinel
cp redis/path/conf/redis.conf path/to/redis-sentinel/redis01.conf
cp redis/path/conf/redis.conf path/to/redis-sentinel/redis02.conf
cp redis/path/conf/redis.conf path/to/redis-sentinel/redis03.conf
touch sentinel.conf
上我們創(chuàng)建了 3個redis配置文件灿椅,1個哨兵配置文件套蒂。我們將 redis01設(shè)置為master,將redis02,redis03設(shè)置為slave茫蛹。
vim redis01.conf
port 63791
vim redis02.conf
port 63792
slaveof 127.0.0.1 63791
vim redis03.conf
port 63793
slaveof 127.0.0.1 63791
vim sentinel.conf
daemonize yes
port 26379
sentinel monitor mymaster 127.0.0.1 63793 1
上面的主從配置都熟悉操刀,只有哨兵配置 sentinel.conf,需要解釋一下:
mymaster 為主節(jié)點(diǎn)名字婴洼,可以隨便取骨坑,后面程序里邊連接的時候要用到
127.0.0.1 63793 為主節(jié)點(diǎn)的 ip,port
1 后面的數(shù)字 1 表示選舉主節(jié)點(diǎn)的時候,投票數(shù)。1表示有一個sentinel同意即可升級為master
1.3.3 啟動哨兵欢唾,使用jedis連接哨兵操作redis
上面我們配置好了redis主從且警,1主2從,以及1個哨兵礁遣。下面我們分別啟動redis斑芜,并啟動哨兵
redis-server path/to/redis-sentinel/redis01.conf
redis-server path/to/redis-sentinel/redis02.conf
redis-server path/to/redis-sentinel/redis03.conf
redis-server path/to/redis-sentinel/sentinel.conf --sentinel
啟動之后,可以分別連接到 3個redis上祟霍,執(zhí)行info查看主從信息杏头。
1.3.4 編寫程序&運(yùn)行
下面使用程序來連接哨兵,并操作redis沸呐。
public static void main(String[] args) throws Exception{
Set<String> hosts = new HashSet<>();
hosts.add("127.0.0.1:26379");
//hosts.add("127.0.0.1:36379"); 配置多個哨兵
JedisSentinelPool pool = new JedisSentinelPool("mymaster",hosts);
Jedis jedis = null;
for(int i=0 ;i<20;i++){
Thread.sleep(2000);
try{
jedis = pool.getResource();
String v = randomString();
jedis.set("hello",v);
System.out.println(v+"-->"+jedis.get("hello").equals(v));
}catch (Exception e){
System.out.println(" [ exception happened]" + e);
}
}
}```
程序非常簡單醇王,循環(huán)運(yùn)行20次,連接哨兵崭添,將隨機(jī)字符串 set到redis寓娩,get結(jié)果。打印信息滥朱,異常捕獲。
1.3.5模擬主節(jié)點(diǎn)宕機(jī)情況
運(yùn)行上面的程序(注意力试,在實(shí)驗(yàn)這個效果的時候徙邻,可以將sleep時間加長或者for循環(huán)增多,以防程序提前停止畸裳,不便看整體效果)缰犁,然后將主redis關(guān)掉,模擬redis掛掉的情況〔篮現(xiàn)在主redis為redis01,端口為63791
redis-cli -p 63791 shutdown
這個時候如果sentinel沒有設(shè)置后臺運(yùn)行帅容,可以在命令行窗口看到 master切換的情況日志。
# Sentinel ID is fd0634dc9876ec60da65db5ff1e50ebbeefdf5ce
# +monitor master mymaster 127.0.0.1 63791 quorum 1
* +slave slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791
* +slave slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791
# +sdown master mymaster 127.0.0.1 63791
# +odown master mymaster 127.0.0.1 63791 #quorum 1/1
# +new-epoch 1
# +try-failover master mymaster 127.0.0.1 63791
# +vote-for-leader fd0634dc9876ec60da65db5ff1e50ebbeefdf5ce 1
# +elected-leader master mymaster 127.0.0.1 63791
# +failover-state-select-slave master mymaster 127.0.0.1 63791
# +selected-slave slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791
* +failover-state-send-slaveof-noone slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791
* +failover-state-wait-promotion slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791
# +promoted-slave slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791
# +failover-state-reconf-slaves master mymaster 127.0.0.1 63791
* +slave-reconf-sent slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791
* +slave-reconf-inprog slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791
* +slave-reconf-done slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791
# +failover-end master mymaster 127.0.0.1 63791
# +switch-master mymaster 127.0.0.1 63791 127.0.0.1 63793
* +slave slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63793
* +slave slave 127.0.0.1:63791 127.0.0.1 63791 @ mymaster 127.0.0.1 63793
# +sdown slave 127.0.0.1:63791 127.0.0.1 63791 @ mymaster 127.0.0.1 63793
# -sdown slave 127.0.0.1:63791 127.0.0.1 63791 @ mymaster 127.0.0.1 63793
* +convert-to-slave slave 127.0.0.1:63791 127.0.0.1 63791 @ mymaster 127.0.0.1 63793
上面的日志較多伍伤,仔細(xì)找找可以看到下面幾行主要的:
初始情況下并徘,1主2從
# +monitor master mymaster 127.0.0.1 63791 quorum 1
* +slave slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791
* +slave slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791
發(fā)現(xiàn)主掛了,準(zhǔn)備 故障轉(zhuǎn)移
# +try-failover master mymaster 127.0.0.1 63791
將主切換到了 63793 即redis03
# +switch-master mymaster 127.0.0.1 63791 127.0.0.1 63793
這個日志比較晦澀扰魂,從代碼運(yùn)行效果看麦乞,如下:
14:45:20.675 [main] INFO redis.clients.jedis.JedisSentinelPool - Trying to find master from available Sentinels...
14:45:25.731 [main] DEBUG redis.clients.jedis.JedisSentinelPool - Connecting to Sentinel 192.168.1.106:26379
14:45:25.770 [main] DEBUG redis.clients.jedis.JedisSentinelPool - Found Redis master at 127.0.0.1:63792
14:45:25.771 [main] INFO redis.clients.jedis.JedisSentinelPool - Redis master running at 127.0.0.1:63792, starting Sentinel listeners...
14:45:25.871 [main] INFO redis.clients.jedis.JedisSentinelPool - Created JedisPool to master at 127.0.0.1:63792
ejahaeegig-->true
deeeadejjf-->true
[ exception happened]redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
[ exception happened]........
[ exception happened]........
[ exception happened]........
[ exception happened]........
[ exception happened]........
[ exception happened]........
[ exception happened]........
[ exception happened]........
[ exception happened]........
14:46:02.737 [MasterListener-mymaster-[192.168.1.106:26379]] DEBUG redis.clients.jedis.JedisSentinelPool - Sentinel 192.168.1.106:26379 published: mymaster 127.0.0.1 63792 127.0.0.1 63793.
14:46:02.738 [MasterListener-mymaster-[192.168.1.106:26379]] INFO redis.clients.jedis.JedisSentinelPool - Created JedisPool to master at 127.0.0.1:63793
haiihiihbb-->true
ifgebdcicd-->true
aajhbjagag-->true
Process finished with exit code 0
從結(jié)果看出
- 開始正常操作redis,并設(shè)置了兩次劝评。
- 主redis掛了姐直,jedis得不到連接,報錯了JedisConnectionException:Could not get a resource from the pool
- 主redis沒選好之前蒋畜,程序持續(xù)報錯声畏。
- 主redis選好了,程序正常運(yùn)行姻成,最后結(jié)束插龄。
我們看到最后一次運(yùn)行設(shè)置的值是aajhbjagag
,我們可以連接剩下的2臺redis中的任意一臺愿棋,get hello,結(jié)果肯定是一致的。
1.4 redis cluster
上面的章節(jié)中辫狼,我們分別學(xué)習(xí)了redis 單點(diǎn)初斑,redis主從,并增加了高可用的 sentinel 哨兵模式膨处。我們所做的這些工作只是保證了數(shù)據(jù)備份以及高可用见秤,目前為止我們的程序一直都是向1臺redis寫數(shù)據(jù),其他的redis只是備份而已真椿。實(shí)際場景中鹃答,單個redis節(jié)點(diǎn)可能不滿足要求,因?yàn)椋?/p>
- 單個redis并發(fā)有限
- 單個redis接收所有的數(shù)據(jù)突硝,最終回導(dǎo)致內(nèi)存太大测摔,內(nèi)存太大回導(dǎo)致rdb文件過大,從很大的rdb文件中同步恢復(fù)數(shù)據(jù)會很慢解恰。
所有锋八,我們需要redis cluster 即redis集群。
Redis 集群是一個提供在多個Redis間節(jié)點(diǎn)間共享數(shù)據(jù)的程序集护盈。
Redis集群并不支持處理多個keys的命令,因?yàn)檫@需要在不同的節(jié)點(diǎn)間移動數(shù)據(jù),從而達(dá)不到像Redis那樣的性能,在高負(fù)載的情況下可能會導(dǎo)致不可預(yù)料的錯誤.
Redis 集群通過分區(qū)來提供一定程度的可用性,在實(shí)際環(huán)境中當(dāng)某個節(jié)點(diǎn)宕機(jī)或者不可達(dá)的情況下繼續(xù)處理命令. Redis 集群的優(yōu)勢:
- 自動分割數(shù)據(jù)到不同的節(jié)點(diǎn)上挟纱。
- 整個集群的部分節(jié)點(diǎn)失敗或者不可達(dá)的情況下能夠繼續(xù)處理命令。
為了配置一個redis cluster,我們需要準(zhǔn)備至少6臺redis腐宋,為啥至少6臺呢紊服?我們可以在redis的官方文檔中找到如下一句話:
Note that the minimal cluster that works as expected requires to contain at least three master nodes.
因?yàn)樽钚〉膔edis集群,需要至少3個主節(jié)點(diǎn)胸竞,既然有3個主節(jié)點(diǎn)欺嗤,而一個主節(jié)點(diǎn)搭配至少一個從節(jié)點(diǎn),因此至少得6臺redis卫枝。然而對我來說煎饼,就是復(fù)制6個redis配置文件。本實(shí)驗(yàn)的redis集群搭建依然在一臺電腦上模擬校赤。
1.4.1 配置 redis cluster 集群
上面提到紧索,配置redis集群需要至少6個redis節(jié)點(diǎn)高诺。因此我們需要準(zhǔn)備及配置的節(jié)點(diǎn)如下:
主:redis01 從 redis02 slaveof redis01
主:redis03 從 redis04 slaveof redis03
主:redis05 從 redis06 slaveof redis05
mkdir redis-cluster
cd redis-cluster
mkdir redis01 到 redis06 6個文件夾
cp redis.conf 到 redis01 ... redis06
修改端口
分別配置3組主從關(guān)系
1.4.2啟動redis集群
上面的配置完成之后赘被,分別啟動6個redis實(shí)例箱叁。配置正確的情況下,都可以啟動成功积蔚。然后運(yùn)行如下命令創(chuàng)建集群:
redis-5.0.3/src/redis-cli --cluster create 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375 127.0.0.1:6376 --cluster-replicas 1
注意意鲸,這里使用的是ip:port,而不是 domain:port ,因?yàn)槲以谑褂?localhost:6371 之類的寫法執(zhí)行的時候碰到錯誤:
ERR Invalid node address specified: localhost:6371
執(zhí)行成功之后怎顾,連接一臺redis读慎,執(zhí)行 cluster info 會看到類似如下信息:
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:1515
cluster_stats_messages_pong_sent:1506
cluster_stats_messages_sent:3021
cluster_stats_messages_ping_received:1501
cluster_stats_messages_pong_received:1515
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:3021
我們可以看到cluster_state:ok
,cluster_slots_ok:16384
,cluster_size:3
。
1.4.3 使用jedis連接redis cluster 集群#
上面我們配置了一個redis集群槐雾,包含6個redis節(jié)點(diǎn)夭委,3主3從。下面我們來使用jedis來連接redis集群募强。代碼如下:
public static void main(String[] args) {
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
//Jedis Cluster will attempt to discover cluster nodes automatically
jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6371));
jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6372));
jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6373));
jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6374));
jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6375));
jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6376));
JedisCluster jc = new JedisCluster(jedisClusterNodes);
jc.set("foo", "bar");
String value = jc.get("foo");
System.out.println(" ===> " + value);
}
上面我們設(shè)置了信息set foo bar
株灸,但是不知道被設(shè)置到那一臺redis上去了。請讀者思考一下擎值,我們是集群模式慌烧,所以數(shù)據(jù)被分散放到不同的槽中了,Redis 集群有16384個哈希槽,每個key通過CRC16校驗(yàn)后對16384取模來決定放置哪個槽.集群的每個節(jié)點(diǎn)負(fù)責(zé)一部分hash槽,舉個例子,比如當(dāng)前集群有3個節(jié)點(diǎn),那么:
- 節(jié)點(diǎn) A 包含 0 到 5500號哈希槽.
- 節(jié)點(diǎn) B 包含5501 到 11000 號哈希槽.
- 節(jié)點(diǎn) C 包含11001 到 16384號哈希槽.
看到這里你應(yīng)該還是不知道set foo bar
放到哪臺redis上去了鸠儿,不妨嘗試連接任意一臺redis探索一下屹蚊,你會知道的。
總結(jié)
至此进每,我們了解并動手實(shí)踐了redis的安裝汹粤,redis單點(diǎn),redis主從田晚,redis 哨兵 sentinel嘱兼,redis 集群cluster。
我們來梳理一下redis主從肉瓦,redis哨兵遭京,redis機(jī)器的區(qū)別和關(guān)系胃惜。
redis主從:是備份關(guān)系泞莉, 我們操作主庫,數(shù)據(jù)也會同步到從庫船殉。 如果主庫機(jī)器壞了鲫趁,從庫可以上。就好比你 D盤的片丟了利虫,但是你移動硬盤里邊備份有挨厚。
redis哨兵:哨兵保證的是HA,保證特殊情況故障自動切換糠惫,哨兵盯著你的“redis主從集群”疫剃,如果主庫死了,它會告訴你新的老大是誰硼讽。
redis集群:集群保證的是高并發(fā)巢价,因?yàn)槎嗔艘恍┬值軒兔σ黄鹂浮M瑫r集群會導(dǎo)致數(shù)據(jù)的分散,整個redis集群會分成一堆數(shù)據(jù)槽壤躲,即不同的key會放到不不同的槽中城菊。
主從保證了數(shù)據(jù)備份,哨兵保證了HA 即故障時切換碉克,集群保證了高并發(fā)性凌唬。
一切動手做了才會熟悉。