導航:首頁 > 源碼編譯 > slots遷移源碼

slots遷移源碼

發布時間:2023-06-01 07:06:17

Ⅰ redis單實例數據遷移到cluster(redis5.0.5)

1、檢查redis集群狀態

/app/redis_6379/bin/redis-cli -p 6379 -a 123456 --cluster check 10.1.1.90:6379

2、將所有master上的slots重新分配到一個master上

/app/redis_6379/bin/redis-cli -p 6379 -a 123456 --cluster reshard --cluster-from --cluster-to --cluster-slots 5462 --cluster-yes 10.1.1.90:6379

/app/redis_6379/bin/redis-cli -p 6379 -a 123456 --cluster reshard --cluster-from --cluster-to --cluster-slots 5462 --cluster-yes 10.1.1.90:6379

3、停止除唯一持有slots的master的其他節點

4、停止唯一持洞和有slots的master節點

5、將單實例的持久化文件rdb/aof文件拷貝到唯一持有slots的master節點的數據目錄下

6、啟動唯一持有slots的master節點

7、啟緩大動除唯一持有slots的master的其他節點

8、在集群masters間重新分配slots

/app/redis_6379/bin/redis-cli -p 6379 -a 123456 --cluster reshard --cluster-from --cluster-to   --cluster-slots 5462 --cluster-yes 10.1.1.90:6379

/app/redis_6379/bin/redis-cli -p 6379 -a 123456 --cluster reshard --cluster-from  納哪盯--cluster-to  --cluster-slots 5462 --cluster-yes 10.1.1.90:6379

9、檢查redis集群狀態

/app/redis_6379/bin/redis-cli -p 6379 -a 123456 --cluster check 10.1.1.90:6379

Ⅱ Redis cluster 原理

Redis cluster 實現滲裂了所有的single key 操作,對於multi key操作的話,這些key必須在一個節點上面,redis cluster 通過 hash tags決定key存貯在哪個slot上面。

節點首要功能是存貯數據,集群狀態,映射key到相應的節點。自動發現其他節點,發現失敗節點,讓從變為主。

為了完成以上功能,cluster使用tcp和二進制協議(Redis Cluster Bus),節點間互聯.node 同時使用gossip協議傳播信息,包括節點的發現,發送ping包,Pub/Sub信息。
因為節點並不代理請求轉發,會返回MOVED和ASk錯誤,clients就可以直連到其他節點。client理論上面可以給任意節點發送請求,如果需要就重定向。但實際應用中client存貯一個從key到node的map來提高性能。

Redis cluster 使用非同步復制的模式,故障轉移的時候,被選為主的節點,會用自己的數據去覆蓋其他副本節點的數據。所以總有一個時間口會丟失數據。
下面一個例子會丟失數據:

master partition 變得不可用

它的一個從變為主

一定時間之後,這個主又可用了

客戶端這時候還使用舊的的路由,在這個主變為從之前,寫請求到達這個主。

3、可用性

假設n個主節點,每個主下面掛載一個從,掛掉一個,集群仍然可用。掛點兩個,可用性是1 -(1/(n 2 -1))(第一個節點掛掉後,還剩下n 2-1個節點),只有一個節點的主掛掉的可能性是 1/n*2 -1)

replicas migration 使可用性更高

4、性能

reids cluster 不代理請求到正確的節點,而是告訴客戶端正確的亮槐節點

client 會保存一份最新的key與node映射,一般情況,會直接訪問到正確的節點。

非同步寫副本

一般的操作和單台redis有相同的性能,一個有n個主節點的集群性能接近n*單個redis

綜上 高性能 線性擴展 合理的寫安全 高可用 是rediscluser 的主要目標

因為首先redis 存貯的數據量會特別大,如果合並需要更大的空間

key空間分布被劃分為16384個slot,所以一個集群,主節點的個數最大敬喊友為16384(一般建議master最大節點數為1000)

HASH_SLOT = CRC16(key) mod 16384

hash tag 是為了保證不同的key,可以分布到同一個slot上面,來執行multi-key的操作

hash tag的規則是以第一個{開始,到第一個}結尾,中間的內容,來做hash。

例子

{user1000}.following 與 {user1000}.followers user1000作為key

foo{}{bar} 整個key

{{bar}} {bar 為key

{bar}{zap} bar 為key

Ruby Example

從左到右依次為:node id, address:port, flags, last ping sent, last pong received, configuration epoch, link state, slots

其中node id是第一次啟動獲得的一個160位元組的隨機字元串,並把id保存在配置文件中,一直不會再變

每個節點有一個額外的TCP埠,這個埠用來和其他節點交換信息。這個埠一般是在與客戶端鏈接埠上面加10000,比如客戶端埠為6379,那麼cluster bus的埠為16379.

node-to-node 交流是通過cluster bus與 cluster bus protocol進行。其中cluster bus protocol 是一個二進制協議,因為官方不建議其他應用與redis 節點進行通信,所以沒有公開的文檔,要查看的話只能去看源碼

Redis cluster 是一個網狀的,每一個節點通過tcp與其他每個節點連接。假如n個節點的集群,每個節點有n-1個出的鏈接,n-1個進的鏈接。這些鏈接會一直存活。假如一個節點發送了一個ping,很就沒收到pong,但還沒到時間把這個節點設為 unreachable,就會通過重連刷新鏈接。

node 會在cluster bus埠一直接受連接,回復ping,即使這個ping 的node是不可信的。但是其他的包會被丟掉,如果發送者不是cluster 一員。

一個node有兩種方式接受其他其他node作為集群一員

這樣只要我們把節點加入到一個節點,就會自動被其他節點自動發現。

客戶端可以自由的連接任何一個node,如果這個node 不能處理會返回一個MOVED的錯誤,類似下面這樣

描述了key 的hash slot,屬於哪個node

client 會維護一個hash slots到IP:port的映射

當收到moved錯誤的時候,可以通過CLUSTER NODES或者CLUSTER SLOTS去刷新一遍整個client

cluster 支持運行狀態下添加和刪除節點。添加刪除節點抽象:把一部分hash slot從一個節點移動到另一個節點。

所以,動態擴容的核心就是在節點之間移動hash slot,hash slot 又是key的集合。所以reshare 就是把key從一個節點移動到其他節點。

redis 提供如下命令

前兩個指令:ADDSLOTS和DELSLOTS,用於向當前node分配或者移除slots,指令可以接受多個slot值。分配slots的意思是告知指定的master(即此指令需要在某個master節點執行)此後由它接管相應slots的服務;slots分配後,這些信息將會通過gossip發給集群的其他nodes。

ADDSLOTS指令通常在創建一個新的Cluster時使用,一個新的Cluster有多個空的Masters構成,此後管理員需要手動為每個master分配slots,並將16384個slots分配完畢,集群才能正常服務。簡而言之,ADDSLOTS只能操作那些尚未分配的(即不被任何nodes持有)slots,我們通常在創建新的集群或者修復一個broken的集群(集群中某些slots因為nodes的永久失效而丟失)時使用。為了避免出錯,Redis Cluster提供了一個redis-trib輔助工具,方便我們做這些事情。

DELSLOTS就是將指定的slots刪除,前提是這些slots必須在當前node上,被刪除的slots處於「未分配」狀態(當然其對應的keys數據也被clear),即尚未被任何nodes覆蓋,這種情況可能導致集群處於不可用狀態,此指令通常用於debug,在實際環境中很少使用。那些被刪除的slots,可以通過ADDSLOTS重新分配。

SETSLOT是個很重要的指令,對集群slots進行reshard的最重要手段;它用來將單個slot在兩個nodes間遷移。根據slot的操作方式,它有兩種狀態「MIGRATING」、「IMPORTING」

1)MIGRATING:將slot的狀態設置為「MIGRATING」,並遷移到destination-node上,需要注意當前node必須是slot的持有者。在遷移期間,Client的查詢操作仍在當前node上執行,如果key不存在,則會向Client反饋「-ASK」重定向信息,此後Client將會把請求重新提交給遷移的目標node。

