千萬級資料並行如何處理?進入學習
推薦學習:
由於近期公司在做系統優化,前段時間將大表進行分表後,現在又來搞redis了。關於redis,其中有一項要求就是將redis服務由阿里雲遷移到公司自己的伺服器中(由於公司性質原因)。剛好藉著這次機會,重新回顧一下redis的高可用叢集架構。redis叢集方式有三種,分別為主從複製模式,哨兵模式以及Cluster叢集模式,一般情況下哨兵和Cluster叢集用的相對多一些,下面就來簡單理解這三種模式。
在理解叢集架構前,先要介紹一下redis的持久化機制,因為在後面的叢集中會涉及到持久化。redis持久化是將快取在記憶體中的資料根據一些規則進行落盤,以防止在redis服務宕機時可以進行資料恢復或者是叢集架構中進行主從節點資料同步。redis持久化的方式有RDB和AOF兩種,在4.0版本後新出了混合持久化模式。
RDB是redis預設開啟的持久化機制,其持久化方式是按照使用者設定的規則"X秒內至少發生過Y次改動"
,生成快照並落盤到dump.rdb二進位制檔案中。預設情況下,redis設定了三種,分別為900秒內至少發生過1次快取key的改動,300秒內至少發生過10次快取key的改動以及60秒內至少發生過10000次改動。
除了redis自動快照持久化資料外,還有兩個命令可以幫助我們手動進行記憶體資料快照,這兩個命令分別為save
和bgsave
。
save:以同步的方式進行資料快照,當快取資料量大,會阻塞其他命令的執行,效率不高。
bgsave:以非同步的方式進行資料快照,有redis主執行緒fork出一個子程序來進行資料快照,不會阻塞其他命令的執行,效率較高。由於是採用非同步快照的方式,那麼就有可能發生在快照的過程中,有其他命令對資料進行了修改。為了避免這個問題reids採用了寫時複製(Cpoy-On-Write)的方式,因為此時進行快照的程序是由主執行緒fork出來的,所以享有主執行緒的資源,當快照過程中發生資料改動時,那麼該資料會被複制一份並生成副本資料,子程序會將改副本資料寫入到dump.rdb檔案中。
RDB快照是以二進位制的方式進行儲存的,所以在資料恢復時,速度會比較快,但是它存在資料丟失的風險。假如設定的快照規則為60秒內至少發生100次資料改動,那麼在50秒時,redis服務由於某種原因突然宕機了,那在這50秒內的所有資料將會丟失。
AOF是Redis的另一種持久化方式,與RDB不同時是,AOF記錄著每一條更改資料的命令並儲存到磁碟下的appendonly.aof檔案中,當redis服務重新啟動時,會載入該文將並再次執行檔案中儲存的命令,從而達到資料恢復的效果。預設情況下,AOF是關閉的,可以通過修改conf組態檔來進行開啟。
# appendonly no 關閉AOF持久化
appendonly yes # 開啟AOF持久化
# The name of the append only file (default: "appendonly.aof")
appendfilename "appendonly.aof" # 持久化檔名
登入後複製
AOF提供了三種方式,可以讓命令儲存到磁碟。預設情況下,AOF採用appendfsync everysec
的方式進行命令持久化。
appendfsync always #每次有新的改寫命令時,都會追加到磁碟的aof檔案中。資料安全性最高,但效率最慢。
appendfsync everysec # 每一秒,都會將改寫命令追加到磁碟中的aof檔案中。如果發生宕機,也只會丟失1秒的資料。
appendfsync no #不會主動進行命令落盤,而是由作業系統決定什麼時候寫入到磁碟。資料安全性不高。
登入後複製
開啟AOF後需要重新啟動redis服務,當再次執行相關改寫命令時,aof檔案中會記錄操作的命令。
相對於RDB,雖然AOF的資料安全性更高,但是隨著服務的持續執行,aof的檔案也會越來越大,等到下次恢復資料時,速度會越來越慢。如果RDB和AOF都開啟,在恢復資料時,redis會優先選擇AOF,畢竟AOF丟失的資料更少啊。
RDB | AOF | |
---|---|---|
恢復效率 | 高 | 低 |
資料安全性 | 低 | 高 |
空間佔用 | 低 | 高 |
由於RDB持久化方式容易造成資料丟失,AOF持久化方式資料恢復較慢,所以在redis4.0版本後,新出來混合持久化模式。混合持久化將RDB和AOF的優點進行了整合,並而且依賴於AOF,所以在使用混合持久化前,需要開啟AOF。在開啟混合持久化後,當發生AOF重寫時,會將記憶體中的資料以RDB的資料格式儲存到aof檔案中,在下一次的重寫之前,混合持久化會追加儲存每條改寫命令到aof檔案中。當需要恢復資料時,會載入儲存的rdb內容資料,然後再繼續同步aof指令。
# AOF重寫設定,當aof檔案達到60MB並且比上次重寫後的體量多100%時自動觸發AOF重寫 auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-use-rdb-preamble yes # 開啟混合持久化# aof-use-rdb-preamble no # 關閉混合持久化
登入後複製
AOF重寫是指當aof檔案越來越大時,redis會自動優化aof檔案中無用的命令,從而減少檔案體積。比如在處理文章閱讀量時,每檢視一次文章就會執行一次Incr命令,但是隨著閱讀量的不斷增加,aof檔案中的incr命令也會積累的越來越多。在AOF重寫後,將會刪除這些沒用的Incr命令,將這些命令直接替換為set key value命令。除了redis自動重寫AOF,如果需要,也可以通過bgrewriteaof
命令手動觸發。
在生產環境中,一般不會直接設定單節點的redis服務,這樣壓力太大。為了緩解redis服務壓力,可以搭建主從複製,做讀寫分離。redis主從複製,是有一個主節點Master和多個從節點Slave組成。主從節點間的資料同步只能是單向傳輸的,只能由Master節點傳輸到Slave節點。
準備三臺linux伺服器,其中一臺作為redis的主節點,兩臺作為reids的從節點。如果沒有足夠的機器可以在同一臺機器上面將redis檔案多複製兩份並更改埠號,這樣可以搭建一個偽叢集。
IP | 主/從節點 | 埠 | 版本 |
---|---|---|---|
192.168.36.128 | 主 | 6379 | 5.0.14 |
192.168.36.130 | 從 | 6379 | 5.0.14 |
192.168.36.131 | 從 | 6379 | 5.0.14 |
修改redis.conf檔案中的replicaof,設定主節點的ip和埠號,並且開啟從節點唯讀。
./src/redis-server redis.conf
登入後複製
登入後複製
3. 依次啟動從節點36.130,36.131機器中的redis服務
./src/redis-server redis.conf
登入後複製
登入後複製
啟動成功後可以看到紀錄檔中顯示已經與Master節點建立的連線。如果出現與Master節點的連線被拒,那麼先檢查Master節點的伺服器是否開啟防火牆,如果開啟,可以開放6379埠或者關閉防火牆。如果防火牆被關閉但連線仍然被拒,那麼可以修改Master節點服務中的redis.conf檔案。將bing 127.0.0.1修改為本機對外的網路卡ip或者直接註釋掉即可,然後重新啟動伺服器即可。
全部節點啟動成功後,Master節點可以檢視從節點的連線狀態,offset偏移量等資訊。
info replication # 主節點檢視連線資訊
登入後複製
全量資料同步主從節點之間的資料同步是通過建立socket長連線來進行傳輸的。當Slave節點啟動時,會與Master節點建立長連線,並且傳送psync同步資料命令。當Master節點收到psync命令時,會執行pgsave進行rdb記憶體資料快照(這裡的rdb快照與conf檔案中是否開啟rdb無關),如果在快照過程中有新的改寫命令,那麼Master節點會將這些命令儲存到repl buffer緩衝區中。當快照結束後,會將rdb傳輸給Slave節點。Slave節點在接收到rdb後,如果存在舊資料,那麼會將這些舊資料清除並載入rdb。載入完成後會接受master快取在repl buffer中的新命令。在這些步驟全部執行完成後,主從節點已經算連線成功了,後續Master節點的命令會不斷的傳送到Slave節點。如果在高並行的情況下,可能會存在資料延遲的情況。
部分資料同步
部分資料同步發生在Slave節點發生宕機,並且在短時間內進行了服務恢復。短時間內主從節點之間的資料差額不會太大,如果執行全量資料同步將會比較耗時。部分資料同步時,Slave會向Master節點建立socket長連線並行送帶有一個offset偏移量的資料同步請求,這個offset可以理解資料同步的位置。Master節點在收到資料同步請求後,會根據offset結合buffer緩衝區內新的改寫命令進行位置確定。如果確定了offset的位置,那麼就會將這個位置往後的所有改寫命令傳送到Slave節點。如果沒有確定offset的位置,那麼會再次執行全量資料同步。比如,在Slave節點沒有宕機之前命令已經同步到了offset=11這個位置,當該節點重新啟動後,向Master節點傳送該offset,Master根據offset在緩衝區中進行定位,在定位到11這個位置後,將該位置往後的所有命令傳送給Slave。在資料同步完成後,後續Master節點的命令會不斷的傳送到該Slave節點
優點
缺點
哨兵模式對主從複製進行了進一步優化,獨立出單獨的哨兵程序用於監控主從架構中的伺服器狀態,一旦發生宕機,哨兵會在短時間內選舉出新的Master節點並進行主從切換。不僅如此,在多哨兵的節點下,每個哨兵都會相互進行監控,監控哨兵節點是否宕機。
IP | 主/從節點 | 埠 | 哨兵埠 | 版本 |
---|---|---|---|---|
192.168.36.128 | 主 | 6379 | 26379 | 5.0.14 |
192.168.36.130 | 從 | 6379 | 26379 | 5.0.14 |
192.168.36.131 | 從 | 6379 | 26379 | 5.0.14 |
主從複製是哨兵模式的基礎,所以在搭建哨兵前需要完成主從複製的設定。在搭建完主從後,哨兵的搭建就容易很多。
找到安裝目錄下的sentinel.conf
檔案並進行修改。主要修改兩個地方,分別為哨兵埠port和監控的主節點ip地址和埠號。
在設定完成後,可以使用命令啟動各機器的哨兵服務。啟動成功後,可檢視redis服務和哨兵服務的進行資訊。
搭建成功後,就來通過程式碼演示主節點宕機的情況下,哨兵是否會幫助系統自動進行主備切換。在springboot專案中引入對應的pom,並設定對應的redis哨兵資訊。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.2.2.RELEASE</version></dependency><dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version></dependency>
登入後複製
server:
port: 8081spring:
redis:
sentinel:
master: mymaster # 主服務節點
nodes: 192.168.36.128:26379,192.168.36.130:26379,192.168.36.131:26379 #哨兵節點
timeout: 3000 #連線超時時間
登入後複製
@Slf4j
@RestController
public class RedisTest {
@Resource
private StringRedisTemplate stringRedisTemplate;
/*
* 每秒鐘向redis中寫入資料,中途kill掉主節點程序,模擬宕機
*/
@GetMapping("/redis/testSet")
public void test(@RequestParam(name = "key") String key,
@RequestParam(name = "value") String value) throws InterruptedException {
int idx=0;
for(;;){
try {
idx++;
stringRedisTemplate.opsForValue().set(key+idx, value);
log.info("=====儲存成功:{},{}=====",key+idx,value);
}catch (Exception e){
log.error("====連線redis伺服器失敗:{}====",e.getMessage());
}
Thread.sleep(1000);
}
}
}
登入後複製
當啟動服務後,通過節後向後端傳遞資料,可以看到輸出的紀錄檔,表示redis哨兵叢集已經可以正常執行了。那麼這個時候kill掉36.128機器上的主節點,模擬服務宕機。通過紀錄檔可以知道,服務出現異常了,在過十幾秒發現哨兵已經自動幫系統進行了主從切換,並且服務也可以正常存取了。
2022-11-14 22:20:23.134 INFO 8764 --- [nio-8081-exec-2] com.gz.redis.RedisTest : =====儲存成功:test14,123=====
2022-11-14 22:20:24.142 INFO 8764 --- [nio-8081-exec-2] com.gz.redis.RedisTest : =====儲存成功:test15,123=====
2022-11-14 22:20:24.844 INFO 8764 --- [xecutorLoop-1-1] i.l.core.protocol.ConnectionWatchdog : Reconnecting, last destination was /192.168.36.128:6379
2022-11-14 22:20:26.909 WARN 8764 --- [ioEventLoop-4-4] i.l.core.protocol.ConnectionWatchdog : Cannot reconnect to [192.168.36.128:6379]: Connection refused: no further information: /192.168.36.128:6379
2022-11-14 22:20:28.165 ERROR 8764 --- [nio-8081-exec-2] com.gz.redis.RedisTest : ====連線redis伺服器失敗:Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed out after 3 second(s)====
2022-11-14 22:20:31.199 INFO 8764 --- [xecutorLoop-1-1] i.l.core.protocol.ConnectionWatchdog : Reconnecting, last destination was 192.168.36.128:6379
登入後複製
2022-11-14 22:20:52.189 ERROR 8764 --- [nio-8081-exec-2] com.gz.redis.RedisTest : ====連線redis伺服器失敗:Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed out after 3 second(s)====
2022-11-14 22:20:53.819 WARN 8764 --- [ioEventLoop-4-2] i.l.core.protocol.ConnectionWatchdog : Cannot reconnect to [192.168.36.128:6379]: Connection refused: no further information: /192.168.36.128:6379
2022-11-14 22:20:56.194 ERROR 8764 --- [nio-8081-exec-2] com.gz.redis.RedisTest : ====連線redis伺服器失敗:Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed out after 3 second(s)====
2022-11-14 22:20:57.999 INFO 8764 --- [xecutorLoop-1-2] i.l.core.protocol.ConnectionWatchdog : Reconnecting, last destination was 192.168.36.128:6379
2022-11-14 22:20:58.032 INFO 8764 --- [ioEventLoop-4-4] i.l.core.protocol.ReconnectionHandler : Reconnected to 192.168.36.131:6379
2022-11-14 22:20:58.040 INFO 8764 --- [nio-8081-exec-2] com.gz.redis.RedisTest : =====儲存成功:test24,123=====
2022-11-14 22:20:59.051 INFO 8764 --- [nio-8081-exec-2] com.gz.redis.RedisTest : =====儲存成功:test25,123=====
2022-11-14 22:21:00.057 INFO 8764 --- [nio-8081-exec-2] com.gz.redis.RedisTest : =====儲存成功:test26,123=====
2022-11-14 22:21:01.065 INFO 8764 --- [nio-8081-exec-2] com.gz.redis.RedisTest : =====儲存成功:test27,123=====
登入後複製
在多個哨兵的模式下,每個哨兵都會向redis節點傳送心跳包來檢測節點的執行狀態。如果某個哨兵發現主節點連線超時了,沒有收到心跳,那麼系統並不會立刻進行故障轉移,這種情況叫做主觀下線
。如果後續的哨兵節點發現,與主節點的心跳也失敗了並且哨兵數量超過2個,那麼這個時候就會認為主節點客觀下線
,並且會進行故障轉移,這個客觀下線的數值可以在哨兵的組態檔中進行設定。
sentinel monitor master 192.168.36.128 6378 2
登入後複製
在故障轉移前,需要選舉出一個哨兵leader來進行Master節點的重新選舉。哨兵的選舉過程大致可以分為三步:
當某個的哨兵確定主節點已經下線時,會像其他哨兵傳送is-master-down-by-addr命令,要求將自己設為leader,並處理故障轉移工作。
其他哨兵在收到命令後,進行投票選舉
如果票數過半時,那麼傳送命令的哨兵節點將成為主節點,並進行故障轉移。
當選舉出主哨兵後,那麼這個主哨兵就會過濾掉宕機的redis節點,重新選舉出Master節點。首先會根據redis節點的優先順序進行選舉(slave-priority),數值越大的從節點將會被選舉為主節點。如果這個優先順序相同,那麼主哨兵節點就會選擇資料最全的從節點作為新的主節點。如果還是選舉失敗,那麼就會選舉出程序id最小的從節點作為主節點。
在叢集環境下會由於網路等原因出現腦裂的情況,所謂的腦裂就是由於主節點和從節點和哨兵處於不同的網路分割區,由於網路波動等原因,使得哨兵沒有能夠即使接收到主節點的心跳,所以通過選舉的方式選舉了一個從節點為新的主節點,這樣就存在了兩個主節點,就像一個人有兩個大腦一樣,這樣會導致使用者端還在像老的主節點那裡寫入資料,新節點無法同步資料,當網路恢復後,哨兵會將老的主節點降為從節點,這時再從新主節點同步資料,這會導致大量資料丟失。如果需要避免腦裂的問題,可以設定下面兩行資訊。
min-replicas-to-write 3 # 最少從節點為3
min-replicas-max-lag 10 # 表示資料複製和同步的延遲不能超過10秒
登入後複製
優點:除了擁有主從複製的優點外,還可以進行故障轉移,主從切換,系統更加可靠。
缺點:故障轉移需要花費一定的時間,在高並行場景下容易出現資料丟失。不容易實現線上擴容。
哨兵模式中雖然在主節點宕機的情況下能夠做到主從切換,但是在切換的過程中需要花費十幾秒或者更久的時間,會造成部分資料的丟失。如果在並行量不高的情況下,可以使用該叢集模式,但是在高並行的情況下,這十幾秒的時間可能會造成嚴重的後果,所以,在很多網際網路公司都是採用Cluster叢集架構。Cluster叢集中由多個redis節點組成,每個redis服務節點都有一個Master節點和多個Slave節點,在進行資料儲存時,redis會對資料的key進行hash運算並根據運算結果分配到不同的槽位。一般情況下,Cluster叢集架構要設定6個節點(三主三從)。
由於只有三臺虛擬機器器,所以需要在每臺伺服器上面搭建兩個redis服務,埠分別為6379和6380,這個剛好可以構建6個節點。
IP | 主/從節點 | 埠 | 版本 |
---|---|---|---|
192.168.36.128 | - | 6379 | 5.0.14 |
192.168.36.128 | - | 6380 | 5.0.14 |
192.168.36.130 | - | 6379 | 5.0.14 |
192.168.36.130 | - | 6380 | 5.0.14 |
192.168.36.131 | - | 6379 | 5.0.14 |
192.168.36.131 | - | 6380 | 5.0.14 |
為了看起來不是那麼混亂,可以為cluster新建一個資料夾,並將redis的檔案拷貝到cluster資料夾中,並修改資料夾名為redis-6379,reids-6380。
新建完成後,修改每個節點的redis.conf組態檔,找到cluster相關的設定位置,將cluster-enable更改為yes,表示開啟叢集模式。開啟後,需要修改叢集節點連線的超時時間cluster-node-timeout,節點組態檔名cluster-config-file等等,需要注意的是,同一臺機器上面的服務節點記得更改埠號。
在每個節點都設定完成後,可以依次啟動各節點。啟動成功後,可以檢視redis的程序資訊,後面有明顯的標識為[cluster]。
現在雖然每個節點的redis都已經正常啟動了,但是每個節點之間並沒有任何聯絡啊。所以這個時候還需要最後一步,將各節點建立關係。在任意一臺機器上執行下面的命令-- cluster create ip:port,進行叢集建立。命令執行成功後,可以看到槽位的分佈情況和主從關係。
./src/redis-cli --cluster create 192.168.36.128:6379 192.168.36.128:6380 192.168.36.130:6379 192.168.36.130:6380 192.168.36.131:6379 192.168.36.131:6380 --cluster-replicas 1複製程式碼
登入後複製
cluster成功啟動後,可以在程式碼中簡單的測試一下,這裡的程式碼依舊採用哨兵模式中的測試程式碼,只是將sentinel相關的資訊註釋掉並加上cluster的節點資訊即可。
spring:
redis:
cluster:
nodes: 192.168.36.128:6379,192.168.36.128:6380,192.168.36.130:6379,192.168.36.130:6380,192.168.36.131:6379,192.168.36.131:6380# sentinel:# master: mymaster# nodes: 192.168.36.128:26379,192.168.36.130:26379,192.168.36.131:26379
timeout: 3000
lettuce:
pool:
max-active: 80
min-idle: 50
登入後複製
Cluster模式下由於存在多個Master節點,所以在儲存資料時,需要確定將這個資料儲存到哪臺機器上。上面在啟動叢整合功後可以看到每臺Master節點都有自己的一個槽位(Slots)範圍,Master[0]的槽位範圍是0 - 5460,Master[1]的槽位範圍是5461 - 10922,Master[2]的槽位範圍是10922 - 16383。redis在儲存前會通過CRC16方法計算出key的hash值,並與16383進行位運算來確定最終的槽位值。所以,可以知道確定槽位的方式就是 CRC16(key) & 16383
。計算出槽位後,此時在java伺服器端並不知道這個槽位對應到哪一臺redis服務,其實在java伺服器端啟動服務時會將redis的相關槽位和對映的ip資訊進行一個本地快取,所以知道槽位後,就會知道對應槽位的ip。
cluster模式中的選舉與哨兵中的不同。當某個從節點發現自己的主節點狀態變為fail狀態時,便嘗試進行故障轉移。由於掛掉的主節點可能會有多個從節點,從而存在多個從節點競爭成為新主節點 。其選舉過程大概如下:
從節點將自己記錄的叢集currentEpoch加1,並廣播FAILOVER_AUTH_REQUEST資訊,通知叢集中的所有節點,需要進行重新選舉了。
其他節點收到該資訊,但只有master節點會進行響應,判斷請求者的合法性,並行送 FAILOVER_AUTH_ACK,對每一個epoch只傳送一次ack。
傳送通知的從節點會收集各master主節點返回的FAILOVER_AUTH_ACK。
如果該從節點收到的ack數過半,那麼該節點就會被選舉為新的Master主節點。成為主節點後,廣播通知其他小叢集節點
優點:
有多個主節點,做到去中心化。
資料可以槽位進行分佈儲存
擴充套件性更高,可用性更高。cluster叢集中的節點可以線上新增或刪除,官方推薦節點數不超1000。當部分Master節點不可用時,整個叢集任然可以正常工作。
缺點:
資料通過非同步複製,不保證資料的強一致性
Slave節點在叢集中充當冷備,不能緩解讀壓力
reids作為當下非常流行的一款中介軟體,它可以用作快取,減少DB的壓力,提高系統的效能。也可以用作分散式鎖保證並行安全性。還可以用作MQ訊息佇列,減少系統的耦合度。它支援單機模式,主從複製,哨兵以及Cluster模式。每個模式都有自己的優點和缺點,在實際專案中可以根據自己的業務需求以及並行程度來進行選擇。
推薦學習:
以上就是Redis高可用架構搭建到原理分析的詳細內容,更多請關注TW511.COM其它相關文章!