1. 程式人生 > >Docker實戰之Redis-Cluster叢集

Docker實戰之Redis-Cluster叢集

概述

接上一篇Docker實戰之MySQL主從複製, 這裡是Docker實戰系列的第二篇,主要進行Redis-Cluster叢集環境的快速搭建。Redis作為基於鍵值對的NoSQL資料庫,具有高效能、豐富的資料結構、持久化、高可用、分散式等特性,同時Redis本身非常穩定,已經得到業界的廣泛認可和使用。

在Redis中,叢集的解決方案有三種

  1. 主從複製
  2. 哨兵機制
  3. Cluster

Redis Cluster是Redis的分散式解決方案,在 3.0 版本正式推出。

叢集方案的對比

1. 主從複製

同Mysql主從複製的原因一樣,Redis雖然讀取寫入的速度都特別快,但是也會產生讀壓力特別大的情況。為了分擔讀壓力,Redis支援主從複製,讀寫分離。一個Master可以有多個Slaves。

優點

  • 資料備份
  • 讀寫分離,提高伺服器效能

缺點

  • 不能自動故障恢復,RedisHA系統(需要開發)
  • 無法實現動態擴容

2. 哨兵機制

Redis Sentinel是社群版本推出的原生高可用解決方案,其部署架構主要包括兩部分:Redis Sentinel叢集和Redis資料叢集。

其中Redis Sentinel叢集是由若干Sentinel節點組成的分散式叢集,可以實現故障發現、故障自動轉移、配置中心和客戶端通知。Redis Sentinel的節點數量要滿足2n+1(n>=1)的奇數個。

優點

  • 自動化故障恢復

缺點

  • Redis 資料節點中 slave 節點作為備份節點不提供服務
  • 無法實現動態擴容

3. Redis-Cluster

Redis Cluster是社群版推出的Redis分散式叢集解決方案,主要解決Redis分散式方面的需求,比如,當遇到單機記憶體,併發和流量等瓶頸的時候,Redis Cluster能起到很好的負載均衡的目的。

Redis Cluster著眼於提高併發量

群集至少需要3主3從,且每個例項使用不同的配置檔案。

在redis-cluster架構中,redis-master節點一般用於接收讀寫,而redis-slave節點則一般只用於備份, 其與對應的master擁有相同的slot集合,若某個redis-master意外失效,則再將其對應的slave進行升級為臨時redis-master。

在redis的官方文件中,對redis-cluster架構上,有這樣的說明:在cluster架構下,預設的,一般redis-master用於接收讀寫,而redis-slave則用於備份,當有請求是在向slave發起時,會直接重定向到對應key所在的master來處理。 但如果不介意讀取的是redis-cluster中有可能過期的資料並且對寫請求不感興趣時,則亦可通過readonly命令,將slave設定成可讀,然後通過slave獲取相關的key,達到讀寫分離。具體可以參閱redis官方文件等相關內容

優點

  • 解決分散式負載均衡的問題。具體解決方案是分片/虛擬槽slot。
  • 可實現動態擴容
  • P2P模式,無中心化

缺點

  • 為了效能提升,客戶端需要快取路由表資訊
  • Slave在叢集中充當“冷備”,不能緩解讀壓力

網路規劃

這裡沒有搭建虛擬機器環境,全部在本地部署。本機的ip為 192.168.124.5

ip port
192.168.124.5 7001
192.168.124.5 7002
192.168.124.5 7003
192.168.124.5 7004
192.168.124.5 7005
192.168.124.5 7006

Redis配置檔案

在docker環境中,配置檔案對映宿主機的時候,(宿主機)必須有配置檔案。附件在這裡。大家可以根據自己的需求定製配置檔案。

下邊是我的配置檔案 redis-cluster.tmpl

