源码分析 | ClickHouse和他的朋友们(3)MySQL Protocol和Write调用栈

本文首发于 2020-06-08 19:57:10

《ClickHouse和他的朋友们》系列文章转载自圈内好友 BohuTANG 的博客,原文链接:
https://bohutang.me/2020/06/08/clickhouse-and-friends-mysql-protocol-write-stack/
以下为正文。

上篇的MySQL Protocol和Read调用里介绍了 ClickHouse 一条查询语句的调用栈,本文继续介绍写的调用栈,开整。

Write请求

  1. 建表:

    mysql> CREATE TABLE test(a UInt8, b UInt8, c UInt8) ENGINE=MergeTree() PARTITION BY (a, b) ORDER BY c;
    Query OK, 0 rows affected (0.03 sec)
    
  2. 写入数据:

    INSERT INTO test VALUES(1,1,1), (2,2,2);
    

调用栈分析

1. 获取存储引擎 OutputStream

DB::StorageMergeTree::write(std::__1::shared_ptr<DB::IAST> const&, DB::Context const&) StorageMergeTree.cpp:174
DB::PushingToViewsBlockOutputStream::PushingToViewsBlockOutputStream(std::__1::shared_ptr<DB::IStorage> const&, DB::Context const&, std::__1::shared_ptr<DB::IAST> const&, bool) PushingToViewsBlockOutputStream.cpp:110
DB::InterpreterInsertQuery::execute() InterpreterInsertQuery.cpp:229
DB::executeQueryImpl(const char *, const char *, DB::Context &, bool, DB::QueryProcessingStage::Enum, bool, DB::ReadBuffer *) executeQuery.cpp:364
DB::executeQuery(DB::ReadBuffer&, DB::WriteBuffer&, bool, DB::Context&, std::__1::function<void (std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)>) executeQuery.cpp:696
DB::MySQLHandler::comQuery(DB::ReadBuffer&) MySQLHandler.cpp:311
DB::MySQLHandler::run() MySQLHandler.cpp:141

2. 从 SQL 组装 InputStream

(1,1,1), (2,2,2) 如何组装成 inputstream 结构呢?

