Redis 功能入門大全和基于scala實現(xiàn)示例(6) -- 集群模式

歡迎轉(zhuǎn)載。轉(zhuǎn)載請注明出處。

image

概要

  • Redis server 基于5.0.0的stable版本
  • Client基于 Jedis 2.9.0
  • Scala 基于 2.11.X

本文內(nèi)容:

  • redis集群模式配置參數(shù)說明
  • 以實例說明如何配置集群模式
  • 創(chuàng)建集群模式連接池的scala實現(xiàn)
  • 遇到和解決問題

cluster服務(wù)配置參數(shù)

cluster模式所有節(jié)點都是Redis實例,不存在sentinel模式下的監(jiān)控進(jìn)程。所以不需要附加配置文件,一切配置信息在redis-port.conf。如下:

cluster-enabled yes
cluster-config-file nodes-6379.conf #啟動該進(jìn)程對應(yīng)的配置文件名稱
cluster-node-timeout 15000
# cluster-replica-no-failover no
# cluster-require-full-coverage yes
# cluster-migration-barrier 1

如何搭建cluster集群

搭建和配置集群是redis模式中“最復(fù)雜的”。
下面就逐步說明:

  1. 啟動多個實例,以6個實例(3主3從為例)所有數(shù)據(jù)只有一個備份。啟動命令: redis-server conf/xxxx.conf
  2. 節(jié)點配對。建立節(jié)點間連接。 redis-cli -h ip -p port cluster meet destip dest port ,生成40字符ID標(biāo)識節(jié)點,后續(xù)據(jù)此區(qū)分節(jié)點.
  3. 建立集群備份機制,分配數(shù)據(jù)如何存放。redis-cli -h ip -p port cluster addslots {0...5461} 自動分配失敗,可以手動修改。分配完成之后,所有給其它的部分節(jié)點設(shè)置了slot。完成之后,所有節(jié)點都是主節(jié)點,還需要建立主從關(guān)系.
  4. 建立主從關(guān)系. cluster replicate 主節(jié)點的id,使用命令查看對應(yīng)關(guān)系./cluster-cli -h ip -p port cluster nodes
數(shù)據(jù)存放和分區(qū)

cluster模式下,每個master節(jié)點存儲部分?jǐn)?shù)據(jù),master集合存儲完整一份數(shù)據(jù)。

在如何搭建cluster集群時步驟3 提到需要分配數(shù)據(jù),那么cluster集群是如何分配數(shù)據(jù),也是如何進(jìn)行數(shù)據(jù)分區(qū)哪?

分布式系統(tǒng)數(shù)據(jù)分區(qū)分為哈希分區(qū)和順序分區(qū)。Redis使用哈??臻g,所有數(shù)據(jù)映射到一個整數(shù)集合內(nèi),也就是我們說的slot(哈希槽)。步驟3提到數(shù)據(jù)分區(qū)就是設(shè)置某個節(jié)點存放slot的范圍。slot值的計算公式:slot=CRC16(key)&16383。通過CRC16算法強制計算出slot值,存儲在對應(yīng)的節(jié)點上。數(shù)據(jù)分區(qū)如同小學(xué)生入學(xué)分班,根據(jù)表現(xiàn)分散在各個地方。

