Redis Cluster深刻與實踐(續)

前文回顧

上一篇文章基於redis的分佈式鎖實現寫了基於redis實現的分佈式鎖。分佈式環境下,不會還使用單點的redis,作到高可用和容災,起碼也是redis主從。redis的單線程工做,一臺物理機只運行一個redis實例太過浪費,redis單機顯然是存在單點故障的隱患。內存資源每每受限,縱向不停擴展內存並非很實際,所以橫向可伸縮擴展,須要多臺主機協同提供服務,即分佈式下多個Redis實例協同運行。前端

在以前的文章Redis Cluster深刻與實踐介紹過Redis Cluster的相關內容,以前特意花時間在redis官網看了redis cluster的相關文檔和實現。本文是那篇文章的續集,由於筆者最近在調研redis的主從切換到redis 集羣的方案,將會講下redis集羣的幾種方案選型和redis cluster的實踐。java

redis集羣的幾種實現方式以下:node

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

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

客戶端分片

Redis Sharding是Redis Cluster出來以前,業界廣泛使用的多Redis實例集羣方法。其主要思想是採用哈希算法將Redis數據的key進行散列,經過hash函數,特定的key會映射到特定的Redis節點上。java redis客戶端驅動jedis,支持Redis Sharding功能,即ShardedJedis以及結合緩存池的ShardedJedisPool。github

Sharding
Sharding

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

客戶端sharding技術其優點在於很是簡單,服務端的Redis實例彼此獨立,相互無關聯,每一個Redis實例像單服務器同樣運行,很是容易線性擴展,系統的靈活性很強。redis

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

基於代理的分片

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

該模式的特性以下:數據庫

  • 透明接入,業務程序不用關心後端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、md五、crc1六、crc3二、fnv1_6四、fnv1a_6四、fnv1_3二、fnv1a_3二、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
相關文章
相關標籤/搜索