本文介紹基于MySQL及Redis搭建統(tǒng)一的kv存儲服務(wù):常用部署方式及其特點(diǎn),Cluster manager,MySQL和Redis集群方案,以及Sync數(shù)據(jù)同步服務(wù)。
一、MySQL+Redis?常用部署方式
1.1? 拓?fù)?/b>

1.2? 特點(diǎn)
業(yè)務(wù)層通過雙寫同時寫MySQL及Redis。讀通常在Redis,若讀取不到,則從MySQL讀取,然后將數(shù)據(jù)同步到Redis,Redis通常設(shè)置expire或者默認(rèn)LRU進(jìn)行數(shù)據(jù)淘汰。
這種使用方式會有如下問題:
1)MySQL及Redis存在數(shù)據(jù)不一致風(fēng)險,尤其是長時間運(yùn)行的系統(tǒng)
2)業(yè)務(wù)層需要處理MySQL sql schema與Redis kv數(shù)據(jù)結(jié)構(gòu)上的邏輯差異
3)無統(tǒng)一運(yùn)維
4)無法方便擴(kuò)容/縮容
二、KV化的存儲使用理念
2.1? MySQL Is great NoSQL
參考文檔:
http://www.aviransplace.com/2015/08/12/Mysql-is-a-great-nosql/
為什么要用MySQL:
“在可擴(kuò)展系統(tǒng)構(gòu)建時,一個很重要的考量是使用的技術(shù)是否成熟,選擇成熟的技術(shù)意味著出錯時能夠迅速恢復(fù)。當(dāng)然,開發(fā)者也可以在項目中使用最新最牛的NoSQL數(shù)據(jù)庫,而這個數(shù)據(jù)庫在理論上也可以良好地運(yùn)行,然而在生產(chǎn)環(huán)境中出現(xiàn)了問題恢復(fù)需要多久?技術(shù)上已有的知識和經(jīng)驗積累對于問題緩解至關(guān)重要,當(dāng)然這個積累也包括了Google可以搜索到的內(nèi)容。
相比之下,關(guān)系型數(shù)據(jù)庫已經(jīng)存在了超過四十年,業(yè)界對于關(guān)系型數(shù)據(jù)庫的維護(hù)也積累了大量的經(jīng)驗。基于這些考慮,在新項目做技術(shù)選型時通常會選擇Mysql,而不是NoSQL數(shù)據(jù)庫,除非NoSQL真的有非常非常明顯的優(yōu)勢?!?/p>
2.2? KV理念
對于億級規(guī)模的數(shù)據(jù)存儲,尤其是涉及到水平拆分跨機(jī)分庫分表的情況下,線上對數(shù)據(jù)庫的訪問只能做的越簡單越好,group by/order by/分頁/通用join/事務(wù)等等的支持 在這個量級下的MySQL系統(tǒng)都是不合適的。
基本上目前所有的類proxy的MySQL方案真正上規(guī)模線上應(yīng)用只能使用按拆分鍵進(jìn)行讀寫操作,實(shí)際上也是一個用拆分鍵做的一個kv系統(tǒng)。
若想使用復(fù)雜的sql處理,最合理的部署方案是將Mysqlbinlog流水同步服務(wù)抽象出來,通過實(shí)時同步到OLAP類的系統(tǒng)進(jìn)行處理。
所以面向海量存儲服務(wù),MySQL從一開始就設(shè)計為一個KV系統(tǒng)是可行的。value使用mediumblob存儲xml/json/protobuf/thrift格式化數(shù)據(jù)序列化之后的數(shù)據(jù)。
2.3? MySQL KV化的使用方式
1、用MySQL原來的主鍵或者索引鍵當(dāng)做key
2、其他所有的非主鍵非索引鍵,全部包裝到value里面,value使用mediumblob存儲xml/json/protobuf/thrift格式化數(shù)據(jù)序列化之后的數(shù)據(jù)。
3、數(shù)據(jù)讀寫操作,均基于key一整行數(shù)據(jù)做讀寫,由業(yè)務(wù)層對里面value的結(jié)構(gòu)做解析及對內(nèi)部結(jié)構(gòu)做增刪改差,而不用變更 MySQL 本身的schema.
2.4? 不適用場景
1、數(shù)據(jù)量和訪問量不大并且業(yè)務(wù)邏輯依賴 MySQL 數(shù)據(jù)庫進(jìn)行處理的業(yè)務(wù)場景
2、涉及到多表join等的處理
對于此限制,也可以通過將關(guān)聯(lián)表加工成基于關(guān)聯(lián)條件的一張寬表進(jìn)行KV化。
3、涉及到事務(wù)等的處理。
三、將MySQL+Redis設(shè)計為統(tǒng)一的KV存儲服務(wù)
3.1??目標(biāo)
1)業(yè)務(wù)層通過統(tǒng)一方式訪問MySQL及Redis,不再使用MySQL客戶端及Redis客戶端訪問
2)MySQL集群化/Redis集群化部署
3)將業(yè)務(wù)雙寫改為MySQL到Redis底層binlog數(shù)據(jù)同步方式完成同步
4)異構(gòu)數(shù)據(jù)存儲支持最終一致性數(shù)據(jù)讀寫服務(wù)
5)支持存儲層面擴(kuò)容縮容、failover且業(yè)務(wù)無感知
6)單機(jī)群日百億次QPS/TPS支持(大類業(yè)務(wù)適度拆分到不同集群中)
3.2??最終實(shí)現(xiàn)
基于MySQL+Redis的統(tǒng)一存儲服務(wù)(UniStore) =
MySQL跨機(jī)分庫分表集群
+ Redis集群
+ MySQL->Redis實(shí)時數(shù)據(jù)同步服務(wù)
+ 統(tǒng)一的對外數(shù)據(jù)訪問接口
+ 內(nèi)在的完整運(yùn)維支持系統(tǒng)(支持在線擴(kuò)容/縮容、failover等)
3.3??架構(gòu)圖

