Beanstalkd消息中间件入门及技术指南

初识Beanstalkd

          Beanstalk,一个高性能、轻量级的分布式内存队列系统,最初设计的目的是想通过后台异步执行耗时的任务来降低高容量Web应用系统的页面访问延迟,支持过有9.5 million用户的Facebook Causes应用。

参考:https://www.cnblogs.com/jkko123/p/8177731.html

安装

# yum install beanstalkd

查看版本

# beanstalkd -v

启动beanstalkd

# beanstalkd -l 127.0.0.1 -p 11300 -b /usr/local/beanstalkd/binlog &

-b表示开启binlog,断电后重启自动恢复任务 。 /usr/local/beanstalkd/binlog为自建的目录。

关于系统自动启动beanstalkd的方法,可以参考如下,

linux配置开机启动进程的方法 https://blog.csdn.net/yan_dk/article/details/104193703

这样就不用每次都重新启动beanstalkd了。 

常用知识

一、Beanstalkd是什么?

Beanstalkd是一个高性能,轻量级的分布式内存队列

二、Beanstalkd特性

1、支持优先级(支持任务插队)
2、延迟(实现定时任务)
3、持久化(定时把内存中的数据刷到binlog日志)
4、预留(把任务设置成预留,消费者无法取出任务,等某个合适时机再拿出来处理)
5、任务超时重发(消费者必须在指定时间内处理任务,如果没有则认为任务失败,重新进入队列)

三、Beanstalkd核心元素

生产者 -> 管道(tube) -> 任务(job) -> 消费者

Beanstalkd可以创建多个管道,管道里面存了很多任务,消费者从管道中取出任务进行处理。

job:一个需要异步处理的任务,需要放在一个tube中。

tube:一个有名字的任务队列,用来存储统一类型的job,可以创建多个管道

producer:job的生产者

consumer:job的消费者

简单流程:由 producer 产生一个任务 job ,并将 job 推进到一个 tube 中,然后由 consumer 从 tube 中取出 job 执行

四、任务job状态

delayed 延迟状态,延迟执行的任务,任务到期后会自动成为当前任务
ready 准备好状态,可以被消费
reserved 消费者把任务读出来,正在执行的任务
buried 预留状态,保留的任务: 任务不会被执行,也不会消失,除非有人把它 "踢" 回队列;将任务取出之后,发现后面执行的逻辑不成熟(比如发邮件,突然发现邮件服务器挂掉了), //或者说还不能执行后面的逻辑,需要把任务先封存起来,等待时机成熟了,再拿出这个任务进行消费

delete 删除状态,消息被彻底删除。Beanstalkd 不再维持这些消息。

七、Pheanstalk使用方法

维护方法
stats() 查看状态方法
listTubes() 目前存在的管道
listTubesWatched() 目前监听的管道
statsTube() 管道的状态
useTube() 指定使用的管道
statsJob() 查看任务的详细信息
peek() 通过任务ID获取任务


生产者方法

putInTube() 往管道中写入数据
put() 配合useTube()使用
消费者方法

watch() 监听管道,可以同时监听多个管道
ignore() 不监听管道
reserve() 以阻塞方式监听管道,获取任务
reserveFromTube()
release() 把任务重新放回管道
bury() 把任务预留
peekBuried() 把预留任务读取出来
kickJob() 把buried状态的任务设置成ready
kick() 批量把buried状态的任务设置成ready
peekReady() 把准备好的任务读取出来
peekDelayed() 把延迟的任务读取出来
pauseTube() 给管道设置延迟
resumeTube() 取消管道延迟
touch() 让任务重新计算ttr时间,给任务续命

 

示例

Beanstalk主要包括4个部分。

  1> job:一个需要异步处理的任务,需要放在一个tube中。

  2> tube:一个有名的任务队列,用来存储统一类型的job,是producer和consumer操作的对象。

  3> producer:job的生产者,通过put命令来将一个job放到一个tube中。

  4> consumer:job的消费者,通过reserve、release、bury、delete命令来获取job或改变job的状态。

我们可以准备composer环境来运行

composer技术可参见:https://mp.csdn.net/postedit/90228559

producer.php

<?php
require "vendor/autoload.php";
try {
//创建一个Pheanstalk对象
    $p = new \Pheanstalk\Pheanstalk("127.0.0.1", 11300);
//模拟任务数据
    $task_data = array('id' => 1, 'name' => 'task-' . date('Ymdhms', time()),);
//向业务任务管道中添加任务,返回任务ID
//put()方法有四个参数:1任务的数据 2任务的优先级,值越小,越先处理;3任务的延迟;4任务的ttr超时时间
    $id = $p->useTube('taskDemo')->put(json_encode($task_data));
//获取任务
    $job = $p->peek($id);
    var_dump($job);
}catch (Exception $ex){
    var_dump('phbeanstalk生产任务出现异常错误=',$ex->getMessage());
}
var_dump('管道状态=',$p->stats());//查看目前pheanStalkd状态信息
var_dump('存在的管道=',$p->listTubes());//显示目前存在的管道

