Ck(三) 灵活批量写入大量数据至多张集群表

本文介绍了一个批量写入多张Clickhouse集群表的解决方案,通过自定义代码确保数据在各节点间的均匀分布。文章详细阐述了数据源初始化、TSV格式转换以及使用线程池进行多节点并发写入的步骤。

一、前言

今天有一个批量写入多张集群表数据的任务。要求集群每个节点都要写入一定数据。于是我多花了几个小时,写了一个灵活的批量写入代码。以后再有类似任务可以直接拿来用。经测试是没问题的,但是细节可能写的不好。有空再改。

主要解决问题:Clickhouse批量写入多个节点数据。

二、开始

1、首先是Clickhouse的数据源初始化。正常使用是考虑使用BalancedClickhouseDataSource,但是我想代码控制写入数据量的均匀,所以使用的ClickHouseDataSource。

public class ClickhouseDataSource {

    private volatile static List<DataSource> sources = null;

    public static List<DataSource> getDataSource() {
        if (sources == null) {
            synchronized (ClickhouseDataSource.class) {
                if (sources == null) {
                    List<DataSource> result = Lists.newArrayList();
                    List<Map<String, Object>> collect = ConfigUtils.getConfig().getList("store.clickhouse.connect").stream().map(configValue -> (Map<String, Object>) configValue.unwrapped())
                            .collect(Collectors.toList());
                    List<String> urlList = collect.stream().map(node -> (String) node.get("connection-url")).collect(Collectors.toList());
                    Map<String, Object> unwrapped = (Map<String, Object>) ConfigUtils.getConfig().getValue("store.clickhouse.param").unwrapped();
                    ClickHouseProperties ckProperties = new ClickHouseProperties();
                    ckProperties.setMaxBlockSize(80000 * 1000);
                    ckProperties.setMaxMemoryUsage(300000000000L);
                    ckProperties.setMaxTotal(1000);
                    ckProperties.setUseServerTimeZone(false);
                    ckProperties.setUseServerTimeZoneForDates(false);
                    ckProperties.setUseTimeZone((String) unwrapped.get("zone"));
                    ckProperties.setDefaultMaxPerRoute(500);
                    ckProperties.setConnectionTimeout(1500 * 1000);
                    ckProperties.setKeepAliveTimeout(-1);
                    ckProperties.setSocketTimeout(Integer.MAX_VALUE);
                    //ckProperties.setUser((String) unwrapped.get("user"));
                    //ckProperties.setPassword((String) unwrapped.get("password"));
                    sources = Lists.newArrayList();
                    for (String url : urlList) {
                        DataSource ds = new ClickHouseDataSource(url, ckProperties);
                        sources.add(ds);
                    }

                }
            }
        }
        return sources;
    }
}

2、ck 批量写入。将数据转成TSV后写入。ClickhouseDao中通过dbName和tableName查询system.columns来获得列的名称和类型。即知道类型那么即可根据类型来模拟数据

public class BatchStringHttpEntity extends AbstractHttpEntity {

    private List<String> batchRows;
    public static final String NULL = "\\N";

    public BatchStringHttpEntity(List<String> batchRows) {
        this.batchRows = batchRows;
    }

    @Override
    public boolean isStreaming() {
        return false;
    }

    @Override
    public boolean isRepeatable() {
        return true;
    }

    @Override
    public long getContentLength() {
        return -1;
    }

    @Override
    public InputStream getContent() throws IOException, IllegalStateException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void writeTo(OutputStream outputStream) throws IOException {
        for (String row : batchRows) {
            outputStream.write(row.getBytes(StreamUtils.UTF_8));
        }
    }
}
@Slf4j
public class ClickhouseDao {

