rabbitMQ + yii2 (php) 工作队列

工作队列又称任务队列,是为了避免等待一些占用大量资源 时间的操作。当我们吧任务当做消息发送到队列中,一个运行在后台的工作者worker 进程就会取出任务,任务就会在它们之间共享。

之前的学习中,我们发送了一个包含“Hello World!”的字符串消息。现在,我们将发送一些字符串,我们这里用sleep()函数来模拟处理任务。我们在字符串中加上点号(.)来表示任务的复杂程度,一个点(.)将会耗时1秒钟。比如"Hello…"就会耗时3秒钟。

一、创建一个ProducerController.php 生产者类 actionNewtask 方法

           public function actionNewtask(){

		try {
			//建立一个到RabbitMQ服务器的连接
			$connection = $this->connection;
			$channel = $this->channel;
			//建立通道
			$channel->queue_declare('task_queue',false,true,false,false);
			//接收数据
			$parms = func_get_args();
			//处理数据
			$data = implode(' ',array_slice($parms,0));
			if(empty($data)) $data = "Hello world";
			//delivery_mode=2  用于做消息持久化
			$msg = new AMQPMessage(
				$data,array('delivery_mode'=>AMQPMessage::DELIVERY_MODE_PERSISTENT)

			);
			//发布单条消息
			$channel->basic_publish($msg,'','task_queue');

			echo " [x] Sent ", $data, "\n";

			$channel->close();
			$connection->close();

		} catch(\Exception $e) {

			echo $e->getMessage();
		}
	}

二、创建一个ConsumerController.php 消费者类 actionWorker 方法

public function actionReceive(){

		try {
			//$channel = $this->channel;
			//var_dump($this->channel);exit;
			//设置和发送者是一样的,我们打开一个连接和一个通道,然后声明我们将要消耗的队列。请注意,这与发送的队列中的队列相匹配。
			//建立一个到RabbitMQ服务器的连接
			$connection = $this->connection;
			$channel = $this->channel;
			$channel->queue_declare('hello', false, false, false, false);
			echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";

			$callback = function($msg){
				echo " [x] Received ", $msg->body, "\n";
//				$msg->delivery_info['channel']->basic_ack(
//					$msg->delivery_info['delivery_tag']);
			};

			$channel->basic_consume('hello', '', false, true, false, false, $callback);

			while(count($channel->callbacks)) {
				$channel->wait();
			}

			$channel->close();
			$connection->close();


		} catch(\Exception $e)
		{
			echo $e->getMessage();
		}
	}

接下来,我们开始运行它,我还是写下console控制台中:
在这里插入图片描述

gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii rabbitmq-consumer/worker
 [*] Waiting for messages. To exit press CTRL+C

gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii rabbitmq-producer/newtask
 [x] Sent Hello world

gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii rabbitmq-producer/newtask "A very hard task which takes two seconds.."
 [x] Sent A very hard task which takes two seconds..


三、 代码详解

循环调度

使用工作列队的一个好处就是它能够并行的处理队列。如果堆积很多任务,我们只需要添加更多的工作者workers就可以了,扩展很简单。

首先,我们先同时运行两个rabbitmq-consumer/worker脚本,它们都会从队列中获取消息,我们看一下结果

你们需要打开三个终端,两个运行rabbitmq-consumer/worker脚本,这两个终端就是我们的两个消费者consumers

gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii rabbitmq-consumer/worker
 [*] Waiting for messages. To exit press CTRL+C

gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii rabbitmq-consumer/worker
 [*] Waiting for messages. To exit press CTRL+C

第三个终端,我们用来发布新任务。 你可以发送一些信息给消费者 consumers:

扫描二维码关注公众号,回复: 11238777 查看本文章
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii  rabbitmq-producer/newtask First message.
 [x] Sent First message.
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii  rabbitmq-producer/newtask Second message..
 [x] Sent Second message..
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$  ./yii  rabbitmq-producer/newtask Third message...
 [x] Sent Third message...
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii  rabbitmq-producer/newtask Fourth message....
 [x] Sent Fourth message....
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii  rabbitmq-producer/newtask Fifth message.....
 [x] Sent Fifth message.....

看看到底发送了什么给我们的工作者 workers:

shell1
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii  rabbitmq-consumer/worker
 [*] Waiting for messages. To exit press CTRL+C
 [x] Received First message.
 [x] Received Third message...
 [x] Received Fifth message.....
 
shell2
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$  ./yii  rabbitmq-consumer/worker
 [*] Waiting for messages. To exit press CTRL+C
 [x] Received Second message..
 [x] Received Fourth message....



默认来说,MQ会按顺序得把消息发送给每个消费者consumer.平均每个消费者都会收到同等数量得消息。 这种发送消息得方式叫做–轮询。试着添加三个或更多得工作者。

