rabbitmq安装与原理讲解&&php扩展编译

消息队列的实现中,RabbitMQ以其健壮和可靠见长.公司的项目中选择了它作为消息队列的实现.关于MQ的机制和原理网上有很多文章可以看,这里就不再赘述,只讲几个比较容易混淆的问题

1,binding key和routing key

  binding key和routing key是都不过是自己设置的一组字符,只是用的地方不同,binding key是在绑定交换机和队列时候通过方法传递的字符串,routing key是在发布消息时候,顺便带上的字符串,有些人说这两个其实是一个东西,也对也不对,说对,是因为这两个可以完全一样,说不对,是因为这两个起的作用不同,一个交换机可以绑定很多队列,但是每个队列也许需要的消息类型不同,binding key就是这个绑定时候留在交换机和队列之间的提示信息,当消息发送出来后,随着消息一起发送的routing key如果和binding key一样就说明消息是这个队列要的东西,如果不一样那就不要给这个队列,交换机你找找下个队列看看要不要.明白了吧,这两个key就是暗号,对上了就是自己人,对不上那麻烦你再找找去.

  binding key和routing key的配对其实也不是就要完全一样,还可以'相似'配对,建立交换机的时候,就要告诉MQ,我要声明的这个交换机和它上面的队列之间传输消息时候要求routing key和binding key完全一样,这种模式叫Direct,如果routing key和binding key可以'模糊'匹配,这种模式叫Topic,如果不需要匹配,尽管发,叫Fanout.

2,持久化

  交换机和队列都可以在创建时候设置为持久化,重启以后会回复,但是其中的消息未不会,如果要消息也恢复,将消息发布到交换机的时候,可以指定一个标志“Delivery Mode”(投递模式),  1为非持久化,2为持久化.

3,流控机制

  当消息生产的速度更快,而进程的处理能力低时,消息就会堆积起来,占用内存越来越多,导致MQ崩溃,所以rabbitmq有一个流控机制,当超过限定时候就会阻止接受消息,mq流控有三种机制
  1,主动阻塞住发消息太快的连接,这个无法调整,如果被阻塞了,在abbitmqctl 控制台上会显示一个blocked的状态。
  2,内存超过限量,会阻塞连接,在vm_memory_high_watermark可调

   3,剩余磁盘在限定以下mq会 主动阻塞所有的生产者,默认为50m,在disk_free_limit可调.


下面是在centos上面的,rabbitmq安装.
  
1, 必要的支持
yum install ncurses-devel unixODBC unixODBC-devel


2, erlang环境
wget http://www.erlang.org/download/ otp_src_17.3.tar.gz
tar zxvf otp_src_17.3.tar.gz
cd otp_src_17.3
./configure --without-javac
#忽略警告
make && make install

安装rabbitmq依赖文件,安装rabbitmq

# 安装rabbitmq依赖包
yum install xmlto

# 安装rabbitmq服务端
wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.5.7/rabbitmq-server-3.5.7.tar.gz
tar zxvf rabbitmq-server-3.5.7.tar.gz
cd rabbitmq-server-3.5.7/
make TARGET_DIR=/usr/local/rabbitmq SBIN_DIR=/usr/local/rabbitmq/sbin MAN_DIR=/usr/local/rabbitmq/man DOC_INSTALL_DIR=/usr/local/rabbitmq/doc
make install TARGET_DIR=/usr/local/rabbitmq SBIN_DIR=/usr/local/rabbitmq/sbin MAN_DIR=/usr/local/rabbitmq/man DOC_INSTALL_DIR=/usr/local/rabbitmq/doc

/usr/local/rabbitmq/sbin/rabbitmq-server -detached 启动rabbitmq
/usr/local/rabbitmq/sbin/rabbitmqctl status 查看状态
/usr/local/rabbitmq/sbin/rabbitmqctl stop 关闭rabbitmq



4,启用管理插件


