目錄
- redis單點、redis主從、redis哨兵 sentinel,redis叢集cluster配置搭建與使用
- 1 .redis 安裝及配置
- 1.1 redis 單點
- 1.1.2 在指令視窗操作redis
- 1.1.3 使用jedis用戶端操作redis
- 1.1.4 使用spring-redis操作
- 1.1.5 使用Lettuce操作redis
- 1.2 redis 主從
- 1.3 哨兵sentinel
- 1.3.2 哨兵sentinel配置
- 1.3.3 啟動哨兵,使用jedis連接配接哨兵操作redis
- 1.3.4 編寫程式&運作
- 1.3.5模拟主節點當機情況
- 1.4 redis cluster
- 1.4.1 配置 redis cluster 叢集
- 1.4.2啟動redis叢集
- 1.4.3 使用jedis連接配接redis cluster 叢集
- 總結
redis單點、redis主從、redis哨兵 sentinel,redis叢集cluster配置搭建與使用
redis是如今被網際網路公司使用最廣泛的一個中間件,我們打開GitHub搜尋redis,邊可以看到,該項目的介紹是這樣的:
Copy
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.
從這句話中,我們可以提取其特性的關鍵字:
- in-memory database ,記憶體資料庫
- support:Strings , lists, sets ,hashes ,hyperloglogs, bitmaps
也就是高性能,支援資料類型多。本文假設你已經了解redis的基本使用,進而讨論redis的單點,高可用,叢集。
1 .redis 安裝及配置#
redis的安裝十分簡單,打開redis的官網 http://redis.io 。
- 下載下傳一個最新版本的安裝包,如 redis-version.tar.gz
- 解壓
tar zxvf redis-version.tar.gz
- 執行 make (執行此指令可能會報錯,例如确實gcc,一個個解決即可)
如果是 mac 電腦,安裝redis将十分簡單執行
brew install redis
即可。
安裝好redis之後,我們先不慌使用,先進行一些配置。打開
redis.conf
檔案,我們主要關注以下配置:
Copy
port # 指定端口為 6379,也可自行修改
daemonize yes # 指定背景運作
1.1 redis 單點#
安裝好redis之後,我們來運作一下。啟動redis的指令為 :
redishome/bin/redis-server path/to/redis.config
假設我們沒有配置背景運作(即:daemonize no),那麼我們會看到如下啟動日志:
Copy
:C Jan :: # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
:C Jan :: # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=93825, just started
:C Jan :: # Configuration loaded
:S Jan :: * Increased maximum number of open files to (it was originally set to ).
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis (/) bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6380
| `-._ `._ / _.-' | PID:
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
無論是否配置了背景運作,啟動成功之後,我們可以新開一個指令行視窗來操作試試。
1.1.2 在指令視窗操作redis#
使用指令:
telnet localhost 6379
來連接配接redis,或者你可以直接使用代碼來連接配接測試。連接配接之後,看到如下資訊:
Copy
Connected to localhost.
Escape character is '^]'.
我們輸入幾個指令試試:
Copy
set hello world 設定key-value
get hello 擷取key值
expire hello 設定秒過期
ttl hello 檢視過期時間
del hello 删除key
如此,我們便體驗了一把redis,可以說是非常簡單了。剛才我們是使用指令行來操作redis的,下面我們來使用代碼操作一下redis,以
Java
為例,我們使用一個開源的 java - redis用戶端。
1.1.3 使用jedis用戶端操作redis#
打開GitHub,搜尋redis,進入到項目首頁之後,我們可以看到使用方法:
- 加入jedis依賴
Copy <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.0.0</version> <type>jar</type> <scope>compile</scope> </dependency>
- 編寫代碼如下
Copy Jedis jedis = new Jedis("localhost",);
jedis.set(“hello”, “world”);
String value = jedis.get(“hello”);
System.out.println(value); // get world
jedis.del(“hello”);
System.out.println(jedis.get(“hello”));// get null
1.1.4 使用spring-redis操作#
上面jedis操作redis的例子很簡單,除了使用jedis之外,還可以使用spring-redis。步驟如下
- 配置redis
Copy <bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/>
<!-- redis template definition -->
<bean id=“redisTemplate”
class=“org.springframework.data.redis.core.RedisTemplate”
p:connection-factory-ref=“jedisConnFactory”/>
- 編寫代碼
Copy public class Example {
<span class="hljs-comment">// inject the actual template</span> <span class="hljs-meta">@Autowired</span> <span class="hljs-keyword">private</span> RedisTemplate<String, String> template; <span class="hljs-comment">// inject the template as ListOperations</span> <span class="hljs-comment">// can also inject as Value, Set, ZSet, and HashOperations</span> <span class="hljs-meta">@Resource</span>(name=<span class="hljs-string">"redisTemplate"</span>) <span class="hljs-keyword">private</span> ListOperations<String, String> listOps; <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addLink</span><span class="hljs-params">(String userId, URL url)</span> </span>{ listOps.leftPush(userId, url.toExternalForm()); <span class="hljs-comment">// or use template directly</span> redisTemplate.boundListOps(userId).leftPush(url.toExternalForm()); }
- }
1.1.5 使用Lettuce操作redis#
Lettuce是一個基于netty的 非阻塞的 redis用戶端。支援Java8以及響應式。其官網為 https://lettuce.io/。Lettuce也可以和spring搭配使用。
使用Lettuce需要加入如下maven依賴:
基本的 get,set示例代碼如下:Copy <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>5.1.5.RELEASE</version> </dependency>
}Copy public class LettuceTest {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{ RedisURI uri = <span class="hljs-keyword">new</span> RedisURI(); uri.setHost(<span class="hljs-string">"myredishost"</span>); uri.setPort(<span class="hljs-number">6379</span>); uri.setDatabase(<span class="hljs-number">0</span>); RedisClient redisClient = RedisClient.create(uri); StatefulRedisConnection<String, String> connection = redisClient.connect(); RedisCommands<String, String> syncCommands = connection.sync(); syncCommands.set(<span class="hljs-string">"testKey"</span>, <span class="hljs-string">"Hello, Redis!"</span>); System.out.println(syncCommands.get(<span class="hljs-string">"testKey"</span>)); connection.close(); redisClient.shutdown(); }
1.2 redis 主從#
上面我們啟動了一台redis,并對其進行操作。當然這隻是實驗性的玩玩。假設我們生産環境使用了一台redis,redis挂了怎麼辦?如果等到運維重新開機redis,并恢複好資料,可能需要花費很長時間。那麼在這期間,我們的服務是不可用的,這應該是不能容忍的。假設我們做了主從,主庫挂了之後,運維讓從庫接管,那麼服務可以繼續運作,正所謂有備無患。
redis主從配置非常簡單,過程如下(ps 示範情況下主從配置在一台電腦上):
- 複制兩個redis配置檔案(啟動兩個redis,隻需要一份redis程式,兩個不同的redis配置檔案即可)
Copy 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
- 修改配置
Copy ## master.conf port 6379 ## master.conf port 6380 slaveof 127.0.0.1 6379
- 分别啟動兩個redis
Copy redis-server path/to/redis-master-slave/master.conf redis-server path/to/redis-master-slave/slave.conf
啟動之後,打開兩個指令行視窗,分别執行telnet localhost 6379 telnet localhost 6380
然後分别在兩個視窗中執行
指令,可以看到info
Copy # Replication role:master
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
主從配置沒問題。
然後在master 視窗執行 set 之後,到slave視窗執行get,可以get到,說明主從同步成功。
這時,我們如果在slave視窗執行 set ,會報錯:
因為從節點是隻讀的。Copy -READONLY You can't write against a read only replica.
1.3 哨兵sentinel#
上面我們介紹了主從,從庫作為一個“傀儡”,可以在需要的時候“頂上來”,”接盤“。我們配置的主從是為了”有備無患“,在主redis挂了之後,可以立馬切換到從redis上,可能隻需要花幾分鐘的時間,但是仍然是需要人為操作。假設主redis在晚上23點挂了,10分鐘之後你接到電話,老闆讓你趕緊修複,于是你從被窩爬起來整,豈不是很頭疼。假如你關機了,又其他人知道伺服器密碼,那系統豈不是要停機一晚上?太可怕了。
這個時候redis sentinel 就派上用場了。sentinel 通常翻譯成哨兵,就是放哨的,這裡它就是用來監控主從節點的健康情況。用戶端連接配接redis主從的時候,先連接配接 sentinel,sentinel會告訴用戶端主redis的位址是多少,然後用戶端連接配接上redis并進行後續的操作。當主節點挂掉的時候,用戶端就得不到連接配接了因而報錯了,用戶端重新想sentinel詢問主master的位址,然後用戶端得到了[新選舉出來的主redis],然後又可以愉快的操作了。
1.3.2 哨兵sentinel配置#
為了說明sentinel的用處,我們做個試驗。配置3個redis(1主2從),1個哨兵。步驟如下:
上我們建立了 3個redis配置檔案,1個哨兵配置檔案。我們将 redis01設定為master,将redis02,redis03設定為slave。Copy 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
Copy 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,需要解釋一下:
Copy mymaster 為主節點名字,可以随便取,後面程式裡邊連接配接的時候要用到 127.0.0.1 63793 為主節點的 ip,port 1 後面的數字 1 表示選舉主節點的時候,投票數。1表示有一個sentinel同意即可更新為master
1.3.3 啟動哨兵,使用jedis連接配接哨兵操作redis#
上面我們配置好了redis主從,1主2從,以及1個哨兵。下面我們分别啟動redis,并啟動哨兵Copy 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上,執行info檢視主從資訊。
1.3.4 編寫程式&運作#
下面使用程式來連接配接哨兵,并操作redis。
程式非常簡單,循環運作20次,連接配接哨兵,将随機字元串 set到redis,get結果。列印資訊,異常捕獲。Copy public static void main(String[] args) throws Exception{
Set<String> hosts = <span class="hljs-keyword">new</span> HashSet<>(); hosts.add(<span class="hljs-string">"127.0.0.1:26379"</span>); <span class="hljs-comment">//hosts.add("127.0.0.1:36379"); 配置多個哨兵</span> JedisSentinelPool pool = <span class="hljs-keyword">new</span> JedisSentinelPool(<span class="hljs-string">"mymaster"</span>,hosts); Jedis jedis = <span class="hljs-keyword">null</span>; <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span> ;i<<span class="hljs-number">20</span>;i++){ Thread.sleep(<span class="hljs-number">2000</span>); <span class="hljs-keyword">try</span>{ jedis = pool.getResource(); String v = randomString(); jedis.set(<span class="hljs-string">"hello"</span>,v); System.out.println(v+<span class="hljs-string">"-->"</span>+jedis.get(<span class="hljs-string">"hello"</span>).equals(v)); }<span class="hljs-keyword">catch</span> (Exception e){ System.out.println(<span class="hljs-string">" [ exception happened]"</span> + e); } } }</code></pre>
1.3.5模拟主節點當機情況#
運作上面的程式(注意,在實驗這個效果的時候,可以将sleep時間加長或者for循環增多,以防程式提前停止,不便看整體效果),然後将主redis關掉,模拟redis挂掉的情況。現在主redis為redis01,端口為63791
這個時候如果sentinel沒有設定背景運作,可以在指令行視窗看到 master切換的情況日志。Copy redis-cli -p shutdown
上面的日志較多,仔細找找可以看到下面幾行主要的:Copy # Sentinel ID is fd0634dc9876ec60da65db5ff1e50ebbeefdf5ce # +monitor master mymaster quorum * +slave slave @ mymaster * +slave slave @ mymaster # +sdown master mymaster # +odown master mymaster #quorum / # +new-epoch # +try-failover master mymaster # +vote-for-leader fd0634dc9876ec60da65db5ff1e50ebbeefdf5ce # +elected-leader master mymaster # +failover-state-select-slave master mymaster # +selected-slave slave @ mymaster * +failover-state-send-slaveof-noone slave @ mymaster * +failover-state-wait-promotion slave @ mymaster # +promoted-slave slave @ mymaster # +failover-state-reconf-slaves master mymaster * +slave-reconf-sent slave @ mymaster * +slave-reconf-inprog slave @ mymaster * +slave-reconf-done slave @ mymaster # +failover-end master mymaster # +switch-master mymaster * +slave slave @ mymaster * +slave slave @ mymaster # +sdown slave @ mymaster # -sdown slave @ mymaster * +convert-to-slave slave @ mymaster
這個日志比較晦澀,從代碼運作效果看,如下:Copy 初始情況下,主從 # +monitor master mymaster quorum * +slave slave @ mymaster * +slave slave @ mymaster 發現主挂了,準備 故障轉移 # +try-failover master mymaster 将主切換到了 即redis03 # +switch-master mymaster
Copy 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
從結果看出
- 開始正常操作redis,并設定了兩次。
- 主redis挂了,jedis得不到連接配接,報錯了JedisConnectionException:Could not get a resource from the pool
- 主redis沒選好之前,程式持續報錯。
- 主redis選好了,程式正常運作,最後結束。
,我們可以連接配接剩下的2台redis中的任意一台,get hello,結果肯定是一緻的。aajhbjagag
1.4 redis cluster#
上面的章節中,我們分别學習了redis 單點,redis主從,并增加了高可用的 sentinel 哨兵模式。我們所做的這些工作隻是保證了資料備份以及高可用,目前為止我們的程式一直都是向1台redis寫資料,其他的redis隻是備份而已。實際場景中,單個redis節點可能不滿足要求,因為:- 單個redis并發有限
- 單個redis接收所有的資料,最終回導緻記憶體太大,記憶體太大回導緻rdb檔案過大,從很大的rdb檔案中同步恢複資料會很慢。
所有,我們需要redis cluster 即redis叢集。
Redis 叢集是一個提供在多個Redis間節點間共享資料的程式集。
Redis叢集并不支援處理多個keys的指令,因為這需要在不同的節點間移動資料,進而達不到像Redis那樣的性能,在高負載的情況下可能會導緻不可預料的錯誤.
Redis 叢集通過分區來提供一定程度的可用性,在實際環境中當某個節點當機或者不可達的情況下繼續處理指令. Redis 叢集的優勢:
- 自動分割資料到不同的節點上。
- 整個叢集的部分節點失敗或者不可達的情況下能夠繼續處理指令。
因為最小的redis叢集,需要至少3個主節點,既然有3個主節點,而一個主節點搭配至少一個從節點,是以至少得6台redis。然而對我來說,就是複制6個redis配置檔案。本實驗的redis叢集搭建依然在一台電腦上模拟。Copy Note that the minimal cluster that works as expected requires to contain at least three master nodes.
1.4.1 配置 redis cluster 叢集#
上面提到,配置redis叢集需要至少6個redis節點。是以我們需要準備及配置的節點如下:Copy 主:redis01 從 redis02 slaveof redis01 主:redis03 從 redis04 slaveof redis03 主:redis05 從 redis06 slaveof redis05
Copy mkdir redis-cluster cd redis-cluster mkdir redis01 到 redis06 個檔案夾 cp redis.conf 到 redis01 ... redis06 修改端口 分别配置組主從關系
1.4.2啟動redis叢集#
上面的配置完成之後,分别啟動6個redis執行個體。配置正确的情況下,都可以啟動成功。然後運作如下指令建立叢集:
注意,這裡使用的是ip:port,而不是 domain:port ,因為我在使用 localhost:6371 之類的寫法執行的時候碰到錯誤:Copy 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
執行成功之後,連接配接一台redis,執行 cluster info 會看到類似如下資訊:Copy ERR Invalid node address specified: localhost:
我們可以看到Copy cluster_state:ok cluster_slots_assigned: cluster_slots_ok: cluster_slots_pfail: cluster_slots_fail: cluster_known_nodes: cluster_size: cluster_current_epoch: cluster_my_epoch: cluster_stats_messages_ping_sent: cluster_stats_messages_pong_sent: cluster_stats_messages_sent: cluster_stats_messages_ping_received: cluster_stats_messages_pong_received: cluster_stats_messages_meet_received: cluster_stats_messages_received:
,cluster_state:ok
,cluster_slots_ok:16384
。cluster_size:3
1.4.3 使用jedis連接配接redis cluster 叢集#
上面我們配置了一個redis叢集,包含6個redis節點,3主3從。下面我們來使用jedis來連接配接redis叢集。代碼如下:
上面我們設定了資訊Copy public static void main(String[] args) {
Set<HostAndPort> jedisClusterNodes = <span class="hljs-keyword">new</span> HashSet<HostAndPort>(); <span class="hljs-comment">//Jedis Cluster will attempt to discover cluster nodes automatically</span> jedisClusterNodes.add(<span class="hljs-keyword">new</span> HostAndPort(<span class="hljs-string">"127.0.0.1"</span>, <span class="hljs-number">6371</span>)); jedisClusterNodes.add(<span class="hljs-keyword">new</span> HostAndPort(<span class="hljs-string">"127.0.0.1"</span>, <span class="hljs-number">6372</span>)); jedisClusterNodes.add(<span class="hljs-keyword">new</span> HostAndPort(<span class="hljs-string">"127.0.0.1"</span>, <span class="hljs-number">6373</span>)); jedisClusterNodes.add(<span class="hljs-keyword">new</span> HostAndPort(<span class="hljs-string">"127.0.0.1"</span>, <span class="hljs-number">6374</span>)); jedisClusterNodes.add(<span class="hljs-keyword">new</span> HostAndPort(<span class="hljs-string">"127.0.0.1"</span>, <span class="hljs-number">6375</span>)); jedisClusterNodes.add(<span class="hljs-keyword">new</span> HostAndPort(<span class="hljs-string">"127.0.0.1"</span>, <span class="hljs-number">6376</span>)); JedisCluster jc = <span class="hljs-keyword">new</span> JedisCluster(jedisClusterNodes); jc.set(<span class="hljs-string">"foo"</span>, <span class="hljs-string">"bar"</span>); String value = jc.get(<span class="hljs-string">"foo"</span>); System.out.println(<span class="hljs-string">" ===> "</span> + value); }</code></pre>
,但是不知道被設定到那一台redis上去了。請讀者思考一下,我們是叢集模式,是以資料被分散放到不同的槽中了,Redis 叢集有16384個哈希槽,每個key通過CRC16校驗後對16384取模來決定放置哪個槽.叢集的每個節點負責一部分hash槽,舉個例子,比如目前叢集有3個節點,那麼:set foo bar
- 節點 A 包含 0 到 5500号哈希槽.
- 節點 B 包含5501 到 11000 号哈希槽.
- 節點 C 包含11001 到 16384号哈希槽.
放到哪台redis上去了,不妨嘗試連接配接任意一台redis探索一下,你會知道的。set foo bar
總結#
至此,我們了解并動手實踐了redis的安裝,redis單點,redis主從,redis 哨兵 sentinel,redis 叢集cluster。
我們來梳理一下redis主從,redis哨兵,redis機器的差別和關系。
redis主從:是備份關系, 我們操作主庫,資料也會同步到從庫。 如果主庫機器壞了,從庫可以上。就好比你 D盤的片丢了,但是你移動硬碟裡邊備份有。
redis哨兵:哨兵保證的是HA,保證特殊情況故障自動切換,哨兵盯着你的“redis主從叢集”,如果主庫死了,它會告訴你新的老大是誰。
redis叢集:叢集保證的是高并發,因為多了一些兄弟幫忙一起扛。同時叢集會導緻資料的分散,整個redis叢集會分成一堆資料槽,即不同的key會放到不不同的槽中。
主從保證了資料備份,哨兵保證了HA 即故障時切換,叢集保證了高并發性。
一切動手做了才會熟悉。
轉載自: BLOG.