Redis Cluster深入與實踐(續)

aoho發表於2019-03-04

前文回顧

上一篇文章基於redis的分散式鎖實現寫了基於redis實現的分散式鎖。分散式環境下,不會還使用單點的redis,做到高可用和容災,起碼也是redis主從。redis的單執行緒工作,一臺物理機只執行一個redis例項太過浪費,redis單機顯然是存在單點故障的隱患。記憶體資源往往受限,縱向不停擴充套件記憶體並不是很實際,因此橫向可伸縮擴充套件,需要多臺主機協同提供服務,即分散式下多個Redis例項協同執行。

在之前的文章Redis Cluster深入與實踐介紹過Redis Cluster的相關內容,之前特地花時間在redis官網看了redis cluster的相關文件和實現。本文是那篇文章的續集,因為筆者最近在調研redis的主從切換到redis 叢集的方案,將會講下redis叢集的幾種方案選型和redis cluster的實踐。

redis叢集的幾種實現方式如下:

  • 客戶端分片,如redis的Java客戶端jedis也是支援的,使用一致性hash
  • 基於代理的分片,如codis和Twemproxy
  • 路由查詢, redis-cluster

下面我們分別介紹下這幾種方案。

客戶端分片

Redis Sharding是Redis Cluster出來之前,業界普遍使用的多Redis例項叢集方法。其主要思想是採用雜湊演算法將Redis資料的key進行雜湊,通過hash函式,特定的key會對映到特定的Redis節點上。java redis客戶端驅動jedis,支援Redis Sharding功能,即ShardedJedis以及結合快取池的ShardedJedisPool。

Sharding
Sharding

Redis Sentinel提供了主備模式下Redis監控、故障轉移功能達到系統的高可用性。在主Redis當機時,備Redis接管過來,上升為主Redis,繼續提供服務。主備共同組成一個Redis節點,通過自動故障轉移,保證了節點的高可用性。

客戶端sharding技術其優勢在於非常簡單,服務端的Redis例項彼此獨立,相互無關聯,每個Redis例項像單伺服器一樣執行,非常容易線性擴充套件,系統的靈活性很強。

客戶端sharding的劣勢也是很明顯的。由於sharding處理放到客戶端,規模進一步擴大時給運維帶來挑戰。客戶端sharding不支援動態增刪節點。服務端Redis例項群拓撲結構有變化時,每個客戶端都需要更新調整。連線不能共享,當應用規模增大時,資源浪費制約優化。

基於代理的分片

客戶端傳送請求到一個代理元件,代理解析客戶端的資料,並將請求轉發至正確的節點,最後將結果回覆給客戶端。

該模式的特性如下:

  • 透明接入,業務程式不用關心後端Redis例項,切換成本低。
  • Proxy 的邏輯和儲存的邏輯是隔離的。
  • 代理層多了一次轉發,效能有所損耗。

簡單的結構圖如下:

proxy
proxy

主流的元件有:Twemproxy和Codis。

Twemproxy

Twemproxy也叫nutcraker,是twtter開源的一個redis和memcache代理伺服器程式。redis作為一個高效的快取伺服器,非常具有應用價值。但在使用者資料量增大時,需要執行多個redis例項,此時將迫切需要一種工具統一管理多個redis例項,避免在每個客戶端管理所有連線帶來的不方便和不易維護,Twemproxy即為此目標而生。

Twemproxy有以下幾個特點:

  • 輕量級
  • 維持永久的服務端連線
  • 支援失敗節點自動刪除;可以設定重新連線該節點的時間,還可以設定連線多少次之後刪除該節點
  • 支援設定HashTag;通過HashTag可以自己設定將同一型別的key對映到同一個例項上去。
  • 減少與redis的直接連線數,保持與redis的長連線,可設定代理與後臺每個redis連線的數目
  • 自帶一致性hash演算法,能夠將資料自動分片到後端多個redis例項上;支援多種hash演算法,可以設定後端例項的權重,目前redis支援的hash演算法有:one_at_a_time、md5、crc16、crc32、fnv1_64、fnv1a_64、fnv1_32、fnv1a_32、hsieh、murmur、jenkins。
  • 支援redis pipelining request,將多個連線請求,組成reids pipelining統一向redis請求。
  • 支援狀態監控;可設定狀態監控ip和埠,訪問ip和埠可以得到一個json格式的狀態資訊串;可設定監控資訊重新整理間隔時間。

