Redis僞集羣的搭建

Redis介紹

1.什麼是Redis?
Redis是用C語言開發的一個開源的高性能鍵值對(key-value)數據庫。它經過提供多種鍵值數據類型來適應不一樣場景下的存儲需求,目前爲止Redis支持的鍵值數據類型以下:前端

  • 字符串類型
  • 散列類型
  • 列表類型
  • 集合類型

2.redis的應用場景node

  • 緩存(數據查詢、短鏈接、新聞內容、商品內容等等)。(最多使用)
  • 分佈式集羣架構中的session分離。
  • 聊天室的在線好友列表。
  • 任務隊列。(秒殺、搶購、12306等等)
  • 應用排行榜。
  • 網站訪問統計。
  • 數據過時處理(能夠精確到毫秒)

3.Redis單機版的安裝
redis是C語言開發,建議在linux上運行,本文使用Centos6.4做爲安裝環境。
安裝redis須要先將官網下載的源碼進行編譯,編譯依賴gcc環境,若是沒有gcc環境,須要安裝gcc:yum install gcc-c++linux

  • 版本說明
    本教程使用redis3.0版本。3.0版本主要增長了redis集羣功能。
  • 源碼下載
    從官網下載
    http://download.redis.io/releases/redis-3.0.0.tar.gz
    將redis-3.0.0.tar.gz拷貝到/usr/local下
  • 解壓源碼
    tar -zxvf redis-3.0.0.tar.gz
  • 進入解壓後的目錄進行編譯
    cd /usr/local/redis-3.0.0
    make
  • 安裝到指定目錄,如 /usr/local/redis
    cd /usr/local/redis-3.0.0
    make PREFIX=/usr/local/redis install
  • redis.conf
    redis.conf是redis的配置文件,redis.conf在redis源碼目錄。
    注意修改port做爲redis進程的端口,port默認6379。
  • 拷貝配置文件到安裝目錄下
    進入源碼目錄,裏面有一份配置文件 redis.conf,而後將其拷貝到安裝路徑下
    cd /usr/local/redis
    mkdir conf
    cp /usr/local/redis-3.0.0/redis.conf /usr/local/redis/bin
  • 安裝目錄bin下的文件列表
    這裏寫圖片描述
    這裏寫圖片描述
    redis3.0新增的redis-sentinel是redis集羣管理工具可實現高可用。
    配置文件目錄:
    這裏寫圖片描述

4.redis啓動c++

  • 前端模式啓動
    直接運行bin/redis-server將之前端模式啓動,前端模式啓動的缺點是ssh命令窗口關閉則redis-server程序結束,不推薦使用此方法。以下圖:
    這裏寫圖片描述
  • 後端模式啓動
    修改redis.conf配置文件, daemonize yes 之後端模式啓動。
    執行以下命令啓動redis:
    cd /usr/local/redis
    ./bin/redis-server ./redis.conf
    redis默認使用6379端口。
    這裏寫圖片描述
    也可更改redis.conf文件,修改端口號:
    這裏寫圖片描述

5.redis集羣web

  • redis-cluster架構圖
    這裏寫圖片描述
    架構細節:
    (1)全部的redis節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬。
    (2)節點的fail是經過集羣中超過半數的節點檢測失效時才生效。
    (3)客戶端與redis節點直連,不須要中間proxy層.客戶端不須要鏈接集羣全部節點,鏈接集羣中任何一個可用節點便可。
    (4)redis-cluster把全部的物理節點映射到[0-16383]slot上,cluster 負責維護node<->slot<->value。
    (5)Redis 集羣中內置了 16384 個哈希槽,當須要在 Redis 集羣中放置一個 key-value 時,redis 先對 key 使用 crc16 算法算出一個結果,而後把結果對 16384 求餘數,這樣每一個 key 都會對應一個編號在 0-16383 之間的哈希槽,redis 會根據節點數量大體均等的將哈希槽映射到不一樣的節點。
  • redis-cluster投票:容錯
    這裏寫圖片描述
    (1)領着投票過程是集羣中全部master參與,若是半數以上master節點與master節點通訊超過(cluster-node-timeout),認爲當前master節點掛掉。
    (2):何時整個集羣不可用(cluster_state:fail)?
    a:若是集羣任意master掛掉,且當前master沒有slave.集羣進入fail狀態,也能夠理解成集羣的slot映射[0-16383]不完成時進入fail狀態. ps : redis-3.0.0.rc1加入cluster-require-full-coverage參數,默認關閉,打開集羣兼容部分失敗。
    b:若是集羣超過半數以上master掛掉,不管是否有slave集羣進入fail狀態。
    ps:當集羣不可用時,全部對集羣的操做作都不可用,收到((error) CLUSTERDOWN The cluster is down)錯誤。
  • ruby環境
    redis集羣管理工具redis-trib.rb依賴ruby環境,首先須要安裝ruby環境:
    安裝ruby
    yum install ruby
    yum install rubygems
    安裝ruby和redis的接口程序
    拷貝redis-3.0.0.gem至/usr/local下
    redis-3.0.0.gem下載地址:
    http://download.csdn.net/detail/nayouzenmyang/9731848
    執行:
    gem install /usr/local/redis-3.0.0.gem

