Presto源码分析之事务性的数据写入

概述

我们知道事务性在数据处理里面是非常重要的,事务性决定了你最终数据的正确与否。在 OLTP 领域里面事务一般通过底层存储提供的事务机制就可以搞定了。但是在分布式数据处理领域里面,由于数据在被很多节点同时大规模分布式地写,OLTP 里面的简单事务处理就很难处理这种场景了。在 Presto 里面也需要解决这样的问题,我们今天就来看看 Presto 里面提供了什么样的机制来达到数据写入的事务性。

物理执行计划的基本单元: Operator

在正式介绍数据事务性写入之前,我们先来看看 Presto 里面物理执行计划的基本单元: Operator

我们知道在一般的“语言”里面,把语言从文本编译到最终可执行程序的过程中都会经历从文本到逻辑执行计划,再从逻辑执行计划到物理执行计划的编译过程,Presto 也不例外,在 Presto 里面逻辑执行计划是由 PlanNode 组成的, 物理执行计划则是由 Operator 组成的。这里先简单介绍一下 Operator 的设计。

语言的编译过程

下面是 Operator 接口主要方法的定义(为了行文的简洁性删除了一些不那么重要的方法):

public interface Operator extends AutoCloseable {
   /**
    * Gets the column types of pages produced by this operator.
    */
   List<Type> getTypes();

   /**
    * Notifies the operator that no more pages will be added and the
    * operator should finish processing and flush results. This method
    * will not be called if the Task is already failed or canceled.
    */
   void finish();

   /**
    * Adds an input page to the operator.  This method will only be called if
    * {@code needsInput()} returns true.
    */
   void addInput(Page page);

   /**
    * Gets an output page from the operator.  If no output data is currently
    * available, return null.
    */
   Page getOutput();
}

这个接口算是 Presto 里面比较良心的接口了, 接口设计优良、每个方法有注释、方法的含义也很好理解。

从上述接口定义可以看出 Operator 定义了一个数据处理单元,处理的数据是 Page , 一批数据流进 Operator ,经过一系列处理逻辑,转换成新的一批数据流出到下游别的 Operator 继续处理。

Operator

数据处理的粒度是 Page, 但是不是所有的 Page 都一样,Page 里面的数据到底长什么样是靠 List<Type> getTypes() 这个方法来描述。Operator 通过 addInput 方法接收来自上游的输入,通过 getOuput 把数据吐给下游。

getOutput() 这个方法名字取得很不好,给人的感觉好像是把这个 Operator 的输出全部拿出来,它实际的作用是拿一个 Page 的输出出来,后面还会有很多输出,因此我觉得 nextOutput() 可能更表意一点。

Presto 里面的事务性数据写入

在 Presto 里面我们是可以事务性地把数据插入底层数据存储的 -- 当然,具体的事务性还是底层存储而不是 Presto 能够决定的,但是 Presto 里面提供了相应的机制以使得我们能够配合底层存储引擎一起来实现事务性的数据写入。这个机制是这样的:

Presto里面的事务性数据写入机制

这个机制本身其实不是很复杂,在写开始之前给你一个初始化的钩子(Hook), 然后你分布式地去写数据,最后给你一个集中式提交的钩子(Hook),有点两阶段提交的意味。

TableWriterOperator 这个名字起的有问题,对应的读数据的 Operator 是 TableScanOperator, 那么这个怎么也应该叫 TableWriteOperator 啊。

上面说的还是太抽象了,我们来看个具体的例子来看看,怎么通过这个”框架”来实现事务性写数据。这个例子就是 Presto 的 JDBC Connector:

JDBC Connector 在 BeginTableWrite 的时候执行了下面这段代码(BaseJdbcClient#beginWriteTable):

String temporaryName = "tmp_presto_" + 
  UUID.randomUUID().toString().replace("-", "");
StringBuilder sql = new StringBuilder()
   .append("CREATE TABLE ")
   .append(quoted(catalog, schema, temporaryName))
   .append(" (");

ImmutableList.Builder<String> columnList = ImmutableList.builder();
for (ColumnMetadata column : tableMetadata.getColumns()) {
   columnList.add(new StringBuilder()
                  .append(quoted(columnName))
                  .append(" ")
                  .append(toSqlType(column.getType()))
                  .toString());
}

Joiner.on(", ").appendTo(sql, columnList.build());
sql.append(")");
execute(connection, sql.toString());

可以看出这段代码建了一个临时表, 在后面的 TableWriterOperator 算子里面,数据其实是在分布式地往这个临时表里面去写。

在最后的 TableFinisherOperator 里面执行了这么一段代码(BaseJdbcClient#finishInsertTable):

String temporaryTable = quoted(handle.getCatalogName(), 
  getRealSchemaName(handle), handle.getTemporaryTableName());
String targetTable = quoted(handle.getCatalogName(), 
   getRealSchemaName(handle), getRealTableName(handle));

String insertSql = format("INSERT INTO %s SELECT * FROM %s", 
   targetTable, temporaryTable);
String cleanupSql = "DROP TABLE " + temporaryTable;

execute(connection, insertSql);
execute(connection, cleanupSql);

从代码可以看出来,JDBC Connector 在最后把数据从 temporaryTable 插入到了 targetTable 里面去了,并且把临时表 DROP 掉了,由于最后这个操作是事务性的,因此保证了整个数据写入的事务性。

目前 Presto 的这个方案其实是有点脏的,细心的同学可能已经看出来了,上面图中的 BeginTableWrite 的形状跟其它节点的形状不一样。这是因为 BeginTableWrite 在 Presto 里面不是一个 Operator。 它甚至不是在任务的运行时执行的,而是在编译期执行的(也就是下文中说的 planning)。Presto 的作者在 BeginTableWrite 的文件注释里面也承认了这一点。

Major HACK alert!!!

This logic should be invoked on query start, not during planning. At that point, the token
returned by beginCreate/beginInsert should be handed down to tasks in a mapping separate
from the plan that links plan nodes to the corresponding token.

总结

不管怎么样 Presto 还是能够支持对数据的事务性插入,虽然方法有点Hack。从这一点也可以看出 Presto 对待代码的态度是实用性优先,先解决问题再说,方法好不好,是不是最正确的方案,以后再说。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容