3.4??架構(gòu)說明-將存儲設(shè)計為一種服務(wù)
1、將MySQL+Redis做成統(tǒng)一KV存儲服務(wù)
2、通過acc proxy提供統(tǒng)一的數(shù)據(jù)訪問接口,通過統(tǒng)一協(xié)議支持跨語言數(shù)據(jù)訪問
訪問協(xié)議(自定義協(xié)議,protobuf協(xié)議,thrift協(xié)議等)
3、MySQL cluster支持跨機(jī)的分庫分表,schemaless設(shè)計,所有業(yè)務(wù)表KV化設(shè)計
4、Redis cluster支持跨機(jī)的實(shí)例拆分
5、Sync數(shù)據(jù)同步服務(wù)提供統(tǒng)一的Mysql到Redis 跨IDC/不跨IDC數(shù)據(jù)同步服務(wù),小于100ms延時
6、整個系統(tǒng)不涉及到分布式事務(wù)處理
3.5??三種部署方式
1、純MySQL集群部署
此種部署方式等同于其他MySQL proxy跨機(jī)分庫分表方案,讀寫均在MySQL
2、純Redis集群部署
此種部署方式等同于其他Redis proxy跨機(jī)分庫分表方案,讀寫均在Redis
3、MySQL+Redis異構(gòu)部署
寫在MySQL,讀可以從MySQL讀或者Redis讀,取決于業(yè)務(wù)對最新數(shù)據(jù)的讀取要求。
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區(qū)分 MySQL 還是Redis,均支持寫操作
4、int delete(int appid, string key)
通過appid區(qū)分 MySQL 還是Redis, 不支持批量刪除
5、int multiget(int appid,vector keys, map& key_value_pairs)
支持批量讀操作,內(nèi)部的數(shù)據(jù)路由及數(shù)據(jù)合并不用關(guān)心
6、intmultiset(int appid, map& key_value_pairs)
不建議支持,涉及到跨機(jī)事務(wù)問題,無法保證ACID
7、int Redis_op(string cmd, ……)
Redis其他原生接口封裝(incr/expire/list/setnx等)
在此我向大家推薦一個架構(gòu)學(xué)習(xí)交流群。交流學(xué)習(xí)群號:575745314 ? 里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備的知識體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源,目前受益良多
四、Cluster Manager服務(wù)
4.1??Cluster Manager是一個service
cluster manager主要由如下幾種功能
1)MySQL/Redis分片路由信息的管理
1、 MySQL 分庫分表路由信息
2、Redis Slot路由信息
3、路由信息變更管理
2)Redis實(shí)例的探活及Redis擴(kuò)容及縮容數(shù)據(jù)的遷移
比如連續(xù)3次,每次間隔30sRedis ping失敗,認(rèn)為實(shí)例掛掉,發(fā)出報警或者自動切換
3)Cluster manager不建議參與Mysql group主備層面的管理
MySQL 主備層面的集群管理方案:
1、MHA+VIP (互聯(lián)網(wǎng)公司最常用)
2、微信phxsql系統(tǒng):https://github.com/tencent-wechat/phxsql?? 金融級可靠性
五、MySQL集群方案
5.1??架構(gòu)圖