mkdir /etc/rabbitmq
cd  /usr/local/rabbitmq/sbin
./rabbitmq-plugins enable rabbitmq_management  (启用插件)
./rabbitmq-plugins disable rabbitmq_management (禁用插件)
# 重启rabbitmq
# 访问 http://127.0.0.1:15672/
# 如果有iptables
# vi /etc/sysconfig/iptables  增加
#    -A INPUT -m state --state NEW -m tcp -p tcp --dport 15672 -j ACCEPT
# 重启动iptable   systemctl restart iptables.service



5,创建配置文件


#在/usr/local/rabbitmq/sbin/rabbitmq-defaults 查看config文件路径
# 创建配置文件
touch/usr/local/rabbitmq/sbin
#vm_memory_high_watermark 内存低水位线,若低于该水位线,则开启流控机制,阻止所有请求,默认值是0.4,即内存总量的40%,
#vm_memory_high_watermark_paging_ratio 内存低水位线的多少百分比开始通过写入磁盘文件来释放内存
vi /usr/local/rabbitmq/sbin/rabbitmq.config 输入
[
{rabbit, [{vm_memory_high_watermark_paging_ratio, 0.75},
         {vm_memory_high_watermark, 0.7}]}
].

相关配置

hostname mq // 设置hostname名称
vim /etc/sysconfig/network // 设置hostname
vim /etc/hosts // 编辑hosts
./rabbitmqctl add_user admin admin // 添加用户
./rabbitmqctl set_user_tags admin administrator // 添加admin 到 administrator分组
./rabbitmqctl set_permissions -p / admin "*." "*." "*." // 添加权限

6,创建环境文件


touch /etc/rabbitmq/rabbitmq-env.conf
#输入
    RABBITMQ_NODENAME=FZTEC-240088 节点名称
    RABBITMQ_NODE_IP_ADDRESS=127.0.0.1 监听IP
    RABBITMQ_NODE_PORT=5672 监听端口
    RABBITMQ_LOG_BASE=/data/rabbitmq/log 日志目录
    RABBITMQ_PLUGINS_DIR=/data/rabbitmq/plugins 插件目录
    RABBITMQ_MNESIA_BASE=/data/rabbitmq/mnesia 后端存储目录



7,安装php的rabbitmq扩展


yum install librabbitmq-devel.x86_64
wget http://pecl.php.net/get/amqp-1.4.0.tgz
tar zxvf amqp-1.4.0.tgz
cd amqp-1.4.0
/usr/local/php/bin/phpize
./configure --with-php-config=/usr/local/php/bin/php-config --with-amqp
make && make install
    
vim /usr/local/php/etc/php.ini
#输入
          extension=amqp.so

service nginx reload
service php-fpm restart

phpinfo查看是否扩展成功
rabbitmq安装与原理讲解&&php扩展编译_第1张图片

操作命令


 查看exchange信息
          /usr/local/rabbitmq/sbin/rabbitmqctl list_exchanges name type durable auto_delete arguments

 查看队列信息
          /usr/local/rabbitmq/sbin/rabbitmqctl list_queues name durable auto_delete messages consumers me
  查看绑定信息
          /usr/local/rabbitmq/sbin/rabbitmqctl list_bindings
 查看连接信息
          /usr/local/rabbitmq/sbin/rabbitmqctl list_connections




翻译了部分mq常量设置,不正确的地方,大家以试验为准


/**
 * Passing in this constant as a flag will forcefully disable all other flags.
 * Use this if you want to temporarily disable the amqp.auto_ack ini setting.
 * 传递这个参数作为标志将完全禁用其他标志,如果你想临时禁用amqp.auto_ack设置起效
 */
define('AMQP_NOPARAM', 0);

/**
 * Durable exchanges and queues will survive a broker restart, complete with all of their data.
 * 持久化交换机和队列,当代理重启动后依然存在,并包括它们中的完整数据
 */
define('AMQP_DURABLE', 2);

/**
 * Passive exchanges and queues will not be redeclared, but the broker will throw an error if the exchange or queue does not exist.
 * 被动模式的交换机和队列不能被重新定义,但是如果交换机和队列不存在,代理将扔出一个错误提示
 */
