maxwell 簡介
Maxwell是一個能實時讀取MySQL二進制日誌binlog,並生成 JSON 格式的消息,作爲生產者發送給 Kafka,Kinesis、RabbitMQ、Redis、Google Cloud Pub/Sub、文件或其它平臺的應用程序。它的常見應用場景有ETL、維護緩存、收集表級別的dml指標、增量到搜索引擎、數據分區遷移、切庫binlog回滾方案等。官網(http://maxwells-daemon.io)、GitHub(https://github.com/zendesk/maxwell)
Maxwell主要提供了下列功能:
支持 SELECT * FROM table 的方式進行全量數據初始化
支持在主庫發生failover後,自動恢復binlog位置(GTID)
可以對數據進行分區,解決數據傾斜問題,發送到kafka的數據支持database、table、column等級別的數據分區
工作方式是僞裝爲Slave,接收binlog events,然後根據schemas信息拼裝,可以接受ddl、xid、row等各種event
除了Maxwell外,目前常用的MySQL Binlog解析工具主要有阿里的canal、mysql_streamer,三個工具對比如下:
canal 由Java開發,分爲服務端和客戶端,擁有衆多的衍生應用,性能穩定,功能強大;canal 需要自己編寫客戶端來消費canal解析到的數據。
maxwell相對於canal的優勢是使用簡單,它直接將數據變更輸出爲json字符串,不需要再編寫客戶端。
快速開始
首先MySQL需要先啓用binlog,關於什麼是MySQL binlog,可以參考文章《MySQL Binlog 介紹》
$ vi my.cnf
[mysqld]
server_id=1
log-bin=master
binlog_format=row
1
2
3
4
5
6
創建Maxwell用戶,並賦予 maxwell 庫的一些權限
CREATE USER 'maxwell'@'%' IDENTIFIED BY '123456';
GRANT ALL ON maxwell.* TO 'maxwell'@'%';
GRANT SELECT, REPLICATION CLIENT, REPLICATION SLAVE on *.* to 'maxwell'@'%';
1
2
3
使用 maxwell 之前需要先啓動 kafka
wget http://mirrors.tuna.tsinghua.edu.cn/apache/kafka/2.1.0/kafka_2.11-2.1.0.tgz
tar -xzf kafka_2.11-2.1.0.tgz
cd kafka_2.11-2.1.0
# 啓動Zookeeper
bin/zookeeper-server-start.sh config/zookeeper.properties
1
2
3
4
5
單機啓動 kafka 之前,需要修改一下配置文件,打開配置文件 vi config/server.properties,在文件最後加入 advertised.host.name 的配置,值爲 kafka 所在機器的IP
advertised.host.name=10.100.97.246
1
不然後面通過 docker 啓動 maxwell 將會報異常(其中的 hadoop2 是我的主機名)
17:45:21,446 DEBUG NetworkClient - [Producer clientId=producer-1] Error connecting to node hadoop2:9092 (id: 0 rack: null)
java.io.IOException: Can't resolve address: hadoop2:9092
at org.apache.kafka.common.network.Selector.connect(Selector.java:217) ~[kafka-clients-1.0.0.jar:?]
at org.apache.kafka.clients.NetworkClient.initiateConnect(NetworkClient.java:793) [kafka-clients-1.0.0.jar:?]
at org.apache.kafka.clients.NetworkClient.ready(NetworkClient.java:230) [kafka-clients-1.0.0.jar:?]
at org.apache.kafka.clients.producer.internals.Sender.sendProducerData(Sender.java:263) [kafka-clients-1.0.0.jar:?]
at org.apache.kafka.clients.producer.internals.Sender.run(Sender.java:238) [kafka-clients-1.0.0.jar:?]
at org.apache.kafka.clients.producer.internals.Sender.run(Sender.java:176) [kafka-clients-1.0.0.jar:?]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_181]
Caused by: java.nio.channels.UnresolvedAddressException
at sun.nio.ch.Net.checkAddress(Net.java:101) ~[?:1.8.0_181]
at sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:622) ~[?:1.8.0_181]
at org.apache.kafka.common.network.Selector.connect(Selector.java:214) ~[kafka-clients-1.0.0.jar:?]
... 6 more
1
2
3
4
5
6
7
8
9
10
11
12
13
14
接着可以啓動 kafka
bin/kafka-server-start.sh config/server.properties
1
測試 kafka
# 創建一個 topic
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
# 列出所有 topic
bin/kafka-topics.sh --list --zookeeper localhost:2181
# 啓動一個生產者,然後隨意發送一些消息
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
This is a message
This is another message
# 在另一個終端啓動一下消費者,觀察所消費的消息
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
This is a message
This is another message
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
通過 docker 快速安裝並使用 Maxwell (當然之前需要自行安裝 docker)
# 拉取鏡像
docker pull zendesk/maxwell
# 啓動maxwell,並將解析出的binlog輸出到控制檯
docker run -ti --rm zendesk/maxwell bin/maxwell --user='maxwell' --password='123456' --host='10.100.97.246' --producer=stdout
1
2
3
4
5
測試Maxwell,首先創建一張簡單的表,然後增改刪數據
CREATE TABLE `test` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`age` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into test values(1,22,"小旋鋒");
update test set name='whirly' where id=1;
delete from test where id=1;
1
2
3
4
5
6
7
8
9
10
觀察docker控制檯的輸出,從輸出的日誌中可以看出Maxwell解析出的binlog的JSON字符串的格式
{"database":"test","table":"test","type":"insert","ts":1552153502,"xid":832,"commit":true,"data":{"id":1,"age":22,"name":"小旋鋒"}}
{"database":"test","table":"test","type":"update","ts":1552153502,"xid":833,"commit":true,"data":{"id":1,"age":22,"name":"whirly"},"old":{"name":"小旋鋒"}}
{"database":"test","table":"test","type":"delete","ts":1552153502,"xid":834,"commit":true,"data":{"id":1,"age":22,"name":"whirly"}}
1
2
3
輸出到 Kafka,關閉 docker,重新設置啓動參數
docker run -it --rm zendesk/maxwell bin/maxwell --user='maxwell' \
--password='123456' --host='10.100.97.246' --producer=kafka \
--kafka.bootstrap.servers='10.100.97.246:9092' --kafka_topic=maxwell --log_level=debug
1
2
3
然後啓動一個消費者來消費 maxwell topic的消息,觀察其輸出;再一次執行增改刪數據的SQL,仍然可以得到相同的輸出
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic maxwell
1
輸出JSON字符串的格式
data 最新的數據,修改後的數據
old 舊數據,修改前的數據
type 操作類型,有insert, update, delete, database-create, database-alter, database-drop, table-create, table-alter, table-drop,bootstrap-insert,int(未知類型)
xid 事務id
commit 同一個xid代表同一個事務,事務的最後一條語句會有commit,可以利用這個重現事務
server_id
thread_id
運行程序時添加參數–output_ddl,可以捕捉到ddl語句
datetime列會輸出爲"YYYY-MM-DD hh:mm:ss",如果遇到"0000-00-00 00:00:00"會原樣輸出
maxwell支持多種編碼,但僅輸出utf8編碼
maxwell的TIMESTAMP總是作爲UTC處理,如果要調整爲自己的時區,需要在後端邏輯上進行處理
與輸出格式相關的配置如下
選項 參數值 描述 默認值
output_binlog_position BOOLEAN 是否包含 binlog position false
output_gtid_position BOOLEAN 是否包含 gtid position false
output_commit_info BOOLEAN 是否包含 commit and xid true
output_xoffset BOOLEAN 是否包含 virtual tx-row offset false
output_nulls BOOLEAN 是否包含值爲NULL的字段 true
output_server_id BOOLEAN 是否包含 server_id false
output_thread_id BOOLEAN 是否包含 thread_id false
output_schema_id BOOLEAN 是否包含 schema_id false
output_row_query BOOLEAN 是否包含 INSERT/UPDATE/DELETE 語句. Mysql需要開啓 binlog_rows_query_log_events false
output_ddl BOOLEAN 是否包含 DDL (table-alter, table-create, etc) events false
output_null_zerodates BOOLEAN 是否將 ‘0000-00-00’ 轉換爲 null? false
進階使用
基本的配置
選項 參數值 描述 默認值
config 配置文件 config.properties 的路徑
log_level [debug | info | warn | error] 日誌級別 info
daemon 指定Maxwell實例作爲守護進程到後臺運行
env_config_prefix STRING 匹配該前綴的環境變量將被視爲配置值
可以把Maxwell的啓動參數寫到一個配置文件 config.properties 中,然後通過 config 選項指定,bin/maxwell --config config.properties
user=maxwell
password=123456
host=10.100.97.246
producer=kafka
kafka.bootstrap.servers=10.100.97.246:9092
kafka_topic=maxwell
1
2
3
4
5
6
mysql 配置選項
Maxwell 根據用途將 MySQL 劃分爲3種角色:
host:主機,建maxwell庫表,存儲捕獲到的schema等信息
主要有六張表,bootstrap用於數據初始化,schemas記錄所有的binlog文件信息,databases記錄了所有的數據庫信息,tables記錄了所有的表信息,columns記錄了所有的字段信息,positions記錄了讀取binlog的位移信息,heartbeats記錄了心跳信息
replication_host:複製主機,Event監聽,讀取該主機binlog
將host和replication_host分開,可以避免 replication_user 往生產庫裏寫數據
schema_host:schema主機,捕獲表結構schema的主機
binlog裏面沒有字段信息,所以maxwell需要從數據庫查出schema,存起來。
schema_host一般用不到,但在binlog-proxy場景下就很實用。比如要將已經離線的binlog通過maxwell生成json流,於是自建一個mysql server裏面沒有結構,只用於發送binlog,此時表機構就可以制動從 schema_host 獲取。
通常,這三個主機都是同一個,schema_host 只在有 replication_host 的時候使用。
與MySQL相關的有下列配置
選項 參數值 描述 默認值
host STRING mysql 地址 localhost
user STRING mysql 用戶名
password STRING mysql 密碼 (no password)
port INT mysql 端口 3306
jdbc_options STRING mysql jdbc connection options DEFAULT_JDBC_OPTS
ssl SSL_OPT SSL behavior for mysql cx DISABLED
schema_database STRING Maxwell用於維護的schema和position將使用的數據庫 maxwell
client_id STRING 用於標識Maxwell實例的唯一字符串 maxwell
replica_server_id LONG 用於標識Maxwell實例的唯一數字 6379 (see notes)
master_recovery BOOLEAN enable experimental master recovery code false
gtid_mode BOOLEAN 是否開啓基於GTID的複製 false
recapture_schema BOOLEAN 重新捕獲最新的表結構(schema),不可在 config.properties中配置 false
replication_host STRING server to replicate from. See split server roles schema-store host
replication_password STRING password on replication server (none)
replication_port INT port on replication server 3306
replication_user STRING user on replication server
replication_ssl SSL_OPT SSL behavior for replication cx cx DISABLED
schema_host STRING server to capture schema from. See split server roles schema-store host
schema_password STRING password on schema-capture server (none)
schema_port INT port on schema-capture server 3306
schema_user STRING user on schema-capture server
schema_ssl SSL_OPT SSL behavior for schema-capture server DISABLED
生產者的配置
僅介紹kafka,其他的生產者的配置詳見官方文檔。
kafka是maxwell支持最完善的一個生產者,並且內置了多個版本的kafka客戶端(0.8.2.2, 0.9.0.1, 0.10.0.1, 0.10.2.1 or 0.11.0.1, 1.0.0.),默認 kafka_version=1.0.0(當前Maxwell版本1.20.0)
Maxwell 會將消息投遞到Kafka的Topic中,該Topic由 kafka_topic 選項指定,默認值爲 maxwell,除了指定爲靜態的Topic,還可以指定爲動態的,譬如 namespace_%{database}_%{table},%{database} 和 %{table} 將被具體的消息的 database 和 table 替換。
Maxwell 讀取配置時,如果配置項是以 kafka. 開頭,那麼該配置將設置到 Kafka Producer 客戶端的連接參數中去,譬如
kafka.acks = 1
kafka.compression.type = snappy
kafka.retries=5
1
2
3
下面是Maxwell通用生產者和Kafka生產者的配置參數
選項 參數值 描述 默認值
producer PRODUCER_TYPE 生產者類型 stdout
custom_producer.factory CLASS_NAME 自定義消費者的工廠類
producer_ack_timeout PRODUCER_ACK_TIMEOUT 異步消費認爲消息丟失的超時時間(毫秒ms)
producer_partition_by PARTITION_BY 輸入到kafka/kinesis的分區函數 database
producer_partition_columns STRING 若按列分區,以逗號分隔的列名稱
producer_partition_by_fallback PARTITION_BY_FALLBACK producer_partition_by=column時需要,當列不存在是使用
ignore_producer_error BOOLEAN 爲false時,在kafka/kinesis發生錯誤時退出程序;爲true時,僅記錄日誌 See also dead_letter_topic true
kafka.bootstrap.servers STRING kafka 集羣列表,HOST:PORT[,HOST:PORT]
kafka_topic STRING kafka topic maxwell
dead_letter_topic STRING 詳見官方文檔
kafka_version KAFKA_VERSION 指定maxwell的 kafka 生產者客戶端版本,不可在config.properties中配置 0.11.0.1
kafka_partition_hash [default | murmur3] 選擇kafka分區時使用的hash方法 default
kafka_key_format [array | hash] how maxwell outputs kafka keys, either a hash or an array of hashes hash
ddl_kafka_topic STRING 當output_ddl爲true時, 所有DDL的消息都將投遞到該topic kafka_topic
過濾器配置
Maxwell 可以通過 --filter 配置項來指定過濾規則,通過 exclude 排除,通過 include 包含,值可以爲具體的數據庫、數據表、數據列,甚至用 Javascript 來定義複雜的過濾規則;可以用正則表達式描述,有幾個來自官網的例子
# 僅匹配foodb數據庫的tbl表和所有table_數字的表
--filter='exclude: foodb.*, include: foodb.tbl, include: foodb./table_\d+/'
# 排除所有庫所有表,僅匹配db1數據庫
--filter = 'exclude: *.*, include: db1.*'
# 排除含db.tbl.col列值爲reject的所有更新
--filter = 'exclude: db.tbl.col = reject'
# 排除任何包含col_a列的更新
--filter = 'exclude: *.*.col_a = *'
# blacklist 黑名單,完全排除bad_db數據庫,若要恢復,必須刪除maxwell庫
--filter = 'blacklist: bad_db.*'
1
2
3
4
5
6
7
8
9
10
數據初始化
Maxwell 啓動後將從maxwell庫中獲取上一次停止時position,從該斷點處開始讀取binlog。如果binlog已經清除了,那麼怎樣可以通過maxwell把整張表都複製出來呢?也就是數據初始化該怎麼做?
對整張表進行操作,人爲地產生binlog?譬如找一個不影響業務的字段譬如update_time,然後加一秒,再減一秒?
update test set update_time = DATE_ADD(update_time,intever 1 second);
update test set update_time = DATE_ADD(update_time,intever -1 second);
1
2
這樣明顯存在幾個大問題:
不存在一個不重要的字段怎麼辦?每個字段都很重要,不能隨便地修改!
如果整張表很大,修改的過程耗時很長,影響了業務!
將產生大量非業務的binlog!
針對數據初始化的問題,Maxwell 提供了一個命令工具 maxwell-bootstrap 幫助我們完成數據初始化,maxwell-bootstrap 是基於 SELECT * FROM table 的方式進行全量數據初始化,不會產生多餘的binlog!
這個工具有下面這些參數:
參數 說明
--log_level LOG_LEVEL 日誌級別(DEBUG, INFO, WARN or ERROR)
--user USER mysql 用戶名
--password PASSWORD mysql 密碼
--host HOST mysql 地址
--port PORT mysql 端口
--database DATABASE 要bootstrap的表所在的數據庫
--table TABLE 要引導的表
--where WHERE_CLAUSE 設置過濾條件
--client_id CLIENT_ID 指定執行引導操作的Maxwell實例
實驗一番,下面將引導 test 數據庫中 test 表,首先是準備幾條測試用的數據
INSERT INTO `test` VALUES (1, 1, '1');
INSERT INTO `test` VALUES (2, 2, '2');
INSERT INTO `test` VALUES (3, 3, '3');
INSERT INTO `test` VALUES (4, 4, '4');
1
2
3
4
然後 reset master; 清空binlog,刪除 maxwell 庫中的表。接着使用快速開始中的命令,啓動Kafka、Maxwell和Kafka消費者,然後啓動 maxwell-bootstrap
docker run -it --rm zendesk/maxwell bin/maxwell-bootstrap --user maxwell \
--password 123456 --host 10.100.97.246 --database test --table test --client_id maxwell
1
2
注意:--bootstrapper=sync 時,在處理bootstrap時,會阻塞正常的binlog解析;--bootstrapper=async 時,不會阻塞。
也可以執行下面的SQL,在 maxwell.bootstrap 表中插入記錄,手動觸發
insert into maxwell.bootstrap (database_name, table_name) values ('test', 'test');
1
就可以在 kafka 消費者端看見引導過來的數據了
{"database":"maxwell","table":"bootstrap","type":"insert","ts":1552199115,"xid":36738,"commit":true,"data":{"id":3,"database_name":"test","table_name":"test","where_clause":null,"is_complete":0,"inserted_rows":0,"total_rows":0,"created_at":null,"started_at":null,"completed_at":null,"binlog_file":null,"binlog_position":0,"client_id":"maxwell"}}
{"database":"test","table":"test","type":"bootstrap-start","ts":1552199115,"data":{}}
{"database":"test","table":"test","type":"bootstrap-insert","ts":1552199115,"data":{"id":1,"age":1,"name":"1"}}
{"database":"test","table":"test","type":"bootstrap-insert","ts":1552199115,"data":{"id":2,"age":2,"name":"2"}}
{"database":"test","table":"test","type":"bootstrap-insert","ts":1552199115,"data":{"id":3,"age":3,"name":"3"}}
{"database":"test","table":"test","type":"bootstrap-insert","ts":1552199115,"data":{"id":4,"age":4,"name":"4"}}
{"database":"maxwell","table":"bootstrap","type":"update","ts":1552199115,"xid":36756,"commit":true,"data":{"id":3,"database_name":"test","table_name":"test","where_clause":null,"is_complete":1,"inserted_rows":4,"total_rows":0,"created_at":null,"started_at":"2019-03-10 14:25:15","completed_at":"2019-03-10 14:25:15","binlog_file":"mysql-bin.000001","binlog_position":64446,"client_id":"maxwell"},"old":{"is_complete":0,"inserted_rows":1,"completed_at":null}}
{"database":"test","table":"test","type":"bootstrap-complete","ts":1552199115,"data":{}}
1
2
3
4
5
6
7
8
中間的4條便是 test.test 的binlog數據了,注意這裏的 type 是 bootstrap-insert,而不是 insert。
然後再一次查看binlog,show binlog events;,會發現只有與 maxwell 相關的binlog,並沒有 test.test 相關的binlog,所以 maxwell-bootstrap 命令並不會產生多餘的 binlog,當數據表的數量很大時,這個好處會更加明顯
Bootstrap 的過程是 bootstrap-start -> bootstrap-insert -> bootstrap-complete,其中,start和complete的data字段爲空,不攜帶數據。
在進行bootstrap過程中,如果maxwell崩潰,重啓時,bootstrap會完全重新開始,不管之前進行到多少,若不希望這樣,可以到數據庫中設置 is_complete 字段值爲1(表示完成),或者刪除該行
Maxwell監控
Maxwell 提供了 base logging mechanism, JMX, HTTP or by push to Datadog 這四種監控方式,與監控相關的配置項有下列這些:
選項 參數值 描述 默認值
metrics_prefix STRING 指標的前綴 MaxwellMetrics
metrics_type [slf4j | jmx | http | datadog] 發佈指標的方式
metrics_jvm BOOLEAN 是否收集JVM信息 false
metrics_slf4j_interval SECONDS 將指標記錄到日誌的頻率,metrics_type須配置爲slf4j 60
http_port INT metrics_type爲http時,發佈指標綁定的端口 8080
http_path_prefix STRING http的路徑前綴 /
http_bind_address STRING http發佈指標綁定的地址 all addresses
http_diagnostic BOOLEAN http是否開啓diagnostic後綴 false
http_diagnostic_timeout MILLISECONDS http diagnostic 響應超時時間 10000
metrics_datadog_type [udp | http] metrics_type爲datadog時發佈指標的方式 udp
metrics_datadog_tags STRING 提供給 datadog 的標籤,如 tag1:value1,tag2:value2
metrics_datadog_interval INT 推指標到datadog的頻率,單位秒 60
metrics_datadog_apikey STRING 當 metrics_datadog_type=http 時datadog用的api key
metrics_datadog_host STRING 當metrics_datadog_type=udp時推指標的目標地址 localhost
metrics_datadog_port INT 當metrics_datadog_type=udp 時推指標的端口 8125
具體可以得到哪些監控指標呢?有如下,注意所有指標都預先配置了指標前綴 metrics_prefix
指標 類型 說明
messages.succeeded Counters 成功發送到kafka的消息數量
messages.failed Counters 發送失敗的消息數量
row.count Counters 已處理的binlog行數,注意並非所有binlog都發往kafka
messages.succeeded.meter Meters 消息成功發送到Kafka的速率
messages.failed.meter Meters 消息發送失敗到kafka的速率
row.meter Meters 行(row)從binlog連接器到達maxwell的速率
replication.lag Gauges 從數據庫事務提交到Maxwell處理該事務之間所用的時間(毫秒)
inflightmessages.count Gauges 當前正在處理的消息數(等待來自目的地的確認,或在消息之前)
message.publish.time Timers 向kafka發送record所用的時間(毫秒)
message.publish.age Timers 從數據庫產生事件到發送到Kafka之間的時間(毫秒),精確度爲+/-500ms
replication.queue.time Timers 將一個binlog事件送到處理隊列所用的時間(毫秒)
上述有些指標爲kafka特有的,並不支持所有的生產者。
實驗一番,通過 http 方式獲取監控指標
docker run -p 8080:8080 -it --rm zendesk/maxwell bin/maxwell --user='maxwell' \
--password='123456' --host='10.100.97.246' --producer=kafka \
--kafka.bootstrap.servers='10.100.97.246:9092' --kafka_topic=maxwell --log_level=debug \
--metrics_type=http --metrics_jvm=true --http_port=8080
1
2
3
4
上面的配置大部分與前面的相同,不同的有 -p 8080:8080 docker端口映射,以及 --metrics_type=http --metrics_jvm=true --http_port=8080,配置了通過http方式發佈指標,啓用收集JVM信息,端口爲8080,之後可以通過 http://10.100.97.246:8080/metrics 便可獲取所有的指標
http 方式有四種後綴,分別對應四種不同的格式
endpoint 說明
/metrics 所有指標以JSON格式返回
/prometheus 所有指標以Prometheus格式返回(Prometheus是一套開源的監控&報警&時間序列數據庫的組合)
/healthcheck 返回Maxwell過去15分鐘是否健康
/ping 簡單的測試,返回 pong
如果是通過 JMX 的方式收集Maxwell監控指標,可以 JAVA_OPTS 環境變量配置JMX訪問權限
export JAVA_OPTS="-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.local.only=false \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-Djava.rmi.server.hostname=10.100.97.246"
1
2
3
4
5
6
多個Maxwell實例
在不同的配置下,Maxwell可以在同一個主服務器上運行多個實例。如果希望讓生產者以不同的配置運行,例如將來自不同組的表(table)的事件投遞到不同的Topic中,這將非常有用。Maxwell的每個實例都必須配置一個唯一的client_id,以便區分的binlog位置。
GTID 支持
Maxwell 從1.8.0版本開始支持基於GTID的複製(GTID-based replication),在GTID模式下,Maxwell將在主機更改後透明地選擇新的複製位置。
什麼是GTID Replication?
GTID (Global Transaction ID) 是對於一個已提交事務的編號,並且是一個全局唯一的編號。
從 MySQL 5.6.5 開始新增了一種基於 GTID 的複製方式。通過 GTID 保證了每個在主庫上提交的事務在集羣中有一個唯一的ID。這種方式強化了數據庫的主備一致性,故障恢復以及容錯能力。
在原來基於二進制日誌的複製中,從庫需要告知主庫要從哪個偏移量進行增量同步,如果指定錯誤會造成數據的遺漏,從而造成數據的不一致。藉助GTID,在發生主備切換的情況下,MySQL的其它從庫可以自動在新主庫上找到正確的複製位置,這大大簡化了複雜複製拓撲下集羣的維護,也減少了人爲設置複製位置發生誤操作的風險。另外,基於GTID的複製可以忽略已經執行過的事務,減少了數據發生不一致的風險。
注意事項
timestamp column
maxwell對時間類型(datetime, timestamp, date)都是當做字符串處理的,這也是爲了保證數據一致(比如0000-00-00 00:00:00這樣的時間在timestamp裏是非法的,但mysql卻認,解析成java或者python類型就是null/None)。
如果MySQL表上的字段是 timestamp 類型,是有時區的概念,binlog解析出來的是標準UTC時間,但用戶看到的是本地時間。比如 f_create_time timestamp 創建時間是北京時間 2018-01-05 21:01:01,那麼mysql實際存儲的是 2018-01-05 13:01:01,binlog裏面也是這個時間字符串。如果不做消費者不做時區轉換,會少8個小時。
與其每個客戶端都要考慮這個問題,我覺得更合理的做法是提供時區參數,然後maxwell自動處理時區問題,否則要麼客戶端先需要知道哪些列是timestamp類型,或者連接上原庫緩存上這些類型。
binary column
maxwell可以處理binary類型的列,如blob、varbinary,它的做法就是對二進制列使用 base64_encode,當做字符串輸出到json。消費者拿到這個列數據後,不能直接拼裝,需要 base64_decode。
表結構不同步
如果是拿比較老的binlog,放到新的mysql server上去用maxwell拉去,有可能表結構已經發生了變化,比如binlog裏面字段比 schema_host 裏面的字段多一個。目前這種情況沒有發現異常,比如阿里RDS默認會爲 無主鍵無唯一索引的表,增加一個__##alibaba_rds_rowid##__,在 show create table 和 schema 裏面都看不到這個隱藏主鍵,但binlog裏面會有,同步到從庫。
另外我們有通過git去管理結構版本,如果真有這種場景,也可以應對。
大事務binlog
當一個事物產生的binlog量非常大的時候,比如遷移日表數據,maxwell爲了控制內存使用,會自動將處理不過來的binlog放到文件系統
Using kafka version: 0.11.0.1
21:16:07,109 WARN MaxwellMetrics - Metrics will not be exposed: metricsReportingType not configured.
21:16:07,380 INFO SchemaStoreSchema - Creating maxwell database
21:16:07,540 INFO Maxwell - Maxwell v?? is booting (RabbitmqProducer), starting at Position[BinlogPosition[mysql-bin.006235:24980714],
lastHeartbeat=0]
21:16:07,649 INFO AbstractSchemaStore - Maxwell is capturing initial schema
21:16:08,267 INFO BinlogConnectorReplicator - Setting initial binlog pos to: mysql-bin.006235:24980714
21:16:08,324 INFO BinaryLogClient - Connected to rm-xxxxxxxxxxx.mysql.rds.aliyuncs.com:3306 at mysql-bin.006235/24980714 (sid:637
9, cid:9182598)
21:16:08,325 INFO BinlogConnectorLifecycleListener - Binlog connected.
03:15:36,104 INFO ListWithDiskBuffer - Overflowed in-memory buffer, spilling over into /tmp/maxwell7935334910787514257events
03:17:14,880 INFO ListWithDiskBuffer - Overflowed in-memory buffer, spilling over into /tmp/maxwell3143086481692829045events
1
2
3
4
5
6
7
8
9
10
11
12
但是遇到另外一個問題,overflow隨後就出現異常 EventDataDeserializationException: Failed to deserialize data of EventHeaderV4,當我另起一個maxwell指點之前的binlog postion開始解析,卻有沒有拋異常。事後的數據也表明並沒有數據丟失。
問題產生的原因還不明,Caused by: java.net.SocketException: Connection reset,感覺像讀取 binlog 流的時候還沒讀取到完整的event,異常關閉了連接。這個問題比較頑固,github上面類似問題都沒有達到明確的解決。(這也從側面告訴我們,大表數據遷移,也要批量進行,不要一個insert into .. select 搞定)
03:18:20,586 INFO ListWithDiskBuffer - Overflowed in-memory buffer, spilling over into /tmp/maxwell5229190074667071141events
03:19:31,289 WARN BinlogConnectorLifecycleListener - Communication failure.
com.github.shyiko.mysql.binlog.event.deserialization.EventDataDeserializationException: Failed to deserialize data of EventHeaderV4{time
stamp=1514920657000, eventType=WRITE_ROWS, serverId=2115082720, headerLength=19, dataLength=8155, nextPosition=520539918, flags=0}
at com.github.shyiko.mysql.binlog.event.deserialization.EventDeserializer.deserializeEventData(EventDeserializer.java:216) ~[mys
ql-binlog-connector-java-0.13.0.jar:0.13.0]
at com.github.shyiko.mysql.binlog.event.deserialization.EventDeserializer.nextEvent(EventDeserializer.java:184) ~[mysql-binlog-c
onnector-java-0.13.0.jar:0.13.0]
at com.github.shyiko.mysql.binlog.BinaryLogClient.listenForEventPackets(BinaryLogClient.java:890) [mysql-binlog-connector-java-0
.13.0.jar:0.13.0]
at com.github.shyiko.mysql.binlog.BinaryLogClient.connect(BinaryLogClient.java:559) [mysql-binlog-connector-java-0.13.0.jar:0.13
.0]
at com.github.shyiko.mysql.binlog.BinaryLogClient$7.run(BinaryLogClient.java:793) [mysql-binlog-connector-java-0.13.0.jar:0.13.0
]
at java.lang.Thread.run(Thread.java:745) [?:1.8.0_121]
Caused by: java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:210) ~[?:1.8.0_121]
at java.net.SocketInputStream.read(SocketInputStream.java:141) ~[?:1.8.0_121]
at com.github.shyiko.mysql.binlog.io.BufferedSocketInputStream.read(BufferedSocketInputStream.java:51) ~[mysql-binlog-connector-
java-0.13.0.jar:0.13.0]
at com.github.shyiko.mysql.binlog.io.ByteArrayInputStream.readWithinBlockBoundaries(ByteArrayInputStream.java:202) ~[mysql-binlo
g-connector-java-0.13.0.jar:0.13.0]
at com.github.shyiko.mysql.binlog.io.ByteArrayInputStream.read(ByteArrayInputStream.java:184) ~[mysql-binlog-connector-java-0.13
.0.jar:0.13.0]
at com.github.shyiko.mysql.binlog.io.ByteArrayInputStream.readInteger(ByteArrayInputStream.java:46) ~[mysql-binlog-connector-jav
a-0.13.0.jar:0.13.0]
at com.github.shyiko.mysql.binlog.event.deserialization.AbstractRowsEventDataDeserializer.deserializeLong(AbstractRowsEventDataD
eserializer.java:212) ~[mysql-binlog-connector-java-0.13.0.jar:0.13.0]
at com.github.shyiko.mysql.binlog.event.deserialization.AbstractRowsEventDataDeserializer.deserializeCell(AbstractRowsEventDataD
eserializer.java:150) ~[mysql-binlog-connector-java-0.13.0.jar:0.13.0]
at com.github.shyiko.mysql.binlog.event.deserialization.AbstractRowsEventDataDeserializer.deserializeRow(AbstractRowsEventDataDeserializer.java:132) ~[mysql-binlog-connector-java-0.13.0.jar:0.13.0]
at com.github.shyiko.mysql.binlog.event.deserialization.WriteRowsEventDataDeserializer.deserializeRows(WriteRowsEventDataDeserializer.java:64) ~[mysql-binlog-connector-java-0.13.0.jar:0.13.0]
at com.github.shyiko.mysql.binlog.event.deserialization.WriteRowsEventDataDeserializer.deserialize(WriteRowsEventDataDeserializer.java:56) ~[mysql-binlog-connector-java-0.13.0.jar:0.13.0]
at com.github.shyiko.mysql.binlog.event.deserialization.WriteRowsEventDataDeserializer.deserialize(WriteRowsEventDataDeserializer.java:32) ~[mysql-binlog-connector-java-0.13.0.jar:0.13.0]
at com.github.shyiko.mysql.binlog.event.deserialization.EventDeserializer.deserializeEventData(EventDeserializer.java:210) ~[mysql-binlog-connector-java-0.13.0.jar:0.13.0]
... 5 more
03:19:31,514 INFO BinlogConnectorLifecycleListener - Binlog disconnected.
03:19:31,590 WARN BinlogConnectorReplicator - replicator stopped at position: mysql-bin.006236:520531744 -- restarting
03:19:31,595 INFO BinaryLogClient - Connected to rm-xxxxxx.mysql.rds.aliyuncs.com:3306 at mysql-bin.006236/520531744 (sid:6379, cid:9220521)
tableMapCache
前面講過,如果我只想獲取某幾個表的binlog變更,需要用 include_tables 來過濾,但如果mysql server上現在刪了一個表t1,但我的binlog是從昨天開始讀取,被刪的那個表t1在maxwell啓動的時候是拉取不到表結構的。然後昨天的binlog裏面有 t1 的變更,因爲找不到表結構給來組裝成json,會拋異常。
手動在 maxwell.tables/columns 裏面插入記錄是可行的。但這個問題的根本是,maxwell在binlog過濾的時候,只在處理row_event的時候,而對 tableMapCache 要求binlog裏面的所有表都要有。
自己(seanlook)提交了一個commit,可以在做 tableMapCache 的時候也僅要求緩存 include_dbs/tables 這些表: https://github.com/seanlook/maxwell/commit/2618b70303078bf910a1981b69943cca75ee04fb
提高消費性能
在用rabbitmq時,routing_key 是 %db%.%table%,但某些表產生的binlog增量非常大,就會導致各隊列消息量很不平均,目前因爲還沒做到事務xid或者thread_id級別的併發回放,所以最小隊列粒度也是表,儘量單獨放一個隊列,其它數據量小的合在一起。
binlog
Maxwell 在 maxwell 庫中維護了 binlog 的位移等信息,由於一些原因譬如 reset master;,導致 maxwell 庫中的記錄與實際的binlog對不上,這時將報異常,這是可以手動修正binlog位移或者直接清空/刪除 maxwell 庫重建
com.github.shyiko.mysql.binlog.network.ServerException: Could not find first log file name in binary log index file
at com.github.shyiko.mysql.binlog.BinaryLogClient.listenForEventPackets(BinaryLogClient.java:885)
at com.github.shyiko.mysql.binlog.BinaryLogClient.connect(BinaryLogClient.java:564)
at com.github.shyiko.mysql.binlog.BinaryLogClient$7.run(BinaryLogClient.java:796)
at java.lang.Thread.run(Thread.java:748)
以及
com.github.shyiko.mysql.binlog.network.ServerException: A slave with the same server_uuid/server_id as this slave has connected to the master; the first event 'mysql-bin.000001' at 760357, the last event read from './mysql-bin.000001' at 1888540, the last byte read from './mysql-bin.000001' at 1888540.
at com.github.shyiko.mysql.binlog.BinaryLogClient.listenForEventPackets(BinaryLogClient.java:885)
at com.github.shyiko.mysql.binlog.BinaryLogClient.connect(BinaryLogClient.java:564)
at com.github.shyiko.mysql.binlog.BinaryLogClient$7.run(BinaryLogClient.java:796)
at java.lang.Thread.run(Thread.java:748)
————————————————
版權聲明:本文爲CSDN博主「小旋鋒」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/wwwdc1012/article/details/88388552