“数据如果是流动的现金,那么每一笔交易都需要万分谨慎。 在Data+AI驱动的时代,如何确保数据的准确性和一致性?这让我想起了一个有趣的场景: 假如你是一家银行的出纳员,每天要处理成百上千笔交易。突然停电了,你正在处理的那笔百万转账进行到一半...冷汗瞬间就下来了!如果是你,你最担心什么?没错,就是数据一致性! 今天,我们就来聊聊数据世界里的"保险箱"——事务机制,基于Doris带你深入理解这套精妙的数据保护机制,让你在处理数据时不再提心吊胆。 无论你是刚接触数据的魔芋手,还是经验丰富的资深数据架构师,相信都能从中获得独特的启发。
"又丢数据了!"架构师小华深夜接到一个紧急电话,运维反馈生产环境出现数据不一致问题。
而作为一个经验丰富的Apache Doris技术内核专家,小勇知道这种问题往往与事务处理有关。
让我们一起走进Doris 2.1版本后的事务世界,看看它如何保护数据一致性。
在Doris中,事务好比盾牌,守护数据的完整性和一致性。这个盾牌有两种形态:显式事务和隐式事务。目前 Doris 不支持嵌套事务。
显式事务需要我们主动操控,就像驾驶一辆手动挡汽车。通过BEGIN指令启动事务,执行一系列数据操作,最后通过COMMIT确认提交或ROLLBACK撤销更改。这种方式让我们能够精确控制数据变更的边界。
隐式事务则像自动挡汽车,它在每个独立的查询和DDL操作中自动开启和结束。这种无感知的事务处理让我们能够专注于业务逻辑,而不用过多关注事务细节。
-- 悄无声息的隐式事务
SELECT * FROM user_table WHERE age > 25;
-- 明明白白的显式事务
BEGIN;
INSERT INTO sales_2024 SELECT * FROM sales_2023 WHERE region = 'East';
UPDATE sales_summary SET total = total + 1 FROM sales_2024);
COMMIT;
Doris 当前支持的唯一隔离级别是 READ COMMITTED。在 READ COMMITTED 隔离级别下,语句只能看到在该语句开始执行之前已经提交的数据,它不会看到未提交的数据。
好比方,在繁忙的数据高速公路上,READ COMMITTED隔离级别就像智能交通灯。每个查询语句开始执行时,都会获得一个数据快照,只能看到该时刻之前已经提交的数据。这就像是给每个司机一张实时路况图,保证他们看到的是确定无误的路况信息。
小王遇到过这样一个场景:一个报表查询在执行过程中,另一个实时写入任务修改了源数据。多亏了READ COMMITTED隔离级别,报表查询使用的是开始时刻的数据快照,不会受到并发写入的影响,保证了数据的一致性。
Doris 有两个机制支持写入的不重不丢,使用 Label 机制提供了单个事务的不重,使用两阶段提交提供了协调多事务不重的能力。
Label机制
Doris 的事务或者写入可以设置一个 Label,而Label就像是数据的身份证。每个事务都可以带有一个独特的标签,通常采用"业务名称_时间戳"的格式,不设置时内部会生成一个 UUID 字符串。这个标签不仅能帮助我们追踪数据的来源,还能防止重复导入。
设想在2024年11月19日22:00:00,业务系统产生了一批数据。我们可以为这批数据赋予一个标签:"my_business1_20241119_220000"。如果这批数据的导入因为网络问题中断,我们可以用相同的Label重试。
通过这种 Label 设定,业务上可以通过 Label 查询导入任务状态,来明确地获知该时间点批次的数据是否已经导入成功。如果没有成功,则可以使用这个 Label 继续重试导入。
StreamLoad 2PC
两阶段提交(2PC)机制让Doris能够优雅地处理跨系统的数据同步。对于数据同步任务,每个导入都像一场精心策划的音乐会:第一阶段是彩排,确保所有准备就绪;第二阶段才是正式演出。这种机制主要用于支持 Flink 写入 Doris 时的 EOS 语义,确保数据能够准确无误地从源系统同步到Doris。
小华最近就在设计一个实时数据同步方案,源系统数据变更需要实时同步到Doris。通过Stream Load的2PC机制,他成功实现了数据的精准同步,即使在网络抖动的情况下也能保证数据的一致性。
# 1. 在 HTTP Header 中设置 two_phase_commit:true 启用两阶段提交。
curl --location-trusted -u user:passwd -H "two_phase_commit:true" -T test.txt http://fe_host:http_port/api/{db}/{table}/_stream_load
{
"TxnId": 18036,
"Label": "my_business1_20241119_220000",
"TwoPhaseCommit": "true",
"Status": "Success",
"Message": "OK",
"NumberTotalRows": 100,
"NumberLoadedRows": 100,
"NumberFilteredRows": 0,
"NumberUnselectedRows": 0,
"LoadBytes": 1031,
"LoadTimeMs": 77,
"BeginTxnTimeMs": 1,
"StreamLoadPutTimeMs": 1,
"ReadDataTimeMs": 0,
"WriteDataTimeMs": 58,
"CommitAndPublishTimeMs": 0
}
# 2. 对事务触发 commit 操作(请求发往 FE 或 BE 均可)
# 例如使用 label 指定事务
curl -X PUT --location-trusted -u user:passwd -H "label:my_business1_20241119_220000" -H "txn_operation:commit" http://fe_host:http_port/api/{db}/{table}/_stream_load_2pc
{
"status": "Success",
"msg": "label [55c8ffc9-1c40-4d51-b75e-f2265b3602ef] commit successfully."
}
# 3. 对事务触发 abort 操作(请求发往 FE 或 BE 均可)
curl -X PUT --location-trusted -u user:passwd -H "label:my_business1_20241119_220000" -H "txn_operation:abort" http://fe_host:http_port/api/{db}/{table}/_stream_load_2pc
{
"status": "Success",
"msg": "label [55c8ffc9-1c40-4d51-b75e-f2265b3602ef] abort successfully."
}
让我们回到开篇小华遇到的问题。通过深入了解Doris的事务机制,问题的根源找到了:原来是某些同步任务没有正确使用Label机制,导致数据重复写入。修复后,数据一致性得到了保障,生产环境也不再半夜"惊醒"运维团队了。
最后,来句结语:数据世界的演进永无止境,Doris事务机制也在不断完善,多多关注支持~
下期,我们将一起探讨Doris其它更有趣有用有价值的内容,敬请期待!