背景
各云厂商Kafka消息组件产品,包括阿里云、华为云、腾讯云、移动云等等均不支持以HTTP协议方式接入Kafka,这和Kafka有丰富的多语言SDK客户端有直接的关系,通常HTTP协议为了解决异构语言下的接入问题,但是Kafka的SDK客户端已经覆盖了绝大部分的语言,所以对HTTP协议接入的需求并不是很强烈,但是在部分项目中仍需要以HTTP协议的方式接入Kafka,本文通过调研主流的Kafka HTTP接入方案,进行对比和分析。
调研
开源技术方案
Kafka-rest是Confluent公司开源的一个Kafka的HTTP代理,它的主要功能是将Kafka的消息以HTTP协议的形式暴露出来,这样就可以通过HTTP协议来访问Kafka的消息,而不需要使用Kafka的SDK客户端。Kafka-rest的主要功能包括:
包含多个HTTP接口,如获取Topic信息、生产消息、创建Consumer Group、订阅Topic、消费消息以及一些管控API接口等等。
Strimzi-kafka-bridge是Strimzi公司开源的一个Kafka的HTTP代理,它的主要功能和 Kafka-rest类似,都提供了以RESTful API的方式生产和消费消息的能力,但Strimzi-kafka-bridge在Kubernetes下有着更丰富的使用场景。
以上两个开源产品均存在同一个共性问题,就是当Proxy为多实例时,消息生产为无状态的,可以发往任意的Proxy地址,而消息消费均需绑定到具体某一个Proxy实例。如下所示:
# 创建消费组
curl -X POST -H "Content-Type: application/vnd.kafka.v2+json" -H "Accept: application/vnd.kafka.v2+json" \
--data '{
"name": "my_consumer_instance",
"format": "json",
"auto.offset.reset": "earliest"
}' \ http://localhost:8082/consumers/my_json_consumer
# Example response
{
"instance_id":"my_consumer_instance",
"base_uri":"http://localhost:8082/consumers/my_json_consumer/instances/my_consumer_instance"
}
创建消费组后,Proxy会返回一个实例地址,后续该消费组的消息消费和消费进度确认均需通过这个Proxy实例进行接口调用,这导致客户端消费者是有状态的,如果是多实例下,不能将请求调用到别的Proxy实例,否则会报消费者实例不存在的错误。
解决方式有两种:
- 客户端需要维护消费组和Proxy实例的映射关系。
客户端需持久化这层映射关系,防止客户端重启之后映射关系丢失。
- LB 会话粘滞
deployment-and-load-balancing 这也是官方默认的方式,在LB层将同一个消费组的请求发送到同一个Proxy实例去。
其它技术方案
Proxy架构
该Proxy架构主要解决多实例间消息消费的问题。如上图所示,Consumer通过LB从Proxy2消费消息,消息确认消费时通过LB轮询到Proxy1进行Commit,代理通过客户端请求传递的MessageId解码出Partition和Offset,根据Partition在消费组的分配情况,若分配在当前Proxy上,则直接Commit,否则转发到对应的Proxy进行Commit。这里的MessageId作为消息确认的唯一句柄,为Partition+Offset进行编码生成,类似于RocketMQ的ReceiptHandle。若直接返回Partition和Offset,客户端需要存储这两个值,方便消息消费出问题时进行排查,当然也可以通过解码MessageId的方式获得。这种架构的好处为,客户端是无状态的,只需要通过LB来访问Proxy即可,若不通过LB进行访问,只需要配置所有Proxy实例地址随机访问即可。
其它特性
- Proxy的安全认证
HTTP接入方式通过SSL+Token认证的方式保证客户端到Proxy的安全认证。
- Proxy Consumer过期超时问题。
为了减小客户端使用的复杂度,因为客户端可能忘记取消订阅,或因为客户端宕机没有取消订阅,所以API设计上不提供取消Consumer订阅的接口,采用订阅超时的方式,如果客户端在默认超时时间没有消费消息,则认为该Consumer已经过期,Proxy会自动取消订阅,释放资源。
总结
通过调研主流的Kafka HTTP接入解决方案进行分析和对比,开源的方案主要存在着客户端为有状态的问题,需要通过前置LB进行会话连接粘滞,而更理想的解决方案是客户端为无状态的,在Proxy集群内部进行消息消费确认的转发,当然这会增加Proxy的复杂度,以及增加一定的请求耗时,但是对于客户端来说是透明的,不需要关注消费组的分配情况,只需要通过LB进行访问即可。