# redis埠
port ${PORT}
# 關閉保護模式
protected-mode no
# 開啟叢集
cluster-enabled yes
# 叢集節點配置
cluster-config-file nodes.conf
# 超時
cluster-node-timeout 5000
# 叢集節點IP host模式為宿主機IP
cluster-announce-ip 192.168.124.5
# 叢集節點埠 7001 - 7006
cluster-announce-port ${PORT}
cluster-announce-bus-port 1${PORT}
# 開啟 appendonly 備份模式
appendonly yes
# 每秒鐘備份
appendfsync everysec
# 對aof檔案進行壓縮時,是否執行同步操作
no-appendfsync-on-rewrite no
# 當目前aof檔案大小超過上一次重寫時的aof檔案大小的100%時會再次進行重寫
auto-aof-rewrite-percentage 100
# 重寫前AOF檔案的大小最小值 預設 64mb
auto-aof-rewrite-min-size 64mb

由於節點IP相同,只有埠上的差別,現在通過指令碼 redis-cluster-config.sh 批量生成配置檔案

for port in `seq 7001 7006`; do \
  mkdir -p ./redis-cluster/${port}/conf \
  && PORT=${port} envsubst < ./redis-cluster.tmpl > ./redis-cluster/${port}/conf/redis.conf \
  && mkdir -p ./redis-cluster/${port}/data; \
done

生成的配置檔案如下圖

Docker環境搭建

這裡還是通過docker-compose進行測試環境的docker編排。

version: '3.7'

services:
  redis7001:
    image: 'redis'
    container_name: redis7001
    command:
      ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./redis-cluster/7001/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - ./redis-cluster/7001/data:/data
    ports:
      - "7001:7001"
      - "17001:17001"
    environment:
      # 設定時區為上海,否則時間會有問題
      - TZ=Asia/Shanghai


  redis7002:
    image: 'redis'
    container_name: redis7002
    command:
      ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./redis-cluster/7002/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - ./redis-cluster/7002/data:/data
    ports:
      - "7002:7002"
      - "17002:17002"
    environment:
      # 設定時區為上海,否則時間會有問題
      - TZ=Asia/Shanghai


  redis7003:
    image: 'redis'
    container_name: redis7003
    command:
      ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./redis-cluster/7003/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - ./redis-cluster/7003/data:/data
    ports:
      - "7003:7003"
      - "17003:17003"
    environment:
      # 設定時區為上海,否則時間會有問題
      - TZ=Asia/Shanghai


  redis7004:
    image: 'redis'
    container_name: redis7004
    command:
      ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./redis-cluster/7004/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - ./redis-cluster/7004/data:/data
    ports:
      - "7004:7004"
      - "17004:17004"
    environment:
      # 設定時區為上海,否則時間會有問題
      - TZ=Asia/Shanghai


  redis7005:
    image: 'redis'
    container_name: redis7005
    command:
      ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./redis-cluster/7005/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - ./redis-cluster/7005/data:/data
    ports:
      - "7005:7005"
      - "17005:17005"
    environment:
      # 設定時區為上海,否則時間會有問題
      - TZ=Asia/Shanghai


  redis7006:
    image: 'redis'
    container_name: redis7006
    command:
      ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./redis-cluster/7006/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - ./redis-cluster/7006/data:/data
    ports:
      - "7006:7006"
      - "17006:17006"
    environment:
      # 設定時區為上海,否則時間會有問題
      - TZ=Asia/Shanghai

啟動結果如圖

叢集配置

redis叢集官方提供了配置指令碼,4.x和5.x略有不同,具體可參見叢集配置

下邊是我自己的環境

docker exec -it redis7001 redis-cli -p 7001 -a 123456 --cluster create 192.168.124.5:7001 192.168.124.5:7002 192.168.124.5:7003 192.168.124.5:7004 192.168.124.5:7005 192.168.124.5:7006 --cluster-replicas 1

看到如下結果說明叢集配置成功

叢集測試

接下來進行一些叢集的基本測試

1. 檢視叢集通訊是否正常

redis7001主節點對它的副本節點redis7005進行ping操作。

-h host -p port -a pwd

➜  docker docker exec -it redis7001 redis-cli -h 192.168.124.5 -p 7005 -a 123456 ping

Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
PONG

