建立一套MySQL及Redis搭建統一的KV儲存服務
一、MySQL+Redis 常用部署方式
1.1 拓撲
1.2 特點
業務層通過雙寫同時寫MySQL及Redis。讀通常在Redis,若讀取不到,則從MySQL讀取,然後將資料同步到Redis,Redis通常設定expire或者預設LRU進行資料淘汰。
這種使用方式會有如下問題:
1)MySQL及Redis存在資料不一致風險,尤其是長時間執行的系統
2)業務層需要處理MySQL sql schema與Redis kv資料結構上的邏輯差異
3)無統一運維
4)無法方便擴容/縮容
二、KV 化的儲存使用理念
2.1 MySQL Is great NoSQL
參考文件:
http://www.aviransplace.com/2015/08/12/Mysql-is-a-great-nosql/
為什麼要用MySQL:
“在可擴充套件系統構建時,一個很重要的考量是使用的技術是否成熟,選擇成熟的技術意味著出錯時能夠迅速恢復。當然,開發者也可以在專案中使用最新最牛的NoSQL資料庫,而這個資料庫在理論上也可以良好地執行,然而在生產環境中出現了問題恢復需要多久?技術上已有的知識和經驗積累對於問題緩解至關重要,當然這個積累也包括了Google可以搜尋到的內容。
相比之下,關係型資料庫已經存在了超過四十年,業界對於關係型資料庫的維護也積累了大量的經驗。基於這些考慮,在新專案做技術選型時通常會選擇Mysql,而不是NoSQL資料庫,除非NoSQL真的有非常非常明顯的優勢。”
2.2 KV理念
對於億級規模的資料儲存,尤其是涉及到水平拆分跨機分庫分表的情況下,線上對資料庫的訪問只能做的越簡單越好,group by/order by/分頁/通用join/事務等等的支援 在這個量級下的MySQL系統都是不合適的。
基本上目前所有的類proxy的MySQL方案真正上規模線上應用只能使用按拆分鍵進行讀寫操作,實際上也是一個用拆分鍵做的一個kv系統。
若想使用複雜的sql處理,最合理的部署方案是將Mysqlbinlog流水同步服務抽象出來,通過實時同步到OLAP類的系統進行處理。
所以面向海量儲存服務,MySQL從一開始就設計為一個KV系統是可行的。value使用mediumblob儲存xml/json/protobuf/thrift格式化資料序列化之後的資料。
2.3 MySQL KV化的使用方式
1、用MySQL原來的主鍵或者索引鍵當做key
2、其他所有的非主鍵非索引鍵,全部包裝到value裡面,value使用mediumblob儲存xml/json/protobuf/thrift格式化資料序列化之後的資料。
3、資料讀寫操作,均基於key一整行資料做讀寫,由業務層對裡面value的結構做解析及對內部結構做增刪改差,而不用變更 MySQL 本身的schema.
2.4 不適用場景
1、資料量和訪問量不大並且業務邏輯依賴 MySQL 資料庫進行處理的業務場景
2、涉及到多表join等的處理
對於此限制,也可以通過將關聯表加工成基於關聯條件的一張寬表進行KV化。
3、涉及到事務等的處理。
三、將MySQL+Redis設計為統一的KV儲存服務
3.1 目標
1)業務層通過統一方式訪問MySQL及Redis,不再使用MySQL客戶端及Redis客戶端訪問
2)MySQL叢集化/Redis叢集化部署
3)將業務雙寫改為MySQL到Redis底層binlog資料同步方式完成同步
4)異構資料儲存支援最終一致性資料讀寫服務
5)支援儲存層面擴容縮容、failover且業務無感知
6)單機群日百億次QPS/TPS支援(大類業務適度拆分到不同叢集中)
3.2 最終實現
基於MySQL+Redis的統一儲存服務(UniStore) =
MySQL跨機分庫分表叢集
+ Redis叢集
+ MySQL->Redis實時資料同步服務
+ 統一的對外資料訪問介面
+ 內在的完整運維支援系統(支援線上擴容/縮容、failover等)
3.3 架構圖
3.4 架構說明-將儲存設計為一種服務
1、將MySQL+Redis做成統一KV儲存服務
2、通過acc proxy提供統一的資料訪問介面,通過統一協議支援跨語言資料訪問
訪問協議(自定義協議,protobuf協議,thrift協議等)
3、MySQL cluster支援跨機的分庫分表,schemaless設計,所有業務表KV化設計
4、Redis cluster支援跨機的例項拆分
5、Sync資料同步服務提供統一的Mysql到Redis 跨IDC/不跨IDC資料同步服務,小於100ms延時
6、整個系統不涉及到分散式事務處理
3.5 三種部署方式
1、純MySQL叢集部署
此種部署方式等同於其他MySQL proxy跨機分庫分表方案,讀寫均在MySQL
2、純Redis叢集部署
此種部署方式等同於其他Redis proxy跨機分庫分表方案,讀寫均在Redis
3、MySQL+Redis異構部署
寫在MySQL,讀可以從MySQL讀或者Redis讀,取決於業務對最新資料的讀取要求。
3.6 介面說明
1、int get(int appid, string key,string& value)
Redis讀操作專用
2、int get_with_version(int appid,string key, string& value, int64& version)
MySQL 讀操作專用,自帶版本號,防止寫覆蓋
3、int set(int appid, string key,string value, int64 version)
通過appid區分 MySQL 還是Redis,均支援寫操作
4、int delete(int appid, string key)
通過appid區分 MySQL 還是Redis, 不支援批量刪除
5、int multiget(int appid,vector<string> keys, map<string, string>& key_value_pairs)
支援批量讀操作,內部的資料路由及資料合併不用關心
6、intmultiset(int appid, map<string, string>& key_value_pairs)
不建議支援,涉及到跨機事務問題,無法保證ACID
7、int Redis_op(string cmd, ……)
Redis其他原生介面封裝(incr/expire/list/setnx等)
四、Cluster Manager服務
4.1 Cluster Manager 是一個service
cluster manager主要由如下幾種功能
1)MySQL/Redis分片路由資訊的管理
1、 MySQL 分庫分表路由資訊
2、Redis Slot路由資訊
3、路由資訊變更管理
2)Redis例項的探活及Redis擴容及縮容資料的遷移
比如連續3次,每次間隔30sRedis ping失敗,認為例項掛掉,發出報警或者自動切換
3)Cluster manager不建議參與Mysql group主備層面的管理
MySQL 主備層面的叢集管理方案:
1、MHA+VIP (網際網路公司最常用)
2、微信phxsql系統:https://github.com/tencent-wechat/phxsql 金融級可靠性
五、 MySQL 叢集方案
5.1 架構圖
5.2 設計原則
1)統一的schemaless表結構
2)跨機的資料分佈
支援將單邏輯表水平拆分到多個Mysql伺服器中
3)其他說明
1、資料儲存可靠性高,所有業務資料通過序列化儲存到value列
2、每行資料自帶版本號,業務通過cas方式防止業務層多例項同時寫造成寫覆蓋
全域性唯一版本號實現:本機微秒時間戳+server_id+proccess_id
3、固定百庫百表/百庫十表的資料拆分方式,多機跨Mysql例項部署
5.3 路由策略
1) 一致性hash
2) 路由計算演算法
crc32/md5/基於字串的各類hash演算法
3) 路由資訊格式
CREATETABLE `Mysql_shard_info` (
`appid` int(32) NOT NULL,
`begin` int(32) NOT NULL,
`end` int(32) NOT NULL,
`ip` varchar(20) NOT NULL DEFAULT '',
`port` int(11) NOT NULL DEFAULT '0',
`user` varchar(50) NOT NULL DEFAULT '',
`pwd` varchar(50) NOT NULL DEFAULT '',
PRIMARY KEY (`appid`,`begin`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
5.4 資料遷移/自動擴充套件
資料遷移:
STEP1:利用 MySQL 主備複製機制進行資料複製
STEP2:資料差異小於某一臨界值,停止老分片寫操作(read-only)
STEP3:等待新分片資料更新完畢
STEP4:更路由規則路由規則,Cluster Manager向所有access proxy更新路由資訊
STEP5:刪除老分片
自動擴充套件:
過程類似於資料遷移
六、Redis 叢集方案
6.1 部署方式
1、異構讀寫分離-MySQL寫,Redis讀
1) 資料寫操作在 MySQL ,讀操作在Redis
2) 資料通過Sync系統對binlog進行解析從Mysql同步到Redis
3) 資料有同步延遲(小於100ms),實現最終一致性
適用場景:要求資料高可靠,且讀量比較大,允許讀資料短時間不一致,若期望一直讀到最新資料,請使用get_with_version()介面從 MySQL 讀取
2、獨立的Redis叢集服務
1)讀寫均在Redis,提供獨立的KV儲存服務
2) 使用者不用關注擴容/縮容/故障恢復等問題
3) 叢集內多業務混存,提高記憶體的使用率
適用場景:獨立的Redis叢集服務,類似twenproxy/codis
6.2 設計要點
1、一致性hash
支援資料跨Redis例項拆分,固定Slot數進行拆分
2、單機多例項部署
1)每個物理機支援多Redis例項
2)每個Redis例項只服務單個業務
3)Redis例項記憶體大小取決於業務需求,同時考慮業務訪問量和資料量
以RedisIP+port標示唯一例項,對於128G記憶體機器,
可配置3 Redis例項*每例項30G
或10 Redis例項*每例項10G
或20 Redis例項*每例項5G
拆分原則:單例項最大記憶體使用 < 本機剩餘記憶體
3、以Slot為單位的平滑擴容/縮容
4、以Redis例項為單位的failover處理
6.3 平滑擴容/縮容
主要步驟如下:
STEP1:確認擴容/縮容
Cluster manager通過對系統負載和資料量進行告警,進而確認進行擴容或者縮容
STEP2:修改路由表
1)修改路由表,將對應shard的狀態修改為migrate狀態,並將新路由推送到所有接入層
2)acc proxy會將寫操作轉到新的Redis例項中,讀操作預設先讀新Redis例項,key不存在會繼續從老的Redis例項中讀取
STEP3:資料遷移
1)Cluster manager通過自動資料遷移工具開始資料遷移,計劃依賴Redis的scan命令將相關的key掃出來,通過MIGRATE進行資料遷移
2)多次掃描執行該過程,確認Slot中所有資料遷移完成
STEP4:修改路由表,遷移完成
Cluster manager將讀寫均切到新Redis例項,不再從老Redis中進行操作
七、 Sync 資料同步服務
7.1 架構
7.2 應用場景
該服務完全可以抽象成獨立的資料同步分發服務,對於因為KV化而丟失的sql處理完全可以通過該服務同步到偏OLAP類的系統中進行處理。除了同步到Redis還可以同步到ElasticSearch或者hbase或者寫hdfs檔案基於hadoop生態去實現複雜計算和分析。
7.3 設計要點
1、叢集對叢集的實時資料同步
MySQL 統一要求binlog日誌為row格式
2、不涉及DDL處理
由於 MySQL schemaless的設計,不用考慮DDL處理,簡化同步服務(跨/不跨IDC)
3、基於時間戳的同步延遲監控
MySQL binlog row格式日誌自帶時間戳,基於此時間戳進行同步延遲監控
4、基於binlog檔名+offset的同步位置管理
定時定量持久化儲存當前同步的binlog檔名及offset,用於各種場景下的同步恢復
5、基於行的並行同步
多執行緒同步模式,主執行緒通過對tableid或者key做hash,將binlogevent時間分發到對應worker執行緒的佇列中,worker執行緒依次從佇列中獲取binlog event執行
7.4 實現原理
原理相對比較簡單:
1)Sync同步工具模擬Mysql slave的互動協議,偽裝自己為 MySQL slave,向Mysqlmaster傳送dump協議
2)Mysqlmaster收到dump請求,開始推送binary log給slave(也就是同步工具)
3)Sync同步工具解析binary log物件(原始為byte流),並轉換成Redis或其他儲存(hdfs/hbase/ES等資料庫)相應資料操作介面或者作為訊息儲存到MQ中(rocketmq或者kafka)
7.5 ROW 格式events
MySQL 5.5 Binlog的事件型別有多種,這裡只介紹與ROW模式相關的事件
1) QUERY_EVENT:與STATEMENT模式處理相同,儲存的是SQL,主要是一些與資料無關的操作,eg: begin、drop table
2) TABLE_MAP_EVENT:記錄了下一條事件所對應的表資訊,在其中儲存了資料庫名和表名
3) WRITE_ROWS_EVENT:操作型別為insert
4) UPDATE_ROWS_EVENT:操作型別為update
5) DELETE_ROWS_EVENT:操作型別為delete
6) XID_EVENT, 用於標識事務提交(commit)
典型的insert語句有如下4個events組成: