首页 > 数据治理 > 数据治理 > ClickHouse最佳实战之分布表写入流程分析


CREATE TABLE {teble} ON CLUSTER {cluster}
AS {local_table}
ENGINE= Distributed({cluster}, {database}, {local_table},{policy})
<remote_servers> <logs> <shard> <weight>1</weight> <internal_replication>true</internal_replication> <replica> <priority>1</priority> <host>example01-01-1</host> <port>9000</port> </replica> <replica> <host>example01-01-2</host> <port>9000</port> </replica> </shard> <shard> <weight>2</weight> <internal_replication>true</internal_replication> <replica> <host>example01-02-1</host> <port>9000</port> </replica> <replica> <host>example01-02-2</host> <port>9000</port> </replica> </shard> </logs> </remote_servers>

DistributedBlockOutputStream(const Context & context_, StorageDistributed & storage_, const ASTPtr & query_ast_, const ClusterPtr & cluster_, bool insert_sync_, UInt64 insert_timeout_);
BlockOutputStreamPtr StorageDistributed::write(const ASTPtr &, const Context &
context)
{
......
/// Force sync insertion if it is remote() table function
bool insert_sync = settings.insert_distributed_sync || owned_cluster;
auto timeout = settings.insert_distributed_timeout;
/// DistributedBlockOutputStream will not own cluster, but will own
ConnectionPools of the cluster
return std::make_shared(
context, *this, createInsertToRemoteTableQuery(remote_database,
remote_table, getSampleBlockNonMaterialized()), cluster,
nsert_sync, timeout);
}
StoragePtr TableFunctionRemoteexecuteImpl(const ASTPtr & astfunction, const Context &
context, const stdstring & tablename) const
{
......
StoragePtr res = remotetablefunction_ptr
? StorageDistributed::createWithOwnCluster(
table_name,
structureremotetable,
remotetablefunction_ptr,
cluster,
context)
: StorageDistributed::createWithOwnCluster(
table_name,
structureremotetable,
remote_database,
remote_table,
cluster,
context);
......
}
StoragePtr StorageDistributed::createWithOwnCluster(
const std::string & tablename,
const ColumnsDescription & columns_,
ASTPtr & remotetablefunctionptr,
ClusterPtr & ownedcluster,
const Context & context_)
{
auto res = create(String{}, tablename, columns, ConstraintsDescription{},
remotetablefunctionptr, String{}, context, ASTPtr(), String(), false);
res->ownedcluster = ownedcluster_;
return res;
}
DistributedBlockOutputStream::write() ↓ if insert_sync | | true false ↓ ↓ writeSync() writeAsync()
writeAsyncImpl()
↓
if shard_info.hasInternalReplication()
| |
true false
↓ ↓
writeToLocal() writeToLocal()
↓ ↓
writeToShard() for(every shard){writeToShard()}
↓ ↓
end end
writeToShard()
↓
for(every dir_names){
|
└──if first iteration
| |
false true
↓ ↓
| ├──storage.requireDirectoryMonitor()
| ├──CompressedWriteBuffer
| ├──writeStringBinary()
| ├──stream.writePrefix()
| ├──stream.write(block)
| ├──stream.writeSuffix()
↘ ↙
link(tmp_file, file)
└──}
void DistributedBlockOutputStream::writeToShard(const Block & block, const
std::vector<std::string> & dir_names)
{
/** tmp directory is used to ensure atomicity of transactions
* and keep monitor thread out from reading incomplete data
*/
std::string first_file_tmp_path{};
auto first = true;
/// write first file, hardlink the others
for (const auto & dir_name : dir_names)
{
const auto & path = storage.getPath() + dir_name + '/';
/// ensure shard subdirectory creation and notify storage
if (Poco::File(path).createDirectory())
storage.requireDirectoryMonitor(dir_name);
const auto & file_name = toString(storage.file_names_increment.get()) +
".bin";
const auto & block_file_path = path + file_name;
/** on first iteration write block to a temporary directory for
subsequent hardlinking to ensure
* the inode is not freed until we're done */
if (first)
{
first = false;
const auto & tmp_path = path + "tmp/";
Poco::File(tmp_path).createDirectory();
const auto & block_file_tmp_path = tmp_path + file_name;
first_file_tmp_path = block_file_tmp_path;
WriteBufferFromFile out{block_file_tmp_path};
CompressedWriteBuffer compress{out};
NativeBlockOutputStream stream{compress, ClickHouseRevision::get(),
block.cloneEmpty()};
writeVarUInt(UInt64(DBMS_DISTRIBUTED_SENDS_MAGIC_NUMBER), out);
context.getSettingsRef().serialize(out);
writeStringBinary(query_string, out);
stream.writePrefix();
stream.write(block);
stream.writeSuffix();
}
if (link(first_file_tmp_path.data(), block_file_path.data()))
throwFromErrnoWithPath("Could not link " + block_file_path + " to "
+ first_file_tmp_path, block_file_path,
ErrorCodes::CANNOT_LINK);
}
......
}
[root@ck test]# tree . ├── 'default@ck2-0:9000,default@ck2-1:9000' │ ├── 25.bin │ └── tmp │ └── 26.bin └── 'default@ck3-0:9000,default@ck3-1:9000' └── tmp


上面提到过writeAsync()的最终实现方法是writeAsyncImpl,这个说法是没问题的,但是中间还有段关键逻辑,如下:
writeAsync() ↓ if storage.getShardingKeyExpr() && (cluster->getShardsInfo().size() > 1 | | true false ↓ ↓ writeAsyncImpl(block) writeSplitAsync(block) ↓ splitBlock(block) ↓ writeAsyncImpl(splitted_blocks,shard_idx)
sharding_key_expr = buildShardingKeyExpression(sharding_key_, global_context, getColumns().getAllPhysical(), false);
const ExpressionActionsPtr & getShardingKeyExpr() const { return
sharding_key_expr; }
splitBlock()
↓
createSelector(block)
↓
for(every shard){column->scatter(num_shards, selector);}
IColumn::Selector DistributedBlockOutputStream::createSelector(const Block &
source_block)
{
Block current_block_with_sharding_key_expr = source_block;
storage.getShardingKeyExpr()-
>execute(current_block_with_sharding_key_expr);
const auto & key_column =
current_block_with_sharding_key_expr.getByName(storage.getShardingKeyColumnName
());
const auto & slot_to_shard = cluster->getSlotToShard();
......
throw Exception{"Sharding key expression does not evaluate to an integer
type", ErrorCodes::TYPE_MISMATCH};
}

看splitBlock()方法,ClickHouse是利用createSelector()方法构造selector来进行后续的处理。在createSelector()方法中最重要的就是key_column和slot_to_shard。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69912185/viewspace-2727007/ ,如需转载,请注明出处,否则将追究法律责任。