2. 測試簡單儲存

redis7001主節點客戶端操作redis7003主節點

➜  docker docker exec -it redis7001 redis-cli -h 192.168.124.5 -p 7003 -a 123456

Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
192.168.124.5:7003> set name admin
(error) MOVED 5798 192.168.124.5:7002

由於Redis Cluster會根據key進行hash運算,然後將key分散到不同slots,name的hash運算結果在redis7002節點上的slots中。所以我們操作redis7003寫操作會自動路由到7002。然而error提示無法路由?沒關係,差一個 -c 引數而已。

再次執行檢視結果如下:

➜  docker docker exec -it redis7001 redis-cli -h 192.168.124.5 -p 7003 -a 123456 -c

Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
192.168.124.5:7003> set name admin
-> Redirected to slot [5798] located at 192.168.124.5:7002
OK
192.168.124.5:7002> get name
"admin"
192.168.124.5:7002>

3. 檢視叢集狀態

4. 檢視slots分片

5. 檢視叢集資訊

6. 測試讀寫分離

試試看,發現讀不到,原來在redis cluster中,如果你要在slave讀取資料,那麼需要帶先執行 readonly 指令,然後 get key

7. 簡單壓測

選項 描述
-t 指定命令
-c 客戶端連線數
-n 總請求數
-d set、get的value大小(單位byte)

測試如下

➜  docker docker exec -it redis7001 bash
root@cbc6e76a3ed2:/data# redis-benchmark -h 192.168.124.5 -p 7001 -t set -c 100 -n 50000 -d 20
====== SET ======
  50000 requests completed in 10.65 seconds
  100 parallel clients
  20 bytes payload
  keep alive: 1

0.00% <= 2 milliseconds
0.01% <= 3 milliseconds
...
100.00% <= 48 milliseconds
100.00% <= 49 milliseconds
4692.63 requests per second

這裡沒啥實際意義,在工作業務上大家可以根據QPS和主機配置進行壓測,計算規劃出節點數量。

容災演練

現在我們殺掉主節點redis7001,看從節點redis7005是否會接替它的位置。

docker stop redis7001

再試著啟動7001,它將自動作為slave掛載到7005

SpringBoot配置Redis叢集

在SpringBoot2.x版本中,redis預設的連線池已經更換為Lettuce,而不再是jedis。

  1. 在pom.xml中引入相關依賴
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
  1. application.yml
spring:
  redis:
    timeout: 6000
    password: 123456
    cluster:
      max-redirects: 3 # 獲取失敗 最大重定向次數 
      nodes:
        - 192.168.124.5:7001
        - 192.168.124.5:7002
        - 192.168.124.5:7003
        - 192.168.124.5:7004
        - 192.168.124.5:7005
        - 192.168.124.5:7006
    lettuce:
      pool:
        max-active: 1000 #連線池最大連線數(使用負值表示沒有限制)
        max-idle: 10 # 連線池中的最大空閒連線
        min-idle: 5 # 連線池中的最小空閒連線
        max-wait: -1 # 連線池最大阻塞等待時間(使用負值表示沒有限制)
  cache:
    jcache:
      config: classpath:ehcache.xml
  1. redis配置
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}
  1. 基本測試
@SpringBootTest
public class RedisTest {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Test
    public void test() {
        redisTemplate.opsForValue().set("name", "admin");
        String name = redisTemplate.opsForValue().get("name");
        System.out.println(name); //輸出admin
    }
}

總結

通過以上演示,基本上可以在本地環境下用我們的Redis Cluster叢集了。最後再上一張本地對映檔案的最終樣子,幫助大家瞭解Redis持久化及叢集相關的東西。感興趣的小夥伴可以自行測試並檢視其中的內容。

內容如有錯漏,還望大家不吝賜教,同時,歡迎大家關注公眾號【當我遇上你】,你們的支援就是我寫作的最大動力。

參考

  • https://redis.io/topics/cluster-tutorial

公眾號 【當我遇上你】