2)IMPORTING:將slot的狀態設置為「IMPORTING」,並將其從source-node遷移到當前node上,前提是source-node必須是slot的持有者。Client交互機制同上。

假如我們有兩個節點A、B,其中slot 8在A上,我們希望將8從A遷移到B,可以使用如下方式:

1)在B上:CLUSTER SETSLOT 8 IMPORTING A

2)在A上:CLUSTER SETSLOT 8 MIGRATING B

在遷移期間,集群中其他的nodes的集群信息不會改變,即slot 8仍對應A,即此期間,Client查詢仍在A上:

1)如果key在A上存在,則有A執行。

2)否則,將向客戶端返回ASK,客戶端將請求重定向到B。

這種方式下,新key的創建就不會在A上執行,而是在B上執行,這也就是ASK重定向的原因(遷移之前的keys在A,遷移期間created的keys在B上);當上述SET SLOT執行完畢後,slot的狀態也會被自動清除,同時將slot遷移信息傳播給其他nodes,至此集群中slot的映射關系將會變更,此後slot 8的數據請求將會直接提交到B上。

動態分片的步驟:

在上文中,我們已經介紹了MOVED重定向,ASK與其非常相似。在resharding期間,為什麼不能用MOVED?MOVED意思為hash slots已經永久被另一個node接管、接下來的相應的查詢應該與它交互,ASK的意思是當前query暫時與指定的node交互;在遷移期間,slot 8的keys有可能仍在A上,所以Client的請求仍然需要首先經由A,對於A上不存在的,我們才需要到B上進行嘗試。遷移期間,Redis Cluster並沒有粗暴的將slot 8的請求全部阻塞、直到遷移結束,這種方式盡管不再需要ASK,但是會影響集群的可用性。

1)當Client接收到ASK重定向,它僅僅將當前query重定向到指定的node;此後的請求仍然交付給舊的節點。

2)客戶端並不會更新本地的slots映射,仍然保持slot 8與A的映射;直到集群遷移完畢,且遇到MOVED重定向。

一旦slot 8遷移完畢之後(集群的映射信息也已更新),如果Client再次在A上訪問slot 8時,將會得到MOVED重定向信息,此後客戶端也更新本地的集群映射信息。

可能有些Cluster客戶端的實現,不會在內存中保存slots映射關系(即nodes與slots的關系),每次請求都從聲明的、已知的nodes中,隨機訪問一個node,並根據重定向(MOVED)信息來尋找合適的node,這種訪問模式,通常是非常低效的。

當然,Client應該盡可能的將slots配置信息緩存在本地,不過配置信息也不需要絕對的實時更新,因為在請求時偶爾出現「重定向」,Client也能兼容此次請求的正確轉發,此時再更新slots配置。(所以Client通常不需要間歇性的檢測Cluster中配置信息是否已經更新)客戶端通常是全量更新slots配置:

遇到MOVED時,客戶端僅僅更新特定的slot是不夠的,因為集群中的reshard通常會影響到多個slots。客戶端通過向任意一個nodes發送「CLUSTER NODES」或者「CLUSTER SLOTS」指令均可以獲得當前集群最新的slots映射信息;「CLUSTER SLOTS」指令返回的信息更易於Client解析。

通常情況下,read、write請求都將有持有slots的master節點處理;因為redis的slaves可以支持read操作(前提是application能夠容忍stale數據),所以客戶端可以使用「READONLY」指令來擴展read請求。

「READONLY」表明其可以訪問集群的slaves節點,能夠容忍stale數據,而且此次鏈接不會執行writes操作。當鏈接設定為readonly模式後,Cluster只有當keys不被slave的master節點持有時才會發送重定向消息(即Client的read請求總是發給slave,只有當此slave的master不持有slots時才會重定向,很好理解):
1)此slave的master節點不持有相應的slots
2)集群重新配置,比如reshard或者slave遷移到了其他master上,此slave本身也不再支持此slot。

集群中的nodes持續的交換ping、pong數據,這兩種數據包的結構一樣,同樣都攜帶集群的配置信息,唯一不同的就是message中的type欄位。

通常,一個node發送ping消息,那麼接收者將會反饋pong消息;不過有時候並非如此,比如當集群中添加新的node時,接收者會將pong信息發給其他的nodes,而不是直接反饋給發送者。這樣的好處是會將配置盡快的在cluster傳播。

通常一個node每秒都會隨機向幾個nodes發送ping,所以無論集群規模多大,每個nodes發送的ping數據包的總量是恆定的。每個node都確保盡可能半個NODE_TIMEOUT時間內,向那些尚未發送過ping或者未接收到它們的pong消息的nodes發送ping。在NODE_TIMEOUT逾期之前,nodes也會嘗試與那些通訊異常的nodes重新建立TCP鏈接,確保不能僅僅因為當前鏈接異常而認為它們就是不可達的。

當NODE_TIMEOUT值較小、集群中nodes規模較大時,那麼全局交換的信息量也會非常龐大,因為每個node都盡力在半個NODE_TIMEOUT時間內,向其他nodes發送ping。比如有100個nodes,NODE_TIMEOUT為60秒,那麼每個node在30秒內向其他99各nodes發送ping,平均每秒3.3個消息,那麼整個集群全局就是每秒330個消息。這些消息量,並不會對集群的帶寬頻來不良問題。

心跳數據包的內容

ping和pong數據包中也包含gossip部分,這部分信息告訴接受者,當前節點持有其他節點的狀態,不過它只包含sender已知的隨機幾個nodes,nodes的數量根據集群規模的大小按比例計算。

gossip部分包含了