define('AMQP_PASSIVE', 4);

/**
 * Valid for queues only, this flag indicates that only one client can be listening to and consuming from this queue.
 * 仅对队列有效,这个人标志定义队列仅允许一个客户端连接并且从其消费消息
 */
define('AMQP_EXCLUSIVE', 8);

/**
 * For exchanges, the auto delete flag indicates that the exchange will be deleted as soon as no more queues are bound
 * to it. If no queues were ever bound the exchange, the exchange will never be deleted. For queues, the auto delete
 * flag indicates that the queue will be deleted as soon as there are no more listeners subscribed to it. If no
 * subscription has ever been active, the queue will never be deleted. Note: Exclusive queues will always be
 * automatically deleted with the client disconnects.
 * 对交换机而言,自动删除标志表示交换机将在没有队列绑定的情况下被自动删除,如果从没有队列和其绑定过,这个交换机将不会被删除.
 * 对队列而言,自动删除标志表示如果没有消费者和你绑定的话将被自动删除,如果从没有消费者和其绑定,将不被删除,独占队列在客户断
 * 开连接的时候将总是会被删除
 */
define('AMQP_AUTODELETE', 16);

/**
 * Clients are not allowed to make specific queue bindings to exchanges defined with this flag.
 * 这个标志标识不允许自定义队列绑定到交换机上
 */
define('AMQP_INTERNAL', 32);

/**
 * When passed to the consume method for a clustered environment, do not consume from the local node.
 * 在集群环境消费方法中传递这个参数,表示将不会从本地站点消费消息
 */
define('AMQP_NOLOCAL', 64);

/**
 * When passed to the {@link AMQPQueue::get()} and {@link AMQPQueue::get()} methods as a flag,
 * the messages will be immediately marked as acknowledged by the server upon delivery.
 * 当在队列get方法中作为标志传递这个参数的时候,消息将在被服务器输出之前标志为acknowledged (已收到)
 */
define('AMQP_AUTOACK', 128);

/**
 * Passed on queue creation, this flag indicates that the queue should be deleted if it becomes empty.
 * 在队列建立时候传递这个参数,这个标志表示队列将在为空的时候被删除
 */
define('AMQP_IFEMPTY', 256);

/**
 * Passed on queue or exchange creation, this flag indicates that the queue or exchange should be
 * deleted when no clients are connected to the given queue or exchange.
 * 在交换机或者队列建立的时候传递这个参数,这个标志表示没有客户端连接的时候,交换机或者队列将被删除
 */
define('AMQP_IFUNUSED', 512);

/**
 * When publishing a message, the message must be routed to a valid queue. If it is not, an error will be returned.
 * 当发布消息的时候,消息必须被正确路由到一个有效的队列,否则将返回一个错误
 */
define('AMQP_MANDATORY', 1024);

/**
 * When publishing a message, mark this message for immediate processing by the broker. (High priority message.)
 * 当发布消息时候,这个消息将被立即处理.
 */
define('AMQP_IMMEDIATE', 2048);

/**
 * If set during a call to {@link AMQPQueue::ack()}, the delivery tag is treated as "up to and including", so that multiple
 * messages can be acknowledged with a single method. If set to zero, the delivery tag refers to a single message.
 * If the AMQP_MULTIPLE flag is set, and the delivery tag is zero, this indicates acknowledgement of all outstanding
 * messages.
 * 当在调用AMQPQueue::ack时候设置这个标志,传递标签将被视为最大包含数量,以便通过单个方法标示多个消息为已收到,如果设置为0
 * 传递标签指向单个消息,如果设置了AMQP_MULTIPLE,并且传递标签是0,将所有未完成消息标示为已收到
 */
define('AMQP_MULTIPLE', 4096);

/**
 * If set during a call to {@link AMQPExchange::bind()}, the server will not respond to the method.The client should not wait
 * for a reply method. If the server could not complete the method it will raise a channel or connection exception.
 * 当在调用AMQPExchange::bind()方法的时候,服务器将不响应请求,客户端将不应该等待响应,如果服务器无法完成该方法,将会抛出一个异常
 */
