kafka消费堆积问题探索

背景

我们的商城项目用PHP写的,原本写日志方案用的是PHP的方案,但是,这个方案导致资源消耗一直降不下来,使用了20个CPU。后面考虑使用通过kafka的方案写日志,商城中把产生的日志丢到kafka中,在以go写的项目中消费kafka中的日志,并打印到控制台,最后,统一使用阿里sls抓取日志。我们kafka的分区有12个,go程序部署在k8s集群中,开启了弹性扩缩容,最多开启了8个pod进行消费,每秒产生的日志数量高峰在1500条左右,在这种情况下,依然产生了消息的堆积。

消费中执行的逻辑只有对象的映射和日志写控制台,所以,这种情况下产生了消息堆积,令我倍感困惑。

探索之路

第一步,确认一下每一步的执行时间。

Go 复制代码
func (s KafkaLogService) ReaderCreateLog(ctx context.Context, msg *customerkafka.CustomKafkaMsg) error {
	now := time.Now().UnixNano()
	var logEntry LogEntry
	data, ok := msg.Data.(string)

	if !ok {
		global.GIN_LOG.Error(ctx, "消息数据类型错误", "data", msg.Data)
		return fmt.Errorf("消息数据类型错误: %v", msg.Data)
	}

	// 解析 JSON 数据
	if err := json.Unmarshal([]byte(data), &logEntry); err != nil {
		global.GIN_LOG.Error(ctx, "解析 JSON 数据失败:", "error", err, "message", data)
		return fmt.Errorf("解析消息失败: %w", err)
	}
	now2 := time.Now().UnixNano()
	fmt.Printf("*******************分隔符*******************Unmarshal logEntry 耗时:%d 纳秒\n", now2-now)
	var logMessage LogMessage
	if err := json.Unmarshal([]byte(logEntry.Message), &logMessage); err != nil {
		global.GIN_LOG.Error(ctx, "解析 JSON 数据失败:", "error", err, "message", logEntry.Message)
		return fmt.Errorf("解析消息失败: %w", err)
	}
	now3 := time.Now().UnixNano()
	fmt.Printf("*******************分隔符*******************Unmarshal LogMessage 耗时:%d 纳秒\n", now3-now2)
	// 日志等级判断
	switch logEntry.Level {
	case "ERROR":
		global.GIN_LOG.Error(ctx, "", zap.Any("data", logMessage), zap.String("project", logMessage.Project))
	case "WARN":
		global.GIN_LOG.Warn(ctx, "", zap.Any("data", logMessage), zap.String("project", logMessage.Project))
	case "INFO":
		global.GIN_LOG.Info(ctx, "", zap.Any("data", logMessage), zap.String("project", logMessage.Project))
	default:
		global.GIN_LOG.Warn(ctx, "", zap.Any("data", logMessage), zap.String("project", logMessage.Project))
	}
	now4 := time.Now().UnixNano()
	fmt.Printf("*******************分隔符*******************log 耗时:%d 纳秒\n", now4-now3)
	return nil
}

json的Unmarshal的耗时,倒是符合我的认知,在预期之中。

但是,打印日志尽然需要耗时1.5毫秒,这个有点超出我的意料之外。这个时间似乎有点夸张啊。

但是,即便如此,一个消费者每秒也可以消费670条左右的消息,在起了8个实例的情况下,也不应该造成kafka消息的阻塞。

继续我们的探索之路

下面这一段是我对于kakfa消费者的封装。大概的逻辑就是每一个ActionType起一个协程进行消费。在这篇《基于kafka-go写的生产者和消费者》文章中写过这个封装背后的设计逻辑,有兴趣的可以移步过去一探究竟。

