php-kafka-consumergroup

知道或者熟悉 kafka(不是写小说的那个卡夫卡), 那么一定知道它有 producer 和 consumer 这两种角色。producer 用来生产消息,consumer 用来消费消息。

1) 可用性

下面是来自维基百科的解释:

在一个给定的时间间隔内,对于一个功能个体来讲,总的可用时间所占的比例

比如我们以年为单位来量化一个服务的可用性。假设一年 365 天当中有 364 天服务是正常服务的,那么我们就说这个服务的可用性是 364/365(用计算器口算了一下约 99.72%)。

我们常用几个 9 来衡量一个服务的可用性,两个 9 就是 99.99%,三个 9 即 99.999%, 四个 9 即 99.9999% ... 以此类推。

可用性 每年宕机时间
99.9% 8 个小时
99.99% 1 个小时
99.999% 5 分钟
99.9999% 30 秒

2)消费者的可用性

一般来说 producer 是嵌入到业务程序,那么可用性就由业务程序来保证。而 consumer 一般就是以独立的程序存在,那么就要自己来保证。

所以想让 consumer 做到 99.99% 以上的可用性,意味着一年内服务挂掉的时间不能超过一个小时。假设我们没有实现一些高可用的机制,部分 consumer 在半夜挂了,而你(或者运维)刚好干完一些不可描述的事情之后倒头大睡而没有注意到报警,这个系统的可用性就达不到要求。

当前 scala, java, golang, c 版本的做法都是监听 group 的 consumer 列表,如果有 consumer 进入或者退出都会触发重新分配分区,把分区均衡到各个 consumer。所以理论上我们 php 版本也可以这样做。现在已有的开源里面有 kafka 和 zookeeper 的客户端扩展和依赖库,但没有实现自动平衡的逻辑(也可能是我没看到), 所以这部分需要自己来做。

3) 尴尬的 php

这里必须先承认 php 是世界上最好的语言。

php 要实现 consumer 的高可用有三中选择:

  • 开启 php 线程扩展
  • c 实现 group 逻辑
  • 不使用多线程,边消费边监控

第一种方案,因为我们线上 php 环境都是没有打开线程安全, 所以如果要使用这个扩展需要重新编译 php 核心代码并重启所有服务,这个基本是无法接受的。

第二种方案,可以不用重新编译 php, 性能好。但开发成本比较高,风险大。

第三种方案, 纯 php 实现,代码简单可控,但性能会比较差一些。

最后我们选择了第三种方案,单进程空跑(只拉消息不处理)的性能是 7w+/s, 这个是可以接受的。

4) 功能和代码说明

  • 分区变化时可自动重新分配分区
  • 消费进程退出或者加入时可自动重新分配分区
  • 自动管理 offset
  • 兼容标准的 consumer group 路径,方便已有的工具监控
  • 接收用户信号,平滑进入和退出 group
  • 允许冗余的消费进程作为备份

github 地址: https://github.com/meitu/php-kafka-consumer

当前我们公司(美图)内部已经有不少业务已经在线上使用,当前版本已经比较稳定。

5)最后

前一段时间发现线上 consumer 内存不断上升的情况,经排查,最终定位并验证是依赖库的 php-zookeeper 有内存泄漏。现在已经反馈以及合并到社区的 master, 具体见 pr

如果使用 release(建议) 版本的 php-zookeeper, 需要手动 patch 这个 bug,否则会造成内存泄漏。

如果有问题或者任何意见,欢迎 issue 或者 pr。