6.建立集羣redis

  • 集羣結點規劃
    這裏在同一臺服務器用不一樣的端口表示不一樣的redis服務器,以下:
    主節點:192.168.101.3:7001 192.168.101.3:7002 192.168.101.3:7003
    從節點:192.168.101.3:7004 192.168.101.3:7005 192.168.101.3:7006
    在/usr/local下建立redis-cluster目錄,其下建立700一、7002。。7006目錄,以下:
    這裏寫圖片描述
    將redis安裝目錄bin下的文件拷貝到每一個700X目錄內,同時將redis源碼目錄src下的redis-trib.rb拷貝到redis-cluster目錄下。
    修改每一個700X目錄下的redis.conf配置文件:
    port XXXX
    #bind 192.168.101.3
    cluster-enabled yes
  • 啓動每一個結點redis服務
    分別進入700一、700二、…7006目錄,執行:
    ./redis-server ./redis.conf
    查看redis進程:
    這裏寫圖片描述
  • 執行建立集羣命令
    執行redis-trib.rb,此腳本是ruby腳本,它依賴ruby環境。
    ./redis-trib.rb create –replicas 1 192.168.101.3:7001 192.168.101.3:7002 192.168.101.3:7003 192.168.101.3:7004 192.168.101.3:7005 192.168.101.3:7006
    說明:
    redis集羣至少須要3個主節點,每一個主節點有一個從節點總共6個節點
    replicas指定爲1表示每一個主節點有一個從節點
    注意:
    若是執行時報以下錯誤:
    [ERR] Node XXXXXX is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0
    解決方法是刪除生成的配置文件nodes.conf,若是不行則說明如今建立的結點包括了舊集羣的結點信息,須要刪除redis的持久化文件後再重啓redis,好比:appendonly.aof、dump.rdb
    建立集羣輸出以下:
>>> Creating cluster
Connecting to node 192.168.101.3:7001: OK
Connecting to node 192.168.101.3:7002: OK
Connecting to node 192.168.101.3:7003: OK
Connecting to node 192.168.101.3:7004: OK
Connecting to node 192.168.101.3:7005: OK
Connecting to node 192.168.101.3:7006: OK
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
192.168.101.3:7001
192.168.101.3:7002
192.168.101.3:7003
Adding replica 192.168.101.3:7004 to 192.168.101.3:7001
Adding replica 192.168.101.3:7005 to 192.168.101.3:7002
Adding replica 192.168.101.3:7006 to 192.168.101.3:7003
M: cad9f7413ec6842c971dbcc2c48b4ca959eb5db4 192.168.101.3:7001
   slots:0-5460 (5461 slots) master
M: 4e7c2b02f0c4f4cfe306d6ad13e0cfee90bf5841 192.168.101.3:7002
   slots:5461-10922 (5462 slots) master
M: 1a8420896c3ff60b70c716e8480de8e50749ee65 192.168.101.3:7003
   slots:10923-16383 (5461 slots) master
S: 69d94b4963fd94f315fba2b9f12fae1278184fe8 192.168.101.3:7004
   replicates cad9f7413ec6842c971dbcc2c48b4ca959eb5db4
S: d2421a820cc23e17a01b597866fd0f750b698ac5 192.168.101.3:7005
   replicates 4e7c2b02f0c4f4cfe306d6ad13e0cfee90bf5841