集群失效檢測就是,當某個master或者slave不能被大多數nodes可達時,用於故障遷移並將合適的slave提升為master。當slave提升未能有效實施時,集群將處於error狀態且停止接收Client端查詢。

每個node持有其已知nodes的列表包括flags,有2個flag狀態:PFAIL和FAIL;PFAIL表示「可能失效」,是一種尚未完全確認的失效狀態(即某個節點或者少數masters認為其不可達)。FAIL表示此node已經被集群大多數masters判定為失效(大多數master已認定為不可達,且不可達時間已達到設定值,需要failover)。

nodes的ID、ip+port、flags,那麼接收者將根據sender的視圖,來判定節點的狀態,這對故障檢測、節點自動發現非常有用。

當node不可達的時間超過NODE_TIMEOUT,這個節點就被標記為PFAIL(Possible failure),master和slave都可以標記其他節點為PFAIL。所謂不可達,就是當「active ping」(發送ping且能受到pong)尚未成功的時間超過NODE_TIMEOUT,因此我們設定的NODE_TIMEOUT的值應該比網路交互往返的時間延遲要大一些(通常要大的多,以至於交互往返時間可以忽略)。為了避免誤判,當一個node在半個NODE_TIMEOUT時間內仍未能pong,那麼當前node將會盡力嘗試重新建立連接進行重試,以排除pong未能接收

Ⅲ 如何實現高可用的 redis 集群

Redis 因具有豐富的數據結構和超高的性能以及簡單的協議,使其能夠很好的作為資料庫的上游緩存層。但在大規模的 Redis 使用過程中,會受限於多個方面:單機內存有限、帶寬壓力、單點問題、不能動態擴容等。

基於以上, Redis 集群方案顯得尤為重要。通常有 3 個途徑:官方 Redis Cluster ;通過 Proxy 分片;客戶端分片 (Smart Client) 。以上三種方案各有利弊。

Redis Cluster( 官方 ) :雖然正式版發布已經有一年多的時間,但還缺乏最佳實踐;對協議進行了較大修改,導致主流客戶端也並非都已支持,部分支持的客戶端也沒有經過大規模生產環境的驗證;無中心化設計使整個系統高度耦合,導致很難對業務進行無痛的升級。

Proxy :現在很多主流的 Redis 集群都會使用 Proxy 方式,例如早已開源的 Codis 。這種方案有很多優點,因為支持原聲 redis 協議,所以客戶端不需要升級,對業務比較友好。並且升級相對平滑,可以起多個 Proxy 後,逐個進行升級。但是缺點是,因為會多一次跳轉,平均會有 30% 左右的性能開銷。而且因為原生客戶端是無法一次綁定多個 Proxy ,連接的 Proxy 如果掛了還是需要人工參與。除非類似 Smart Client 一樣封裝原有客戶端,支持重連到其他 Proxy ,但這也就帶來了客戶端分片方式的一些缺點。並且雖然 Proxy 可以使用多個,並且可以動態增加 proxy 增加性能,但是所有客戶端都是共用所有 proxy ,那麼一些異常的服務有可能影響到其他服務。為每個服務獨立搭建 proxy ,也會給部署帶來額外的工作。

而我們選擇了第三種方案,客戶端分片 (Smart Client) 。客戶端分片相比 Proxy 擁有更好的性能,及更低的延遲。當然也有缺點,就是升級需要重啟客戶端,而且我們需要維護多個語言的版本,但我們更愛高性能。

下面我們來介紹一下我們的Redis集群:

概貌:

如圖0所示,

我們的 Redis 集群一共由四個角色組成:

Zookeeper :保存所有 redis 集群的實例地址, redis 實例按照約定在特定路徑寫入自身地址,客戶端根據這個約定查找 redis 實例地址,進行讀寫。

Redis 實例:我們修改了 redis 源碼,當 redis 啟動或主從切換時,按照約定自動把地址寫到 zookeeper 特定路徑上。

Sentinel : redis 自帶的主從切換工具,我們通過 sentinel 實現集群高可用。

客戶端( Smart Client ):客戶端通帆拆過約定查找 redis 實例在 ZooKeeper 中攔轎森寫入的地址。並且根據集群的 group 數,進行一致性哈希計算,確定 key 唯一落入的 group ,隨後對這個 group 的主庫進行操作。客戶端會在Z ooKeeper 設置監視,當某個 group 的主庫發生變化時,Z ooKeeper 會主動通知客戶端,客戶端會更新對應 group 的最新主庫。

我們的Redis 集群是以業務為單位進行劃分的,不同業務使用不同集群(即業務和集群是一對一關系)。一個 Redis 集群會由多個 group 組成 ( 一個 group 由一個主從對 redis 實例組成 ) 。即 group 越多,可以部署在更多的機器上,可利用的內存、帶寬也會更多。在圖0中,這個業務使用的 redis 集群由 2 個 group 組成,每個 group 由一對主從實例組成。

Failover

如圖1所示,

當 redis 啟動時,會 把自己的 IP:Port 寫入到 ZooKeeper 中。其中的 主實例模式啟動時會在 /redis/ 業務名 / 組名 永久節點寫入簡畝自己的 IP:Port (如果節點不存在則創建)。由 主模式 變成 從模式 時,會創建 /redis/ 業務名 / 組名 /slaves/ip:port 臨時節 點,並寫入自己的 IP:Port (如果相同節點已經存在,則先刪除,再創建)。而從實例 模式 啟動時會創建 /redis/ 業務名 / 組名 /slaves/ip:port 臨時節點,並寫入自己的 ip:port (如果相同節點已經存在,則先刪除,再創建)。由 從模式 變成 主模式 時,先刪除 /redis/ 業務名 / 組名 /slaves/ip:port 臨時節點,並在 /redis/ 業務名 / 組名 永久節點寫入自己的 IP:Port 。

ZooKeeper 會一直保存當前有效的 主從實例 IP:Port 信息。至於主從自動切換過程,使用 redis 自帶的 sentinel 實現,現設置為超過 30s 主 server 無響應,則由 sentinel 進行主從實例的切換,切換後就會觸發以主、從實例通過以上提到的一系列動作,從而完成最終的切換。

而客戶端側通過給定業務名下的所有 groupName 進行一致性哈希計算,確定 key 落入哪個組。 客戶端啟動時,會從 ZooKeeper 獲取指定業務名下所有 group 的 主從 IP:Port ,並在 ZooKeeper 中設置監視(監視的作用是當 ZooKeeper 的節點發生變化時,會主動通知客戶端)。若客戶端從 Zookeeper 收到節點變化通知,會重新獲取最新的 主從 I:Port ,並重新設置監視( ZooKeeper 監視是一次性的)。通過此方法,客戶端可以實時獲知當前可訪問最新的 主從 IP:Port 信息。

