go micro框架的kafka插件异步生产者bug解析与修复

10 阅读2分钟

适用版本

  • go micro v4或以上

项目地址

背景

最近在用go micro做事件驱动架构的微服务,使用的broker插件为“github.com/go-micro/plugins/v4/broker/kafka” ,插件默认使用同步生产者,但在kafka节点多的时候同步生产者效率较低,尤其是设置WaitForALL的时候延迟升高,2个节点就达到107ms所以只能改成异步生产者,阅读官方文档得知可以通过AsyncProducer方法设置,代码如下:

func AsyncProduceMessage()  {
    var errorsChan = make(chan *sarama.ProducerError)
    var successesChan = make(chan *sarama.ProducerMessage)
    go func() {
        for err := range errorsChan {
            fmt.Println(err)
        }
    }
    go func() {
        for v := range successesChan {
            fmt.Println(v)
        }
    }
    b := NewBroker(AsyncProducer(errorsChan,successesChan))
    b.Publish(`topic`, &broker.Message{})
}

我按照官方文档的提示在自己的包下创建broker,代码如下(可以通过项目地址查看,示例代码忽略其他片段):

successChan := make(chan *sarama.ProducerMessage, conf.Broker.Kafka.ChannelBufferSize)
errorChan := make(chan *sarama.ProducerError, conf.Broker.Kafka.ChannelBufferSize)
var eb event.Listener
broker := infrastructure.NewKafkaBroker(conf.Broker.Kafka, kafkabroker.AsyncProducer(errorChan, successChan))

successChan和errorChan还设置到自编的事件侦听器里,打开2个Goroutine监听,回调处理包含日志、链路追踪、死信主题等多项操作,结果消息推送成功后没有执行,说明根本没有用到2个创建好的channel很有可能是源码的问题。

原因分析

我们先定位到kafka插件里设置异步生产者管道的源码:

func AsyncProducer(errors chan<- *sarama.ProducerError, successes chan<- *sarama.ProducerMessage) broker.Option {
	// set default opt
	var opt = func(options *broker.Options) {}
	if successes != nil {
		opt = setBrokerOption(asyncProduceSuccessKey{}, successes)
	}
	if errors != nil {
		opt = setBrokerOption(asyncProduceErrorKey{}, errors)
	}
	return opt
}

从源码可以看出opt的设置失效没有把两个channel设置进去,只要把它们放入函数体即可。修改后的效果如下:

func AsyncProducer(errors chan<- *sarama.ProducerError, successes chan<- *sarama.ProducerMessage) broker.Option {
    // set default opt
    return func(options *broker.Options) { // 把2个channel配置到options
       if errors != nil {
          setBrokerOption(asyncProduceErrorKey{}, errors)(options)
       }
       if successes != nil {
          setBrokerOption(asyncProduceSuccessKey{}, successes)(options)
       }
    }
}

这样就能引用外部传入的channel让options生效,我们可以放心地使用异步生产者,此时可以发现请求的延迟明显降低,因为异步生产者的原理是推入channel以后即返回成功,由sarama底层去发送消息,回调处理也能正常进行。

这是一个源码级的Bug,Github上有人提交了相关的PR但不知道怎么回事一直没有merge,PR地址:github.com/go-micro/pl…