    private
<think> 嗯,用户这次的问题是关于ClickHouse大数据写入时的批次规划方法。从问题描述看,用户应该是个技术人员,正在处理实际的数据入库问题,可能遇到了性能瓶颈或配置困惑。 翻看之前的引用材料,有几个关键点需要整合:引用[1]提到ClickHouse的数据分区和索引颗粒设计,引用[3]特别强调了max_partitions_per_insert_block这个重要参数,引用[4]则提到Buffer引擎的缓冲机制。用户隐藏的需求可能是想解决写入卡顿或超时问题。 需要重点考虑分区控制这个核心矛盾。ClickHouse的分区机制是把双刃剑——合理规划能提升查询效率,但单次写入涉及过多分区会导致:1)频繁触发max_partitions_per_insert_block限制;2)每个分区都要和ZooKeeper交互产生开销。这正好解释了为什么用户会遇到性能问题。 解决方案应该分个层面:首先是最基础的分区规则,要提醒用户分区键不宜过细,比如按天分区比按小时更稳妥;其次是批次切割逻辑,需要根据数据特征动态计算,比如10万行或100MB作为阈值;最后是缓冲机制的特殊价值,Buffer引擎能平滑写入峰值但要注意数据可见延迟。 技术细节上需要强调几个易错点:1)单批次数据包含的分区数必须低于max_partitions_per_insert_block(默认100);2)分布式写入要避免直写distributed;3)Buffer引擎的flush条件设置需要预估数据流速。这些在文档里都不太醒目但实际很重要。 用户可能还会关心监控验证,所以补充了system.parts和system.buffer的使用方法。最后用水龙头的比喻帮助理解流控逻辑——这样既解决技术问题又建立直观认知。 </think> 以下是根据ClickHouse特性总结的大数据写入批次规划方法,结合分区控制、参数优化和架构设计个关键维度: ### 一、分区数量控制(核心原则) 1. **单批次分区上限** 必须遵守 `max_partitions_per_insert_block` 参数限制(默认100)。若单批次数据涉及分区数超过此值: ```sql -- 临时调整(需重启生效) SET max_partitions_per_insert_block = 200; -- 永久配置(修改config.xml) <max_partitions_per_insert_block>200</max_partitions_per_insert_block> ``` **重要原则**:优先通过**数据预处理**控制单批次内分区数量,而非盲目调高参数[^3]。 2. **分区键设计策略** * 避免高频更新字段(如用户ID)作分区键 * 推荐按 **时间维度** 分区(如 `toYYYYMMDD(event_time)`) * 单个分区数据量建议控制在 **1-10GB** 范围 ### 二、批次切割逻辑 | 切割依据 | 推荐值 | 适用场景 | |-----------------|----------------|----------------------------| | 数据行数 | 50万-200万行 | 行大小均匀的日志类数据 | | 数据体积 | 50-100 MB/Batch| 宽/大字段数据 | | 时间窗口 | 5-10分钟/批次 | 流式写入场景(如Kafka管道) | **代码示例** (Python批处理切割): ```python from clickhouse_driver import Client def batch_insert(data, batch_size=500000): client = Client(host='clickhouse-server') for i in range(0, len(data), batch_size): batch = data[i:i+batch_size] # 显式检查分区数 partition_count = len(set(item['partition_key'] for item in batch)) if partition_count > 100: # 匹配max_partitions配置 raise Exception(f"单批次包含{partition_count}个分区,超过限制!") client.execute("INSERT INTO table VALUES", batch) ``` ### 写入优化技术 1. **Buffer引擎缓冲层** 在目标前创建缓冲,避免小批量写入冲击: ```sql CREATE TABLE buffer_table AS target_table ENGINE = Buffer(default, target_table, 16, 10, 100, 10000, 1000000, 10000000, 100000000) ``` * 参数解读:当满足以下任一条件即flush数据到主 `16min` 超时 | `10个` 阻塞批次 | `100MB` 内存数据 | `1000万` 行[^4] 2. **分布式写入架构** ```mermaid graph LR A[数据源] --> B{负载均衡器} B --> C[Shard1-Writer] B --> D[Shard2-Writer] C --> E[(Shard1-Replica1)] C --> F[(Shard1-Replica2)] D --> G[(Shard2-Replica1)] D --> H[(Shard2-Replica2)] ``` **关键规则**: - 直写**分片节点**而非Distributed - 每个分片配置双副本保证可靠性[^2] ### 四、参数调优清单 ```sql -- 提升写入吞吐(根据内存调整) SET max_memory_usage = 20000000000; -- 20GB内存 SET max_insert_block_size = 1048576; -- 1MB块大小 -- 减少ZK交互(降低分区压力) SET prefer_not_to_merge = 1; -- 禁止后台合并 ALTER TABLE table MODIFY SETTING parts_to_delay_insert = 1500; ``` ### 五、效果验证方法 1. 监控写入瓶颈: ```sql SELECT * FROM system.parts WHERE table = 'target_table' AND active=1 ``` 关注 `parts_to_throw` 和 `parts_to_delay` 指标 2. Buffer引擎状态检查: ```sql SELECT * FROM system.buffer WHERE table = 'buffer_table' ``` > **写入批次黄金法则**:将数据流想象成水流——通过分区控制(管道粗细)、批量切割(水桶尺寸)、缓冲层(蓄水池)实现稳定流速,避免洪水式写入冲垮系统[^1][^3]。 --- **相关问题** 1. ClickHouse分区键设计不当会导致哪些性能问题? 2. 如何监控ClickHouse批量写入时的ZooKeeper压力? 3. Buffer引擎在数据丢失场景下的恢复机制是什么? 4. 分布式集群中如何平衡写入负载与副本同步延迟?
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值