redis單點、redis主從、redis哨兵sentinel,redis集羣cluster配置搭建與使用

redis單點、redis主從、redis哨兵 sentinel,redis集羣cluster配置搭建與使用

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

  • in-memory database ,內存數據庫
  • support:Strings , lists, sets ,hashes ,hyperloglogs, bitmaps

也就是高性能,支持數據類型多。本文假設你已經瞭解redis的基本使用,進而討論redis的單點,高可用,集羣。redis

1 .redis 安裝及配置

redis的安裝十分簡單,打開redis的官網 http://redis.iospring

  1. 下載一個最新版本的安裝包,如 redis-version.tar.gz
  2. 解壓 tar zxvf redis-version.tar.gz
  3. 執行 make (執行此命令可能會報錯,例如確實gcc,一個個解決便可)

若是是 mac 電腦,安裝redis將十分簡單執行brew install redis便可。數據庫

安裝好redis以後,咱們先不慌使用,先進行一些配置。打開redis.conf文件,咱們主要關注如下配置:vim

port 6379             # 指定端口爲 6379,也可自行修改 
daemonize yes         # 指定後臺運行

1.1 redis 單點

安裝好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        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'

不管是否配置了後臺運行,啓動成功以後,咱們能夠新開一個命令行窗口來操做試試。

1.1.2 在命令窗口操做redis

使用命令: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客戶端。

1.1.3 使用jedis客戶端操做redis

打開GitHub,搜索redis,進入到項目主頁以後,咱們能夠看到使用方法:

  1. 加入jedis依賴

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.0.0</version>
        <type>jar</type>
        <scope>compile</scope>
    </dependency>
  2. 編寫代碼以下

    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

1.1.4 使用spring-redis操做

上面jedis操做redis的例子很簡單,除了使用jedis以外,還可使用spring-redis。步驟以下

  1. 配置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"/>
  2. 編寫代碼

    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());
        }
    }

1.1.5 使用Lettuce操做redis

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();
    }

}

1.2 redis 主從

上面咱們啓動了一臺redis,並對其進行操做。固然這只是實驗性的玩玩。假設咱們生產環境使用了一臺redis,redis掛了怎麼辦?若是等到運維重啓redis,並恢復好數據,可能須要花費很長時間。那麼在這期間,咱們的服務是不可用的,這應該是不能容忍的。假設咱們作了主從,主庫掛了以後,運維讓從庫接管,那麼服務能夠繼續運行,正所謂有備無患。

redis主從配置很是簡單,過程以下(ps 演示狀況下主從配置在一臺電腦上):

  1. 複製兩個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
  1. 修改配置
## master.conf
port 6379

## master.conf
port 6380
slaveof 127.0.0.1 6379
  1. 分別啓動兩個redis
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.

由於從節點是隻讀的。

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個哨兵。步驟以下:

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

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上,執行info查看主從信息。

1.3.4 編寫程序&運行

下面使用程序來鏈接哨兵,並操做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結果。打印信息,異常捕獲。

1.3.5模擬主節點宕機狀況

運行上面的程序(注意,在實驗這個效果的時候,能夠將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

從結果看出

  1. 開始正常操做redis,並設置了兩次。
  2. 主redis掛了,jedis得不到鏈接,報錯了JedisConnectionException:Could not get a resource from the pool
  3. 主redis沒選好以前,程序持續報錯。
  4. 主redis選好了,程序正常運行,最後結束。

咱們看到最後一次運行設置的值是aajhbjagag,咱們能夠鏈接剩下的2臺redis中的任意一臺,get hello,結果確定是一致的。

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 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集羣搭建依然在一臺電腦上模擬。

1.4.1 配置 redis cluster 集羣

上面提到,配置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組主從關係

1.4.2啓動redis集羣

上面的配置完成以後,分別啓動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

1.4.3 使用jedis鏈接redis cluster 集羣

上面咱們配置了一個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個節點,那麼:

  • 節點 A 包含 0 到 5500號哈希槽.
  • 節點 B 包含5501 到 11000 號哈希槽.
  • 節點 C 包含11001 到 16384號哈希槽.

看到這裏你應該仍是不知道set foo bar 放到哪臺redis上去了,不妨嘗試鏈接任意一臺redis探索一下,你會知道的。

總結

至此,咱們瞭解並動手實踐了redis的安裝,redis單點,redis主從,redis 哨兵 sentinel,redis 集羣cluster。
咱們來梳理一下redis主從,redis哨兵,redis機器的區別和關係。

redis主從:是備份關係, 咱們操做主庫,數據也會同步到從庫。 若是主庫機器壞了,從庫能夠上。就比如你 D盤的片丟了,可是你移動硬盤裏邊備份有。
redis哨兵:哨兵保證的是HA,保證特殊狀況故障自動切換,哨兵盯着你的「redis主從集羣」,若是主庫死了,它會告訴你新的老大是誰。
redis集羣:集羣保證的是高併發,由於多了一些兄弟幫忙一塊兒扛。同時集羣會致使數據的分散,整個redis集羣會分紅一堆數據槽,即不一樣的key會放到不不一樣的槽中。

主從保證了數據備份,哨兵保證了HA 即故障時切換,集羣保證了高併發性。

一切動手作了纔會熟悉。

Lua腳本在redis分佈式鎖場景的運用

Netty開發redis客戶端,Netty發送redis命令,netty解析redis消息

相關文章
相關標籤/搜索