目錄html
redis是現在被互聯網公司使用最普遍的一箇中間件,咱們打開GitHub搜索redis,邊能夠看到,該項目的介紹是這樣的:java
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.
從這句話中,咱們能夠提取其特性的關鍵字:node
也就是高性能,支持數據類型多。本文假設你已經瞭解redis的基本使用,進而討論redis的單點,高可用,集羣。redis
redis的安裝十分簡單,打開redis的官網 http://redis.io 。spring
tar zxvf redis-version.tar.gz
若是是 mac 電腦,安裝redis將十分簡單執行brew install redis
便可。數據庫
安裝好redis以後,咱們先不慌使用,先進行一些配置。打開redis.conf
文件,咱們主要關注如下配置:vim
port 6379 # 指定端口爲 6379,也可自行修改 daemonize yes # 指定後臺運行
安裝好redis以後,咱們來運行一下。啓動redis的命令爲 :bash
redishome/bin/redis-server path/to/redis.config
服務器
假設咱們沒有配置後臺運行(即: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). _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 5.0.3 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in standalone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6380 | `-._ `._ / _.-' | PID: 93825 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-'
不管是否配置了後臺運行,啓動成功以後,咱們能夠新開一個命令行窗口來操做試試。
使用命令:telnet localhost 6379
來鏈接redis,或者你能夠直接使用代碼來鏈接測試。鏈接以後,看到以下信息:
Connected to localhost. Escape character is '^]'.
咱們輸入幾個命令試試:
set hello world 設置key-value get hello 獲取key值 expire hello 10 設置10秒過時 ttl hello 查看過時時間 del hello 刪除key
如此,咱們便體驗了一把redis,能夠說是很是簡單了。剛纔咱們是使用命令行來操做redis的,下面咱們來使用代碼操做一下redis,以Java
爲例,咱們使用一個開源的 java - redis客戶端。
打開GitHub,搜索redis,進入到項目主頁以後,咱們能夠看到使用方法:
加入jedis依賴
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.0.0</version> <type>jar</type> <scope>compile</scope> </dependency>
編寫代碼以下
Jedis jedis = new Jedis("localhost",6379); 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
上面jedis操做redis的例子很簡單,除了使用jedis以外,還可使用spring-redis。步驟以下
配置redis
<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"/>
編寫代碼
public class Example { // inject the actual template @Autowired private RedisTemplate<String, String> template; // inject the template as ListOperations // can also inject as Value, Set, ZSet, and HashOperations @Resource(name="redisTemplate") private ListOperations<String, String> listOps; public void addLink(String userId, URL url) { listOps.leftPush(userId, url.toExternalForm()); // or use template directly redisTemplate.boundListOps(userId).leftPush(url.toExternalForm()); } }
Lettuce是一個基於netty的 非阻塞的 redis客戶端。支持Java8以及響應式。其官網爲 https://lettuce.io/。Lettuce也能夠和spring搭配使用。
使用Lettuce須要加入以下maven依賴:
<dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>5.1.5.RELEASE</version> </dependency>
基本的 get,set示例代碼以下:
public class LettuceTest { public static void main(String[] args) { RedisURI uri = new RedisURI(); uri.setHost("myredishost"); uri.setPort(6379); uri.setDatabase(0); RedisClient redisClient = RedisClient.create(uri); StatefulRedisConnection<String, String> connection = redisClient.connect(); RedisCommands<String, String> syncCommands = connection.sync(); syncCommands.set("testKey", "Hello, Redis!"); System.out.println(syncCommands.get("testKey")); connection.close(); redisClient.shutdown(); } }
上面咱們啓動了一臺redis,並對其進行操做。固然這只是實驗性的玩玩。假設咱們生產環境使用了一臺redis,redis掛了怎麼辦?若是等到運維重啓redis,並恢復好數據,可能須要花費很長時間。那麼在這期間,咱們的服務是不可用的,這應該是不能容忍的。假設咱們作了主從,主庫掛了以後,運維讓從庫接管,那麼服務能夠繼續運行,正所謂有備無患。
redis主從配置很是簡單,過程以下(ps 演示狀況下主從配置在一臺電腦上):
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-server path/to/redis-master-slave/master.conf redis-server path/to/redis-master-slave/slave.conf
啓動以後,打開兩個命令行窗口,分別執行telnet localhost 6379 telnet localhost 6380
而後分別在兩個窗口中執行 info
命令,能夠看到
# Replication role:master # Replication role:slave master_host:127.0.0.1 master_port:6379
主從配置沒問題。
而後在master 窗口執行 set 以後,到slave窗口執行get,能夠get到,說明主從同步成功。
這時,咱們若是在slave窗口執行 set ,會報錯:
-READONLY You can't write against a read only replica.
由於從節點是隻讀的。
上面咱們介紹了主從,從庫做爲一個「傀儡」,能夠在須要的時候「頂上來」,」接盤「。咱們配置的主從是爲了」有備無患「,在主redis掛了以後,能夠立馬切換到從redis上,可能只須要花幾分鐘的時間,可是仍然是須要人爲操做。假設主redis在晚上23點掛了,10分鐘以後你接到電話,老闆讓你趕忙修復,因而你從被窩爬起來整,豈不是很頭疼。假如你關機了,又其餘人知道服務器密碼,那系統豈不是要停機一夜?太可怕了。
這個時候redis sentinel 就派上用場了。sentinel 一般翻譯成哨兵,就是放哨的,這裏它就是用來監控主從節點的健康狀況。客戶端鏈接redis主從的時候,先鏈接 sentinel,sentinel會告訴客戶端主redis的地址是多少,而後客戶端鏈接上redis並進行後續的操做。當主節點掛掉的時候,客戶端就得不到鏈接了於是報錯了,客戶端從新想sentinel詢問主master的地址,而後客戶端獲得了[新選舉出來的主redis],而後又能夠愉快的操做了。
爲了說明sentinel的用處,咱們作個試驗。配置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
上咱們建立了 3個redis配置文件,1個哨兵配置文件。咱們將 redis01設置爲master,將redis02,redis03設置爲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 爲主節點名字,能夠隨便取,後面程序裏邊鏈接的時候要用到 127.0.0.1 63793 爲主節點的 ip,port 1 後面的數字 1 表示選舉主節點的時候,投票數。1表示有一個sentinel贊成便可升級爲master
上面咱們配置好了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上,執行info查看主從信息。
下面使用程序來鏈接哨兵,並操做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); } } }
程序很是簡單,循環運行20次,鏈接哨兵,將隨機字符串 set到redis,get結果。打印信息,異常捕獲。
運行上面的程序(注意,在實驗這個效果的時候,能夠將sleep時間加長或者for循環增多,以防程序提早中止,不便看總體效果),而後將主redis關掉,模擬redis掛掉的狀況。如今主redis爲redis01,端口爲63791
redis-cli -p 63791 shutdown
這個時候若是sentinel沒有設置後臺運行,能夠在命令行窗口看到 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
上面的日誌較多,仔細找找能夠看到下面幾行主要的:
初始狀況下,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 發現主掛了,準備 故障轉移 # +try-failover master mymaster 127.0.0.1 63791 將主切換到了 63793 即redis03 # +switch-master mymaster 127.0.0.1 63791 127.0.0.1 63793
這個日誌比較晦澀,從代碼運行效果看,以下:
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
從結果看出
咱們看到最後一次運行設置的值是aajhbjagag
,咱們能夠鏈接剩下的2臺redis中的任意一臺,get hello,結果確定是一致的。
上面的章節中,咱們分別學習了redis 單點,redis主從,並增長了高可用的 sentinel 哨兵模式。咱們所作的這些工做只是保證了數據備份以及高可用,目前爲止咱們的程序一直都是向1臺redis寫數據,其餘的redis只是備份而已。實際場景中,單個redis節點可能不知足要求,由於:
全部,咱們須要redis cluster 即redis集羣。
Redis 集羣是一個提供在多個Redis間節點間共享數據的程序集。
Redis集羣並不支持處理多個keys的命令,由於這須要在不一樣的節點間移動數據,從而達不到像Redis那樣的性能,在高負載的狀況下可能會致使不可預料的錯誤.
Redis 集羣經過分區來提供必定程度的可用性,在實際環境中當某個節點宕機或者不可達的狀況下繼續處理命令. Redis 集羣的優點:
爲了配置一個redis cluster,咱們須要準備至少6臺redis,爲啥至少6臺呢?咱們能夠在redis的官方文檔中找到以下一句話:
Note that the minimal cluster that works as expected requires to contain at least three master nodes.
由於最小的redis集羣,須要至少3個主節點,既然有3個主節點,而一個主節點搭配至少一個從節點,所以至少得6臺redis。然而對我來講,就是複製6個redis配置文件。本實驗的redis集羣搭建依然在一臺電腦上模擬。
上面提到,配置redis集羣須要至少6個redis節點。所以咱們須要準備及配置的節點以下:
主: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組主從關係
上面的配置完成以後,分別啓動6個redis實例。配置正確的狀況下,均可以啓動成功。而後運行以下命令建立集羣:
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 ,由於我在使用 localhost:6371 之類的寫法執行的時候碰到錯誤:
ERR Invalid node address specified: localhost:6371
執行成功以後,鏈接一臺redis,執行 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
。
上面咱們配置了一個redis集羣,包含6個redis節點,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); }
上面咱們設置了信息set foo bar
,可是不知道被設置到那一臺redis上去了。請讀者思考一下,咱們是集羣模式,因此數據被分散放到不一樣的槽中了,Redis 集羣有16384個哈希槽,每一個key經過CRC16校驗後對16384取模來決定放置哪一個槽.集羣的每一個節點負責一部分hash槽,舉個例子,好比當前集羣有3個節點,那麼:
看到這裏你應該仍是不知道set foo bar
放到哪臺redis上去了,不妨嘗試鏈接任意一臺redis探索一下,你會知道的。
至此,咱們瞭解並動手實踐了redis的安裝,redis單點,redis主從,redis 哨兵 sentinel,redis 集羣cluster。
咱們來梳理一下redis主從,redis哨兵,redis機器的區別和關係。
redis主從:是備份關係, 咱們操做主庫,數據也會同步到從庫。 若是主庫機器壞了,從庫能夠上。就比如你 D盤的片丟了,可是你移動硬盤裏邊備份有。
redis哨兵:哨兵保證的是HA,保證特殊狀況故障自動切換,哨兵盯着你的「redis主從集羣」,若是主庫死了,它會告訴你新的老大是誰。
redis集羣:集羣保證的是高併發,由於多了一些兄弟幫忙一塊兒扛。同時集羣會致使數據的分散,整個redis集羣會分紅一堆數據槽,即不一樣的key會放到不不一樣的槽中。
主從保證了數據備份,哨兵保證了HA 即故障時切換,集羣保證了高併發性。
一切動手作了纔會熟悉。