因為我們的所有 redis 實例信息都按照約定保存在 ZooKeeper 上,所以不需要針對每個實例部署監控,我們編寫了一個可以自動通過 ZooKeeper 獲取所有 redis 實例信息,並且監控 cpu 、 qps 、內存、主從延遲、主從切換、連接數等的工具。

發展:

現在 redis 集群在某些業務內存需求超過預期很多後,無法通過動態擴容進行擴展。所以我們正在做動態擴容的支持。原先的客戶端我們是通過一致性哈希進行 key 的
路由策略,但這種方式在動態擴容時會略顯復雜,所以我們決定採用實現起來相對簡單的預分片方式。一致性哈希的好處是可以無限擴容,而預分片則不是。預分片
時我們會在初始化階段指定一個集群的所有分片數量,這個數量一旦指定就不能再做改變,這個預分片數量就是後續可以擴容到最大的 redis 實例數。假設預分片 128 個 slot ,每個實例 10G 也可以達到 TB 級別的集群,對於未來數據增長很大的集群我們可以預分片 1024 ,基本可以滿足所有大容量內存需求了。

原先我們的 redis 集群有四種角色, Smart Client, redis , sentinel , ZooKeeper 。為了支持動態擴容,我們增加了一個角色, redis_cluster_manager (以下簡稱 manager ),用於管理 redis 集群。主要工作是初始化集群(即預分片),增加實例後負責修改Z ooKeeper 狀態,待客戶端做好准備後遷移數據到新增實例上。為了盡量減少數據遷移期間對現性能帶來的影響,我們每次只會遷移一個分片的數據,待遷移完成,再進行下一個分片的遷移。

如圖2所示

相比原先的方案,多了 slots 、M anager Lock 、 clients 、M igrating Clients 節點。

Slots: 所有分片會把自身信息寫入到 slots 節點下面。 Manager 在初始化集群時,根據設置的分片數,以及集群下的 group 數,進行預分片操作,把所有分片均勻分配給已有 group 。分片的信息由一個 json 串組成,記錄有分片的狀態 (stats) ,當前擁有此分片的 group(src) ,需要遷移到的 group(dst) 。分片的狀態一共有三種: online 、 pre_migrate 、 migrating 。

Online 指這個分片處於正常狀態,這時 dst 是空值,客戶端根據 src 的 group 進行讀寫。

Pre_migrate 是指這個分片被 manager 標記為需要遷移,此時 dst 仍然為空, manager 在等所有 client 都已經准備就緒,因為 ZooKeeper 回掉所有客戶端有時間差,所以如果某些 client 沒有準備就緒的時候 manager 進行了數據遷移,那麼就會有數據丟失。

Migrating 是 manager 確認了所有客戶端都已經做好遷移准備後,在 dst 寫入此分片需要遷移的目標 group 。待遷移完成,會在 src 寫入目標 group_name , dst 設為空, stats 設為 online 。

Manager Lock: 因為我們是每次只允許遷移一個 slot ,所以不允許超過一個 manager 操作一個集群。所以 manager 在操作集群前,會在M anager Lock 下注冊臨時節點,代表這個集群已經有 manager 在操作了,這樣其他 manager 想要操作這個集群時就會自動退出。

Clients 和M igrating Clients 是為了讓 manager 知道客戶端是否已經准備就緒的節點。客戶端通過 uid 代表自己,格式是 客戶端語言 _ 主機名 _pid 。當集群沒有進行遷移,即所有分片都是 online 的時候,客戶端會在 clients 下創建 uid 的臨時節點。

當某個 slot 從 online 變成 pre_migrate 後,客戶端會刪除 clients 下的 uid 臨時節點,然後在M igrating Clients 創建 uid 臨時節點。注意,因為需要保證數據不丟失,從 pre_migrate 到 migrating 期間,這個 slot 是被鎖定的,即所有對這個 slot 的讀寫都會被阻塞。所以 mananger 會最多等待 10s ,確認所有客戶端都已經切換到准備就緒狀態,如果發現某個客戶端一直未准備就緒,那麼 mananger 會放棄此次遷移,把 slot 狀態由 pre_migrate 改為 online 。如果客戶端發現 slot 狀態由 pre_migrate 變成 online 了,那麼會刪除 migrating_clients 下的 uid 節點,在 clients 下重新創建 uid 節點。還需要注意的一點是,有可能一個客戶剛啟動,並且正在往 clients 下創建 uid 節點,但是因為網路延遲還沒創建完成,導致 manager 未確認到這個 client 是否准備就緒,所以 mananger 把 slot 改為 pre_migrate 後會等待 1s 再確認所有客戶端是否准備就緒。

如果 Manager 看到 clients 下已經沒有客戶端的話(都已經准備就緒),會把 slot 狀態改為 migrating 。 Slot 變成 migrating 後,鎖定也隨之解除, manager 會遍歷 src group 的數據,把對應 slot 的數據遷移到 dst group 里。客戶端在 migrating 期間如果有讀寫 migrating slot 的 key ,那麼客戶端會先把這個 key 從 src group 遷移到 dst group ,然後再做讀寫操作。即這期間客戶端性能會有所下降。這也是為什麼每次只遷移一個 slot 的原因。這樣即使只有 128 個分片的集群,在遷移期間受到性能影響的 key 也只有 1/128 ,是可以接受的。

Manager 發現已經把 slot 已經遷移完畢了,會在 src 寫入目標 group_name , dst 設為空, stats 設為 online 。客戶端也刪除 migrating_clients 下的 uid ,在 clients 下創建 uid 節點。

Ⅳ ant-design-vue之form源碼解讀

form組件理慶唯解關鍵問題

1.form的form屬性怎麼和form-item的v-decorator對應起來?

遍歷form-item的棚納slots的子元素,利用vnode的屬性vnode.data.directives來獲取v-decorator綁定的對象

2.v-decorator 中的屬性改變時,怎麼聯動form表單的數據進行修改?

在渲染(render)form-item的時候,先判斷父組件是否存在鏈差沒要綁定的form屬性,如果存在,給所有v-decorator的元素綁定默認的change事件(或者其他在rules設置的觸發的事件),根據數據變化觸發收集數據的函數

3.getFieldDecorator 和 v-decorator 有什麼區別?

getFieldDecorator是一個返回vnode的函數,適用於jsx

v-decorator適用於模版

Ⅳ 急急急急!!!求qtopia/qpeapplication.h源代碼