S: 444e7bedbdfa40714ee55cd3086b8f0d5511fe54 192.168.101.3:7006
   replicates 1a8420896c3ff60b70c716e8480de8e50749ee65
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join...
>>> Performing Cluster Check (using node 192.168.101.3:7001)
M: cad9f7413ec6842c971dbcc2c48b4ca959eb5db4 192.168.101.3:7001
   slots:0-5460 (5461 slots) master
M: 4e7c2b02f0c4f4cfe306d6ad13e0cfee90bf5841 192.168.101.3:7002
   slots:5461-10922 (5462 slots) master
M: 1a8420896c3ff60b70c716e8480de8e50749ee65 192.168.101.3:7003
   slots:10923-16383 (5461 slots) master
M: 69d94b4963fd94f315fba2b9f12fae1278184fe8 192.168.101.3:7004
   slots: (0 slots) master
   replicates cad9f7413ec6842c971dbcc2c48b4ca959eb5db4
M: d2421a820cc23e17a01b597866fd0f750b698ac5 192.168.101.3:7005
   slots: (0 slots) master
   replicates 4e7c2b02f0c4f4cfe306d6ad13e0cfee90bf5841
M: 444e7bedbdfa40714ee55cd3086b8f0d5511fe54 192.168.101.3:7006
   slots: (0 slots) master
   replicates 1a8420896c3ff60b70c716e8480de8e50749ee65
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
  • 查詢集羣信息
    集羣建立成功登錄任意redis結點查詢集羣中的節點狀況。
    客戶端以集羣方式登錄:
    這裏寫圖片描述
    說明:
    ./redis-cli -c -h 192.168.101.3 -p 7001 ,其中-c表示以集羣方式鏈接redis,-h指定ip地址,-p指定端口號
    cluster nodes 查詢集羣結點信息
    cluster info 查詢集羣狀態信息
    這裏寫圖片描述
  • 添加主節點
    集羣建立成功後能夠向集羣中添加節點,下面是添加一個master主節點
    添加7007結點,參考集羣結點規劃章節添加一個「7007」目錄做爲新節點。
    執行下邊命令:
    ./redis-trib.rb add-node 192.168.101.3:7007 192.168.101.3:7001
    這裏寫圖片描述
    查看集羣結點發現7007已添加到集羣中:
    這裏寫圖片描述
  • hash槽從新分配
    添加完主節點須要對主節點進行hash槽分配這樣該主節才能夠存儲數據。
    redis集羣有16384個槽,集羣中的每一個結點分配自已槽,經過查看集羣結點能夠看到槽佔用狀況。
    這裏寫圖片描述
    給剛添加的7007結點分配槽:
    第一步:鏈接上集羣
    ./redis-trib.rb reshard 192.168.101.3:7001(鏈接集羣中任意一個可用結點都行)
    第二步:輸入要分配的槽數量
    這裏寫圖片描述
    輸入 500表示要分配500個槽
    第三步:輸入接收槽的結點id
    這裏寫圖片描述
    這裏準備給7007分配槽,經過cluster nodes查看7007結點id爲15b809eadae88955e36bcdbb8144f61bbbaf38fb
    輸入:15b809eadae88955e36bcdbb8144f61bbbaf38fb
    第四步:輸入源結點id
    這裏寫圖片描述
    這裏輸入all
    第五步:輸入yes開始移動槽到目標結點id
    這裏寫圖片描述
  • 添加從節點
    集羣建立成功後能夠向集羣中添加節點,下面是添加一個slave從節點。
    添加7008從結點,將7008做爲7007的從結點。
    ./redis-trib.rb add-node –slave –master-id 主節點id 添加節點的ip和端口 集羣中已存在節點ip和端口
    執行以下命令:
    ./redis-trib.rb add-node –slave –master-id cad9f7413ec6842c971dbcc2c48b4ca959eb5db4 192.168.101.3:7008 192.168.101.3:7001
    cad9f7413ec6842c971dbcc2c48b4ca959eb5db4 是7007結點的id,可經過cluster nodes查看。
    這裏寫圖片描述
    注意:若是原來該結點在集羣中的配置信息已經生成cluster-config-file指定的配置文件中(若是cluster-config-file沒有指定則默認爲nodes.conf),這時可能會報錯:
    [ERR] Node XXXXXX is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0
    解決方法是刪除生成的配置文件nodes.conf,刪除後再執行./redis-trib.rb add-node指令
    查看集羣中的結點,剛添加的7008爲7007的從節點:
    這裏寫圖片描述
  • 刪除結點
    ./redis-trib.rb del-node 127.0.0.1:7005 4b45eb75c8b428fbd77ab979b85080146a9bc017
    刪除已經佔有hash槽的結點會失敗,報錯以下:
    [ERR] Node 127.0.0.1:7005 is not empty! Reshard data away and try again.
    須要將該結點佔用的hash槽分配出去(參考hash槽從新分配)。