Go 复制代码
// Start 方法启动消费者并开始读取消息,根据actionType调用不同的处理函数
func (c *ConsumerClient) Start(ctx context.Context, handlers map[string]ActionHandler) error {
	for {
		select {
		case <-ctx.Done():
			return nil // 上下文取消,直接返回
		default:
			msg, err := c.reader.ReadMessage(ctx)
			if err != nil {
				c.logger.Error(ctx, "Failed to read message from Kafka", "error", err)
				continue
			}
			c.logger.Info(ctx, fmt.Sprintf("Message on topic: %s value: %s partion:%d offset:%d", msg.Topic, string(msg.Value), msg.Partition, msg.Offset))
			var kafkaMsg CustomKafkaMsg
			if err := json.Unmarshal(msg.Value, &kafkaMsg); err != nil {
				c.logger.Error(ctx, "Failed to unmarshal Kafka message", "error", err)
				continue
			}
			channel := make(chan *CustomKafkaMsg)
			// 使用 sync.Map 来管理 worker
			worker, loaded := c.workerMap.LoadOrStore(kafkaMsg.ActionType, channel)
			if !loaded {
				c.wg.Add(1) // 增加 WaitGroup 计数
				if ch, ok := worker.(chan *CustomKafkaMsg); ok {
					go c.startWorker(ctx, kafkaMsg.ActionType, handlers, ch)
				}
			}
			// 发送消息到对应的通道,避免阻塞其他消息消费
			// 只有在 handlers 中存在对应的 actionType 时才发送消息到对应的通道
			if _, ok := handlers[kafkaMsg.ActionType]; ok {
				if ch, ok := worker.(chan *CustomKafkaMsg); ok {
					ch <- &kafkaMsg
				}
			}
		}
	}
}

由于我的这个写法,让我产生了一点担忧,虽然,我想的是每个ActionType只起一个协程进行消费,难道,实际情况并不是如我预期一样运行,而是,一条kafka消息就起了一个协程进行消费,如果是这种情况的话,那么,会导致大量的垃圾回收,程序的性能就会下降,那么,消息阻塞的问题也就可以解释了。

为了,验证我的这一想法,基于pprof工具看一下实际情况。

实际验证,排除我的担忧,符合我的预期,不是有一条kafka消息就开一个协程进行消费,而是,一个ActionType就只有一个协程进行消费。

模拟生产环境测试

上述的探索,依然不能够完美解释文章开头提到的现象,起了8个消费者,依然导致消息堆积的现象。为了进一步探究其背后的原因,我模拟生产环境的状态,每秒钟往kafka中丢了1000条消息,再观察,我发现,在这种情况下,有时json.Unmarshal也有比较长的耗时,会出现1.5毫秒的耗时,另外,而写日志需要5毫秒左右,如此,每秒只能消费140条消息,消息堆积的现象也就能够解释了。

结论

消息堆积的主要原因是日志打印操作耗时较长,最差时每秒只能消费140条消息。此外,有时JSON解析的时间也较长,这也是一个需要关注的问题。

接下来的目标是找出JSON解析耗时较长和日志打印慢的具体原因,并进行优化。通过解决这些问题,我们有望提高日志处理的效率,从而解决消息堆积的问题。

相关推荐
此时不提桶,更待何时6 小时前
06-23-A-RabbitMQ生态集成详解
分布式·rabbitmq
m0_6464299711 小时前
RabbitMQ 核心知识总结(生产实战版)
分布式·rabbitmq
DBA_G13 小时前
GBase 8c分布式数据库客户端工具 gsql
数据库·分布式
chunyublog13 小时前
Spark 词频统计练习
大数据·分布式·spark
ClouGence15 小时前
开源数据库管理工具 CloudDM 4.4.0 发布,新增 Kafka、SAP HANA 数据源
数据库·kafka
acrel715 小时前
高速公路分布式光伏并网电能质量与安全保护方案应用
分布式·安全
RobinXYuan15 小时前
【Kafka 源码手记】01 从消息队列到 Event Streaming
kafka·源码阅读
IT小小猪17 小时前
# Hadoop 3.3.6 完全分布式集群部署知识库
hadoop·分布式·eclipse
此时不提桶,更待何时17 小时前
06-24-B-RabbitMQ面试与生产事故实战
分布式·面试·rabbitmq
来福猿17 小时前
分布式锁 Redis 实现源码分析:SETNX、过期时间与可重入设计
数据库·redis·分布式