define('AMQP_NOWAIT', 8192);

/**
 * If set during a call to {@link AMQPQueue::nack()}, the message will be placed back to the queue.
 * 如果在调用AMQPQueue::nack方法时候设置,消息将会被传递回队列
 */
define('AMQP_REQUEUE', 16384);

/**
 * A direct exchange type.
 * direct类型交换机
 */
define('AMQP_EX_TYPE_DIRECT', 'direct');

/**
 * A fanout exchange type.
 * fanout类型交换机
 */
define('AMQP_EX_TYPE_FANOUT', 'fanout');

/**
 * A topic exchange type.
 * topic类型交换机
 */
define('AMQP_EX_TYPE_TOPIC', 'topic');

/**
 * A header exchange type.
 * header类型交换机
 */
define('AMQP_EX_TYPE_HEADERS', 'headers');

/**
 * socket连接超时设置
 */
define('AMQP_OS_SOCKET_TIMEOUT_ERRNO', 536870947);
创建mqserver.php

'key_0',1=>'key_1'];
$que_name= "que_name";

//连接RabbitMQ
$conn_args = array( 'host'=>'127.0.0.1' , 
					'port'=> '5672', 
					'login'=>'guest' , 
					'password'=> 'guest',
					'vhost' =>'/');

$conn = new AMQPConnection($conn_args);
//$conn->setTimeout(1);
$conn->connect() or die('error');
//创建exchange名称和类型
$channel = new AMQPChannel($conn);
$ex = new AMQPExchange($channel);
$ex->setName($ex_name);
$ex->setType(AMQP_EX_TYPE_DIRECT);
$ex->setFlags(AMQP_DURABLE | AMQP_AUTODELETE);
$ex->declareExchange();
//创建queue名称,使用exchange,绑定routingkey
/*
$q = new AMQPQueue($channel);
$q->setName($que_name);
$q->setFlags(AMQP_DURABLE | AMQP_AUTODELETE);

$q->declareQueue();
$q->bind($ex_name, '');
*/
//echo $route_k[0];
//消息发布 
$pagestartime=microtime();

$channel->startTransaction();
$message = "key0 test msg";//json_encode(array('Hello World!','DIRECT'));
//for($i=0;$i<10000;$i++){
	//$rk = $route_k[$i%2];
	//$ex->publish("test ".$i,$rk);
//}
$ex->publish($message,$route_k[0]);
$ex->publish('key1 content1',$route_k[1]);
$ex->publish('key1 content2',$route_k[1]);
$channel->commitTransaction();
$conn->disconnect();


$pageendtime = microtime();
$starttime = explode(" ",$pagestartime);
$endtime = explode(" ",$pageendtime);
$totaltime = $endtime[0]-$starttime[0]+$endtime[1]-$starttime[1];
$timecost = sprintf("%s",$totaltime);
echo "页面运行时间: $timecost 秒";

?>
运行后运行如下 获取消息端代码

mqcli.php

'key_0',1=>'key_1'];
$que_name= $route_k[$k];//队列名尽量与路由名一致或者避免重复,否则在同一交换机下的不同路由之间会导致数据获取轮询,而不是只获取特定路由下的消息;
//连接RabbitMQ
$conn_args = array( 'host'=>'127.0.0.1' , 
					'port'=> '5672', 
					'login'=>'guest' , 
					'password'=> 'guest',
					'vhost' =>'/');
$conn = new AMQPConnection($conn_args);
$conn->connect() or die('error');
//设置queue名称,使用exchange,绑定routingkey
$channel = new AMQPChannel($conn);

/*
$ex = new AMQPExchange($channel);
$ex->setName($ex_name);
$ex->setType(AMQP_EX_TYPE_DIRECT);
$ex->setFlags(AMQP_DURABLE | AMQP_AUTODELETE);
$ex->declareExchange();
*/

