1. 程式人生 > >Redis practise(二)使用Docker部署Redis高可用,分散式叢集

Redis practise(二)使用Docker部署Redis高可用,分散式叢集

思路

鑑於之間學習過的Docker一些基礎知識,這次準備部署一個簡單的分散式,高可用的redis叢集,如下的拓撲

tuopu.png

下面介紹下,對於這張拓撲圖而言,需要了解的一些基礎概念。

Redis持久化

Redis有兩種持久化策略。

Rdb

  • 全量備份
  • 形成二進位制檔案: dump.rdb

在使用命令 save(停寫), BgSave。或者Save配置條件觸發時,開始全量持久化Rdb檔案。
相關的Redis.conf配置有:

  • dir
  • dbfilename
  • save(停寫)
    例如:
save 900 1  #after 900 sec (15 min) if at least 1 key changed
save 300 10 #after 300 sec (5 min) if at least 10 keys changed
save 60 10000 #after 60 sec if at least 10000 keys changed dbfilename dump.rdb # The filename where to dump the DB dir ./ # The DB will be written inside this directory

aof

  • Append only file
  • 增量備份

Redis.conf配置

appendonly yes
appendfilename "appendonly.aof" # The name of the append only file (default: "appendonly.aof")

那麼aof和Rdb的一些對比呢:

Col1rdbaof
效能影響平時不影響效能
備份時效能影響較大
效能一直會受到影響
資料丟失兩次備份之間的增量資料會丟失不會丟失資料
備份檔案大小只有資料,較小除了資料還記錄了資料的變化過程,較大

可參考 官方文件



作者:professorLea
連結:https://www.jianshu.com/p/ad878b513f9a
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。

Redis高可用

從兩個角度來考慮高可用:主從複製,主從切換

主從複製

可以通過命令SLAVEOF,來配置當前節點是某個Redis的Slave節點。參考 slaveof

主從切換

使用官方的Redis-sentinel可以實現Redis的主從切換。
參考

Redis官方文件sentinel.md

值得注意的是,配飾抖個Sentinel例項,他們之間是互相通訊的。官方推薦:一個健康的叢集部署,至少需要3個Sentinel例項。這樣他們之間會通過quorum引數來執行主從切換操作ODOWN。

  • SDOWN:主觀離線,Sentinel發現redis掛了。
  • ODOWN:客觀離線,Sentinel根據規則投票後確定redis掛了
  • 規則為 #of SDOWN>quorum

啟動Sentinel

redis-sentinel /path/to/sentinel.conf

在本文的例子中,Sentienl的配置檔案如下所示:
sentinel 例項1:

port 26379
daemonize yes
logfile "/var/log/redis/sentinel_26379.log"
sentinel monitor firstmaster 10.165.124.10 16379 2
sentinel auth-pass firstmaster 123456
sentinel config-epoch firstmaster 0
sentinel leader-epoch firstmaster 0
sentinel monitor sencondmaster 10.165.124.10 16381 2
sentinel auth-pass sencondmaster 123456
sentinel config-epoch sencondmaster 0
sentinel leader-epoch sencondmaster  0

sentinel 例項2:

port 26380
daemonize yes
logfile "/var/log/redis/sentinel_26380.log"
sentinel monitor firstmaster 10.165.124.10 16379 2
sentinel auth-pass firstmaster 123456
sentinel config-epoch firstmaster 0
sentinel leader-epoch firstmaster 0
sentinel monitor sencondmaster 10.165.124.10 16381 2
sentinel auth-pass sencondmaster 123456
sentinel config-epoch sencondmaster 0
sentinel leader-epoch sencondmaster  0

sentinel 例項3:

port 26381
daemonize yes
logfile "/var/log/redis/sentinel_26381.log"
sentinel monitor firstmaster 10.165.124.10 16379 2
sentinel auth-pass firstmaster 123456
sentinel config-epoch firstmaster 0
sentinel leader-epoch firstmaster 0
sentinel monitor sencondmaster 10.165.124.10 16381 2
sentinel auth-pass sencondmaster 123456
sentinel config-epoch sencondmaster 0
sentinel leader-epoch sencondmaster  0

可以看出,3個例項監控了 firstmaster及secondmaster,兩個叢集。
對於其中一個sentinel例項,看到它的資訊如下:

$ redis-cli -h  10.165.124.10 -p 26380 -a 123456
10.165.124.10:26380> info
# Server
redis_version:2.8.24
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:7de005b109aa3dd5
redis_mode:sentinel
os:Linux 3.16.0-0.bpo.4-amd64 x86_64
arch_bits:64
multiplexing_api:epoll
gcc_version:4.7.2
process_id:28837
run_id:630dfd8f09105bc84147923319afbf1d5cebe9a5
tcp_port:26380
uptime_in_seconds:347096
uptime_in_days:4
hz:13
lru_clock:12213499
config_file:/etc/redis/sentinel_26380.conf

# Sentinel
sentinel_masters:2
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
master0:name=firstmaster,status=ok,address=10.165.124.10:16379,slaves=1,sentinels=3
master1:name=secondmaster,status=ok,address=10.165.124.10:16381,slaves=1,sentinels=3

最後2行可以看出監控的叢集,其餘2個sentinel例項也是如此。

下面講,如何在這樣的叢集上,通過Jedis來進行叢集操作

JedisSentinelPool

它解決了痛點:

  • 不確定主redis 的ip port
  • 需要從sentinel獲取

