Redis集群管理
1.簡介
Redis在生產環境中一般是通過集群的方式進行運行,Redis集群包括主從復制集群和數據分片集群兩種類型。
*主從復制集群提供高可用性,而數據分片集群提供負載均衡。
*數據分片集群中能實現主從復制集群的功能。
2.Redis主從復制集群
主從復制集群中由Master節點提供讀寫服務,Slave節點負責同步Master節點中的數據,當Master節點發生故障時,由Slave節點充當Master對外提供服務。
主從復制集群中可以使用一主一從模式,亦可以使用一主多從模式,在一主多從模式中主節點需要將修改同步給各個從節點從而增加了主節點的壓力(帶寬)
*在主從復制集群中Slave節點能夠進行讀取(不建議),但如果沒有開啟TCP的NO_DELAY功能,那麽讀取的數據可能是臟數據,在Slave節點進行寫入時,會提示Slave節點不能進行寫入。
關於讀寫分離
應用對於數據庫而言都是讀多寫少的,即數據庫的讀取壓力要比寫入的壓力大(即100個請求95個都是讀的),由於受數據庫自身性能影響,因此一般都會搭建主從數據庫,由多個從數據庫提供讀取服務,分擔壓力,實現讀寫分離。
讀寫分離是相對於有磁盤IO操作的數據庫而言的,對於基於內存的NoSQL來說不存在此問題,其讀取和寫入的性能都很快,每秒能處理幾萬個請求,因此沒有必要進行讀寫分離,Redis中應用主從復制集群是為了保障集群的高可用性,當Master節點發生故障時,由Slave節點充當Master對外提供服務。
主從數據庫實現讀寫分離的根本原因是數據庫自身性能低下。
2.1 搭建主從復制集群
#Slave節點同步Master節點中的數據
slaveof <master> <port>
#Slave節點修改為只讀模式
slave-read-only=yes
*當Master節點啟動後再啟動所有的Slave節點。
*當主從復制集群搭建後可以通過info replication命令查看集群間的信息。
2.2 主從復制集群中的數據同步
主從復制集群中的數據同步使用PSYNC命令進行完成,PSYNC命令包含全量復制以及部分復制。
1.全量復制:首次加入集群的Slave節點會同步Master節點中的所有數據。
2.部分復制:再次與Master節點建立連接的Slave節點同步Master節點中的部分數據。
*Redis提供了repl-disable-tcp-nodelay命令,表示是否啟用TCP的NO_DELAY功能,當該命令為yes時,表示禁用TCP的NO_DELAY功能,那麽Master節點在同步修改給各個Slave節點時會合並小的TCP包從而節省帶寬,但此方式會增加同步延時(40ms左右),造成主從數據不一致的問題。當該命令為no時,表示啟用TCP的NO_DELAY功能,那麽Master節點會實時同步修改給各個Slave節點。
2.3 主從復制集群中的故障轉移
當Master節點發生故障時,可以通過手動或者自動的方式進行故障轉移
手動故障轉移
1.將其中一個Slave節點斷開與Master節點的連接,並使其成為新的Master節點。
2.將其他的Slave節點與該新的Master節點建立連接。
3.修改各個節點的redis.conf配置文件,更新主從映射關系,保證下次重啟時使用最新的主從關系啟動,避免主從數據不一致問題。
#Slave節點斷開與Master節點的連接
slaveof no one
自動故障轉移(哨兵機制)
Redis中提供了Sentinel哨兵機制,由多個哨兵組成一個哨兵集群,負責保障Redis集群的高可用性,當Master節點發生故障時,自動的將其中一個Slave節點斷開與Master節點的連接並使其成為新的Master節點,並將其他的Slave節點與新的Master節點建立連接,最後修改各個節點的redis.conf配置文件,更新主從映射關系。
1.每個哨兵節點每隔10s會向Master發送info replication命令,獲取當前集群最新的拓撲結構,此時每個哨兵就能獲取到各個Slave的連接信息。
2.每個哨兵每隔1s會向集群中的Master和各個Slave節點發送心跳,根據心跳來判斷節點是否存活,若在一定時間段內節點沒有回復,那麽該哨兵認為該節點已經故障了。
3.每個哨兵每隔2s會向Redis中的指定頻道發布其對Master節點的判斷,同時每個哨兵會訂閱該頻道,因此每個哨兵都能知道其他哨兵對Master節點的判斷。
4.當其中一個哨兵發現Master節點故障後,會查看其他哨兵對Master節點的判斷,若超過指定個數個哨兵都認為該節點故障,那麽由該哨兵充當哨兵集群的Leader進行故障轉移,故障轉移的步驟與手動轉移的一致,挑選其中一個Slave節點斷開與Master節點的連接,並使其成為新的Master節點,然後其他Slave節點與該新的Master節點建立聯系,最後修改各個節點的redis.conf配置文件,更新主從映射關系,保證當集群重啟時以最新的主從映射關系運行,避免主從數據不一致的問題。
在Redis的源碼目錄中存在sentinal.conf配置文件,該文件是哨兵的配置文件。
#監聽Master節點的信息
sentinel monitor <master-name> <ip> <port> <quorum>
*其中quorum表示當哨兵集群中有quorum個哨兵都認為Master節點不可用時則哨兵集群認為該節點已經故障.
#心跳超時時間
sentinel down-after-milliseconds <master-name> <milliseconds>
#故障轉移超時時間
sentinel failover-timeout <master-name> <milliseconds>
#允許同時有多少個從節點同步新節點的數據
sentinel parallel-syncs <master-name> <numreplicas>
*一個哨兵集群可以監控多個Redis主從復制集群。
分別啟動Redis各個節點,然後通過redis-sentinel分別啟動各個哨兵,由於每個哨兵都關聯同一個Master,因此這多個哨兵自動成為集群關系。
*在主從復制集群中,一般都會使用自動故障轉移方案(哨兵機制)
*由於在主從復制集群中的Slave節點寫入時會直接拒絕請求,因此在JAVA客戶端使用時,直接通過Jedis或者JedisPool連接Master節點進行使用。
3.Redis數據分片集群
Redis在3.0版本後推出了RedisCluster用於搭建數據分片集群。
*其中每個Master節點負責指定槽範圍內的數據,並提供讀寫服務,Slave節點只負責同步Master節點中的數據,不支持進行讀取。
*使用RedisCluster時,Master節點的個數至少需要三個,每個Master可以有任意個Slave節點。
*RedisCluster使用虛擬槽的方式進行數據分片,Redis中虛擬槽的範圍為0~16383,每個Master節點維護指定範圍的槽。
*所有的Key在進行讀取和寫入時,都需要根據H(K) = CRC16[K] & 16383散列函數計算出Key對應的槽位,通過槽位找到其對應處理的Redis節點。
由於使用了RedisCluster,數據將分散到各個節點中,因此有些操作是不允許的
1.涉及多個Key的操作,比如mset、sinter等。
2.事務不能跨節點。
3.不支持多數據庫,每個Master節點只能有一個數據庫。
關於數據分片的路由策略
數據分片的路由策略一般有三種,分別是除留余數法、一致性Hash、虛擬槽,RedisCluster使用虛擬槽的方式實現數據分片。
除留余數法:以元素被某個整數M整除後所得到的余數找對其對應處理的節點( H(K) = K % M,M等於節點的個數)
*當增加或減少節點時,數據的路由將發生變化,伸縮性很差。
一致性Hash:以元素通過某個散列函數H(K)所得到的散列值坐落在Hash環上的位置,找到其對應處理的節點。
1.首先將集群中的節點IP通過散列函數H(K)計算出散列值並使其坐落在Hash環上,每個節點負責Hash環上特定範圍的請求。
2.將元素通過相同的散列函數H(K)計算出散列值,以該散列值坐落在Hash環上的位置找對其對應的處理節點。
*使用此方式很難保證客戶端的請求平均分配到各個節點中,不能很好的實現負載均衡。
虛擬槽:以元素通過某個散列函數H(K)所得到的槽位,找到其對應處理的節點。
1.每個節點負責指定槽範圍內的請求。
2.將元素通過散列函數H(K)計算出槽位,找到其對應處理的節點。
3.1 搭建數據分片集群
可以通過手動或者自動的方式搭建數據分片集群。
手動搭建數據分片集群
1.準備配置文件
#開啟RedisCluster模式
cluster-enabled yes
#RedisCluster集群配置文件,存放集群間節點的信息。
cluster-config-file nodes-6379.conf
#節點超時時間(ms)
cluster-node-timeout 15000
2.分別啟動各個Redis節點
*當啟動Redis節點後,會生成nodes.conf文件,該文件記錄著集群間節點的關系(此時只有本節點信息)
*每個節點都有一個ClusterID,且角色默認都是Master。
3.握手(使各個節點建立關系)
*連接任意一個節點,然後分別對剩余的節點進行握手。
*當握手成功後,在node.conf文件中能看到集群間完整的節點信息。
4.分配槽
5.主從映射
*分別連接要作為Slave的節點,然後通過ClusterID與Master進行關聯。
6.使用集群的模式連接RedisCluster
*其中cluster nodes命令能夠查看集群間節點的信息,其讀取的是node.conf文件中信息,cluster info命令能夠查看集群的狀態信息。
*當節點分配槽完成後,此時集群將處於上線狀態,當集群中任意一個Master節點故障後,如果沒有對應的Slave節點,那麽集群將處於下線狀態,當集群處於下線狀態時,不能對外提供服務。
*當集群搭建完成後,可以進行關閉以及重啟,當重啟集群時,會自動讀取node.conf文件中的信息恢復集群間的關系,並讀取dump.rdb文件進行數據的恢復。
*當需要重新構建集群關系時,需要刪除每個節點的node.conf以及rdb文件,否則集群搭建不成功。
*當使用集群的模式連接RedisCluster後,當進行讀取和寫入操作時,會通過H(K)散列函數計算出Key所在的槽,然後找到其對應處理的Master節點,最後自動跳轉到該節點進行操作。
*不管是讀取還是寫入操作,都會統一跳轉到對應處理的Master節點,slave-read-only=yes配置只適用於主從復制集群模式。
自動搭建數據分片集群
RedisCluster使用ruby來自動搭建數據分片集群。
1.環境準備
需要安裝ruby,並且安裝redis.gem
2.準備配置文件
#開啟RedisCluster模式
cluster-enabled yes
#RedisCluster集群配置文件,存放集群間節點的信息。
cluster-config-file nodes-6379.conf
#節點超時時間(ms)
cluster-node-timeout 15000
3.分別啟動各個Redis節點
4.使用redis-trib.rb命令自動完成握手、分配槽、主從映射
redis-trib.rb create --replicas <slaveNum> <ip:port..>
*其中slaveNum為每個Master節點的Slave個數,可以為0。
*只能使用ip地址,不能使用主機名。
3.2 數據分片集群中的狀態同步
RedisCluster是基於Gossip協議的PING/PONG通訊來保證數據分片集群中的數據一致性。
Gossip協議
Gossip協議主要用在分布式系統中各個節點的數據同步。
Gossip協議由種子節點發起請求(種子節點即狀態發生改變的節點),當一個種子節點有狀態需要更新到網絡中的其他節點時,它會隨機選擇周圍幾個節點進行散播消息,收到消息的節點也會重復此過程,直至網絡中的所有節點都收到消息,這個過程需要一定的時間,因此Gossip是一個最終一致性協議。
Gossip協議中提供了三種通訊類型:
1.PUSH類型:A節點將數據發送給B節點,B節點更新A節點比自己新的數據。
2.PULL類型:A節點將數據發送給B節點,B節點返回比A節點新的數據,A節點再更新自己。
3.PULL/PUSH類型:A節點將數據發送給B節點,B節點返回比A節點新的數據,A節點再更新自己,然後A節點將數據發送給B節點,B節點更新A節點比自己新的數據。
*PUSH類型發送一次請求,目的是讓其他節點更新。
*PULL類型發送兩次請求,目的是更新自身節點的信息。
*每個消息都有一個時間戳,用來區分新老信息。
RedisCluster中的PING/PONG通訊
PING:發送集群中節點的信息、角色、集群ID、時間戳。
PONG:響應PING的請求。
*PING請求即Gossip協議中的PUSH,目的是讓其他節點進行更新。
RedisCluster中的每個節點都會定期的向其他節點發送PING請求(包括Slave節點),用於集群間狀態的同步以及檢測節點的可用性。
當集群中有新節點加入時(經過Meet操作),該節點會向其他節點發送PING請求,同時其他節點也會向其發送PING請求,最終達到數據一致性。
*RedisCluster中的節點故障是通過Master投票決定的,當有半數的Master認為該節點故障時,那麽集群認為該節點故障,如果故障的節點是Master,那麽會將其Slave節點切換為Master。
*當RedisCluster中有一半的Master同時失效,那麽整個集群將不可用,因為已經沒有足夠的Master進行投票。
4.JAVA中使用Redis集群
4.1 使用主從復制集群
*Jedis中通過JedisSentinelPool實例來使用主從復制集群,連接主從復制集群中所有哨兵的地址,並指定哨兵配置文件中Master的名稱。
/**
* @Auther: ZHUANGHAOTANG
* @Date: 2019/4/2 17:11
* @Description:
*/
public class RedisUtils {
private static final String masterName = "mymaster";
private static JedisSentinelPool jedisSentinelPool = null;
static {
//連接主從復制集群中所有哨兵地址
Set<String> connectionMes = new HashSet<>();
connectionMes.add("192.168.2.90:26379");
connectionMes.add("192.168.2.91:26379");
connectionMes.add("192.168.2.92:26379");
//連接池配置
JedisPoolConfig poolConfig = new JedisPoolConfig();
//最大連接數
poolConfig.setMaxTotal(10);
//最大空閑連接數
poolConfig.setMaxIdle(5);
jedisSentinelPool = new JedisSentinelPool(masterName, connectionMes, poolConfig);
}
public static Jedis getConnection() {
return jedisSentinelPool.getResource();
}
}
4.2 使用數據分片集群
*Jedis中通過JedisCluster實例使用數據分片集群,連接數據分片集群中所有節點(可以直接創建JedisCluster實例,也可以添加連接池進行管理)
/**
* @Auther: ZHUANGHAOTANG
* @Date: 2019/4/2 17:11
* @Description:
*/
public class RedisUtils {
private static JedisCluster jedisCluster = null;
static {
//連接RedisCluster中的所有節點
Set<HostAndPort> connectionMes = new HashSet<>();
connectionMes.add(new HostAndPort("192.168.2.90", 6379));
connectionMes.add(new HostAndPort("192.168.2.90", 6380));
connectionMes.add(new HostAndPort("192.168.2.91", 6379));
connectionMes.add(new HostAndPort("192.168.2.91", 6380));
connectionMes.add(new HostAndPort("192.168.2.92", 6379));
connectionMes.add(new HostAndPort("192.168.2.92", 6380));
//連接池配置
JedisPoolConfig poolConfig = new JedisPoolConfig();
//最大連接數
poolConfig.setMaxTotal(10);
//最大空閑連接數
poolConfig.setMaxIdle(5);
jedisCluster = new JedisCluster(connectionMes, poolConfig);
}
}
*當使用集群的模式連接RedisCluster時,當進行讀取和寫入操作時,會隨機連接集群中的一個節點,然後根據H(K)散列函數計算出Key所坐落的槽,然後找到該槽所對應處理的Master節點,最後自動跳轉到該節點中進行操作。
5.Redis集群數據遷移方案
5.1 主從復制集群的數據遷移
當需要將當前主從復制集群中的數據遷移到一個新的主從復制集群時
1.搭建新的主從復制集群,然後關閉集群,先關閉所有Slave節點,再關閉Master節點,避免發生主從切換。
2.將舊集群中的Master節點的RDB文件復制到新集群中的Master節點。
3.重啟新集群,自動進行數據的恢復。
*先關閉新集群再進行RDB文件的遷移,是因為當節點進行shutdown操作時,會自動觸發bgsave命令,避免RDB文件被替換。
*關閉集群時先關閉所有Slave節點再關閉Master節點,是為了避免發生主從切換,當重啟集群時,導致主從數據不一致的問題。
5.2 數據分片集群的數據遷移
當需要將當前數據分片集群中的數據遷移到一個新的數據分片集群時
1.使用與舊集群相同的Master節點個數以及相同的槽範圍搭建新的數據分片集群(手動/自動搭建)
2.依次關閉新集群中的各個節點,先關閉所有Slave節點,再關閉所有Master節點,避免發生主從切換。
3.根據舊集群的node.conf文件查看Master節點的相關信息,再查看新集群的node.conf文件中Master節點的相關信息,然後將舊集群中的Master節點的RDB文件復制到新集群對應槽範圍的Master節點中,最後重啟新集群,自動進行數據恢復。
*根據兩個集群的node.conf文件來確定不同槽範圍的Master節點的映射關系。
*不管是主從復制集群還是數據分片集群,新集群中的Slave節點的個數都不需要和舊集群的一致。
6.Redis集群擴容
Redis集群擴容是相對於數據分片集群來說的,在現有集群的架構下,通過增加多個Master節點來提高Redis集群的負載能力。
6.1 準備要加入到集群的節點
*根據實際的業務情況決定要增加的Master節點個數,以及對應Slave節點的個數。
6.2 分別進行握手,使其加入到集群中
6.3 進行槽位和數據的遷移
*使用redis-trib.rb reshared --timeout <timeout> <ip>:<port>命令進行槽位和數據的遷移,其中timeout指定每個槽遷移時最大的超時時間,ip和端口指定接收槽和數據的目標節點。
*需要指定在當前集群中移動多少個槽位到目標節點中,值可以選1~16384,並且需要指定目標節點在集群中的ID。
*可以選擇all和done兩種遷移模式,其中all表示將集群中的所有節點作為槽的源節點(平均分配),done表示指定集群中的某個節點作為槽的源節點。
*在槽和數據遷移的過程中,用戶的請求會有相應的延時,整個遷移過程所需要的時間與Redis集群中的數據量成正比(線上試過平均2.5s遷移一個槽,遷移6000個槽耗費了4小時)
*在槽和數據遷移的過程中,如果某個槽位在遷移時報錯,那麽Redis會停止遷移,在它之前遷移的槽和數據仍會生效,當再次遷移時需要執行fix命令(會有提示),刪除遷移失敗的槽位的數據。
*使用了all模式,可見遷移後的槽從之前的Master中分別平均抽取1364/1365個槽。
6.4 主從映射
Redis集群管理