$q = new AMQPQueue($channel);
$q->setName($que_name);
$q->setFlags(AMQP_DURABLE | AMQP_AUTODELETE);
$q->declareQueue();
$q->bind($ex_name, $que_name);//$route_k[1]); 
//消息获取
$messages = $q->get(AMQP_AUTOACK) ;
if ($messages){
var_dump($messages->getBody());
}else{
	var_dump($messages);
}
$conn->disconnect();

?>
运行结果:

mcli.php?k=0   1 次显示如下

string(13) "key0 test msg"s

mcli.php?k=0   第2 次则显示如下(路由器key_0下的消息共1条已经被取出完)

bool(false)

mcli.php?k=1   1 次显示如下

string(13) "key1 content1"

mcli.php?k=1   第2 次显示如下

string(13) "key1 content2"

mcli.php?k=1        第3 次则显示如下(路由器key_1下的消息共2条已经被取出完)

bool(false)

接下来 摘取天上星 对rabbitmq进行一点深入了解(以下内容来自互联网):

rabbitmq原理讲解

AMPQ协议为了能够满足各种消息队列需求,在概念上比较复杂。首先,rabbitMQ启动默认是没有任何配置的,需要客户端连接上去,设置交换机等才能工作。不把这些基础概念弄清楚,后面程序设计就容易产生问题。

1.vhosts : 虚拟主机。

一个RabbitMQ的实体上可以有多个vhosts,用户与权限设置就是依附于vhosts。对一般PHP应用,不需要用户权限设定,直接使用默认就存在的"/"就可以了,用户可以使用默认就存在的"guest"。一个简单的配置示例:

$conn_args = array(
    'host' => '127.0.0.1',
    'port' => '5672',
    'login' => 'guest',
    'password' => 'guest',
    'vhost'=>'/'
);

2.connection 与 channel :  连接与信道

connection是指物理的连接,一个client与一个server之间有一个连接;一个连接上可以建立多个channel,可以理解为逻辑上的连接。一般应用的情况下,有一个channel就够用了,不需要创建更多的channel。示例代码:

//创建连接和channel
$conn = new AMQPConnection($conn_args);
if (!$conn->connect()) {
    die("Cannot connect to the broker!\n");
}
$channel = new AMQPChannel($conn);

3.exchange 与  routingkey : 交换机与路由键

为了将不同类型的消息进行区分,设置了交换机与路由两个概念。比如,将A类型的消息发送到名为‘C1’的交换机,将类型为B的发送到'C2'的交换机。当客户端连接C1处理队列消息时,取到的就只是A类型消息。进一步的,如果A类型消息也非常多,需要进一步细化区分,比如某个客户端只处理A类型消息中针对K用户的消息,routingkey就是来做这个用途的。

$e_name = 'e_linvo'; //交换机名
$k_route = array(0=> 'key_1', 1=> 'key_2'); //路由key
//创建交换机
$ex = new AMQPExchange($channel);
$ex->setName($e_name);
$ex->setType(AMQP_EX_TYPE_DIRECT); //direct类型
$ex->setFlags(AMQP_DURABLE); //持久化
echo "Exchange Status:".$ex->declare()."\n";
for($i=0; $i<5; ++$i){
    echo "Send Message:".$ex->publish($message . date('H:i:s'), $k_route[i%2])."\n";
}

由以上代码可以看到,发送消息时,只要有“交换机”就够了。至于交换机后面有没有对应的处理队列,发送方是不用管的。routingkey可以是空的字符串。在示例中,我使用了两个key交替发送消息,是为了下面更便于理解routingkey的作用。

对于交换机,有两个重要的概念:

A,类型。有三种类型: Fanout类型最简单,这种模型忽略routingkey;Direct类型是使用最多的,使用确定的routingkey。这种模型下,接收消息时绑定'key_1'则只接收key_1的消息;最后一种是Topic,这种模式与Direct类似,但是支持通配符进行匹配,比如: 'key_*',就会接受key_1和key_2。Topic貌似美好,但是有可能导致不严谨,所以还是推荐使用Direct。