/**********************************************************************
** Copyright (C) 2000-2005 Trolltech AS. All rights reserved.
**
** This file is part of the Qtopia Environment.
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 2 of the License, or (at your
** option) any later version.
**
** A of the GNU GPL license version 2 is included in this package as
** LICENSE.GPL.
**
** This program is distributed in the hope that it will be useful, but
** WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU General Public License for more details.
**
** In addition, as a special exception Trolltech gives permission to link
** the code of this program with Qtopia applications righted, developed
** and distributed by Trolltech under the terms of the Qtopia Personal Use
** License Agreement. You must comply with the GNU General Public License
** in all respects for all of the code used other than the applications
** licensed under the Qtopia Personal Use License Agreement. If you modify
** this file, you may extend this exception to your version of the file,
** but you are not obligated to do so. If you do not wish to do so, delete
** this exception statement from your version.
**
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact [email protected] if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/
#ifndef __QPE_APPLICATION_H__
#define __QPE_APPLICATION_H__

#include <qtopia/qpeglobal.h>
#include <qapplication.h>
#include <qdialog.h>
#include <qtopia/timestring.h>

#ifdef QTOPIA_TEST
# include "qpetestslave.h"
#endif

class QCopChannel;
class QPEApplicationData;
struct QWSEvent;
struct QWSKeyEvent;
class PluginLibraryManager;

class QTOPIA_EXPORT QPEApplication : public QApplication
{
Q_OBJECT
public:
QPEApplication( int& argc, char **argv, Type=GuiClient );
~QPEApplication();

static QString qpeDir();
static QString documentDir();
void applyStyle();
static int defaultRotation();
static void setDefaultRotation(int r);
static void grabKeyboard();
static void ungrabKeyboard();

enum StylusMode {
LeftOnly,
RightOnHold
// RightOnHoldLeftDelayed, etc.
};
static void setStylusOperation( QWidget*, StylusMode );
static StylusMode stylusOperation( QWidget* );

enum InputMethodHint {
Normal,
AlwaysOff,
AlwaysOn
,
Number,
PhoneNumber,
Words,
Text,
Named,
};

enum screenSaverHint {
Disable = 0,
DisableLightOff = 1,
DisableSuspend = 2,
Enable = 100
};

static void setInputMethodHint( QWidget *, InputMethodHint, const QString& param=QString::null );
static void setInputMethodHint( QWidget *, const QString& named );
static InputMethodHint inputMethodHint( QWidget * );
static QString inputMethodHintParam( QWidget * );

void showMainWidget( QWidget*, bool nomax=FALSE );
void showMainDocumentWidget( QWidget*, bool nomax=FALSE );
static void showDialog( QDialog*, bool nomax=FALSE ); // libqtopia
static int execDialog( QDialog*, bool nomax=FALSE ); // libqtopia
static void setMenuLike( QDialog *, bool ); // libqtopia2
static bool isMenuLike( const QDialog* ); // libqtopia2
static void setTempScreenSaverMode(screenSaverHint); // libqtopia

static void setKeepRunning();
static void setHideWindow(); // internal

bool keepRunning() const;

bool keyboardGrabbed() const;

int exec();

#ifdef QTOPIA_INTERNAL_LOADTRANSLATIONS
static void loadTranslations(const QStringList&);
#endif
#ifdef QTOPIA_INTERNAL_INITAPP
void initApp( int argc, char **argv );
#endif

signals:
void clientMoused();
void timeChanged();
void categoriesChanged();
void clockChanged( bool pm );
void volumeChanged( bool muted );
void appMessage( const QCString& msg, const QByteArray& data);
void weekChanged( bool startOnMonday );
void dateFormatChanged( DateFormat );
void flush();
void reload();
void linkChanged( const QString &linkFile );

private slots:
void systemMessage( const QCString &msg, const QByteArray &data );
void pidMessage( const QCString &msg, const QByteArray &data );
void removeSenderFromStylusDict();
void removeSenderFromIMDict();
void hideOrQuit();
void pluginLibraryManager(PluginLibraryManager**);
void lineEditTextChange(const QString &);
void multiLineEditTextChange();

void removeFromWidgetFlags();

protected:
#if defined(QTOPIA_PHONE) || defined(QTOPIA_TEST) // since not binary compatible
bool notify(QObject*,QEvent*);
#endif
bool qwsEventFilter( QWSEvent * );
void internalSetStyle( const QString &style );
void prepareForTermination(bool willrestart);
virtual void restart();
virtual void shutdown();
bool eventFilter( QObject *, QEvent * );
void timerEvent( QTimerEvent * );
bool raiseAppropriateWindow();
virtual void tryQuit();

private:
void mapToDefaultAction( QWSKeyEvent *ke, int defKey );
void processQCopFile();

#if defined(QTOPIA_INTERNAL_SENDINPUTHINT)
static void sendInputHintFor(QWidget*,QEvent::Type);
#endif

#if defined(Q_WS_QWS) && !defined(QT_NO_COP)
QCopChannel *sysChannel;
QCopChannel *pidChannel;
#endif
QPEApplicationData *d;

bool reserved_sh;

#if defined QTOPIA_TEST
public:
QString appName();
void stopCycleCount() { hasPerfMonitor = FALSE; };
void startCycleCount() { hasPerfMonitor = TRUE; };
QPETestSlave app_slave;
private:
bool hasPerfMonitor;
#endif
};

#ifdef Q_OS_WIN32
#include <stdlib.h>
QTOPIA_EXPORT int setenv(const char* name, const char* value, int overwrite);
QTOPIA_EXPORT void unsetenv(const char *name);
#endif
#endif

// Application main/plugin macro magic

#include <qmap.h>
#include <qtopia/applicationinterface.h>
#include <qmetaobject.h>

typedef QWidget* (*qpeAppCreateFunc)(QWidget*,const char *,Qt::WFlags);
typedef QMap<QString,qpeAppCreateFunc> QPEAppMap;

#define QTOPIA_ADD_APPLICATION(NAME,IMPLEMENTATION) \
static QWidget *create_ ## IMPLEMENTATION( QWidget *p, const char *n, Qt::WFlags f ) { \
return new IMPLEMENTATION(p,n,f); } \
QPEAppMap *qpeAppMap(); \
static QPEAppMap::Iterator mmy_ ## IMPLEMENTATION = qpeAppMap()->insert(NAME,create_ ## IMPLEMENTATION);

#ifdef QTOPIA_NO_MAIN
#define QTOPIA_MAIN

#else

#ifdef QTOPIA_APP_INTERFACE

# define QTOPIA_MAIN \
struct ApplicationImpl : public ApplicationInterface { \
ApplicationImpl() : ref(0) {} \
QRESULT queryInterface( const QUuid &uuid, QUnknownInterface **iface ) { \
*iface = 0; \
if ( uuid == IID_QUnknown ) *iface = this; \
else if ( uuid == IID_QtopiaApplication ) *iface = this; \
else return QS_FALSE; \
(*iface)->addRef(); \
return QS_OK; \
} \
virtual QWidget *createMainWindow( const QString &appName, QWidget *parent, const char *name, Qt::WFlags f ) { \
if ( qpeAppMap()->contains(appName) ) \
return (*qpeAppMap())[appName](parent, name, f); \
return 0; \
} \
virtual QStringList applications() const { \
QStringList list; \
for ( QPEAppMap::Iterator it=qpeAppMap()->begin(); it!=qpeAppMap()->end(); ++it ) \
list += it.key(); \
return list; \
} \
Q_REFCOUNT \
private: \
ulong ref; \
}; \
QPEAppMap *qpeAppMap() { \
static QPEAppMap *am = 0; \
if ( !am ) am = new QPEAppMap(); \
return am; \
} \
Q_EXPORT_INTERFACE() { Q_CREATE_INSTANCE( ApplicationImpl ) }

#else

# define QTOPIA_MAIN \
QPEAppMap *qpeAppMap(); \
int main( int argc, char ** argv ) { \
QPEApplication a( argc, argv ); \
QWidget *mw = 0; \
\
QString executableName(argv[0]); \
executableName = executableName.right(executableName.length() \
- executableName.findRev('/') - 1); \
\
if ( qpeAppMap()->contains(executableName) ) \
mw = (*qpeAppMap())[executableName](0,0,0); \
else if ( qpeAppMap()->count() ) \
mw = qpeAppMap()->begin().data()(0,0,0); \
if ( mw ) { \
if ( mw->metaObject()->slotNames(true).contains("setDocument(const QString&)") ) \
a.showMainDocumentWidget( mw ); \
else \
a.showMainWidget( mw ); \
int rv = a.exec(); \
delete mw; \
return rv; \
} else { \
return -1; \
} \
} \
QPEAppMap *qpeAppMap() { \
static QPEAppMap *am = 0; \
if ( !am ) am = new QPEAppMap(); \
return am; \
} \

#endif
#endif

Ⅵ 玩轉Redis的高可用(主從、哨兵、集群)

所謂的高可用,也叫 HA(High Availability),是分布式系統架構設計中必須考慮的因素之一,它是保證系統SLA的重要指標。Redis 高可用的主要有三種模式: 主從模式 哨兵模式和集群模式

Redis 提供了 Redis 提供了復制(replication)功能,當一台 redis 資料庫中的數據發生了變化,這個變化會被自動地同步到其答鍵念他的 redis 機器上去。

Redis 多機器部署時,這些機器節點會被分成兩類,一類是主節點(master 節點),一類是從節點(slave 節點)。一般 主節點可以進行讀、寫操作 ,而 從節點只能進行讀操作 。一個主節點可以有多個從節點,但是一個從節點只會有一個主節點,也就是所謂的 一主多從結構

· 支持主從復制,主機會自動將數據同步到從機,可以進行讀寫分離;

· Master 是以非阻塞的方式為主 Slaves 提供服務。所以在 Master-Slave 同步期間,客戶端仍然可以提交查詢或修改請求;

· Slave 同樣是以非阻塞的方式完成數據同步。在同步期間,如果有客戶端提交查詢請求,Redis 則返回同步之前的數據。

· Redis 不具備自動容錯和恢復功能,主機從機的宕機都會導致前端部分讀寫請求失敗,需要等待機器重啟或者手動切換前端的 IP 才能恢復;

· 主機宕機,宕機前有部分數據未能及時同步到從機,切換 IP 後面還會引入數據不一致的問題,降低了系統的可用性;

· Redis 較難支持在線擴容,在集群容量達到上限時在線擴容會變得很復雜;

· Redis 的主節點和亮塵從節點中的數據是一樣的,降低的內存的可用性


實際生產中,我們優先考慮哨兵模式。這種模式下,master 宕機,哨兵會自動選舉 master 並將其他的 slave 指向新的 master。

在主從模式下,redis 同時提供了哨兵命令 redis-sentinel ,哨兵是一個獨立的進程,作為進程,它會獨立運行。其原理是哨兵進程向所有的 redis 機器人發送命令,等待 Redis 伺服器響應,從而監控運行的多個 Redis 實例。一般為了便於決策選舉,使用 奇數個哨兵 。多個哨兵構成一個哨兵集群,哨兵直接也會相互通信,檢查哨兵是否正常運行,同時發現 master 戰機哨兵之間會進行決策選舉新的 master


哨兵模式的作用:

· 通過發送命令,讓 Redis 伺服器返回監控其運行狀態,包括主伺服器和從伺服器;

· 然而一個哨兵進程對 Redis 伺服器進行監控,也可能會清困出現問題,為此,我們可以使用多個哨兵進行監控。各個哨兵之間還會進行監控,這樣就形成了多種哨兵模式。

哨兵很像 kafka 集群中的 zookeeper 的功能。

· 哨兵模式是基於主從模式的,所有主從的優點,哨兵模式都具有。

· 主從可以自動切換,系統更健壯,可用性更高。

· 具有主從模式的缺點,每台機器上的數據是一樣的,內存的可用性較低。

· Redis 較難支持在線擴容,在集群容量達到上限時在線擴容會變得很復雜。


Redis 集群模式本身沒有使用一致性 hash 演算法,而是使用 slots 插槽

Redis 哨兵模式基本已經可以實現高可用,讀寫分離 ,但是在這種模式下每台 Redis 伺服器都存儲相同的數據,很浪費內存,所以在 redis3.0 上加入了 Cluster 集群模式,實現了 Redis 的分布式存儲,對數據進行分片,也就是說每台 Redis 節點上存儲不同的內容;每個節點都會通過集群匯流排(cluster bus),與其他的節點進行通信。 通訊時使用特殊的埠號,即對外服務埠號加 10000。例如如果某個 node 的埠號是 6379,那麼它與其它 nodes 通信的埠號是 16379。nodes 之間的通信採用特殊的二進制協議。


對客戶端來說,整個 cluster 被看做是一個整體,客戶端可以連接任意一個 node 進行操作,就像操作單一 Redis 實例一樣, 當客戶端操作的時候 key 沒有分配到該 node 上時,Redis 會返回轉向指令,指向正確的 node,這有點兒像瀏覽器頁面的 302 redirect 跳轉。

根據官方推薦,集群部署至少要 3 台以上的 master 節點,最好使用 3 主 3 從六個節點的模式。

在 Redis 的每一個節點上,都有這么兩個東西, 一個是插槽(slot),它的的取值范圍是:0-16383, 可以從上面 redis-trib.rb 執行的結果看到這 16383 個 slot 在三個 master 上的分布。還有一個就是 cluster,可以理解為是一個集群管理的插件,類似的哨兵。

當我們的存取的 Key 到達的時候,Redis 會根據 crc16 的演算法對計算後得出一個結果,然後把結果和 16384 求余數,這樣每個 key 都會對應一個編號在 0-16383 之間的哈希槽,通過這個值,去找到對應的插槽所對應的節點,然後直接自動跳轉到這個對應的節點上進行存取操作。

為了保證高可用, redis-cluster 集群引入了主從模式 ,一個主節點對應一個或者多個從節點。當其它主節點 ping 主節點 master 1 時,如果半數以上的主節點與 master 1 通信超時,那麼認為 master 1 宕機了,就會啟用 master 1 的從節點 slave 1,將 slave 1 變成主節點繼續提供服務。

如果 master 1 和它的從節點 slave 1 都宕機了,整個集群就會進入 fail 狀態,因為集群的 slot 映射不完整。 如果集群超過半數以上的 master 掛掉,無論是否有 slave,集群都會進入 fail 狀態。

redis-cluster 採用去中心化的思想 ,沒有中心節點的說法,客戶端與 Redis 節點直連,不需要中間代理層,客戶端不需要連接集群所有節點,連接集群中任何一個可用節點即可。

對 redis 集群的擴容就是向集群中添加機器,縮容就是從集群中刪除機器,並重新將 16383 個 slots 分配到集群中的節點上(數據遷移)。

擴縮容也是使用集群管理工具 redis-tri.rb。

擴容時,先使用 redis-tri.rb add-node 將新的機器加到集群中,這是新機器雖然已經在集群中了,但是沒有分配 slots,依然是不起做用的。在使用 redis-tri.rb reshard 進行分片重哈希(數據遷移),將舊節點上的 slots 分配到新節點上後,新節點才能起作用。

縮容時,先要使用 redis-tri.rb reshard 移除的機器上的 slots,然後使用 redis-tri.rb add-del 移除機器。

採用去中心化思想,數據按照 slot 存儲分布在多個節點,節點間數據共享,可動態調整數據分布;

可擴展性:可線性擴展到 1000 多個節點,節點可動態添加或刪除;

高可用性:部分節點不可用時,集群仍可用。通過增加 Slave 做 standby 數據副本,能夠實現故障自動 failover,節點之間通過 gossip 協議交換狀態信息,用投票機制完成 Slave 到 Master 的角色提升;

降低運維成本,提高系統的擴展性和可用性。

1.Redis Cluster 是無中心節點的集群架構,依靠 Goss 協議(謠言傳播)協同自動化修復集群的狀態。但 GosSIp 有消息延時和消息冗餘的問題,在集群節點數量過多的時候,節點之間需要不斷進行 PING/PANG 通訊,不必須要的流量佔用了大量的網路資源。雖然 Reds4.0 對此進行了優化,但這個問題仍然存在。

2.數據遷移問題

Redis Cluster 可以進行節點的動態擴容縮容,這一過程,在目前實現中,還處於半自動狀態,需要人工介入。在擴縮容的時候,需要進行數據遷移。

而 Redis 為了保證遷移的一致性,遷移所有操作都是同步操作 ,執行遷移時,兩端的 Redis 均會進入時長不等的阻塞狀態,對於小 Key,該時間可以忽略不計,但如果一旦 Key 的內存使用過大,嚴重的時候會接觸發集群內的故障轉移,造成不必要的切換。

主從模式:master 節點掛掉後,需要手動指定新的 master,可用性不高,基本不用。

哨兵模式:master 節點掛掉後,哨兵進程會主動選舉新的 master,可用性高,但是每個節點存儲的數據是一樣的,浪費內存空間。數據量不是很多,集群規模不是很大,需要自動容錯容災的時候使用。

集群模式:數據量比較大,QPS 要求較高的時候使用。 Redis Cluster 是 Redis 3.0 以後才正式推出,時間較晚,目前能證明在大規模生產環境下成功的案例還不是很多,需要時間檢驗。

Ⅶ Mono源碼閱讀-GC造成內存泄露問題

本文主要記錄Mono源碼中會因為GC的問題,造成Unity游戲不可避免的都會存在一定得內存泄露問題的底層原因,涉及到Mono源碼中GC機制的邏輯。

要出現這種內存泄露,必須先准備一塊任意的內存塊:(無任何外部引用,理論上應該會在用完後被GC,但在該BUG下會錯誤的泄露,不被GC掉)

NOTE:大小任意,越大越容易被泄露。

一塊struct結構的數組:(struct內必須有一個類似指針的值類型, 如int,和另一個引用類型,如string)

NOTE:大數陪小任意,數組內的元素越多越容易觸發泄露。例如 HashSet<String> 內部使用了該數據格式。

通過在GC中打點,和使用GDB調用GC過程,以便觀察所有對象的分配和GC的過程發現:buffer對象錯誤的被slots對象引用,導致buffer對象無法被正常GC,造成內存泄露。

首先對於mono/il2cpp的Boehm GC庫而言, mono/il2cpp的對象在分配內存的時候,會有幾種類型:

而在本例中:slots的分配是用NORMAL類型,buffer對象的分配是用PTRFREE類型。

因此在做GC的禪配時候,對於slots對象,GC會掃描該對象的內存區間,查找其內部的指針地址,即從0xde45f000到0xde468c50地址按照指針對齊的方式查找指針地址:
例賀畢指如:0xde45f000 0xde464d44 0xde464f40 0xde46513c ....
其中出現了 0xde464f40 這個地址的值剛好為:0xbe82f000(即Slot結構體內hashCode的值),而GC會錯誤的將該int型數值當做指針,而該指針剛好又指向了一塊GC託管的內存塊,即buffer對象,因此GC認為該buffer對象被slots對象內部引用了,buffer對象也被GC標記,不會被釋放。

該問題的關鍵在於,GC將slot結構體內的hashcode這個int值錯誤的當做的指針,而該int值剛好又指向了另一個託管的對象,因此GC錯認為了兩個對象存在引用關系,而造成內存泄露。

最小化Demo:

將struct Slot修改為class Slot,則可修復內存泄露問題,因為class對象的內存分配時TYPED類型。

因為Mono的GC的設計問題,Unity游戲中幾乎不可避免的都會隨著時間出現內存泄露問題,因為例如HashSet這種數據結構內部都會出現該問題。但我們可以做的事情,依然是內存使用的兩大真理(特別是虛擬機類型的語言):

這樣做,不能完全避免Mono的底層GC問題,但是它可以讓這種內存泄露的變得更加平緩。

Ⅷ 【Redis】Redis Cluster-集群數據遷移

Redis通過對KEY計算hash,將KEY映射到slot,集群中每個節點負責一部分slot的方式管理數據,slot最大個數為16384。
在集群節點對應的結構體變數clusterNode中可以看到slots數組,數組的大小為CLUSTER_SLOTS除以8,CLUSTER_SLOTS的值是16384:

clusterState

clusterNode裡面保存了節點相關的信息,集群數據遷移信息並未保存在clusterNode中,而是使用了clusterState結構體來保存:

clusterState與clusterNode的關系

在手動進行數據遷移時,需要執行以下步驟:

在進行數據遷移之前,首先在需要遷入的目標節點使用 SETSLOT 命令標記要將SLOT從哪個節點遷入到當前節點:

然後在源節點也就是slot所在節點使用 MIGRATING 命令標記將數據遷出到哪個節點:

比如slot1當前在node1中,需要將slot1遷出到node2,那麼首先在nodd2上執行 IMPORTING 命令,標記slot准備從node1遷到當前節點node2中:

然後在node1中執行 MIGRATING 命令標記slot1需要遷移到node2:

clusterCommand
SETSLOT 命令的處理在clusterCommand函數(cluster.c文件中)中:

在標記完遷入、遷出節點後,就可以使用 CLUSTER GETKEYSINSLOT 命令獲取待遷出的KEY:

<slot>:哈希槽的值

<count>:遷出KEY的數量

getkeysinslot 命令的處理也在clusterCommand函數中,處理邏輯如下:

完成上兩步之後,接下來需要在源節點中執行 MIGRATE 命令進行數據遷移, MIGRATE 既支持單個KEY的遷移,也支持多個KEY的遷移,語法如下:

migrateCommand

MIGRATE 命令對應的處理函數在migrateCommand中(cluster.c文件中),處理邏輯如下:

createDumpPayload

createDumpPayload函數在cluster.c文件中:

restoreCommand

目標節點收到遷移的數據的處理邏輯在restoreCommand中(cluster.c文件中):

數據遷移的最後一步, 需要使用 CLUSTER SETSLOT 命令,在源節點和目標節點執行以下命令,標記slot最終所屬的節點,並清除第一步中標記的遷移信息

<slot>:哈希槽

<node>:哈希槽最終所在節點id

clusterCommand

CLUSTER SETSLOT <slot> NODE <node> 命令的處理依舊在 clusterCommand 函數中,處理邏輯如下:

總結

參考

極客時間 - Redis源碼剖析與實戰(蔣德鈞)

Redis版本:redis-6.2.5

Ⅸ redis架構模式(4) Codis

codis是在redis官方集群方案redis-cluster發布之前便已被業界廣泛使用的redis集群解決方案。

codis server :這是進行了二次開發的 Redis 實例,其中增加了額外的數據結構,支持

數據遷移操作,主要負責處理具體的數據讀寫請求。

codis proxy :接收客戶端請求,並把請求轉發給 codis server。

Zookeeper 集群 :保存集群元數據,例如數據位置信息和 codis proxy 信息。

codis dashboard 和 codis fe :共同組成了集群管理工具。其中,codis dashboard 負

責執行集群管理工作,包括增刪 codis server、codis proxy 和進行數據遷移。

而 codis fe 負責提供dashboard的Web 操作界面,便於我們直接在 Web 界面上進行集群管理

codis proxy本身支持 Redis 的 RESP 交互協議,所以只需和proxy建立連接,就相當於和

整個codis集群建立連接,codis proxy 接收到請求,就會查詢請求數據和 codis server 的

映射關系,並把請求轉發給相應的 codis server 進行處理。當 codis server 處理完請求後,

會把結果返回給codis proxy,proxy 再把數據返回給客戶端。

codis集群喊模兆共有1024個slot,編號從0-1023,可以自定義或者自動均勻分配到各個server上,

使用 CRC32 演算法計算數據 key 的哈希值然後對1024取模,得到slot編號,然後通過slot

和server的映射關系請求到具體實例。

Slot 和 codis server 的映射關系稱為數據路由表,我們在 codis dashboard 上分配好路由表後,

dashboard 會把路由表發送給 codis proxy,同時,dashboard 也會把路由表保存在

 Zookeeper 中。codis-proxy 會把路由表緩存在本地,當它接收到客戶端請求後,直接查詢

本地的路由表進而完成請求的轉發。

跟redis cluster的區別是,codis的把映射關系存在zookeeper和proxy中,redis cluster

是通過各個實例相互通信進而存在各個實例本地,當實例較多時,可能會受到網路狀況

影響而且整個集群的網路資源消耗相較之下也比較大。

codis按照slot粒度進行數據遷移,比如從serverA遷移至serverB,會從A要遷移的slot中

隨機選擇一個數據,發送給B,A得到確認消息後,刪除本地數據。重復這個過程直到

遷移完畢。

遷移方式有兩種,一種是同步遷移,一種是非同步遷移。

同鄭租步遷移時,源server是阻塞的,由於遷移過程中需要碼型數據的序列化、網路傳輸、刪除

等等操作,如果是bigkey則會阻塞較長時間。

所以通常採用非同步遷移。非同步遷移模式下,源server發送完遷移數據之後,就可以繼續接受

處理請求,等到目標server返回ack之後,再執行刪除操作,這個過程中,為了防止數據

不一致的情況,被遷移的數據是只讀模式。

對於bigkey的數據,非同步遷移採用拆分指令的方式,對bigkey中的每個元素用一條指令

進行遷移,而不是等待整個數據序列化完畢再遷移,進而避免了需要序列化大量數據而

阻塞的風險,因為整個bigkey的數據是被拆分的,如果遷移期間server宕機,便會破壞

遷移的原子性,所以codis對bigkey的數據設置了一個過期時間,如果原子性遭到破壞,

目標server的數據就會過期後刪除以保證遷移的原子性。可以通過非同步遷移命令 

SLOTSMGRTTAGSLOT-ASYNC 的參數numkeys 設置每次遷移的 key 數量,以提升

bigkey非同步遷移的速度。

最後分享一下我的一些架構設計心得,通過redis cluster和codis的slot設計,把原本

數百上千萬的key和實例的映射抽象成slot和實例的映射,大大減少了映射量,進而把

slot和key的映射分擔到各個實例中,這種粒度的抽象,值得借鑒,另外codis與redis cluser

不同的是,codis通過zk作為第三方儲存系統,保存了集群實例、狀態和slot分配等信息,

這樣避免了實例間的大量心跳來同步集群狀態進而減少實例間的網路通信量,對於實現

大規模的集群是有益的設計,網路帶寬可以集中在客戶端請求上面,需要注意的是需要保證

zk的通信帶寬,畢竟集群的信息同步都交給了zk

閱讀全文

與slots遷移源碼相關的資料

熱點內容
android智能家居藍牙 瀏覽:646
pt螺紋編程 瀏覽:451
手機電音app哪個好 瀏覽:749
checksum命令 瀏覽:637
java創建xml文件 瀏覽:170
算命源碼國際版 瀏覽:283
三菱模塊化編程 瀏覽:718
控制項讀取文件源碼 瀏覽:445
文件夾側面目錄標簽怎麼製作 瀏覽:232
做程序員學什麼 瀏覽:320
pdfeditor教程 瀏覽:880
fortran把文件放入文件夾 瀏覽:709
程序員1年經驗不敢投簡歷 瀏覽:481
如何看電腦的源碼 瀏覽:897
找工作app軟體哪個好 瀏覽:96
信息管理網站源碼 瀏覽:439
小說app哪個好免費 瀏覽:224
域名在線加密 瀏覽:146
軟體編程西安交大 瀏覽:453
是不是串貨的奶粉查不到溯源碼的 瀏覽:825