JedisSentinelPool可以直接想sentinel查詢當前master的ip port,在建立連線。


import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;

import java.util.HashSet;
import java.util.Set;

public class JedisSentinelTest {
    public static Set<String> sentinels = new HashSet<String>();
    public static JedisPoolConfig config = new JedisPoolConfig();
    static{
        config.setMaxTotal(100);
        config.setMaxIdle(100);
        config.setMaxWaitMillis(1000);
        sentinels.add("10.165.124.10:26379");
    }
    public static JedisSentinelPool sentinelPool = new JedisSentinelPool("firstmaster", sentinels, config);

    public static void main(String[] args){
        Jedis jedis = sentinelPool.getResource();
        try{
            jedis.auth("123456");
            jedis.set("sentinel_one", "sentinel_one");
            System.out.println(jedis.get("sentinel_one"));
        }finally {
            if (jedis!= null){
                jedis.close();
            }

        }
    }
}

從程式碼裡可以看出,我們的Jedis連結是通過Sentinel來獲取的。

分散式

解決痛點:

  • 使用者較多資料量大,一臺伺服器記憶體不足。
  • 部署多臺增加容量,但是需要考慮如何分割資料

Redis的分散式使用的是一致性雜湊演算法。滿足:

  • 分散性:避免相同的內容對映到不同節點
  • 平衡性:均勻分佈,每個節點上的內容數量差不多
  • 單調性:增刪節點時,不影響舊的對映。

一致性雜湊的原理如圖所示:

haxi.png

可參考:中文介紹英文介紹

有了演算法,那麼資料分片在Redis中還有3種分類:

1,客戶端分片
使用比如ShardedJedis,Predis,Redis-rb等客戶端的包,在客戶端金鑫該資料分片。

shardJedis.png

在本例中,使用ShardedJedisPool,程式碼如下:

public class SharedJedisTest {
    public  static JedisPoolConfig poolConfig = new JedisPoolConfig();
    public  static List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
    static {
        poolConfig.setMaxTotal(100);
        poolConfig.setMaxIdle(10);
        poolConfig.setMaxWaitMillis(2000);
        JedisShardInfo info1 = new JedisShardInfo("10.165.124.10",16379);
        info1.setPassword("123456");
        JedisShardInfo info2 = new JedisShardInfo("10.165.124.10",16381);
        info2.setPassword("123456");
        shards.add(info1);
        shards.add(info2);
    }
    public static ShardedJedisPool shardedJedisPool = new ShardedJedisPool(poolConfig,shards);

    public static void main(String[] args){
        ShardedJedis jedis = null;
        try {
            jedis = shardedJedisPool.getResource();
            for (int i =0; i<50; i++){
                String key = String.format("key%d",i);
                String value = String.format("value%d",i);
                jedis.set(key, value);
                Client client1= jedis.getShard(key).getClient();
                System.out.println(String.format(("%s in server: %s, and port is: %d"),key,client1.getHost(),client1.getPort()));
            }
        }finally {
            if (jedis != null){
                jedis.close();
            }
        }

    }
}

可以看出,兩個Master都在一個shardedJedisPool裡。但是這裡有個問題就是需要維護Master的地址,所以後續如果可能,也需要開發Sentinel管理的JedisPool的外掛。

2,代理分片
Twemproxy, codis, onecache。如圖:

proxy.png

特點:

  • 服務端計算分片,客戶端簡單
  • 客戶端無需維護redis的ip
  • Proxy會增加響應時間

3,查詢路由
Redis 3.0:Redis叢集自己做好分片。
特點:

  • 無需proxy
  • 客戶端可以記錄下每個key對應的redis以增加效能
  • 支援3.0 的客戶端還很少
route.png

Redis監控統計

Info {options}

  • Server/Clients/Memory/Persistence/Stats/Replications/Cpu/Commandstats/Keyspaces

常用的

  • Memory : used_memory_human 當前使用記憶體量
  • Memory : mem_fragmentation_ratio 記憶體碎片率
  • Stats : instantaneous_ops_per_sec 實時的QPS
  • Stats: expired_keys, evicted_keys, keyspace_hits, keyspace_misses 過期的key,被置換的key,命中的key數量,未命中的key數量
  • Replication 主從連線是否正常,複製是否正常
  • Commandstats 每個命令的執行情況
  • Keyspaces 每個db上有多少key

彩蛋

一款成功產品的架構圖:

單點式: 一主多從

single.png

分散式:

  • Redis:多個Redis節點,每個節點都一主一從。
  • Redis-sentinel: 主從探獲、切換。
  • Proxy:業務代理轉發,資料分片
  • NLB:負載均衡,Proxy去單點
multi.png
部署Redis的Docker file
FROM tutum/redis:latest

# redis configuration
ENV REDIS_PASS "123456"
ENV REDIS_MAXMEMORY_POLICY="allkeys-lru"
ENV REDIS_MAXMEMORY="10mb"
ENV REDIS_DATABASES="2"
ENV REDIS_APPENDONLY="yes"
ENV REDIS_APPENDFSYNC=everysec

啟動容器:
叢集1:

docker run -d --name master1 -p 16379:6379 test/redis
docker run -d --name slave1 -p 16380:6379 -e -e REDIS_MASTERAUTH="123456" test/redis

叢集2:

docker run -d --name master2 -p 16381:6379 test/redis
docker run -d --name slave2 -p 16382:6379 -e REDIS_MASTERAUTH="123456" test/redis