1. 簡介
Databus是一個低延遲、可靠的、支持事務的、保持一致性的數據變更抓取系統。由LinkedIn於2013年開源。Databus通過挖掘數據庫日誌的方式,將數據庫變更實時、可靠的從數據庫拉取出來,業務可以通過定製化client實時獲取變更並進行其他業務邏輯。
Databus有以下特點:
- 數據源和消費者之間的隔離。
- 數據傳輸能保證順序性和至少一次交付的高可用性。
- 從變化流的任意時間點進行消費,包括通過bootstrap獲取所有數據。
- 分區消費
- 源一致性保存,消費不成功會一直消費直到消費成功
2. 功能&特性
- 來源獨立:Databus支持多種數據來源的變更抓取,包括Oracle和MySQL。
- 可擴展、高度可用:Databus能擴展到支持數千消費者和事務數據來源,同時保持高度可用性。
- 事務按序提交:Databus能保持來源數據庫中的事務完整性,並按照事務分組和來源的提交順尋交付變更事件。
- 低延遲、支持多種訂閱機制:數據源變更完成後,Databus能在毫秒級內將事務提交給消費者。同時,消費者使用Databus中的服務器端過濾功能,可以只獲取自己需要的特定數據。
- 無限回溯:對消費者支持無限回溯能力,例如當消費者需要產生數據的完整拷貝時,它不會對數據庫產生任何額外負擔。當消費者的數據大大落後於來源數據庫時,也可以使用該功能。
3. 使用場景舉例
BUSSINESS1 和 BUSSINESS2 是兩個不同的業務邏輯,他們的變更需要同時寫入到 DB 和 CACHE ,那麼當他們同時修改同一個數據的時候是否能保證數據的一致性呢?可以發現如果按照下圖標明的順序進行操作並不能保證數據的一致性!
還有一個問題是變更完DB之後,更新CACHE失敗怎麼辦?如果忽略的話,會造成後續讀取到CACHE中舊的數據,如果重試的話,業務代碼會寫得更加複雜。針對這些場景,如果沒有一個強一致協議是很難解決掉的。如果要業務邏輯去實現這些晦澀的一致性協議,卻又是不現實的。
現在,有了Databus,上面提到的這些一致性問題就都沒有了,並且那些冗長的雙寫邏輯也可以去掉了,如下圖所示:
4. 系統整體架構與主要組件
4.1 系統整體架構
上圖中介紹了Databus系統的構成,包括Relays、bootstrap服務和Client lib等。Bootstrap服務中包括Bootstrap Producer和Bootstrap Server。快速變化的消費者直接從Relay中取事件。如果一個消費者的數據更新大幅落後,它要的數據就不在Relay的日誌中,而是需要請求Bootstrap服務,返回的將會是自消費者上次處理變更之後的所有數據變更快照。
- Source Databases:MySQL以及Oracle數據源
- Relays:負責抓取和存儲數據庫變更,全內存存儲,也可配置使用mmap內存映射文件方式
- Schema Registry:數據庫數據類型到Databus數據類型的一個轉換表
- Bootstrap Service:一個特殊的客戶端,功能和Relays類似,負責存儲數據庫變更,主要是磁盤存儲
- Application:數據庫變更消費邏輯,從Relay中拉取變更,並消費變更
- Client Lib:提供挑選關注變更的API給消費邏輯
- Consumer Code:變更消費邏輯,可以是自身消費或者再將變更發送至下游服務
4.2 主要組件及功能
上圖系統整體架構圖畫的比較簡略,下載源碼觀察項目結構後不難發現databus的主要由以下四個組件構成:
- Databus Relay:
- 從源數據庫中的Databus源中讀取變化的行並序列化爲Databus變化事件保存到內存緩衝區中。
- 監聽Databus客戶端的請求(包括引導程序的請求)並傳輸Databus數據變化事件。
- Databus Client:
- 在Relay上檢查新的數據變化事件和處理特定的業務邏輯的回調。
- 如果它們在relay後面落下太遠,到引導程序服務運行一個追溯查詢。
- 單獨的客戶端可以處理全部的Databus流,它們也可以作爲集羣的一部分而每個客戶端處理一部分流。
- Databus Bootstrap Producer:
- 只是一個特殊的客戶端。
- 檢查Relay上的新的數據變化事件。
- 保存數據變化事件到Mysql數據庫,Mysql數據庫用於引導程序和爲了客戶端追溯數據。
- Databus Bootstrap Server:
- 監聽來自Databus客戶端的請求併爲了引導和追溯返回一個超長的回溯的數據變化事件。
5. Databus Relay和Databus Client詳細分析
5.1 Databus Relay
5.1.1 架構與組件功能
-
Databus Event Producer(DBEP):定期從數據庫中查詢變更,如果檢測到變更,它將讀取數據庫中的所有已更改的行,並將其轉換爲Avro記錄。因爲數據庫數據類型和Databus數據類型不一致,因此需要 Schema Registry 做轉換。
-
SCN(System Change Number):系統改變號,是數據庫中非常重要的一個數據結構。SCN用以標識數據庫在某個確切時刻提交的版本。在事務提交時,它被賦予一個唯一的標識事務的SCN。
-
Event Buffers:按照SCN的順序存儲databus事件,buffer可以是純內存的,也可以是mmap到文件系統的。每個buffer在內存中還有一個對應的SCN Index和一個MaxSCN reader/writer,SCN Index可以加快查詢指定事件的速度。
-
Request Processor:通過監聽Netty的channel,實現收發client的請求。
-
MaxSCN Reader/Writer:用於跟蹤DBEP的處理進度;Reader在Databus啓動的時候會讀取存儲的文件上一次DBEP處理的位置,當Databus從DBEP中讀取變更存儲到Event Buffers時,Writer就會最後一個SCN寫入到文件中存儲,這樣就能保證下次啓動可以從正確的位置讀取數據庫變更。
-
JMX(Java Management Extensions):支持標準的Jetty容器,databus提供了多個Mbean來監控relay
- ContainerStatsMBean
- DbusEventsTotalStatsMBean
- DbusEventsStatisticsCollectorMBean
-
RESTFul Interface:Realy提供了相關http接口供外部調用,Client與Relay建立http長連接,並從Relay拉取Event。
5.1.2 源碼分析
-
ServerContainer._globalStatsThread:統計信息的線程
-
OpenReplicatorEventProducer.EventProducerThread:針對mysql binlog日誌的Event生產者線程,每個source一個線程,持有_orListener,管理和數據庫的連接,將變更寫入到Event Buffer裏。
-
EventProducerThread啓動後會初始化類型爲OpenReplicator的日誌解析對象開始解析日誌,同時初始化類型爲ORListener的_orListener開始監聽,代碼如下:
@Override public void run() { _eventBuffer.start(_sinceScn); _startPrevScn.set(_sinceScn); initOpenReplicator(_sinceScn); try { boolean started = false; while (!started) { try { _or.start(); started = true; } catch (Exception e) { _log.error("Failed to start OpenReplicator: " + e); _log.warn("Sleeping for 1000 ms"); Thread.sleep(1000); } } _orListener.start(); } catch (Exception e) { _log.error("failed to start open replicator: " + e.getMessage(), e); return; } }
初始化方法如下:
void initOpenReplicator(long scn) { int offset = offset(scn); int logid = logid(scn); String binlogFile = String.format("%s.%06d", _binlogFilePrefix, logid); // we should use a new ORListener to drop the left events in binlogEventQueue and the half processed transaction. _orListener = new ORListener(_sourceName, logid, _log, _binlogFilePrefix, _producerThread, _tableUriToSrcIdMap, _tableUriToSrcNameMap, _schemaRegistryService, 200, 100L); _or.setBinlogFileName(binlogFile); _or.setBinlogPosition(offset); _or.setBinlogEventListener(_orListener); //must set transport and binlogParser to null to drop the old connection environment in reinit case _or.setTransport(null); _or.setBinlogParser(null); _log.info("Connecting to OpenReplicator " + _or.getUser() + "@" + _or.getHost() + ":" + _or.getPort() + "/" + _or.getBinlogFileName() + "#" + _or.getBinlogPosition()); }
EventProducerThread._orListener:監聽數據庫變更,將變更轉換爲Avro記錄,寫入到transaction裏面,最終調用_producerThread的onEndTransaction()方法將事務裏的事件寫入到Event Buffer裏,代碼如下:
@Override public void onEndTransaction(Transaction txn) throws DatabusException { try { addTxnToBuffer(txn); _maxSCNReaderWriter.saveMaxScn(txn.getIgnoredSourceScn()!=-1 ? txn.getIgnoredSourceScn() : txn.getScn()); } catch (UnsupportedKeyException e) { _log.fatal("Got UnsupportedKeyException exception while adding txn (" + txn + ") to the buffer", e); throw new DatabusException(e); } catch (EventCreationException e) { _log.fatal("Got EventCreationException exception while adding txn (" + txn + ") to the buffer", e); throw new DatabusException(e); } }
FileMaxSCNHandler負責讀寫SCN,注意在寫入文件時會將原有文件重命名爲XXX.temp,原因是爲了防止在更新文件的時候發生錯誤,導致SCN丟失,代碼如下:
-
private void writeScnToFile() throws IOException { long scn = _scn.longValue(); File dir = _staticConfig.getScnDir(); if (! dir.exists() && !dir.mkdirs()) { throw new IOException("unable to create SCN file parent:" + dir.getAbsolutePath()); } // delete the temp file if one exists File tempScnFile = new File(_scnFileName + TEMP); if (tempScnFile.exists() && !tempScnFile.delete()) { LOG.error("unable to erase temp SCN file: " + tempScnFile.getAbsolutePath()); } File scnFile = new File(_scnFileName); if (scnFile.exists() && !scnFile.renameTo(tempScnFile)) { LOG.error("unable to backup scn file"); } if (!scnFile.createNewFile()) { LOG.error("unable to create new SCN file:" + scnFile.getAbsolutePath()); } FileWriter writer = new FileWriter(scnFile); writer.write(Long.toString(scn)); writer.write(SCN_SEPARATOR + new Date().toString()); writer.flush(); writer.close(); LOG.debug("scn persisted: " + scn); }
-
以源碼例子中PersonRelayServer的主類啓動爲起點,大致的啓動流程如下:
PersonRelayServer主方法 -> new DatabusRelayMain實例 -> 調用initProducers方法初始化生產者->根據配置調用addOneProducer增加生產者->new DbusEventBufferAppendable獲得Event Buffer->new EventProducerServiceProvider實例-> 調用createProducer獲得OpenReplicatorEventProducer->OpenReplicatorEventProducer中包含 EventProducerThread->啓動線程開始獲取Event
5.2 Databus Client
5.2.1 架構與組件功能
-
Relay Puller:負責從relay拉取數據,具體工作有挑選relay,請求source,請求Register,校驗schema,設置dispatcher等。
-
Dispatcher:從event buffers中讀取事件,調用消費邏輯的回調,主要職責有:
- 判斷回調是否正確,回調失敗後會進行重試,重試次數超限後拋出異常
- 監控錯誤和超時
- 持久化checkpoint
-
Checkpoint persistence Provider:checkpoint是消費者消費變更記錄點的位置,負責將checkpoint持久化到本地,保證下次啓動後可以從正常的位置pull event。
-
Event Callback:調用消費者自定義業務邏輯代碼。
-
Bootstrap Puller:負責從Bootstrap servers拉取數據,功能類似Relay Puller。
5.2.2 源碼分析
執行Client的啓動腳本後會調用main方法,main方法會根據命令行參數中指定的屬性文件創建StaticConfig類,然後配置類創建dbusHttpClient實例來與Relay進行通信,參數defaultConfigBuilder爲默認配置類信息,可以爲空,代碼如下:
public static DatabusHttpClientImpl createFromCli(String[] args, Config defaultConfigBuilder) throws Exception
{
Properties startupProps = ServerContainer.processCommandLineArgs(args);
if (null == defaultConfigBuilder) defaultConfigBuilder = new Config();
ConfigLoader<StaticConfig> staticConfigLoader =
new ConfigLoader<StaticConfig>("databus.client.", defaultConfigBuilder);
StaticConfig staticConfig = staticConfigLoader.loadConfig(startupProps);
DatabusHttpClientImpl dbusHttpClient = new DatabusHttpClientImpl(staticConfig);
return dbusHttpClient;
}
設置要連接的Relay信息,然後通過參數defaultConfigBuilder傳遞給dbusHttpClient,代碼如下:
DatabusHttpClientImpl.Config configBuilder = new DatabusHttpClientImpl.Config();
configBuilder.getRuntime().getRelay("1").setHost("localhost");
configBuilder.getRuntime().getRelay("1").setPort(11115);
configBuilder.getRuntime().getRelay("1").setSources(PERSON_SOURCE);
}
啓動databus client過程如下:
protected void doStart()
{
_controlLock.lock();
try
{
// 綁定並開始接收來到的連接
int portNum = getContainerStaticConfig().getHttpPort();
_tcpChannelGroup = new DefaultChannelGroup();
_httpChannelGroup = new DefaultChannelGroup();
_httpServerChannel = _httpBootstrap.bind(new InetSocketAddress(portNum));
InetSocketAddress actualAddress = (InetSocketAddress)_httpServerChannel.getLocalAddress();
_containerPort = actualAddress.getPort();
// 持久化端口號 (文件名對容器來說必須唯一)
File portNumFile = new File(getHttpPortFileName());
portNumFile.deleteOnExit();
try {
FileWriter portNumFileW = new FileWriter(portNumFile);
portNumFileW.write(Integer.toString(_containerPort));
portNumFileW.close();
LOG.info("Saving port number in " + portNumFile.getAbsolutePath());
} catch (IOException e) {
throw new RuntimeException(e);
}
_httpChannelGroup.add(_httpServerChannel);
LOG.info("Serving container " + getContainerStaticConfig().getId() +
" HTTP listener on port " + _containerPort);
if (_containerStaticConfig.getTcp().isEnabled())
{
int tcpPortNum = _containerStaticConfig.getTcp().getPort();
_tcpServerChannel = _tcpBootstrap.bind(new InetSocketAddress(tcpPortNum));
_tcpChannelGroup.add(_tcpServerChannel);
LOG.info("Serving container " + getContainerStaticConfig().getId() +
" TCP listener on port " + tcpPortNum);
}
_nettyShutdownThread = new NettyShutdownThread();
Runtime.getRuntime().addShutdownHook(_nettyShutdownThread);
// 5秒後開始producer線程
if (null != _jmxConnServer && _containerStaticConfig.getJmx().isRmiEnabled())
{
try
{
_jmxShutdownThread = new JmxShutdownThread(_jmxConnServer);
Runtime.getRuntime().addShutdownHook(_jmxShutdownThread);
_jmxConnServer.start();
LOG.info("JMX server listening on port " + _containerStaticConfig.getJmx().getJmxServicePort());
}
catch (IOException ioe)
{
if (ioe.getCause() != null && ioe.getCause() instanceof NameAlreadyBoundException)
{
LOG.warn("Unable to bind JMX server connector. Likely cause is that the previous instance was not cleanly shutdown: killed in Eclipse?");
if (_jmxConnServer.isActive())
{
LOG.warn("JMX server connector seems to be running anyway. ");
}
else
{
LOG.warn("Unable to determine if JMX server connector is running");
}
}
else
{
LOG.error("Unable to start JMX server connector", ioe);
}
}
}
_globalStatsThread.start();
}
catch (RuntimeException ex)
{
LOG.error("Got runtime exception :" + ex, ex);
throw ex;
}
finally
{
_controlLock.unlock();
}
}
6. Databus for Mysql實踐
6.1 相關解釋
-
實現原理:通過解析mysql的binlog日誌來獲取變更事件,解析過程利用Java開源工具OpenReplicator,Open Replicator 首先連接到MySQL(就像一個普通的MySQL Slave一樣),然後接收和分析binlog,最終將分析得出的binlog events以回調的方式通知應用,所有的Event實現了BinlogEventV4接口。
-
binlog 格式:Databus設計爲針對Row格式日誌進行解析
- Statement:基於SQL語句的複製(statement-based replication,SBR)
- Row:基於行的複製(row-based replication,RBR)
- Mixed:混合模式複製(mixed-based replication,MBR)
-
SCN的確定:64bits組成,高32位表示binlog的文件序號,低32位代表event在binlog文件的offset,例如在 mysql-bin.000001文件中 offset爲 4的scn表示爲(1 << 32) | 4 = 4294967300
6.2 數據庫環境配置
-
安裝mysql數據庫,本次使用mysql-5.5.56版本。
-
查看數據庫是否開啓binlog,如果binlog沒有開啓,可以通過set sql_log_bin=1命令來啓用;如果想停用binlog,可以使用set sql_log_bin=0。
-
配置數據庫binlog_format=ROW, show variables like ‘binlog_format‘可查看日誌格式, set globle binlog_format=ROW’可設置,通過修改my.cnf文件也可以,增加或修改行binlog_format=ROW即可。
-
binlog_checksum設置爲空,show global variables like ‘binlog_checksum’命令可查看,set binlog_checksum=none可設置。
-
在mysql上創建名爲or_test的數據庫,or_test上創建表名爲person的表,定義如下:
6.3 Demo配置與運行
6.3.1 下載源碼
-
Databus官網下載源碼,下載地址https://github.com/linkedin/databus.git,我們需要用到databus目錄下的databus2-example文件夾,在此基礎上改造並運行,目錄結構及介紹如下:
- database:數據庫模擬相關的腳本和工具
- databus2-example-bst-producer-pkg:bootstrap producer的屬性配置文件夾,包括bootstrap producer和log4j屬性文件,build腳本以及bootstrap producer的啓動和停止腳本。
- databus2-example-client-pkg:client的屬性配置文件夾,包括各種屬性文件和啓動和停止腳本。
- databus2-example-client:client源代碼,包含啓動主類和消費者代碼邏輯。
- databus2-example-relay-pkg:relay的屬性配置文件夾,包含監控的表的source信息和Avro schema。
- databus2-example-relay:relay的啓動主類。
- schemas_registry:存放表的avsc文件。
6.3.2 Relay端的操作
-
配置Relay屬性文件:databus2-example-relay-pkg/conf/relay-or-person.properties的內容如下配置,包括端口號,buffer存儲策略,maxScn存放地址等信息:
databus.relay.container.httpPort=11115 databus.relay.container.jmx.rmiEnabled=false databus.relay.eventBuffer.allocationPolicy=DIRECT_MEMORY databus.relay.eventBuffer.queuePolicy=OVERWRITE_ON_WRITE databus.relay.eventLogReader.enabled=false databus.relay.eventLogWriter.enabled=false databus.relay.schemaRegistry.type=FILE_SYSTEM databus.relay.schemaRegistry.fileSystem.schemaDir=./schemas_registry databus.relay.eventBuffer.maxSize=1024000000 databus.relay.eventBuffer.readBufferSize=10240 databus.relay.eventBuffer.scnIndexSize=10240000 databus.relay.physicalSourcesConfigsPattern=../../databus2-example/databus2-example-relay-pkg/conf/sources-or-person.json databus.relay.dataSources.sequenceNumbersHandler.file.scnDir=/tmp/maxScn databus.relay.startDbPuller=true
-
配置被監控表的source信息:databus2-example-relay-pkg/conf/sources-or-person.json的內容如下配置,其中URI format:mysql://username/password@mysql_host[:mysql_port]/mysql_serverid/binlog_prefix,注意%2F爲轉義字符,用戶名爲root,數據庫密碼爲123。
{ "name" : "person", "id" : 1, "uri" : "mysql://root%2F123@localhost:3306/1/mysql-bin", "slowSourceQueryThreshold" : 2000, "sources" : [ { "id" : 40, "name" : "com.linkedin.events.example.or_test.Person", "uri": "or_test.person", "partitionFunction" : "constant:1" } ] }
-
databus2-example-relay-pkg/schemas_registry/下定義person的Avro schema文件 com.linkedin.events.example.or_test.Person.1.avsc,其中1表示版本(Databus目前沒有針對mysql提供生成Avro schema文件的工具,所以只能手工編寫)具體內容如下所示:
{ "name" : "Person_V1", "doc" : "Auto-generated Avro schema for sy$person. Generated at Dec 04, 2012 05:07:05 PM PST", "type" : "record", "meta" : "dbFieldName=person;pk=id;", "namespace" : "com.linkedin.events.example.or_test", "fields" : [ { "name" : "id", "type" : [ "long", "null" ], "meta" : "dbFieldName=ID;dbFieldPosition=0;" }, { "name" : "firstName", "type" : [ "string", "null" ], "meta" : "dbFieldName=FIRST_NAME;dbFieldPosition=1;" }, { "name" : "lastName", "type" : [ "string", "null" ], "meta" : "dbFieldName=LAST_NAME;dbFieldPosition=2;" }, { "name" : "birthDate", "type" : [ "long", "null" ], "meta" : "dbFieldName=BIRTH_DATE;dbFieldPosition=3;" }, { "name" : "deleted", "type" : [ "string", "null" ], "meta" : "dbFieldName=DELETED;dbFieldPosition=4;" } ] }
-
註冊Avro schema到index.schemas_registry文件,databus2-example-relay-pkg/schemas_registry/index.schemas_registry文件中添加行com.linkedin.events.example.or_test.Person.1.avsc ,每定義一個Avro schema都需要添加進去,relay運行時會到此文件中查找表對應的定義的Avro schema。
6.3.3 Client端的操作
-
配置Client屬性文件:databus2-example-client-pkg/conf/client-person.properties的內容如下配置,包括端口號,buffer存儲策略,checkpoint持久化等信息:
databus.relay.container.httpPort=11125 databus.relay.container.jmx.rmiEnabled=false databus.relay.eventBuffer.allocationPolicy=DIRECT_MEMORY databus.relay.eventBuffer.queuePolicy=BLOCK_ON_WRITE databus.relay.schemaRegistry.type=FILE_SYSTEM databus.relay.eventBuffer.maxSize=10240000 databus.relay.eventBuffer.readBufferSize=1024000 databus.relay.eventBuffer.scnIndexSize=1024000 databus.client.connectionDefaults.pullerRetries.initSleep=1 databus.client.checkpointPersistence.fileSystem.rootDirectory=./personclient-checkpoints databus.client.checkpointPersistence.clearBeforeUse=false databus.client.connectionDefaults.enablePullerMessageQueueLogging=true
-
databus2-example-client/src/main/java下的PersonConsumer類是消費邏輯回調代碼,主要是取出每一個event後依次打印每個字段的名值對,主要代碼如下:
private ConsumerCallbackResult processEvent(DbusEvent event, DbusEventDecoder eventDecoder) { GenericRecord decodedEvent = eventDecoder.getGenericRecord(event, null); try { Utf8 firstName = (Utf8)decodedEvent.get("firstName"); Utf8 lastName = (Utf8)decodedEvent.get("lastName"); Long birthDate = (Long)decodedEvent.get("birthDate"); Utf8 deleted = (Utf8)decodedEvent.get("deleted"); LOG.info("firstName: " + firstName.toString() + ", lastName: " + lastName.toString() + ", birthDate: " + birthDate + ", deleted: " + deleted.toString()); } catch (Exception e) { LOG.error("error decoding event ", e); return ConsumerCallbackResult.ERROR; } return ConsumerCallbackResult.SUCCESS; }
-
databus2-example-client/src/main/java下的PersonClient類是relay的啓動主類,主要是設置啓動Client的配置信息,將消費者實例註冊到監聽器中,後續可對其進行回調,主要代碼如下:
public static void main(String[] args) throws Exception { DatabusHttpClientImpl.Config configBuilder = new DatabusHttpClientImpl.Config(); //Try to connect to a relay on localhost configBuilder.getRuntime().getRelay("1").setHost("localhost"); configBuilder.getRuntime().getRelay("1").setPort(11115); configBuilder.getRuntime().getRelay("1").setSources(PERSON_SOURCE); //Instantiate a client using command-line parameters if any DatabusHttpClientImpl client = DatabusHttpClientImpl.createFromCli(args, configBuilder); //register callbacks PersonConsumer personConsumer = new PersonConsumer(); client.registerDatabusStreamListener(personConsumer, null, PERSON_SOURCE); client.registerDatabusBootstrapListener(personConsumer, null, PERSON_SOURCE); //fire off the Databus client client.startAndBlock(); }
6.3.4 build-啓動-測試
-
Build:Databus採用gradle進行編譯,所以需要安裝gradle環境,安裝安成後進入databus根目錄執行命令gradle -Dopen_source=true assemble即可完成build,成功後在databus根目錄下生成名爲build的文件夾
-
啓動Relay:
- cd build/databus2-example-relay-pkg/distributions
- tar -zxvf databus2-example-relay-pkg.tar.gz解壓
- 執行啓動腳本 ./bin/start-example-relay.sh or_person -Y ./conf/sources-or-person.json
- 執行命令 curl -s http://localhost:11115/sources返回如下內容說明啓動成功:
- 啓動Client:
- cd build/databus2-example-client-pkg/distributions
- tar -zxvf databus2-example-client-pkg.tar.gz解壓
- 執行啓動腳本 ./bin/start-example-client.sh person
- 執行命令 curl http://localhost:11115/relayStats/outbound/http/clients返回如下內容說明啓動成功:
-
測試:
Relay和Client啓動成功後,就已經開始對person表進行數據變更捕獲了,現在向person表插入一條如下記錄:
databus2-example-relay-pkg/distributions/logs下的relay.log記錄如下:
databus2-example-client-pkg/distributions/logs下的client.log記錄如下:
可以看到已經可以抓取到改變的數據了!
7. 總結
遇到的問題:
- 主要是屬性文件的配置問題,包括source-or-person.json, schemas_registry的文件缺失或配置錯誤。
- 腳本方式啓動時JVM無法創建,由於腳本啓動時包含了自定義的JVM參數,與系統環境不符導致啓動失敗,去掉相關參數後正常啓動。
- Relay可以獲取增刪改查的Event,但Client只能解析到更新操作的Event,主要原因是Mysql默認的binlog_format=MIXED,而databus的設計是針對ROW格式的binlog,修改格式後可正常解析。
- Windows平臺無法使用,啓動方式是用腳本啓動,腳本啓動時包含命令行參數較多,啓動後無法進行調試,只能通過對日誌觀察的方式來進行。