consumer.php

<?php
require "vendor/autoload.php";
try {
//创建一个Pheanstalk对象
    $p = new \Pheanstalk\Pheanstalk("127.0.0.1", 11300);
    while (true){
        $job=$p->watchOnly("taskDemo")->reserve();
        if(!empty($job)){
            var_dump($job->getData());
        }
    }
}catch (Exception $ex){
    var_dump('phbeanstalk消费者任务出现异常错误=',$ex->getMessage());
}

1.启动beanstakld

# beanstalkd -l 127.0.0.1 -p 11300 -b /usr/local/beanstalkd/binlog &

2.运行生产任务

#php producer.php

3.运行消费任务

#php consumer.php

beanstalkd+redis实现方法

可以用redis缓存任务状态的方式实现。

#php consumer_redis.php

require "vendor/autoload.php";
//演示多个消费者可能同时获取任务的流程,需要保证任务的同步处理(通过redis保存任务状态信息)。
//1.任务可靠性问题,假设一个消费者获取到任务,执行任务时,突然宕机了,导致任务不能正常执行,
//比如订单付款后增加积分或余额的任务,没有被正常执行,那么数据会不一致,问题就很严重,
//2.消费幂等性问题,也就是消费者的任务重复执行问题,如果一个任务被多个消费者重复执行,也会发生很严重问题,
//比如,订单付款后库存减少,如果被多次执行,会出现库存不对的情况,
//消息的双向确认,去重/重传
//为保证上述情况能正确处理,避免上述发生情况,我们可以将任务设置状态,1任务正在执行中,2任务执行完成
$redis=new Predis\Client(['scheme'=>'tcp','host'=>"127.0.0.1",'port'=>6379]);
try {
//创建一个Pheanstalk对象
    $p = new \Pheanstalk\Pheanstalk("127.0.0.1", 11300);
    //从管道中取出任务
    //如果没有数据会挂起等待数据,所以while不会进入死循环
    while (true){
        $job=$p->watchOnly("taskDemo")->reserve();//每隔3秒执行获取一个管道任务
        if(!empty($job)){
            $data=json_decode($job->getData(),true);           $msg_id=$data['msg_id'];
            //假设某个消费者在接收任务时,突然宕机了,没有及时消费任务,可以将任务分发给其他消费者
            $jobKey="job".$msg_id;
            $state=$redis->get($jobKey);
            //var_dump("job".$msg_id.';state='.$state);
            if($state==1){//有消费者正在执行当中
                //重新把任务放入到队列,否则其他程序无法删除
                var_dump("任务正在执行中-".$data['msg_id']);
                //阶梯式重试
                $p->release($job,0,5);//延迟5秒继续投递任务
            }elseif($state==2){
                //$redis->set("job".$msg_id,2);
                var_dump("任务已经执行-".$data['msg_id']);
            }else{
                //假设设置的任务状态正在执行,但设置完就崩溃或者任务执行失败
                //设置任务状态的生存期
                $redis->setex($jobKey,6,1);
//执行任务业务逻辑(发短信、分佣金、加积分)
                sleep(10);
                $redis->set($jobKey,2);
                var_dump($job->getData());
                $p->delete($job);////ack应答机制设计是为了消息的可靠性
            }
        }
    }
}catch (Exception $ex){
    var_dump('phbeanstalk消费者任务出现异常错误='.$ex->getMessage());
}

beanstalkd+swoole实现方法

为提高任务异步处理性能,我们可以使用swoole来处理。

#php consumer_swoole.php

<?php

require "vendor/autoload.php";

$workerNum = 4;
$pool = new Swoole\Process\Pool($workerNum);

$pool->on("WorkerStart", function ($pool, $workerId) {
    try {
//创建一个Pheanstalk对象
        $p = new \Pheanstalk\Pheanstalk("127.0.0.1", 11300);
        while (true) {
            $job = $p->watchOnly("taskDemo")->reserve();
            if (!empty($job)) {
                var_dump('workerid='.$workerId.";jobdata=".$job->getData());
                //极端情况,刚好取出数据,数据终止了任务没有执行
               //我取出数据还没有执行,程序出现了致命错误结束掉了
                //一般任务被reserve读出后,业务处理完成时应及时进行delete操作,防止重新被放回管道
                $p->delete($job);
                //执行业务逻辑,发短信、加积分
            }
        }
    } catch (Exception $ex) {
        var_dump('phbeanstalk消费者任务出现异常错误=', $ex->getMessage());
    }
});

$pool->on("WorkerStop", function ($pool, $workerId) {
    echo "Worker#{$workerId} is stopped\n";
});
$pool->start();

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章