PHP的WMB队列消费代理的实现

一、问题描述

WMB消息总线是58内部提供的消息队列服务。消息队列主要解决了应用间的耦合、异步处理事件、流量削峰填谷等问题,是系统架构不可缺少的组件。

现有的消费者客户端,通过注册回调函数来处理消息,

    function callback ($msg) {var_dump($msg);}
    $keyPath = "./testkey.key?clientid=2";
    ESBclient_consumer_loop('callback', $keyPath, 123456);

这种使用方式存在一些问题,包括 
1)不方便开发调试,需要消息生产者和消费者同时参与。而且多数情况下,生产者和消费者在不同的业务部门,增大了调试成本。 
2)消费端依次处理拉取来的消息,性能不高。 
3)由于PHP语言本身的特性,对多线程支持不好,消费端不能开启多线程模式。为了加快处理,只能多开进程。但是由于wmb服务端限制,只能开64个消费端,假设处理每个消息需要100ms,QPS上限就是640左右。 
4)消费者客户端和消息处理代码耦合在一起,而且与业务集群要分开部署,浪费机器。

二、引入代理

计算机领域有句话:“计算机科学中的任何问题都可以通过增加一个中间层来解决”。为了解决上面的问题,引入了消费队列代理。代理通过消费客户端拉取消息,然后分发到真正处理消息的接口上,回调接口可以部署在本机,也可以在其他机器。设计图如下:

PHP的WMB队列消费代理的实现_第1张图片

主进程启动后,解析配置文件,为每个主题初始化Channel、创建puller和dispatcher进程,注册信号处理程序,执行swoole_event_wait()进入事件循环。

Channel继承自swoole_channel。swoole_channel是一个类似于Go的Channel,可用于用户态的高性能内存队列。Channel在swoole_channel基础上,增加了导入、导出数据到本地文件,在程序退出且共享内存中还有未处理的消息时,导出到本地文件,在程序下次启动时处理。

puller进程是wmb的消费者客户端,从wmb server端拉取消息,通过回调函数,把消息保存到Channel中。puller会定时检查Channel中的消息数量,如果超过dispatcher的处理能力,则暂缓拉取消息。decoder提供消息解码,据当前主题的配置把protobuf或者json格式的消息解码成PHP数组。filter提供消息过滤功能,根据正则匹配,把不关注的消息提前过滤掉。

dispatcher进程从Channel中读取消息,通过http调用,把消息传递给real consumer处理。http客户端使用的是swoole_http_client,基于事件的异步客户端,有较高的性能。dispatcher根据配置文件控制http调用的并发数和失败时的重试次数、重试间隔。encoder在发送前把消息编码,目前只支持json。

三、稳定性和扩展性

消息队列作为基础服务,对可用性的要求高一些。由于下游依赖的服务多,一旦出现异常,会影响大面积服务。代理在设计时,对下面问题做了考虑,保障代理的可用性:

  1. 如何保证代理稳定,是否是单点,是否可扩展?
  2. 如何保证代理重启,消息不丢失?
  3. 如何保证回调接口挂了,消息不丢失?
  4. 如何解决高吞吐量的问题?

首先wmb服务支持多消费者客户端,同样部署多个代理时,服务端会均衡各代理的负载。当单个代理出现问题时,消息会由其他代理处理。对于回调接口,可以通过域名配置到集群上,保证了整个系统没有单点。代理和集群可以通过增加机器来实现水平扩容,具有可扩展性。

Channel在共享内存的基础上,增加了导入、导出消息到本地文件的功能,即当程序退出时,会主动读取共享内存的内容,写入文件;在代理下次启动时,首先检查本地文件是否有上一次未处理的消息,如果有则写入到共享内存,由dispatcher优先处理。

只有当回调接口明确返回成功时,才会认为该消息处理完成,否则会根据配置重试。为了降低重试带给回调接口的压力,下一次请求至少在1s后开始,重试指定次数,直到请求成功或者到达次数限制。当然也可以配置成不重试,这样请求失败后,当前消息直接抛弃,继续处理下一条。

代理本身并不处理消息,它的主要工作在于接收和转发。puller和dispatcher都是基于epoll的异步通信进程,理论上代理的吞吐量由回调接口可支持的并发数和处理时间决定。

四、性能和吞吐量

在8核CPU、16G内存的虚拟机上压测代理,回调接口部署在另一台机器上,回调耗时设置成100ms。接入一个主题的情况时,数据如下:

QPS CPU占用率(puller) CPU占用率(dispatcher) 内存使用(puller) 内存使用(dispatcher) 网络吞吐量(kB/s)
800 7.3% 15.8% 19m 147m 1970
1600 7.8% 31.7% 19M 149m 3948
2400 10.9% 50.8% 19M 152m 5900
3200 13.9% 66.8% 19M 155m 7851

代理处理一个主题时需要2个进程,占用2个CPU核心。在8核虚拟机上,可以对同一个主题接入4次,即在开启4个消费端的情况下,QPS可以达到12800,远大于直接使用消费端时的640。

同时,代理可以水平扩展。在代理扩展到16台虚拟机时,每台虚拟机开启4个消费端,达到64个消费端的上限,此时可以处理的QPS理论上为204800。

五、易于接入

通过代理也简化了开发过程。如果需要接入一个主题,只需要简单的实现一个回调接口,而调试http形式的回调接口要简单的多,可由接入方独立完成。需要注意的是,回调接口要保证幂等性。

部署上更加方便,只需要在配置文件上加入新的配置项,包括新接入主题的id、客户端id、wmb key、回调接口等,重新启动即可。

运维上需要注意不要暴力操作。代理在退出时需要将还没处理的消息保存到本地文件中。

你可能感兴趣的:(php)