關於流資料上的事務操作
概述
最近Flink母公司Data Artisans釋出了一篇部落格關於一個新的元件Streaming Ledger,給出了流資料的事務解決方案(就是常說的資料庫的事務,滿足ACID,隔離級別為Serializable)。
使用姿勢
- 舉例使用經典的轉賬和存款問題
- 它是基於Flink的,關於Flink任務初始化的一些內容就不放在這裡了
- 首先建立StreamingLedger。
// start building the transactional streams
StreamingLedger tradeLedger = StreamingLedger.create("simple trade example");
- 第二定義所需要用到的狀態和相應的KV型別,這裡分別是賬戶和賬單明細。
// define transactors on states
tradeLedger.usingStream(deposits, "deposits")
.apply(new DepositHandler())
.on(accounts, DepositEvent::getAccountId, "account", READ_WRITE)
.on(books, DepositEvent::getBookEntryId, "asset", READ_WRITE);
- 第三步是分別將輸入流,具體的事務操作,操作的狀態、從事件中獲取key的方法、別名(會在事務操作,即此處的TxnHandler中體現)、和許可權分別注入StreamLedger並輸出為一個sideOutput。
// produce transactions stream
DataStream<TransactionEvent> transfers = env.addSource(new TransactionsGenerator(1));
OutputTag<TransactionResult> transactionResults = tradeLedger.usingStream(transfers, "transactions")
.apply(new TxnHandler())
.on(accounts, TransactionEvent::getSourceAccountId, "source-account", READ_WRITE)
.on(accounts, TransactionEvent::getTargetAccountId, "target-account", READ_WRITE)
.on(books, TransactionEvent::getSourceBookEntryId, "source-asset", READ_WRITE)
.on(books, TransactionEvent::getTargetBookEntryId, "target-asset", READ_WRITE)
.output();
- 第四步是根據sideOuput的OutputTag輸出結果,到這裡,除了TxnHandler需要去實現以外,主幹邏輯已經完成了。
// compute the resulting streams.
ResultStreams resultsStreams = tradeLedger.resultStreams();
// output to the console
resultsStreams.getResultStream(transactionResults).print();
- 最後就是實現TxnHandler, 具體的轉賬和寫入明細的邏輯都在這裡。值得注意的是狀態的獲取依賴於上一步中在StreamLedger注入的別名,更新完狀態之後再輸出。
private static final class TxnHandler extends TransactionProcessFunction<TransactionEvent, TransactionResult> {
private static final long serialVersionUID = 1;
@ProcessTransaction
public void process(
final TransactionEvent txn,
final Context<TransactionResult> ctx,
final @State("source-account") StateAccess<Long> sourceAccount,
final @State("target-account") StateAccess<Long> targetAccount,
final @State("source-asset") StateAccess<Long> sourceAsset,
final @State("target-asset") StateAccess<Long> targetAsset) {
final long sourceAccountBalance = sourceAccount.readOr(ZERO);
final long sourceAssetValue = sourceAsset.readOr(ZERO);
final long targetAccountBalance = targetAccount.readOr(ZERO);
final long targetAssetValue = targetAsset.readOr(ZERO);
// check the preconditions
if (sourceAccountBalance > txn.getMinAccountBalance()
&& sourceAccountBalance > txn.getAccountTransfer()
&& sourceAssetValue > txn.getBookEntryTransfer()) {
// compute the new balances
final long newSourceBalance = sourceAccountBalance - txn.getAccountTransfer();
final long newTargetBalance = targetAccountBalance + txn.getAccountTransfer();
final long newSourceAssets = sourceAssetValue - txn.getBookEntryTransfer();
final long newTargetAssets = targetAssetValue + txn.getBookEntryTransfer();
// write back the updated values
sourceAccount.write(newSourceBalance);
targetAccount.write(newTargetBalance);
sourceAsset.write(newSourceAssets);
targetAsset.write(newTargetAssets);
// emit result event with updated balances and flag to mark transaction as processed
ctx.emit(new TransactionResult(txn, true, newSourceBalance, newTargetBalance));
}
else {
// emit result with unchanged balances and a flag to mark transaction as rejected
ctx.emit(new TransactionResult(txn, false, sourceAccountBalance, targetAccountBalance));
}
}
}
原理
- 其實我的第一想法是,臥槽好牛逼,這得涉及到分散式事務。把repo clone下來之後發現包含例子只有2000多行程式碼,一下子震驚了。但是實際的實現還是比較簡單地,當然也肯定會帶來一些問題。
- 實際上上面這些API會轉換為一個source,一個sink,兩個map和一個包含了SerialTransactor(ProcessFunction的實現)的運算元。
- 在這邊展示幾行程式碼應該就能明白是如何做到的。關鍵在於forceNonParallel,這就讓所有事情都變得明瞭了,事實上就是把狀態全部都託管到一個並行度為1的運算元上,處理的時候也是序列的,這裡我才反應過來關鍵在於隔離級別是Serializable。這裡帶來的問題就是所有狀態都儲存在一個節點,並且不能支援水平擴充套件,所能支撐的吞吐量也不能通過加機器來提升。
SingleOutputStreamOperator<Void> resultStream = input
.process(new SerialTransactor(specs(streamLedgerSpecs), sideOutputTags))
.name(serialTransactorName)
.uid(serialTransactorName + "___SERIAL_TX")
.forceNonParallel()
.returns(Void.class);
感想
其實看到這個功能的第一感覺是很牛逼,但是仔細看過了它的實現覺得真正應用上可能會有不少問題。因為對於最重要的處理事務的那個運算元來說,本質上它並不是Scalable的,沒有辦法橫向擴充套件。不過從功能上來說,確實引出了一個新的發展方向,希望以後還能看到有更優的解決方案,比如針對另外兩種隔離級別Read Committed和Repeatable read。
相關文章
- 關於事務對資料塊的操作過程的分析和試驗(1)(轉)
- 關於事務對資料塊的操作過程的分析和試驗(2)(轉)
- 關於資料庫事務併發的理解和處理資料庫
- 關於資料庫事務和鎖的一些分析資料庫
- 關於快取的那些風流事兒快取
- 網上關於碰撞的資料
- 關於SqlServer資料表操作SQLServer
- 關於 MySQL 的巢狀事務MySql巢狀
- 關於分散式事務的理解分散式
- 關於大資料的那些事兒(一)大資料
- 關於 Web 快取的那些風流事兒Web快取
- 關於資料庫事務的基本概念,我還弄不明白。資料庫
- SQL Server 表的管理_關於事務操作的詳解(案例程式碼)SQLServer
- 關於爛程式碼的那些事(上)
- 關於操作失誤的資料修復
- 關於SPRING的事務管理_求助Spring
- 關於jdon 的事務處理疑惑?
- 關於日誌事務的問題
- 關於事務的儲存過程儲存過程
- ACCESS資料庫C#操作類(包含事務)資料庫C#
- 關於資料視覺化那些事視覺化
- .NET關於資料庫操作的類-囊括所有的操作資料庫
- 請教一個關於多資料來源的分散式事務問題?分散式
- Spring 下,關於動態資料來源的事務問題的探討Spring
- 關於資料庫操作的封裝程式碼資料庫封裝
- 關於資料庫操作多個操作組合的處理資料庫
- 關於mysqldump備份非事務表的注意事項MySql
- Swift 中關於操作符的那些事兒Swift
- 2.8.1.1 關於資料庫服務資料庫
- MySQL關於事務常見的問題MySql
- 關於事務的英文說明 Transaction OverviewView
- 關於事務補償機制
- 資料庫事務以及事務的四個特性資料庫
- 關於資料庫事務和鎖的必會知識點,你掌握了多少?資料庫
- 關於Spring+Mybatis事務管理中資料來源的思考SpringMyBatis
- mongodb 的事務性操作MongoDB
- 資料庫事務的特徵資料庫特徵
- Redis事務操作Redis