问题描述
测试集群使用Debezium snapshot一张 2000多万的表耗时 40多分钟,使用线上集群snapshot 耗时 6小时51分钟,线上集群snapshot慢了10倍
问题分析
测试集群和线上集群不同
- 测试集群kafka单个broker,线上集群3个broker每个broke挂4个盘
- 测试集群默认创建topic Replicas=1,线上集群 Replicas=2
排查过程
开始怀疑是线上磁盘可能有坏道导致写入慢,联系运维排查磁盘OK。有点怀疑是副本数的问题,由于对kafka有一定的了解,感觉多一个副本数对写入性能影响不会这么大,不太相信是因为副本数的引起问题。同时找了前辈的性能测试文章的发现确没有这么大影响(https://www.cnblogs.com/arli/p/12574524.html),为了验证猜想还让运维的同学利用kafka压测工具做了副本数对写入影响的测试,结论是一个副本 ack=1和ack=-1的情况情况下并没有10配这么明显的差异,只是由小的波动。这里有个坑因为之前使用过kafka知道producer的ack默认配置是1的,由此推出connector使用的producer写入ack也是1。更坚信不是因为副本数引起的写入慢。于是只能查看Debezium的源代码。由于之前踩过时间问题的坑,阅读起来还是比较顺利的,只找到了Debezium采集后将其转换成了SourceRecord (io.debezium.connector.mysql.RecordMakers#assign),找不到producer写数据到kafka的代码和相关配置的代码。全局搜索也没有。心中有些沮丧。然后google一波connector source的写入流程,然而并没有太多的介绍。耐心的去官网学习(https://docs.confluent.io/current/connect/design.html),同时看了kafka的connect源码,发现org.apache.kafka.connect.runtime.Worker#producerConfigs 如下图,默认ack=all
static Map<String, Object> producerConfigs(ConnectorTaskId id,
String defaultClientId,
WorkerConfig config,
ConnectorConfig connConfig,
Class<? extends Connector> connectorClass,
ConnectorClientConfigOverridePolicy connectorClientConfigOverridePolicy) {
Map<String, Object> producerProps = new HashMap<>();
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, Utils.join(config.getList(WorkerConfig.BOOTSTRAP_SERVERS_CONFIG), ","));
producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArraySerializer");
producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArraySerializer");
// These settings will execute infinite retries on retriable exceptions. They *may* be overridden via configs passed to the worker,
// but this may compromise the delivery guarantees of Kafka Connect.
producerProps.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, Integer.toString(Integer.MAX_VALUE));
producerProps.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, Long.toString(Long.MAX_VALUE));
producerProps.put(ProducerConfig.ACKS_CONFIG, "all");
producerProps.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, "1");
producerProps.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, Integer.toString(Integer.MAX_VALUE));
producerProps.put(ProducerConfig.CLIENT_ID_CONFIG, defaultClientId);
// User-specified overrides
producerProps.putAll(config.originalsWithPrefix("producer."));
// Connector-specified overrides
Map<String, Object> producerOverrides =
connectorClientConfigOverrides(id, connConfig, connectorClass, ConnectorConfig.CONNECTOR_CLIENT_PRODUCER_OVERRIDES_PREFIX,
ConnectorType.SOURCE, ConnectorClientConfigRequest.ClientType.PRODUCER,
connectorClientConfigOverridePolicy);
producerProps.putAll(producerOverrides);
return producerProps;
}
找到问题了解决就简单了,面临的就是怎么覆盖这个参数的问题了。
解决方案
覆盖connect默认的producer的ack配置,需要将connector.client.config.override.policy=All 即可在注册connector是通过 producer.override.*和consumer.override.*来分别指定覆盖 producer和consumer的配置。参数优调对比(partition=1,replice=2)
从上面数据明显看出使用avro序列化,snappy压缩 ,ack=1,有明显的吞吐量优势,如果可用性要求很高 acks=all。