DB::InputStreamFromASTInsertQuery::InputStreamFromASTInsertQuery(std::__1::shared_ptr<DB::IAST> const&, DB::ReadBuffer*,
DB::InterpreterInsertQuery::execute() InterpreterInsertQuery.cpp:300
DB::executeQueryImpl(char const*, char const*, DB::Context&, bool, DB::QueryProcessingStage::Enum, bool, DB::ReadBuffer*) executeQuery.cpp:386
DB::MySQLHandler::comQuery(DB::ReadBuffer&) MySQLHandler.cpp:313
DB::MySQLHandler::run() MySQLHandler.cpp:150

然后

res.in = std::make_shared<InputStreamFromASTInsertQuery>(query_ptr, nullptr, query_sample_block, context, nullptr);
res.in = std::make_shared<NullAndDoCopyBlockInputStream>(res.in, out_streams.at(0));

通过 NullAndDoCopyBlockInputStream的 copyData 方法构造出 Block:

DB::ValuesBlockInputFormat::readRow(std::__1::vector<COW<DB::IColumn>::mutable_ptr<DB::IColumn>, std::__1::allocator<COW<DB::IColumn>::mutable_ptr<DB::IColumn> > >&, unsigned long) ValuesBlockInputFormat.cpp:93
DB::ValuesBlockInputFormat::generate() ValuesBlockInputFormat.cpp:55
DB::ISource::work() ISource.cpp:48
DB::InputStreamFromInputFormat::readImpl() InputStreamFromInputFormat.h:48
DB::IBlockInputStream::read() IBlockInputStream.cpp:57
DB::InputStreamFromASTInsertQuery::readImpl() InputStreamFromASTInsertQuery.h:31
DB::IBlockInputStream::read() IBlockInputStream.cpp:57
void DB::copyDataImpl<DB::copyData(DB::IBlockInputStream&, DB::IBlockOutputStream&, std::__1::atomic<bool>*)::$_0&, void (&)(DB::Block const&)>(DB::IBlockInputStream&, DB::IBlockOutputStream&, DB::copyData(DB::IBlockInputStream&, DB::IBlockOutputStream&, std::__1::atomic<bool>*)::$_0&, void (&)(DB::Block const&)) copyData.cpp:26
DB::copyData(DB::IBlockInputStream&, DB::IBlockOutputStream&, std::__1::atomic<bool>*) copyData.cpp:62
DB::NullAndDoCopyBlockInputStream::readImpl() NullAndDoCopyBlockInputStream.h:47
DB::IBlockInputStream::read() IBlockInputStream.cpp:57
void DB::copyDataImpl<std::__1::function<bool ()> const&, std::__1::function<void (DB::Block const&)> const&>(DB::IBlockInputStream&, DB::IBlockOutputStream&, std::__1::function<bool ()> const&, std::__1::function<void (DB::Block const&)> const&) copyData.cpp:26
DB::copyData(DB::IBlockInputStream&, DB::IBlockOutputStream&, std::__1::function<bool ()> const&, std::__1::function<void (DB::Block const&)> const&) copyData.cpp:73
DB::executeQuery(DB::ReadBuffer&, DB::WriteBuffer&, bool, DB::Context&, std::__1::function<void (std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)>) executeQuery.cpp:785
DB::MySQLHandler::comQuery(DB::ReadBuffer&) MySQLHandler.cpp:313
DB::MySQLHandler::run() MySQLHandler.cpp:150

3. 组装 OutputStream

DB::InterpreterInsertQuery::execute() InterpreterInsertQuery.cpp:107
DB::executeQueryImpl(const char *, const char *, DB::Context &, bool, DB::QueryProcessingStage::Enum, bool, DB::ReadBuffer *) executeQuery.cpp:364
DB::executeQuery(DB::ReadBuffer&, DB::WriteBuffer&, bool, DB::Context&, std::__1::function<void (std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)>) executeQuery.cpp:696
DB::MySQLHandler::comQuery(DB::ReadBuffer&) MySQLHandler.cpp:311
DB::MySQLHandler::run() MySQLHandler.cpp:141

组装顺序:

  1. NullAndDoCopyBlockInputStream
  2. CountingBlockOutputStream
  3. AddingDefaultBlockOutputStream
  4. SquashingBlockOutputStream
  5. PushingToViewsBlockOutputStream
  6. MergeTreeBlockOutputStream

4. 写入OutputStream

DB::MergeTreeBlockOutputStream::write(DB::Block const&) MergeTreeBlockOutputStream.cpp:17
DB::PushingToViewsBlockOutputStream::write(DB::Block const&) PushingToViewsBlockOutputStream.cpp:145
DB::SquashingBlockOutputStream::finalize() SquashingBlockOutputStream.cpp:30
DB::SquashingBlockOutputStream::writeSuffix() SquashingBlockOutputStream.cpp:50
DB::AddingDefaultBlockOutputStream::writeSuffix() AddingDefaultBlockOutputStream.cpp:25
DB::CountingBlockOutputStream::writeSuffix() CountingBlockOutputStream.h:37
DB::copyDataImpl<DB::copyData(DB::IBlockInputStream&, DB::IBlockOutputStream&, std::__1::atomic<bool>*)::<lambda()>&, void (&)(const DB::Block&)>(DB::IBlockInputStream &, DB::IBlockOutputStream &, <lambda()> &, void (&)(const DB::Block &)) copyData.cpp:52
DB::copyData(DB::IBlockInputStream&, DB::IBlockOutputStream&, std::__1::atomic<bool>*) copyData.cpp:138
DB::NullAndDoCopyBlockInputStream::readImpl() NullAndDoCopyBlockInputStream.h:57
DB::IBlockInputStream::read() IBlockInputStream.cpp:60
void DB::copyDataImpl<std::__1::function<bool ()> const&, std::__1::function<void (DB::Block const&)> const&>(DB::IBlockInputStream&, DB::IBlockOutputStream&, std::__1::function<bool ()> const&, std::__1::function<void (DB::Block const&)> const&) copyData.cpp:29
DB::copyData(DB::IBlockInputStream&, DB::IBlockOutputStream&, std::__1::function<bool ()> const&, std::__1::function<void (DB::Block const&)> const&) copyData.cpp:154
DB::executeQuery(DB::ReadBuffer&, DB::WriteBuffer&, bool, DB::Context&, std::__1::function<void (std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)>) executeQuery.cpp:748
DB::MySQLHandler::comQuery(DB::ReadBuffer&) MySQLHandler.cpp:311
DB::MySQLHandler::run() MySQLHandler.cpp:141

通过 copyData 方法,让数据在 OutputStream 间层层透传,一直到 MergeTreeBlockOutputStream。

5. 返回 Client

DB::MySQLOutputFormat::finalize() MySQLOutputFormat.cpp:62
DB::IOutputFormat::doWriteSuffix() IOutputFormat.h:78
DB::OutputStreamToOutputFormat::writeSuffix() OutputStreamToOutputFormat.cpp:18
DB::MaterializingBlockOutputStream::writeSuffix() MaterializingBlockOutputStream.h:22
void DB::copyDataImpl<std::__1::function<bool ()> const&, std::__1::function<void (DB::Block const&)> const&>(DB::IBlockInputStream&, DB::IBlockOutputStream&, std::__1::function<bool ()> const&, std::__1::function<void (DB::Block const&)> const&) copyData.cpp:52
DB::copyData(DB::IBlockInputStream&, DB::IBlockOutputStream&, std::__1::function<bool ()> const&, std::__1::function<void (DB::Block const&)> const&) copyData.cpp:154
DB::executeQuery(DB::ReadBuffer&, DB::WriteBuffer&, bool, DB::Context&, std::__1::function<void (std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)>) executeQuery.cpp:748
DB::MySQLHandler::comQuery(DB::ReadBuffer&) MySQLHandler.cpp:311
DB::MySQLHandler::run() MySQLHandler.cpp:141

总结

INSERT INTO test VALUES(1,1,1), (2,2,2);

首先内核解析 SQL 语句生成 AST,根据 AST 获取 Interpreter:InterpreterInsertQuery。
其次 Interpreter 依次添加相应的 OutputStream。
然后从 InputStream 读取数据,写入到 OutputStream,stream 会层层渗透,一直写到底层的存储引擎。
最后写入到 Socket Output,返回结果。

ClickHouse 的 OutputStream 编排还是比较复杂,缺少类似 Pipeline 的调度和编排,但是由于模式比较固化,目前看还算清晰。


欢迎关注我的微信公众号【数据库内核】:分享主流开源数据库和存储引擎相关技术。

标题网址
GitHubhttps://dbkernel.github.io
知乎https://www.zhihu.com/people/…
思否(SegmentFault)https://segmentfault.com/u/db…
掘金https://juejin.im/user/5e9d3e…
开源中国(oschina)https://my.oschina.net/dbkernel
博客园(cnblogs)https://www.cnblogs.com/dbkernel
<think>嗯,用户问的是时序数据库关系型数据库之间使用的接口协议是什么,比如REST API、gRPC还是SQL标准。首先,我需要回忆一下时序数据库关系型数据库的基本特点。时序数据库主要用于处理时间序列数据,比如传感器数据、日志等,而关系型数据库则用于结构化数据存储,支持复杂的查询事务。 接下来,用户提到的接口协议,可能需要了解常见的数据库通信协议。比如,关系型数据库通常使用SQL作为查询语言,而接口协议可能有JDBC、ODBC这样的标准协议。时序数据库可能根据不同的设计有不同的接口,比如InfluxDB使用HTTP/REST API它们自己的查询语言InfluxQL,而TimescaleDB因为是基于PostgreSQL的扩展,可能支持标准的SQLPostgres的协议。 然后,考虑到用户的问题是关于两者之间的通信接口协议,也就是说,时序数据库关系型数据库如何交互。这时候可能需要分情况讨论,比如是否有时序数据库直接支持关系型数据库的协议,或者是否需要通过中间件、ETL工具来进行数据传输,这时候可能用到不同的协议,比如REST API、gRPC或者消息队列如Kafka。 另外,用户提供的引用中有提到非关系数据库用作读存储,微服务使用独立的数据存储,可能需要考虑在微服务架构下,这两种数据库如何协同工作,可能涉及API网关、gRPC服务调用,或者通过应用程序层进行数据同步。 还要注意,不同的时序数据库可能有不同的接口选择,比如AWS的Timestream支持SQL查询,而Prometheus使用自己的HTTP APIPromQL。因此,答案可能需要列举常见的接口协议,并说明不同数据库的具体情况,同时提到可能的集成方式,比如使用适配器或中间件。 最后,用户可能需要实际应用中的例子,比如如何通过REST API将数据从关系型数据库导入时序数据库,或者使用SQL标准接口直接查询时序数据库,这时候需要具体说明协议的使用场景方法,以及注意事项,如性能优化、数据格式转换等。</think>时序数据库与关系型数据库之间的通信接口协议主要由具体数据库系统的实现决定,以下是技术层面的分析: ### 一、通信接口类型对比 1. **REST API** - 常用于现代分布式时序数据库(如InfluxDB 2.x、Prometheus) - 通过HTTP协议传输JSON格式数据 - 示例交互: ```bash POST /api/v2/write?bucket=my-bucket measurement,tag=value field=42 1465839830100400200 ``` 2. **SQL协议** - 支持SQL的时序数据库(如TimescaleDB、TDengine)直接兼容JDBC/ODBC - 通过TCP协议建立数据库连接 - 示例查询: ```sql SELECT avg(temperature) FROM sensors WHERE time > NOW() - INTERVAL &#39;1 hour&#39; GROUP BY device_id ``` 3. **gRPC** - 高性能时序数据库(如VictoriaMetrics)采用protobuf编码 - 支持双向流式传输(如IoT场景下持续发送传感器数据) 4. **专有协议** - OpenTSDB的Telnet协议 - Graphite的Plaintext Protocol ### 二、跨数据库交互模式 1. **直接对接场景** - 使用统一SQL网关(如Presto)建立联邦查询: ```sql SELECT * FROM timescale.sensors JOIN mysql.device_info USING (device_id) ``` 2. **ETL管道场景** - 典型架构组合: ``` MySQL -> Kafka Connect -> InfluxDB ``` 使用Avro格式进行序列化[^2] 3. **混合存储架构** - 热数据存储在InfluxDB - 冷数据归档到PostgreSQL - 通过自定义迁移策略实现自动转存 ### 三、协议选择考量维度 | 维度 | REST API | SQL | gRPC | |-----------|------------|-----------|-----------| | 延迟 | 100-500ms | 50-200ms | 10-50ms | | 吞吐量 | 1-5k QPS | 5-20k QPS | 50k+ QPS | | 开发复杂度 |||| | 可观测性 | 高(HTTP状态码) ||| ### 四、典型应用案例 某工业物联网平台使用以下协议栈: 1. 边缘设备通过MQTT协议发送数据到IoT Hub 2. 数据经流处理引擎转换为结构化的INSERT SQL 3. 使用PostgreSQL扩展TimescaleDB实现时序存储 4. BI系统通过JDBC直接访问时序数据库 5. 每日凌晨通过pg_dump将冷数据同步到Oracle数据库[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值