TwemProxy 官網介紹瞭如上的特性。TwemProxy的使用可以像訪問redis客戶端一樣訪問TwemProxy。然而Twitter已經很久放棄了更新TwemProxy。Twemproxy最大的痛點在於,無法平滑地擴容/縮容。Twemproxy另一個痛點是,運維不友好,甚至沒有控制皮膚。

Codis

Codis是豌豆莢開源的redis叢集方案,是一個分散式 Redis 解決方案, 對於上層的應用來說, 連線到 Codis Proxy 和連線原生的 Redis Server 沒有顯著區別 , 上層應用可以像使用單機的 Redis 一樣使用, Codis 底層會處理請求的轉發, 不停機的資料遷移等工作, 所有後邊的一切事情, 對於前面的客戶端來說是透明的, 可以簡單的認為後邊連線的是一個記憶體無限大的 Redis 服務。

Codis當前最新release 版本為 codis-3.2,codis-server 基於 redis-3.2.8。有一下元件組成:

Codis
Codis架構

  • Codis Server:基於 redis-3.2.8 分支開發。增加了額外的資料結構,以支援 slot 有關的操作以及資料遷移指令。
  • Codis Proxy:客戶端連線的 Redis 代理服務, 實現了 Redis 協議。 除部分命令不支援以外(不支援的命令列表),表現的和原生的 Redis 沒有區別(就像 Twemproxy)。
  • Codis Dashboard:叢集管理工具,支援 codis-proxy、codis-server 的新增、刪除,以及據遷移等操作。在叢集狀態發生改變時,codis-dashboard 維護叢集下所有 codis-proxy 的狀態的一致性。 對於同一個業務叢集而言,同一個時刻 codis-dashboard 只能有 0個或者1個;所有對叢集的修改都必須通過 codis-dashboard 完成。
  • Codis Admin:叢集管理的命令列工具。 可用於控制 codis-proxy、codis-dashboard 狀態以及訪問外部儲存。
  • Codis FE:叢集管理介面。 多個叢集例項共享可以共享同一個前端展示頁面; 通過配置檔案管理後端 codis-dashboard 列表,配置檔案可自動更新。
  • Storage:為叢集狀態提供外部儲存。 提供 Namespace 概念,不同叢集的會按照不同 product name 進行組織;目前僅提供了 Zookeeper、Etcd、Fs 三種實現,但是提供了抽象的 interface 可自行擴充套件。

至於具體的安裝與使用,見官網CodisLabs,不在此涉及。

Codis的特性:

  • Codis支援的命令更加豐富,基本支援redis的命令。
  • 遷移成本低,遷移到codis沒這麼麻煩,只要使用的redis命令在codis支援的範圍之內,只要修改一下配置即可接入。
  • Codis提供的運維工具更加友好,提供web圖形介面管理叢集。
  • 支援多核心CPU,twemproxy只能單核
  • 支援group劃分,組內可以設定一個主多個從,通過sentinel 監控redis主從,當主down了自動將從切換為主

路由查詢

Redis Cluster是一種伺服器Sharding技術,3.0版本開始正式提供。Redis Cluster並沒有使用一致性hash,而是採用slot(槽)的概念,一共分成16384個槽。將請求傳送到任意節點,接收到請求的節點會將查詢請求傳送到正確的節點上執行。當客戶端操作的key沒有分配到該node上時,就像操作單一Redis例項一樣,當客戶端操作的key沒有分配到該node上時,Redis會返回轉向指令,指向正確的node,這有點兒像瀏覽器頁面的302 redirect跳轉。

Redis叢集,要保證16384個槽對應的node都正常工作,如果某個node發生故障,那它負責的slots也就失效,整個叢集將不能工作。為了增加叢集的可訪問性,官方推薦的方案是將node配置成主從結構,即一個master主節點,掛n個slave從節點。這時,如果主節點失效,Redis Cluster會根據選舉演算法從slave節點中選擇一個上升為主節點,整個叢集繼續對外提供服務。

Cluster
Redis Cluster

特點:

  • 無中心架構,支援動態擴容,對業務透明
  • 具備Sentinel的監控和自動Failover能力
  • 客戶端不需要連線叢集所有節點,連線叢集中任何一個可用節點即可
  • 高效能,客戶端直連redis服務,免去了proxy代理的損耗

缺點是運維也很複雜,資料遷移需要人工干預,只能使用0號資料庫,不支援批量操作,分散式邏輯和儲存模組耦合等。

