对于确保消息在生产者和消费者之间进行传输而言一般有三种传输保障(delivery guarantee):At most once,至多一次,消息可能丢失,但绝不会重复传输;At least once,至少一次,消息绝不会丢,但是可能会重复;Exactly once,精确一次,每条消息肯定会被传输一次且仅一次。对于大多数消息中间件而言,一般只提供At most once和At least once两种传输保障,对于第三种一般很难做到,由此消息幂等性也很难保证。
Kafka自0.11版本开始引入了幂等性和事务,Kafka的幂等性是指单个生产者对于单分区单会话的幂等,而事务可以保证原子性地写入到多个分区,即写入到多个分区的消息要么全部成功,要么全部回滚,这两个功能加起来可以让Kafka具备EOS(Exactly Once Semantic)的能力。
不过如果要考虑全局的幂等,还需要与从上下游方面综合考虑,即关联业务层面,幂等处理本身也是业务层面所需要考虑的重要议题。以下游消费者层面为例,有可能消费者消费完一条消息之后没有来得及确认消息就发生异常,等到恢复之后又得重新消费原来消费过的那条消息,那么这种类型的消息幂等是无法有消息中间件层面来保证的。如果要保证全局的幂等,需要引入更多的外部资源来保证,比如以订单号作为唯一性标识,并且在下游设置一个去重表。
事务性消息
事务本身是一个并不陌生的词汇,事务是由事务开始(Begin Transaction)和事务结束(End Transaction)之间执行的全体操作组成。支持事务的消息中间件并不在少数,Kafka和RabbitMQ都支持,不过此两者的事务是指生产者发生消息的事务,要么发送成功,要么发送失败。消息中间件可以作为用来实现分布式事务的一种手段,但其本身并不提供全局分布式事务的功能。
下表是对Kafka与RabbitMQ功能的总结性对比及补充说明。
功能项 Kafka**(1.1.0版本)** RabbitMQ**(3.6.10版本)**
优先级队列 不支持 支持。建议优先级大小设置在0-10之间。
延迟队列 不支持 支持
死信队列 不支持 支持
重试队列 不支持 不支持。RabbitMQ中可以参考延迟队列实现一个重试队列,二次封装比较简单。如果要在Kafka中实现重试队列,首先得实现延迟队列的功能,相对比较复杂。
消费模式 推模式 推模式+拉模式
广播消费 支持。Kafka对于广播消费的支持相对而言更加正统。 支持,但力度较Kafka弱。
消息回溯 支持。Kafka支持按照offset和timestamp两种维度进行消息回溯。 不支持。RabbitMQ中消息一旦被确认消费就会被标记删除。
消息堆积 支持 支持。一般情况下,内存堆积达到特定阈值时会影响其性能,但这不是绝对的。如果考虑到吞吐这因素,Kafka的堆积效率比RabbitMQ总体上要高很多。
持久化 支持 支持
消息追踪 不支持。消息追踪可以通过外部系统来支持,但是支持粒度没有内置的细腻。 支持。RabbitMQ中可以采用Firehose或者rabbitmq_tracing插件实现。不过开启rabbitmq_tracing插件件会大幅影响性能,不建议生产环境开启,反倒是可以使用Firehose与外部链路系统结合提供高细腻度的消息追踪支持。
消息过滤 客户端级别的支持 不支持。但是二次封装一下也非常简单。
多租户 不支持 支持
多协议支持 只支持定义协议,目前几个主流版本间存在兼容性问题。 RabbitMQ本身就是AMQP协议的实现,同时支持MQTT、STOMP等协议。
跨语言支持 采用Scala和Java编写,支持多种语言的客户端。 采用Erlang编写,支持多种语言的客户端。
流量控制 支持client和user级别,通过主动设置可将流控作用于生产者或消费者。 RabbitMQ的流控基于Credit-Based算法,是内部被动触发的保护机制,作用于生产者层面。
消息顺序性 支持单分区(partition)级别的顺序性。 顺序性的条件比较苛刻,需要单线程发送、单线程消费并且不采用延迟队列、优先级队列等一些高级功能,从某种意义上来说不算支持顺序性。
安全机制 (TLS/SSL、SASL)身份认证和(读写)权限控制 与Kafka相似
幂等性 支持单个生产者单分区单会话的幂等性。 不支持
事务性消息 支持 支持
2. 性能