標籤:資訊 多進程 虛線 流程圖 資料解析 glob reg parser last
來自:74964366/
關係型資料庫和Hadoop生態的溝通越來越密集,時效要求也越來越高。本篇就來調研下即時抓取MySQL更新資料到HDFS。
本篇僅作為調研報告。
初步調研了canal(Ali)+kafka connect+kafka、maxwell(Zendesk)+kafka和mysql_streamer(Yelp)+kafka。這幾個工具抓取MySQL的方式都是通過掃描binlog,類比MySQL master和slave(Mysql Replication架構–解決了:資料多點備份,提高資料可用性;讀寫分流,提高叢集的並發能力。(並非是負載平衡);讓一些非即時的資料操作,轉移到slaves上進行。)之間的協議來實現即時更新的。
先科普下Canal
Canal簡介原理
Canal原理圖
原理相對比較簡單:
- canal類比mysql slave的互動協議,偽裝自己為mysql slave,向mysql master發送dump協議
- mysql master收到dump請求,開始推送(slave拉取,不是master主動push給slaves)binary log給slave(也就是canal)
- canal解析binary log對象(原始為byte流)
架構
Canal架構圖
組件說明:
- server代表一個canal運行執行個體,對應於一個jvm
- instance對應於一個資料隊列(1個server對應1..n個instance)
而instance模組又由eventParser(資料來源接入,類比slave協議和master進行互動,協議解析)、eventSink(Parser和Store連接器,進行資料過濾,加工,分發的工作)、eventStore(資料存放區)和metaManager(增量訂閱&消費資訊管理器)組成。
EventParser在向mysql發送dump命令之前會先從Log Position中擷取上次解析成功的位置(如果是第一次啟動,則擷取初始指定位置或者當前資料區段binlog位點)。mysql接受到dump命令後,由EventParser從mysql上pull binlog資料進行解析並傳遞給EventSink(傳遞給EventSink模組進行資料存放區,是一個阻塞操作,直到儲存成功),傳送成功之後更新Log Position。流程圖如下:
EventParser流程圖
EventSink起到一個類似channel的功能,可以對資料進行過濾、分發/路由(1:n)、歸併(n:1)和加工。EventSink是串連EventParser和EventStore的橋樑。
EventStore實現模式是記憶體模式,記憶體結構為環形隊列,由三個指標(Put、Get和Ack)標識資料存放區和讀取的位置。
MetaManager是增量訂閱&消費資訊管理器,增量訂閱和消費之間的協議包括get/ack/rollback,分別為:
Message getWithoutAck(int batchSize),允許指定batchSize,一次可以擷取多條,每次返回的對象為Message,包含的內容為:batch id[唯一標識]和entries[具體的資料對象]
void rollback(long batchId),顧命思議,復原上次的get請求,重新擷取資料。基於get擷取的batchId進行提交,避免誤操作
void ack(long batchId),顧命思議,確認已經消費成功,通知server刪除資料。基於get擷取的batchId進行提交,避免誤操作
增量訂閱和消費之間的協議互動如下:
增量訂閱和消費協議
canal的get/ack/rollback協議和常規的jms協議有所不同,允許get/ack非同步處理,比如可以連續調用get多次,後續非同步按順序提交ack/rollback,項目中稱之為流式api.
流式api設計的好處:
- get/ack非同步化,減少因ack帶來的網路延遲和操作成本 (99%的狀態都是處於正常狀態,異常的rollback屬於個別情況,沒必要為個別的case犧牲整個效能)
- get擷取資料後,業務消費存在瓶頸或者需要多進程/多線程消費時,可以不停的輪詢get資料,不停的往後發送任務,提高並行化. (在實際業務中的一個case:業務資料消費需要跨中美網路,所以一次操作基本在200ms以上,為了減少延遲,所以需要實施並行化)
流式api設計如下:
流式api
- 每次get操作都會在meta中產生一個mark,mark標記會遞增,保證運行過程中mark的唯一性
- 每次的get操作,都會在上一次的mark操作記錄的cursor繼續往後取,如果mark不存在,則在last ack cursor繼續往後取
- 進行ack時,需要按照mark的順序進行數序ack,不能跳躍ack. ack會刪除當前的mark標記,並將對應的mark位置更新為last ack cusor
- 一旦出現異常情況,用戶端可發起rollback情況,重新置位:刪除所有的mark, 清理get請求位置,下次請求會從last ack cursor繼續往後取
這個流式api是不是類似hdfs write在pipeline中傳輸packet的形式,先將packet放入dataQueue,然後向下遊傳輸,此時將packet放入ackQueue等到下遊返回的ack,這也是非同步。
HA機制
canal是支援HA的,其實現機制也是依賴zookeeper來實現的,用到的特性有watcher和EPHEMERAL節點(和session生命週期綁定),與HDFS的HA類似。
canal的ha分為兩部分,canal server和canal client分別有對應的ha實現
- canal server: 為了減少對mysql dump的請求,不同server上的instance(不同server上的相同instance)要求同一時間只能有一個處於running,其他的處於standby狀態(standby是instance的狀態)。
- canal client: 為了保證有序性,一份instance同一時間只能由一個canal client進行get/ack/rollback操作,否則用戶端接收無法保證有序。
server ha的架構圖如下:
ha
大致步驟:
- canal server要啟動某個canal instance時都先向zookeeper進行一次嘗試啟動判斷(實現:建立EPHEMERAL節點,誰建立成功就允許誰啟動)
- 建立zookeeper節點成功後,對應的canal server就啟動對應的canal instance,沒有建立成功的canal instance就會處於standby狀態。
- 一旦zookeeper發現canal server A建立的instance節點消失後,立即通知其他的canal server再次進行步驟1的操作,重新選出一個canal server啟動instance。
- canal client每次進行connect時,會首先向zookeeper詢問當前是誰啟動了canal instance,然後和其建立連結,一旦連結不可用,會重新嘗試connect。
Canal Client的方式和canal server方式類似,也是利用zookeeper的搶佔EPHEMERAL節點的方式進行控制.
Canal部署及使用MySQL配置
canal同步資料需要掃描MySQL的binlog日誌,而binlog預設是關閉的,需要開啟,並且為了保證同步資料的一致性,使用的日誌格式為row-based replication(RBR),在my.conf
中開啟binlog,
1234 |
[mysqld]log-bin=mysql-bin #添加這一行就okbinlog-format=ROW #選擇row模式server_id=1 #配置mysql replaction需要定義,不能和canal的slaveId重複 |
更改my.conf之後,需要重啟MySQL,重啟的方式有很多找到合適自己的就行。
Canal配置
由上面的介紹得知Canal由Server
和Instance
組成,而Server中又可以包含很多個Instance,一個Instance對應一個資料庫執行個體,則Canal將配置分為兩類,一類是server的配置,名字為canal.properties
,另一類是instance的配置,名字為instance.properties
,一般會在conf目錄下建立一個instance同名的目錄,將其放入此目錄中。
先介紹canal.properties中的幾個關鍵屬性
參數名字 |
參數說明 |
預設值 |
canal.destinations |
當前server上部署的instance列表 |
無 |
canal.conf.dir |
conf/目錄所在的路徑 |
../conf |
canal.instance.global.spring.xml |
全域的spring配置方式的組件檔案 |
classpath:spring/file-instance.xml (spring目錄相對於canal.conf.dir) |
canal.zkServers |
canal server連結zookeeper叢集的連結資訊 |
無 |
canal.zookeeper.flush.period |
canal持久化資料到zookeeper上的更新頻率,單位毫秒 |
1000 |
canal.file.data.dir |
canal持久化資料到file上的目錄 |
../conf (預設和instance.properties為同一目錄,方便營運和備份) |
canal.file.flush.period |
canal持久化資料到file上的更新頻率,單位毫秒 |
1000 |
canal.instance.memory.batch.mode |
canal記憶體store中資料緩衝模式 1. ITEMSIZE : 根據buffer.size進行限制,只限制記錄的數量 2. MEMSIZE : 根據buffer.size * buffer.memunit的大小,限制緩衝記錄的大小 |
MEMSIZE |
canal.instance.memory.buffer.size |
canal記憶體store中可緩衝buffer記錄數,需要為2的指數 |
16384 |
canal.instance.memory.buffer.memunit |
記憶體記錄的單位大小,預設1KB,和buffer.size組合決定最終的記憶體使用量大小 |
1024 |
下面看下instance.properties,這裡的屬性較少:
參數名字 |
參數說明 |
預設值 |
canal.instance.mysql.slaveId |
mysql叢集配置中的serverId概念,需要保證和當前mysql叢集中id唯一 |
1234 |
canal.instance.master.address |
mysql主庫連結地址 |
127.0.0.1:3306 |
canal.instance.master.journal.name |
mysql主庫連結時起始的binlog檔案 |
無 |
canal.instance.master.position |
mysql主庫連結時起始的binlog位移量 |
無 |
canal.instance.master.timestamp |
mysql主庫連結時起始的binlog的時間戳記 |
無 |
canal.instance.dbUsername |
mysql資料庫帳號 |
canal |
canal.instance.dbPassword |
mysql資料庫密碼 |
canal |
canal.instance.defaultDatabaseName |
mysql連結時預設schema |
無 |
canal.instance.connectionCharset |
mysql 資料解析編碼 |
UTF-8 |
canal.instance.filter.regex |
mysql 資料解析關注的表,PerlRegex. 多個正則之間以逗號(,)分隔,轉義符需要雙斜杠 |
.*\\..* |
除了上面兩個設定檔,conf目錄下還有一個目錄需要強調下,那就是spring目錄,裡面存放的是instance.xml設定檔,目前預設支援的instance.xml有memory-instance.xml、file-instance.xml、default-instance.xml和group-instance.xml。這裡主要維護的增量訂閱和消費的關係資訊(解析位點和消費位點)。
對應的兩個位點組件,目前都有幾種實現:
- memory (memory-instance.xml中使用)
- zookeeper
- mixed
- file (file-instance.xml中使用,集合了file+memory模式,先寫記憶體,定時重新整理資料到本地file上)
- period (default-instance.xml中使用,集合了zookeeper+memory模式,先寫記憶體,定時重新整理資料到zookeeper上)
分別介紹下這幾種配置的功能
所有的組件(parser , sink , store)都選擇了記憶體版模式,記錄位點的都選擇了memory模式,重啟後又會回到初始位點進行解析
特點:速度最快,依賴最少(不需要zookeeper)
情境:一般應用在quickstart,或者是出現問題後,進行資料分析的情境,不應該將其應用於生產環境
所有的組件(parser , sink , store)都選擇了基於file持久化模式(組件內容持久化的file存在哪裡???),注意,不支援HA機制.
特點:支援單機持久化
情境:生產環境,無HA需求,簡單可用.
所有的組件(parser , sink , store)都選擇了持久化模式,目前持久化的方式主要是寫入zookeeper,保證資料集群共用.(所有組件持久化的內容只有位置資訊吧???)
特點:支援HA
情境:生產環境,叢集化部署.
主要針對需要進行多庫合并時,可以將多個物理instance合并為一個邏輯instance,提供用戶端訪問。
情境:分庫業務。 比如產品資料拆分了4個庫,每個庫會有一個instance,如果不用group,業務上要消費資料時,需要啟動4個用戶端,分別連結4個instance執行個體。使用group後,可以在canal server上合并為一個邏輯instance,只需要啟動1個用戶端,連結這個邏輯instance即可.
canal example 部署
- 在需要同步的MySQL資料庫中建立一個使用者,用來replica資料,這裡建立的使用者名稱和密碼都為
canal
,命令如下:
1234 |
CREATE USER canal IDENTIFIED BY ‘canal‘;GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO ‘canal‘@‘%‘;-- GRANT ALL PRIVILEGES ON *.* TO ‘canal‘@‘%‘ ;FLUSH PRIVILEGES; |
- Mysql建立
canal
使用者並為其賦要求的權限之後,需要對Canal的設定檔(canal.properties和instance.properties)進行設定。
canal.properties和instance.properties裡採用預設配置即可(這裡只是運行個範例,生產中可以參考具體的參數屬性進行設定),
- Canal配置好之後,啟動Canal client(client的作用是將Canal裡的解析的binlog日誌固化到儲存介質中)。
client組件Canal本身是不提供的,需要根據api進行開發,這裡將官方提供的client代碼打包成jar進行消費Canal資訊。
canal HA配置
canal的HA機制是依賴zk來實現的,需要更改canal.properties檔案,修改內容如下:
1234 |
# zk叢集地址canal.zkServers=10.20.144.51:2181# 選擇記錄方式canal.instance.global.spring.xml = classpath:spring/default-instance.xml |
更改兩台canal機器上instance執行個體的配置instance.properties,修改內容如下:
12 |
canal.instance.mysql.slaveId = 1234 ##另外一台機器改成1235,保證slaveId不重複即可canal.instance.master.address = 10.20.144.15:3306 |
配置好之後啟動canal進程,在兩台伺服器上執行sh bin/startup.sh
client進行消費時,可以直接指定zookeeper地址和instance name,也可以讓canal client會自動從zookeeper中的running節點,擷取當前服務的工作節點,然後與其建立連結。
maxwell簡介
maxwell即時抓取mysql資料的原理也是基於binlog,和canal相比,maxwell更像是canal server + 即時client
。(資料幫浦 + 資料轉換)
maxwell整合了kafka producer,直接從binlog擷取資料更新並寫入kafka,而canal則需要自己開發即時client將canal讀取的binlog內容寫入kafka中。
maxwell特色:
- 支援bootstrap啟動,同步曆史資料
- 整合kafka,直接將資料落地到kafka
- 已將binlog中的DML和DDL進行了模式比對,將其解碼為有schema的json(有利於後期將其重組為nosql支援的語言)
{“database”:”test”,”table”:”e”,”type”:”update”,”ts”:1488857869,”xid”:8924,”commit”:true,”data”:{“id”:1,”m”:5.556666,”torvalds”:null},”old”:{“m”:5.55}}
缺點:
- 一個MySQL執行個體需要對應一個maxwell進程
- bootstrap的方案使用的是
select *
maxwell的設定檔只有一個config.properties,在home目錄。其中除了需要配置mysql master的地址、kafka地址還需要配置一個用於存放maxwell相關資訊的mysql地址,maxwell會把讀取binlog關係的資訊,如binlog name、position。
工具對比
以上是Canal的原理及部署,其餘類似maxwell和mysql_streamer對mysql進行即時資料抓取的原理一樣就不再進行一一介紹,這裡只對他們進行下對比:
特色 |
Canal |
Maxwell |
mysql_streamer |
語言 |
Java |
Java |
Python |
活躍度 |
活躍 |
活躍 |
不活躍 |
HA |
支援 |
定製 |
支援 |
資料落地 |
定製 |
落地到kafka |
落地到kafka |
分區 |
支援 |
不支援 |
不支援 |
bootstrap |
不支援 |
支援 |
支援 |
資料格式 |
格式自由 |
json(格式固定) |
json(格式固定) |
文檔 |
較詳細 |
較詳細 |
略粗 |
隨機讀 |
支援 |
支援 |
支援 |
以上只是將mysql裡的即時變化資料的binlog以同種形式同步到kafka,但要即時更新到hadoop還需要使用一個即時資料庫來儲存資料,並自定製開發將kafka中資料解析為nosql資料庫可以識別的DML進行即時更新Nosql資料庫,使其與MySQL裡的資料即時同步。
基礎架構
架構圖如下:
基礎架構圖
虛線框是可選的方案
方案對比
- 方案1使用阿里開源的Canal進行Mysql binlog資料的抽取,另需開發一個資料轉換工具將從binlog中解析出的資料轉換成內建schema的json資料並寫入kafka中。而方案2使用maxwell可直接完成對mysql binlog資料的抽取和轉換成內建schema的json資料寫入到kafka中。
- 方案1中不支援表中已存在的曆史資料進行同步,此功能需要開發(如果使用sqoop進行曆史資料同步,不夠靈活,會使結果表與原始表結構相同,有區別於資料交換平台所需的schema)。方案2提供同步曆史資料的解決方案。
- 方案1支援HA部署,而方案2不支援HA
方案1和方案2的區別只在於kafka之前,當資料緩衝到kafka之後,需要一個定製的資料路由群組件來將內建schema的資料解析到目標儲存中。
資料路由群組件主要負責將kafka中的資料即時讀出,寫入到目標儲存中。(如將所有日誌資料儲存到HDFS中,也可以將資料落地到所有支援jdbc的資料庫,落地到HBase,Elasticsearch等。)
綜上,
方案1需要開發的功能有:
- bootstrap功能
- 即時資料轉換工具
- 資料路由工具
方案2需要開發的功能有:
- 資料路由工具
- HA模組(初期可暫不支援HA,所以開發緊急度不高)
資料路由工具是兩個方案都需要開發的,則我比較偏向於第二種方案,因為在初期試水階段可以短期出成果,可以較快的驗證想法,並在嘗試中能夠較快的發現問題,好及時的調整方案。即使方案2中maxwell最終不能滿足需求,而使用canal的話,我們也可能將即時資料轉換工具的資料輸出模式與maxwell一致,這樣初始投入人力開發的資料路由工具依然可以繼續使用,而不需要重新開發。
把增量的Log作為一切系統的基礎。後續的資料使用方,通過訂閱kafka來消費log。
比如:
大資料的使用方可以將資料儲存到Hive表或者Parquet檔案給Hive或Spark查詢;
提供搜尋服務的使用方可以儲存到Elasticsearch或HBase 中;
提供快取服務的使用方可以將日誌緩衝到Redis或alluxio中;
資料同步的使用方可以將資料儲存到自己的資料庫中;
由於kafka的日誌是可以重複消費的,並且緩衝一段時間,各個使用方可以通過消費kafka的日誌來達到既能保持與資料庫的一致性,也能保證即時性;
{“database”:”test”,”table”:”e”,”type”:”update”,”ts”:1488857869,”xid”:8924,”commit”:true,”data”:{“id”:1,”m”:5.556666,”torvalds”:null},”old”:{“m”:5.55}}
{“database”:”test”,”table”:”e”,”type”:”insert”,”ts”:1488857922,”xid”:8932,”commit”:true,”data”:{“id”:2,”m”:4.2,”torvalds”:null}}
20180705關於mysql binlog的解析方式