5.2??設(shè)計原則
1)統(tǒng)一的schemaless表結(jié)構(gòu)

2)跨機(jī)的數(shù)據(jù)分布
支持將單邏輯表水平拆分到多個Mysql服務(wù)器中
3)其他說明
1、數(shù)據(jù)存儲可靠性高,所有業(yè)務(wù)數(shù)據(jù)通過序列化存儲到value列
2、每行數(shù)據(jù)自帶版本號,業(yè)務(wù)通過cas方式防止業(yè)務(wù)層多實(shí)例同時寫造成寫覆蓋
全局唯一版本號實(shí)現(xiàn):本機(jī)微秒時間戳+server_id+proccess_id
3、固定百庫百表/百庫十表的數(shù)據(jù)拆分方式,多機(jī)跨Mysql實(shí)例部署
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??數(shù)據(jù)遷移/自動擴(kuò)展
數(shù)據(jù)遷移:
STEP1:利用 MySQL 主備復(fù)制機(jī)制進(jìn)行數(shù)據(jù)復(fù)制
STEP2:數(shù)據(jù)差異小于某一臨界值,停止老分片寫操作(read-only)
STEP3:等待新分片數(shù)據(jù)更新完畢
STEP4:更路由規(guī)則路由規(guī)則,Cluster Manager向所有access proxy更新路由信息
STEP5:刪除老分片
自動擴(kuò)展:
過程類似于數(shù)據(jù)遷移

六、Redis集群方案
6.1??部署方式
1、異構(gòu)讀寫分離-MySQL寫,Redis讀
1) 數(shù)據(jù)寫操作在 MySQL ,讀操作在Redis
2) 數(shù)據(jù)通過Sync系統(tǒng)對binlog進(jìn)行解析從Mysql同步到Redis
3) 數(shù)據(jù)有同步延遲(小于100ms),實(shí)現(xiàn)最終一致性
適用場景:要求數(shù)據(jù)高可靠,且讀量比較大,允許讀數(shù)據(jù)短時間不一致,若期望一直讀到最新數(shù)據(jù),請使用get_with_version()接口從 MySQL 讀取
2、獨(dú)立的Redis集群服務(wù)
1)讀寫均在Redis,提供獨(dú)立的KV存儲服務(wù)
2) 用戶不用關(guān)注擴(kuò)容/縮容/故障恢復(fù)等問題
3) 集群內(nèi)多業(yè)務(wù)混存,提高內(nèi)存的使用率
適用場景:獨(dú)立的Redis集群服務(wù),類似twenproxy/codis
6.2??設(shè)計要點(diǎn)
1、一致性hash
支持?jǐn)?shù)據(jù)跨Redis實(shí)例拆分,固定Slot數(shù)進(jìn)行拆分
2、單機(jī)多實(shí)例部署
1)每個物理機(jī)支持多Redis實(shí)例
2)每個Redis實(shí)例只服務(wù)單個業(yè)務(wù)
3)Redis實(shí)例內(nèi)存大小取決于業(yè)務(wù)需求,同時考慮業(yè)務(wù)訪問量和數(shù)據(jù)量
以RedisIP+port標(biāo)示唯一實(shí)例,對于128G內(nèi)存機(jī)器,
可配置3 Redis實(shí)例*每實(shí)例30G
或10 Redis實(shí)例*每實(shí)例10G
或20 Redis實(shí)例*每實(shí)例5G
拆分原則:單實(shí)例最大內(nèi)存使用 < 本機(jī)剩余內(nèi)存
3、以Slot為單位的平滑擴(kuò)容/縮容
4、以Redis實(shí)例為單位的failover處理
6.3??平滑擴(kuò)容/縮容
主要步驟如下:
STEP1:確認(rèn)擴(kuò)容/縮容
Cluster manager通過對系統(tǒng)負(fù)載和數(shù)據(jù)量進(jìn)行告警,進(jìn)而確認(rèn)進(jìn)行擴(kuò)容或者縮容
STEP2:修改路由表
1)修改路由表,將對應(yīng)shard的狀態(tài)修改為migrate狀態(tài),并將新路由推送到所有接入層
2)acc proxy會將寫操作轉(zhuǎn)到新的Redis實(shí)例中,讀操作默認(rèn)先讀新Redis實(shí)例,key不存在會繼續(xù)從老的Redis實(shí)例中讀取
STEP3:數(shù)據(jù)遷移
1)Cluster manager通過自動數(shù)據(jù)遷移工具開始數(shù)據(jù)遷移,計劃依賴Redis的scan命令將相關(guān)的key掃出來,通過MIGRATE進(jìn)行數(shù)據(jù)遷移
2)多次掃描執(zhí)行該過程,確認(rèn)Slot中所有數(shù)據(jù)遷移完成
STEP4:修改路由表,遷移完成
Cluster manager將讀寫均切到新Redis實(shí)例,不再從老Redis中進(jìn)行操作
在此我向大家推薦一個架構(gòu)學(xué)習(xí)交流群。交流學(xué)習(xí)群號:575745314 ? 里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備的知識體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源,目前受益良多
七、Sync數(shù)據(jù)同步服務(wù)
7.1??架構(gòu)