B,持久化。指定了持久化的交换机,在重新启动时才能重建,否则需要客户端重新声明生成才行。

需要特别明确的概念:交换机的持久化,并不等于消息的持久化。只有在持久化队列中的消息,才能持久化;如果没有队列,消息是没有地方存储的;消息本身在投递时也有一个持久化标志的,PHP中默认投递到持久化交换机就是持久的消息,不用特别指定。

4.queue: 队列

讲了这么多,才讲到队列呀。事实上,队列仅是针对接收方(consumer)的,由接收方根据需求创建的。只有队列创建了,交换机才会将新接受到的消息送到队列中,交换机是不会在队列创建之前的消息放进来的。换句话说,在建立队列之前,发出的所有消息都被丢弃了。下面这个图比RabbitMQ官方的图更清楚——Queue是属于ReceiveMessage的一部分。

rabbitmq安装与原理讲解&&php扩展编译_第2张图片

接下来看一下创建队列及接收消息的示例:

$e_name = 'e_linvo'; //交换机名
$q_name = 'q_linvo'; //队列名
$k_route = ''; //路由key

//创建连接和channel
$conn = new AMQPConnection($conn_args);
if (!$conn->connect()) {
    die("Cannot connect to the broker!\n");
}
$channel = new AMQPChannel($conn);

//创建交换机
$ex = new AMQPExchange($channel);
$ex->setName($e_name);
$ex->setType(AMQP_EX_TYPE_DIRECT); //direct类型
$ex->setFlags(AMQP_DURABLE); //持久化
echo "Exchange Status:".$ex->declare()."\n";

//创建队列
$q = new AMQPQueue($channel);
$q->setName($q_name);
$q->setFlags(AMQP_DURABLE); //持久化

//绑定交换机与队列,并指定路由键
echo 'Queue Bind: '.$q->bind($e_name, $k_route)."\n";

//阻塞模式接收消息
echo "Message:\n";
$q->consume('processMessage', AMQP_AUTOACK); //自动ACK应答

$conn->disconnect();

/**
* 消费回调函数
* 处理消息
*/
function processMessage($envelope, $queue) {
    var_dump($envelope->getRoutingKey);
    $msg = $envelope->getBody();
    echo $msg."\n"; //处理消息
}

从上述示例中可以看到,交换机既可以由消息发送端创建,也可以由消息消费者创建。

创建一个队列(line:20)后,需要将队列绑定到交换机上(line:25)队列才能工作,routingkey也是在这里指定的。有的资料上写成bindingkey,其实一回事儿,弄两个名词反倒容易混淆。

消息的处理,是有两种方式:

A,一次性。用 $q->get([...]),不管取到取不到消息都会立即返回,一般情况下使用轮询处理消息队列就要用这种方式;

B,阻塞。用 $q->consum( callback, [...] ) 程序会进入持续侦听状态,每收到一个消息就会调用callback指定的函数一次,直到某个callback函数返回FALSE才结束。

关于callback,这里多说几句: PHP的call_back是支持使用数组的,比如: $c = new MyClass(); $c->counter = 100; $q->consume( array($c,'myfunc') ) 这样就可以调用自己写的处理类。MyClass中myfunc的参数定义,与上例中processMessage一样就行。

在上述示例中,使用的$routingkey = '', 意味着接收全部的消息。我们可以将其改为 $routingkey = 'key_1',可以看到结果中仅有设置routingkey为key_1的内容了。

注意: routingkey = 'key_1'  与 routingkey = 'key_2' 是两个不同的队列。假设: client1 与 client2 都连接到 key_1 的队列上,一个消息被client1处理之后,就不会被client2处理。而 routingkey = '' 是另类,client_all绑定到 '' 上,将消息全都处理后,client1和client2上也就没有消息了。

在程序设计上,需要规划好exchange的名称,以及如何使用key区分开不同类型的标记,在消息产生的地方插入发送消息代码。后端处理,可以针对每一个key启动一个或多个client,以提高消息处理的实时性。





你可能感兴趣的:(linux,php,消息队列,rabbitmq)