7.jedisCluster算法

-測試代碼spring

// 鏈接redis集羣
@Test
public void testJedisCluster() {
JedisPoolConfig config = new JedisPoolConfig();
// 最大鏈接數
config.setMaxTotal(30);
// 最大鏈接空閒數
config.setMaxIdle(2);
//集羣結點
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
jedisClusterNode.add(new HostAndPort("192.168.101.3", 7001));
jedisClusterNode.add(new HostAndPort("192.168.101.3", 7002));
jedisClusterNode.add(new HostAndPort("192.168.101.3", 7003));
jedisClusterNode.add(new HostAndPort("192.168.101.3", 7004));
jedisClusterNode.add(new HostAndPort("192.168.101.3", 7005));
jedisClusterNode.add(new HostAndPort("192.168.101.3", 7006));
JedisCluster jc = new JedisCluster(jedisClusterNode, config);
JedisCluster jcd = new JedisCluster(jedisClusterNode);
        jcd.set("name", "zhangsan");
        String value = jcd.get("name");
        System.out.println(value);
    }
  • 使用spring
    配置applicationContext.xml
<!-- 鏈接池配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大鏈接數 -->
<property name="maxTotal" value="30" />
<!-- 最大空閒鏈接數 -->
<property name="maxIdle" value="10" />
<!-- 每次釋放鏈接的最大數目 -->
<property name="numTestsPerEvictionRun" value="1024" />
<!-- 釋放鏈接的掃描間隔(毫秒) -->
<property name="timeBetweenEvictionRunsMillis" value="30000" />
<!-- 鏈接最小空閒時間 -->
<property name="minEvictableIdleTimeMillis" value="1800000" />
<!-- 鏈接空閒多久後釋放, 當空閒時間>該值 且 空閒鏈接>最大空閒鏈接數 時直接釋放 -->
<property name="softMinEvictableIdleTimeMillis" value="10000" />
<!-- 獲取鏈接時的最大等待毫秒數,小於零:阻塞不肯定的時間,默認-1 -->
<property name="maxWaitMillis" value="1500" />
<!-- 在獲取鏈接的時候檢查有效性, 默認false -->
<property name="testOnBorrow" value="true" />
<!-- 在空閒時檢查有效性, 默認false -->
<property name="testWhileIdle" value="true" />
<!--鏈接耗盡時是否阻塞, false報異常,ture阻塞直到超時, 默認true -->
<property name="blockWhenExhausted" value="false" />
</bean> 
<!-- redis集羣 -->
<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
<constructor-arg index="0">
<set>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.101.3"></constructor-arg>
<constructor-arg index="1" value="7001"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.101.3"></constructor-arg>
<constructor-arg index="1" value="7002"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.101.3"></constructor-arg>
<constructor-arg index="1" value="7003"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.101.3"></constructor-arg>
<constructor-arg index="1" value="7004"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.101.3"></constructor-arg>
<constructor-arg index="1" value="7005"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">    <constructor-arg index="0" value="192.168.101.3"></constructor-arg>
<constructor-arg index="1" value="7006"></constructor-arg>
</bean>
</set>
</constructor-arg>
<constructor-arg index="1" ref="jedisPoolConfig"></constructor-arg>
</bean>

測試代碼數據庫

private ApplicationContext applicationContext;
@Before
public void init() {
applicationContext = new ClassPathXmlApplicationContext(
                "classpath:applicationContext.xml");
}
//redis集羣
@Test
public void testJedisCluster() {
JedisCluster jedisCluster = (JedisCluster) applicationContext.getBean("jedisCluster");
jedisCluster.set("name", "zhangsan");
String value = jedisCluster.get("name");
System.out.println(value);
}