7.2??應(yīng)用場景
該服務(wù)完全可以抽象成獨(dú)立的數(shù)據(jù)同步分發(fā)服務(wù),對于因為KV化而丟失的sql處理完全可以通過該服務(wù)同步到偏OLAP類的系統(tǒng)中進(jìn)行處理。除了同步到Redis還可以同步到ElasticSearch或者h(yuǎn)base或者寫hdfs文件基于hadoop生態(tài)去實(shí)現(xiàn)復(fù)雜計算和分析。
7.3??設(shè)計要點(diǎn)
1、集群對集群的實(shí)時數(shù)據(jù)同步
MySQL 統(tǒng)一要求binlog日志為row格式
2、不涉及DDL處理
由于 MySQL schemaless的設(shè)計,不用考慮DDL處理,簡化同步服務(wù)(跨/不跨IDC)
3、基于時間戳的同步延遲監(jiān)控
MySQL binlog row格式日志自帶時間戳,基于此時間戳進(jìn)行同步延遲監(jiān)控
4、基于binlog文件名+offset的同步位置管理
定時定量持久化保存當(dāng)前同步的binlog文件名及offset,用于各種場景下的同步恢復(fù)
5、基于行的并行同步
多線程同步模式,主線程通過對tableid或者key做hash,將binlogevent時間分發(fā)到對應(yīng)worker線程的隊列中,worker線程依次從隊列中獲取binlog event執(zhí)行
7.4? 實(shí)現(xiàn)原理

原理相對比較簡單:
1)Sync同步工具模擬Mysql slave的交互協(xié)議,偽裝自己為 MySQL slave,向Mysqlmaster發(fā)送dump協(xié)議
2)Mysqlmaster收到dump請求,開始推送binary log給slave(也就是同步工具)
3)Sync同步工具解析binary log對象(原始為byte流),并轉(zhuǎn)換成Redis或其他存儲(hdfs/hbase/ES等數(shù)據(jù)庫)相應(yīng)數(shù)據(jù)操作接口或者作為消息存儲到MQ中(rocketmq或者kafka)
7.5??ROW格式events
MySQL 5.5 Binlog的事件類型有多種,這里只介紹與ROW模式相關(guān)的事件
1) QUERY_EVENT:與STATEMENT模式處理相同,存儲的是SQL,主要是一些與數(shù)據(jù)無關(guān)的操作,eg: begin、drop table
2) TABLE_MAP_EVENT:記錄了下一條事件所對應(yīng)的表信息,在其中存儲了數(shù)據(jù)庫名和表名
3) WRITE_ROWS_EVENT:操作類型為insert
4) UPDATE_ROWS_EVENT:操作類型為update
5) DELETE_ROWS_EVENT:操作類型為delete
6) XID_EVENT, 用于標(biāo)識事務(wù)提交(commit)
典型的insert語句有如下4個events組成:

7.6??其他開源同步方案
1.?tungsten-replicator(JAVA)
http://code.google.com/p/tungsten-replicator/
2.?linkedin? databus(JAVA)
https://github.com/linkedin/databus
3.?Alibaba? canal(JAVA)
https://github.com/alibaba/canal /