scala創(chuàng)建cluster模式連接池

  • 集群模式下配置參數(shù)

    trait MyClusterPool extends Pool {
      def pool: JedisCluster
    }
    class JedisClusterConf(conf: JedisPoolConfig,
                           nodes: Set[HostAndPort]) extends MyClusterPool {
      lazy private val pl = init
      override def pool : JedisCluster = pl
    
      def init: JedisCluster = {
        val s: util.Set[HostAndPort] = JavaConversions.setAsJavaSet[HostAndPort](nodes)
        new JedisCluster(nodes, conf)
      
      }
    }
    
  • 創(chuàng)建cluster單例對象

    object RedisFactory extends LogSupport {
    
       def newJedisPool(conf: JedisPoolConfig,
                       nodes: Set[HostAndPort]): MyClusterPool = {
        logWarning("#### RedisFactory create ClusterPool start")
        val ret = new JedisClusterConf(conf, nodes)
        logWarning("#### RedisFactory create ClusterPool end")
        ret
      }
    }
    
  • 如何使用

    trait RedisClusterInterface extends LogSupport with RedisAction with RedisPara{
      val hostList = nodesList.map(HostAndPort.parseString(_))
      val nodes = JavaConversions.setAsJavaSet[HostAndPort](hostList)
      lazy val handler = new JedisSlotBasedConnectionHandler(nodes, conf, 2000)
    
      private lazy val jedisCluster = {
         //cluster pool
         RedisFactory.newJedisPool(conf, hostList)
      }
    
      def connect: JedisCluster = {
        redisCluster.pool
       }
    
     lazy val clusterNodes = jedisCluster.pool.getClusterNodes
    
      //todo in formal version val should be update period.
      //節(jié)點故障問題會導(dǎo)致主從變換,需要周期更新狀態(tài)。jedis沒有自動更新機制
     lazy val clusterInfoCache = {
    
       val cache = new JedisClusterInfoCache(conf, 1000)
       val it = nodesList.map(HostAndPort.parseString(_))
    
       for(el <-  it.toList) {
         val ne = el
         val jedis = new Jedis(ne.getHost,ne.getPort)
         cache.discoverClusterNodesAndSlots(jedis)
         jedis.close() //close connect jedis
       }
    
      for(el <-  it.toList) cache.setupNodeIfNotExist(el)
       cache
     }
    //批量數(shù)據(jù)分配到不同的slot上,要對k-v進(jìn)行分組,才能使用pipeline 事務(wù)等批處理
      def group(t: List[(String, util.HashMap[String,String])])  = {
       val ret = t.map{
         case (k, v) =>
           val slot = JedisClusterCRC16.getSlot(k)
          val conn = clusterInfoCache.getSlotPool(slot)
           (conn, (k,v))
       }
       ret.groupBy(_._1).map {
         el =>
           val key = el._1
           val re = el._2.map {
            v => (v._2._1,v._2._2)
           }
           (key, re)
       }
      }
    }  
    

以上是集群模式代碼實現(xiàn),拿來即用。祝你嗨皮!

實踐中遇到問題

集群模式下,Redis接受到任何k-v相關(guān)的命令,都會先計算出slot的值,然后根據(jù)slot去訪問節(jié)點。數(shù)據(jù)是分布在不同的Redis節(jié)點上,使用客戶端命令訪問時,會出現(xiàn)數(shù)據(jù)不在該客戶端訪問節(jié)點上,Redis提供重定向(MOVED)實現(xiàn)對數(shù)據(jù)的訪問??蛻舳说卿洉r,通過設(shè)置可以實現(xiàn)自動重定向: ./redis-cli -h ip -p port -c.發(fā)生重定向時,提示**-> Redirected to slot [5798] located **

訪問流程:key -> slot -> node -> 數(shù)據(jù)操作

原因: 讀寫分離指 redis 服務(wù)端 提供機制,只有在master提供讀寫功能,slave只能讀不能寫。用戶利用該原理在客戶端實現(xiàn) 讀寫分離的功能,redis提供了相關(guān)API。

批量設(shè)置slot失敗 (error) ERR Invalid or out of range slot?

原因:redis集群服務(wù)創(chuàng)建問題。檢查各個集群節(jié)點的IP和port

集群模式是更高復(fù)雜度系統(tǒng),數(shù)據(jù)分區(qū)、故障遷移、負(fù)載均衡、開發(fā)運維困難等,每一個都是大課題,有機會在討論。

后記

Redis提供緩存服務(wù)分布式鎖解決數(shù)據(jù)一致性問題;數(shù)據(jù)恢復(fù)和容災(zāi)需要優(yōu)化Redis配置;負(fù)載均衡用戶使用第三方工具解決 ......等等。 遺留問題隨著使用深入會有整理出更多的文檔。

sohutv開源了Redis運維平臺CacheCloud,非常棒的運維工具。

參考資料

  1. Redis開發(fā)和運維 [付磊 張益軍]
  2. Redisn 英文官網(wǎng)
  3. Redis中國社區(qū)

寫在最后:

本系列redis基礎(chǔ)功能和基于scala的實現(xiàn)到這里,依然到了尾聲.理論層面上文章很多,我只是蜻蜓點水略過,深入探索靠實踐.scala代碼實現(xiàn)讀寫和各中模式下訪問,奉行拿來主義.

我們征程是星辰大海,持續(xù)探索才有是未來.
被人追問:只有那點訪問和點贊數(shù),寫作意義何在?有一個閱讀量也是需求。凡事講意義,就是功利。

對于ta,出門左轉(zhuǎn),不送。
對讀者,只有感謝.感謝你們.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容