Redis分散式叢集搭建
Redis叢集架構圖
上圖藍色為redis叢集的節點。
節點之間通過ping命令來測試連線是否正常,節點之間沒有主區分,連線到任何一個節點進行操作時,都可能會轉發到其他節點。
1、Redis的容錯機制
節點之間會定時的互相傳送ping命令,測試節點的健康狀態,當節點接受到ping命令後,會返回一個pong字串。
投票機制:如果一個節點A給節點B傳送ping沒有得到pong返回,會通知其他節點再次給B傳送ping,如果叢集中有超過一半的節點收不B節點的pong。那麼就認為B節點掛了。一般會為每個節點提供一個備份節點,如果掛掉會切換到備份節點。
2、Redis叢集儲存原理
Redis對於每個存放的key會進行hash操作,生成一個[0-16384]的hash值(先進行
crc 演算法再對16384取餘)。
叢集的情況下,就是把[0-16384]的區間進行拆分,放到不同的redis中。
3、Redis的持久化
Snapshotting:定時的將Redis記憶體中的資料儲存到硬碟中
AOF:將所有的command操作儲存到aof中,AOP的同步頻率很高,資料即使丟失,粒度也很小,但會在效能上造成影響。
二、Redis叢集準備工作
Redis安裝
原始碼下載
下載地址https://pan.baidu.com/s/1bCcLv4 密碼i5k6
解壓原始碼
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
nredis.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/redis-server 將出現下圖畫面:
Redis預設是前臺執行的,可以修改redis.conf的daemonize yes ,將其變成後臺執行。
叢集環境搭建
redis叢集管理工具redis-trib.rb依賴ruby環境,首先需要安裝ruby環境
安裝ruby
yum install ruby
yum install rubygems
安裝ruby和redis的介面程式
拷貝redis-3.0.0.gem至/usr/local下
執行:
gem install /usr/local/redis-3.0.0.gem
三、建立Redis叢集
在一臺伺服器上,可以用不同埠號來表示不同redis伺服器。
Redis叢集最少需要三臺伺服器,而每臺伺服器有需要備用伺服器,所以最少需要6臺伺服器。埠規劃如下:
主伺服器:192.168.100.66 :7001 :7002 :7003
從伺服器:192.168.100.66 :7004 :7005 :7006
在/usr/local 建立資料夾用來存放伺服器程式
mkdir 7001 7002 7003 7004 7005 7006
如果想讓redis支援叢集需要修改redis.config配置檔案的cluster-enabled yes
本例中我們以埠來區別不同的redis服務,所以還需要修改redis.config的port為對應埠
修改完配置檔案,將redis安裝目錄的bin複製到上面每個目錄中。
分別進入7001/bin/ 7002/bin .....
啟動服務./redis-server ./redis.conf
檢視redis程序:ps -aux|grep redis 如下圖則說明啟動成功
建立叢集:
將之前解壓的資料夾的redis-3.0.0/src/redis-trib.rb複製到redis-cluster目錄
執行
./redis-trib.rb create --replicas 1 192.168.100.66:7001 192.168.100.66:7002 192.168.100.66:7003 192.168.100.66:7004 192.168.100.66:7005 192.168.100.66:7006
如果執行時報如下錯誤:
[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
如果成功最終輸入如下:
查詢叢集資訊:
說明:
./redis-cli -c -h 192.168.101.3 -p 7001 ,其中-c表示以叢集方式連線redis,-h指定ip地址,-p指定埠號
cluster nodes 查詢叢集結點資訊
cluster info 查詢叢集狀態資訊
hash槽重新分配
第一步:連線上叢集
./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的從節點:
1.1. 刪除結點:
./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槽重新分配章節)。
測試:
Maven:
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.10.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
普通測試:
@Test
public void redisClusterTest1(){
JedisPoolConfig config=new JedisPoolConfig();
config.setMaxTotal(30);
config.setMaxIdle(2);
Set<HostAndPort> jedisNode=new HashSet<HostAndPort>();
jedisNode.add(new HostAndPort("192.168.100.66",7001));
jedisNode.add(new HostAndPort("192.168.100.66",7002));
jedisNode.add(new HostAndPort("192.168.100.66",7003));
jedisNode.add(new HostAndPort("192.168.100.66",7004));
jedisNode.add(new HostAndPort("192.168.100.66",7005));
jedisNode.add(new HostAndPort("192.168.100.66",7006));
JedisCluster jc=new JedisCluster(jedisNode,config);
jc.set("name","老王");
String value=jc.get("name");
System.out.println(value);
}
Spring測試:
配置檔案:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 連線池配置 -->
<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.100.66"></constructor-arg>
<constructor-arg index="1" value="7001"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.100.66"></constructor-arg>
<constructor-arg index="1" value="7002"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.100.66"></constructor-arg>
<constructor-arg index="1" value="7003"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.100.66"></constructor-arg>
<constructor-arg index="1" value="7004"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.100.66"></constructor-arg>
<constructor-arg index="1" value="7005"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.100.66"></constructor-arg>
<constructor-arg index="1" value="7006"></constructor-arg>
</bean>
</set>
</constructor-arg>
<constructor-arg index="1" ref="jedisPoolConfig"></constructor-arg>
</bean>
</beans>
測試類:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring-config.xml"})
public class RedisClusterTest {
@Autowired
private JedisCluster jedisCluster;
@Test
public void redisClusterTest2(){
jedisCluster.set("username","小明啦啦");
String name=jedisCluster.get("username");
System.out.println(name);
}
}
分類: 作業系統