選型最後確定redis cluster。主要原因是效能高,去中心化支援擴充套件。運維方面的資料遷移暫時業內也沒有特別成熟的方案解決,redis cluster是redis官方提供,我們期待redis官方在後面能夠完美支援。

安裝

官方推薦叢集至少需要六個節點,即三主三從。六個節點的配置檔案基本相同,只需要修改埠號。

port 7000
cluster-enabled yes  #開啟叢集模式
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
複製程式碼

啟動後,可以看到如下的日誌。

[82462] 26 Nov 11:56:55.329 * No cluster configuration found, I'm 97a3a64667477371c4479320d683e4c8db5858b1

由於沒有nodes.conf存在,每個例項啟動後都會給自己分配一個ID。為了在叢集的環境中有一個唯一的名字,該ID將會被永久使用。每個例項都會儲存其他節點使用的ID,而不是通過IP和埠。IP和埠可能會改變,但是唯一的node ID將不會改變直至該node的死亡。

我們現在已經啟動了六個redis例項, 需要通過寫一些有意義的配置資訊到各個節點來建立叢集。 redis cluster的命令列工具redis-trib,利用Ruby程式在例項上執行一些特殊的命令,很容易實現建立新的叢集、檢查或者reshard現有的叢集等。

./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
複製程式碼

--replicas 1引數是將每個master帶上一個slave。

配置JedisClusterConfig

@Configuration
public class JedisClusterConfig {
    private static Logger logger = LoggerFactory.getLogger(JedisClusterConfig.class);

    @Value("${redis.cluster.nodes}")
    private String clusterNodes;

    @Value("${redis.cluster.timeout}")
    private int timeout;

    @Value("${redis.cluster.max-redirects}")
    private int redirects;
    
    @Autowired
    private JedisPoolConfig jedisPoolConfig;

    @Bean
    public RedisClusterConfiguration getClusterConfiguration() {
        Map<String, Object> source = new HashMap();

        source.put("spring.redis.cluster.nodes", clusterNodes);
        logger.info("clusterNodes: {}", clusterNodes);
        source.put("spring.redis.cluster.max-redirects", redirects);
        return new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
    }

    @Bean
    public JedisConnectionFactory getConnectionFactory() {
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(getClusterConfiguration());
        jedisConnectionFactory.setTimeout(timeout);
        return jedisConnectionFactory;
    }

    @Bean
    public JedisClusterConnection getJedisClusterConnection() {
        return (JedisClusterConnection) getConnectionFactory().getConnection();
    }

    @Bean
    public RedisTemplate getRedisTemplate() {
        RedisTemplate clusterTemplate = new RedisTemplate();
        clusterTemplate.setConnectionFactory(getConnectionFactory());
        clusterTemplate.setKeySerializer(new StringRedisSerializer());
        clusterTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
        return clusterTemplate;

    }

}
複製程式碼

可以配置密碼,cluster對密碼支援不太友好,如果對叢集設定密碼,那麼requirepass和masterauth都需要設定,否則發生主從切換時,就會遇到授權問題。

配置redis cluster

redis:
  cluster:
    enabled: true
    timeout: 2000
    max-redirects: 8
    nodes: 127.0.0.1:7000,127.0.0.1:7001
複製程式碼

主要配置了redis cluster的節點、超時時間等。

使用RedisTemplate

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisConfigTest {
    @Autowired
    RedisTemplate redisTemplate;
    
    @Test
    public void clusterTest() {
      redisTemplate.opsForValue().set("foo", "bar");
      System.out.println(redisTemplate.opsForValue().get("foo"));
    }
}
複製程式碼

用法很簡單,注入RedisTemplate即可進行操作,RedisTemplate用法比較豐富,可以自行查閱。

總結

本文主要講了redis叢集的選型,主要有三種:客戶端分片、基於代理的分片以及路由查詢。對於前兩種方式,分別進行簡單地介紹,最後選擇redis官方提供的redis cluster方案,並進行了實踐。雖然正式版的推出時間不長,目前成功實踐的案例也還不多,但是總體來說,redis cluster的整個設計是比較簡單的,大部分操作都可以按照單點的操作流程進行操作。筆者使用的jedis客戶端支援JedisCluster也是比較好,用起來也很方便。其實還有個壓測的資料,後面再補上吧。

訂閱最新文章,歡迎關注我的公眾號

微信公眾號

參考

  1. Redis叢集方案應該怎麼做?
  2. cluster-tutorial
  3. twemproxy
  4. CodisLabs

相關文章