消息确认

当处理一个比较耗时得任务的时候,你也许想知道消费者是否运行到一半就挂掉。
在当前的代码中,当消息被MQ发送给消费者之后,马上就会在内存中移除。这种情况下,你只要一个工作者挂掉了,正在处理的消息就会丢失。同时,所有发送到这个工作者的还没处理的消息都会丢失。

我们不想丢失任何任务消息。如果一个工作者挂掉了,我们希望任务会发送给其他的工作者。

为了防止消息丢失,MQ提供了消息响应(ack)。消费者会通过一个ack响应,告诉MQ已经收到并处理了某条消息,然后MQ就会释放并删除这条消息。

如果消费者挂了,没有发送响应,MQ就会认为消息没有完全处理,然后重新发送给其他消费者。这样的话工作者偶尔挂掉,也不会丢失消息。

消息是没有超时的概念。当工作者与它断开连接的时候,MQ会重新发送消息,这样在处理一个耗时非常长的消息任务的时候就不会出问题了。

消息响应默认是开启的,我们可以使用no_ack=True标识吧他关闭。时候设置第四个参数basic_consume为false (true 意味着不响应ack),当工作者完成了任务就会发送一个响应。


			//回调
			$callback = function($msg){
				//输出消息体
				echo " [x] Received ",$msg->body, "\n";
				//设置睡眠时间
				sleep(substr_count($msg->body, '.'));
				//ack 确认机制
				$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
			};
			//公平调度 设置prefetch_count=1。这样是告诉RabbitMQ,再同一时刻,不要发送超过1条消息给一个工作者(worker),直到
			//它已经处理了上一条消息并且作出了响应。这样,RabbitMQ就会把消息分发给下一个空闲的工作者(worker)。
			$channel->basic_qos(null,1,null);
			//接收信息
			$channel->basic_consume('task_queue','',false,false,false,false,$callback);

这样即使工作者挂掉,没有响应的消息都会重新发送。

忘记确认

如果忘了basic_ack,后果是很严重的。消息在你的程序 退出之后就会重新发送。如果他不能够释放没有响应的消息,MQ就会占用越来越多的内存。

为了排除这种错误,你可以使用rabbitmqctl命令,输出messages_unacknowledged字段:

sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged```

消息的持久化
在退出或者崩溃的时候,将会丢失所有的队列和消息,为了确保消息不丢失,有两个事情必须注意:我们必须把“队列”和“消息’设置为持久化。

首先,为了不让队列消失,需要把队列声明为持久化durable,为此我们通过queue_declare的第三参数为true:

$channel->queue_declare('hello', false, true, false, false);```

尽管这行代码本身是对的,但仍然不会正常运行,因为我们已经定义了一个hello的费持久化的队列。MQ是不允许使用不同的参数重新定义一个队列,它会返回一个错误,但是我们现在使用一个快捷的方法。使用不同的名字,

$channel->queue_declare('task_queue', false, true, false, false);

这个 queue_declare 必须在生产者producer 和消费者consumer对应代码中修改。

这个时候,我们就可以确保MQ重启后queue_declare队列不会丢失。另外,我们也要把我们的消失也要设为持久化–delivery_mode=2

	$msg = new AMQPMessage(
				$data,array('delivery_mode'=>AMQPMessage::DELIVERY_MODE_PERSISTENT)

			);

注意:消失持久化

将消息持久化并不能完全保证不会丢失。以上代码告诉了MQ要把消息存到硬盘。但从MQ收到消息到保存之间还是有一个很小的间隔时间,并不是所有的消息都使用fsync函数将文件数据同步到硬盘–有可能只是保存到缓存中,并不一定会写在硬盘中。并不能保证真正的持久化。但已经足够应对我们简单的工作队列。

公平调度

你应该已经发现,你会发现它处理奇数个工作者比较繁忙。处理偶数是比较轻松。然而MQ并知道这些。它任然一如既往的派发工作。

因为MQ只管分发进入队列的消息,不会关心有多少的消费者没有做出响应。

这样的话我们可以使用basic_qos方法,并设置prefetch_count=1。这样就告诉MQ,在同一时刻,不要发出超过1条消息给一个工作者。直到它处理玩上一条信息并且做出响应。这样的话 MQ就会把消息分发给下一个空闲的工作者。

$channel->basic_qos(null, 1, null);```

关于队列大小

如果所有的工作者都处理繁忙状态。你的队列就会被填满,你需要留意这个问题。要么添加更多的工作者,要么使用其他的策略。

这样就可以搭建起一个工作队列。

猜你喜欢

转载自blog.csdn.net/weixin_36851500/article/details/92831107
今日推荐