rabbitmq的六种模式学习笔记

来源于官方手册(通俗易懂) https://www.rabbitmq.com/tutorials/tutorial-one-php.html

1. Hello World / Simple /简单 模式

生产者:send.php

<?php
/**
 * Simple 模式 - 生产者
 * RabbitMq 6种模式
 * Date: 2020/4/20 下午4:15
 */

require_once __DIR__. '/../vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

//创建一个 连接 到 RabbitMq服务器
//注意 协议版本
$connection = new AMQPStreamConnection('192.168.8.234', 5672, 'guest', 'guest');

//创建一个channel,大多数 API 都要使用它来做一些事情。
$channel = $connection->channel();

//为了发送,我们必须声明 一个队列queue 来发送
//声明队列 是幂等性的,队列只在它不存在的时候创建
$channel->queue_declare(
    'hello',
    false,
    false,
    false,
    false
);

//消息的内容是 字节数组 格式,所以你可以 对其进行编码(encode)
$msg = new AMQPMessage('Hello World:');
$channel->basic_publish(
    $msg,
    '',         //交换机名称
    'hello'   //路由key
);

echo " [x] Sent 'Hello World!'\n";

//关闭channel
$channel->close();

//关闭连接
$channel->close();

receive.php

<?php
/**
 * Simple 模式 - 消费者
 * RabbitMq 6种模式
 * Date: 2020/4/20 下午4:15
 */

require_once __DIR__. '/../vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;

// 不像生产者(publisher),消费者receiver 为了接收消息 需要监听(listening) RabbitMQ

$connection = new AMQPStreamConnection('192.168.8.234', 5672, 'guest', 'guest');
$channel = $connection->channel();

//注意, 这里也声明了队列(queue),因为这个消费者启动的程序 可能 早于生产者程序的启动,
//在这里也声明为了确保,在我们试着从它里边消费时,这个队列是存在的。
$channel->queue_declare(
    'hello',
    false,
    false,
    false,
    false
);

//记住,消息被server 异步发送给 client(消费端)的
//需要定义一个 回调函数, 参数是 消息
$callback = function ($msg) {
    echo ' [x] Received ', $msg->body, "\n";
};

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

//不论何时,我们接收到了消息,我们将调用 $callback 函数,参数为"消息"
while ($channel->is_consuming()) {
    $channel->wait();
}

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


2、 工作队列模式 Work Queue

 

【消息丢失】

在没有ack 情况下 像 一个任务 执行需要几秒钟,如果一个worker 被kill 或者其他服务器问题,这个worker 接收的这个消息被没有被正确处理, 而在队列里边 它已经被标记为for deletion。这样情况 这个消息就丢了

为了 确保 一个消息 永不丢失,RabbitMQ 支持 消息确认(message acknowledgments)

【消息确认机制】

//如果一个 消费者 consumer 死掉了(他的channel 被关闭,connection被关闭,或者 tcp连接断了),这时它没有发送 一个 ack RabbitMQ 将理解为 这个消息没有被完全处理,将重新把它放到 队列里。如果这时候,有其他的 消费者在线,它将很快将此消息投递给其他的消费者。

注意:不存在 消息 超时时间,即便是 处理一个消息需要非常长非常长的时间。

【忘记确认】

这是常见的错误,后果很严重。由于它不能够释放任何没被确认的消息,它将占用越来越多的内存。

为了能够找出 这种类型 的错误。使用命令:

//sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged
 

消息的持久性 (durability)

当 RabbitMQ quit 或者宕机,它会丢掉 队列或消息 除非你告诉它不要这样。

为了消息不丢失。 我们需要做2件事,标记队列 和 消息 都为 持久化(durable)

1. 这个 durable =true 的标志,需要生产者 和消费者 都去设置 

2. 在AMQPMessage 中 设置 delivery_mode=2

【注意消息的 持久化!】

标记消息为 持久化,不能够完全 保证消息不丢失。因为这里存在很短的时间窗口:当RabbitMQ收到消息还没有保存。 // RabbitMQ 不会针对每个消息 来进行 fsync, 他会先保存到 缓存里。 // 这种持久机制 不是强壮的,但是对于简单的任务队列是足够了。如果你需要一个更加强壮的保障机制, // 你可以使用 publisher_confirms.

【注意】:RabbitMQ 不允许你 使用不同的参数 去重新定义一个 已经存在的 队列queue,如果这样做了,它将返回一个错误error

【公平的分发】

 默认地: RabbitMQ 它 不看 消费者的未确认消息的数量,它仅仅不加思考地 分发每一个 n-th 消息到 n-th 消费者。

为了改变这种分发策略! 可以使用 basic_qos 设置 prefetch_count=1;

// 含义是:不要分发 一个新消息给 一个消费者 除非它已经处理并确认了之前的一个消息。

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

 

new_task.php 代码如下:

<?php
/**
 * workerQueue 模式 - 生产者
 * 这种模式与 simple没有本质的区别(代码层面的)它加上了持久化,公平分发等参数,启用多个worker 来模拟真正的工作上的用法
 * 基于此原因,生产环境中不要使用simple例子中的代码
 * RabbitMq 6种模式
 * Date: 2020/4/20 下午4:15
 */

require_once __DIR__. '/../../vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

//创建一个 连接 到 RabbitMq服务器
//注意 协议版本
$connection = new AMQPStreamConnection('192.168.21.234', 5672, 'guest', 'guest');

//创建一个channel,大多数 API 都要使用它来做一些事情。
$channel = $connection->channel();

//为了发送,我们必须声明 一个队列queue 来发送
//声明队列 是幂等性的,队列只在它不存在的时候创建
$channel->queue_declare(
    'task_queue',  //前一个 hello
    false,
    true,  //持久化 false
    false,
    false
);

//消息的内容是 字节数组 格式,所以你可以 对其进行编码(encode)
$data = implode(' ', array_slice($argv, 1));
if (empty($data)) {
    $data = "Hello World!";
}

$msg = new AMQPMessage(
    $data,
    array('delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT)  //想让消息持久化必须进行此设置
);
$channel->basic_publish(
    $msg,
    '',         //交换机名称
    'task_queue'   //路由key ; 之前为task_queue
);

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

//关闭channel
$channel->close();

//关闭连接
$channel->close();


//默认地
// RabbitMQ 将 按照顺序地 发送每个消息 给 下一个 消费者;
// 平均每个消费者 将得到 相同数量的消息。这种分发消息的方式 叫  round-robin (轮询)

/**
 * //运行结果 -shell1
[root@liang workQueues]# php new_task.php First message.
[x] Sent First message.
[root@liang workQueues]# php new_task.php Second message..
[x] Sent Second message..
[root@liang workQueues]# php new_task.php Third message...
[x] Sent Third message...
[root@liang workQueues]# php new_task.php Fourth message....
[x] Sent Fourth message....
[root@liang workQueues]# php new_task.php Fifth message.....
[x] Sent Fifth message.....

 // shell2
[x] Received Second message..
[x] Done
[x] Received Fourth message....
[x] Done
 
 // shell3
[x] Received First message.
[x] Done
[x] Received Third message...
[x] Done
[x] Received Fifth message.....
[x] Done

 *
 *
 */

worker.php 代码如下:

<?php
/**
 * Simple 模式 - 消费者
 * RabbitMq 6种模式
 * Date: 2020/4/20 下午4:15
 */

require_once __DIR__. '/../../vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;

// 不像生产者(publisher),消费者receiver 为了接收消息 需要监听(listening) RabbitMQ

$connection = new AMQPStreamConnection('192.168.21.234', 5672, 'guest', 'guest');
$channel = $connection->channel();

//注意, 这里也声明了队列(queue),因为这个消费者启动的程序 可能 早于生产者程序的启动,
//在这里也声明为了确保,在我们试着从它里边消费时,这个队列是存在的。
$channel->queue_declare(
    'task_queue', //之前为hello
    false,
    true,   //之前为false
    false,
    false
);

//假的任务 消耗的时间,一个点一秒钟
$callback = function ($msg) {
    echo ' [x] Received ', $msg->body, "\n";
    sleep(substr_count($msg->body, '.'));
    echo " [x] Done\n";
    
    $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
};

$channel->basic_qos(
    null,
    1, //不要分发 一个新消息给 一个消费者 除非它已经处理并确认了之前的一个消息。
    null
);
//
$channel->basic_consume(
    'task_queue', //之前为hello
    '',
    false,
    false,  //true 不确认; false 确认
    false,
    false,
    $callback
);

//不论何时,我们接收到了消息,我们将调用 $callback 函数,参数为"消息"
while ($channel->is_consuming()) {
    $channel->wait();
}

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


3、发布订阅模式Publish/Subscribe 

 

 

投递一个消息给 多个消费者。

模式被叫做   发布/订阅  “publish/subscribe”

 

为了说明这个模式 。我们将 构建一个 简单的 日志系统。

发布日志 消息 将广播给 所有 消费者receivers

一个 消费者 worker 记录日志到 硬盘; 另一个消费者 worker 打印日志到 屏幕上。

RbbitMQ 的消息模式里面,生产者从来 不要直接发送 任何消息到一个 队列queue.

替代地, 生产者可以 发送消息 到 exchange.   

exchange 决定 发送给哪个 队列。

exchange 的类型 有 direct, topic, headers  , fanout.

列出 exchanges

sudo rabbitmqctl list_exchanges

default exchange 默认exchange

在之前的教程中,我们不知道exchanges,但是 仍然能够发送消息给 队列queues.

这个很可能是因为: 我们正在使用一个默认的 exchange, 它 被定义 使用 空字符串“”

临时队列

1 。不论何时 我们连接到 Rabbit 我们需要一个 新的,空的 队列。为了做到这样,

我们可以创建一个队列,使用随机的名字,更好的方式是 让 rabbitmq server 来选择一个随机的名字

,我们来用。

2 。 一旦 我们 断开连接, 消费者队列 应该被 自动地删除。

list($queue_name, ,) = $channel->queue_declare("");

名字可能像:amq.gen-JzTY20BRgKO-HjmUJj0wLg

当声明它的连接  关闭时, queue 将被 关闭,因为 它被声明为 exclusive

绑定:

已经创建了一个 fanout类型的 exchange 和 一个 queue, 现在

我们需要告诉 exchage 去 发送 消息 给 我们的 queue

$channel->queue_bind($queue_name, 'logs');

相关命令:

列出存在的绑定。

rabbitmqctl list_bindings

生产者代码:emit_log.php

<?php
/**
 * Date: 2020/4/21 下午3:46
 */

require_once __DIR__. '/../../vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

//创建一个 连接 到 RabbitMq服务器
$connection = new AMQPStreamConnection('192.168.21.234', 5672, 'guest', 'guest');

//创建一个channel,大多数 API 都要使用它来做一些事情。
$channel = $connection->channel();

$channel->exchange_declare('logs', 'fanout', false,false, false);

$data = implode('', array_slice($argv, 1));
if (empty($data)) {
    $data = "info: Hello World!";
}

$msg = new AMQPMessage($data);

$channel->basic_publish($msg, 'logs');

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

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

 

如果exchange还没有绑定队列,这些消息将被 丢失 。不过这个对我们来说还 ok了,

因为没有 消费者正在监听

消费者代码:receive_logs.php

<?php
/**
 * Date: 2020/4/21 下午4:29
 */

require_once __DIR__ . '/../../vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;

$connection = new AMQPStreamConnection('192.168.21.234', 5672, 'guest', 'guest');
$channel = $connection->channel();

$channel->exchange_declare('logs', 'fanout', false, false, false);

list($queue_name, ,) = $channel->queue_declare("", false, false,true, false);

$channel->queue_bind($queue_name, 'logs');

echo " [*] Waiting for logs. To exit press CTRL+C\n";

$callback = function ($msg) {
    echo ' [x] ', $msg->body, "\n";
};

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

while ($channel->is_consuming()) {
    $channel->wait();
}

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

如果你想保存日志 到文件:

php receive_logs.php > logs_from_rabbit.log

如果你想 在屏幕上 看到日志输出:

php receive_logs.php

生产者。

php emit_log.php

为了 解决 如何 去 监听 一个 消息的子集,看 教程4

4. 路由模式

 

在前面日志的系统的基础上,增加功能。

如: 只有 严重错误的 消息 才记录到 硬盘上,另一方面打印 所有的日志信息到控制台上。

绑定:

$channel->queue_bind($queue_name, 'logs');

可以简单地读: 此queue  对 exchange 上的消息 感兴趣

 

第三个参数: binding key

binding key 含义 取决于  exchange 类型

对于 fanout 类型 exchange。 我们可以简单的忽略掉 此参数(不传递)

 

Direct 类型 exchange

fanout 类型的 exchange ,这种不能给我们 更多的 灵活性,它只是 简单的广播

type =direct  ;  只要queue 它的binding key 匹配上 消息的 routing key ,队列就能收到消息。

 

多绑定:

多个队列queue 使用 相同的 binding key。 如果这样来设计,那么它的行为就 类似 fanout。如下图:

生产日志/ 发射日志 (emitting logs)

改进后的日志系统 使用的 queue 结构如下:

生产者代码:emit_log_direct.log

<?php
/**
 * Date: 2020/4/21 下午3:46
 */

require_once __DIR__. '/../../vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

//创建一个 连接 到 RabbitMq服务器
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');

//创建一个channel,大多数 API 都要使用它来做一些事情。
$channel = $connection->channel();

$channel->exchange_declare('direct_logs', 'direct', false,false, false);

$severity = isset($argv[1]) && !empty($argv[1]) ? $argv[1] : 'info';

$data = implode('', array_slice($argv, 2));
if (empty($data)) {
    $data = "info: Hello World!";
}

$msg = new AMQPMessage($data);

$channel->basic_publish($msg, 'direct_logs', $severity);

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

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

消费者代码:receive_logs_direct.log

<?php
/**
 * Date: 2020/4/21 下午4:29
 */

require_once __DIR__ . '/../../vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;

$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();

$channel->exchange_declare('direct_logs', 'direct', false, false, false);

list($queue_name, ,) = $channel->queue_declare("", false, false,true, false);

$severities = array_slice($argv, 1);
if (empty($severities)) {
    file_put_contents('php://stderr', "Usage: $argv[0] [info] [warning] [error]\n");
    exit(1);
}


//对 日志 各种级别 进行绑定
foreach($severities as $severity) {
    $channel->queue_bind($queue_name, 'direct_logs', $severity);
}

echo " [*] Waiting for logs. To exit press CTRL+C\n";

$callback = function ($msg) {
    echo ' [x] ', $msg->body, "\n";
};

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

while ($channel->is_consuming()) {
    $channel->wait();
}

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

5 、topics  通配符模式

尽管使用 direct exchange 提高了 我们的系统,它仍然有限制, 它不能 基于多个准则来 路由。

像unix 的 syslog 工具,它 是基于 安全级别( info/warning/critical …) 和 设备来源 (auth/cron/kernel…)

比如 syslog 可以做到 仅 监听 来自 cron 的errors 信息 和 来自 kern 的所有log信息

我们的log系统 为了做到 像syslog 那样,我们需要 topic exchange

routing_key  由  点号连接着的一列单词 如:quick.orange.rabbit  最大长度 255个字符。

binding keys  有两个特别重要的情况:

星号* 代替一个单词

# 号  可以代替 0 或 多个单词。

 

routing key 由 3个单词 组成 speed.colour.species   //速度。颜色。物种

我们创建3个 binding key:

Q1  是  用 binding key     *.orange.*

Q2  的 binding key   *.*.rabbit  和 lazy.#

一个消息 它的 routing key 被设置为 “quick.orange.rabbit” ,将被投递给 2个queue

消息“ lazy.orange.elephant ,也将被投递给2个queue

"quick.orange.fox"  将去到 第一个 queue

lazy.brown.fox 将去到 第二个queue

lazy.pink.rabbit" 将被投递给 第二个 queue 一次。即便是它 和 2个 binding key 都匹配

"quick.brown.fox"  不匹配任何binding ,所以它将被丢弃。

 

像  "orange" or "quick.orange.male.rabbit  是 一个单词或者 四个单词 ,不匹配任何 binding key ,将被丢弃

 "lazy.orange.male.rabbit 尽管它有四个单词,将匹配 lazy.#  将去到 第二个 queue

注意:当一个 queue 绑定了 “#”,它将接收所有的消息,忽略 routing key ,就像 fanout exchange

当 binding key 中没有 *  和 #,那么 topic exchange 的行为就像 direct exchange

生产者代码:emit_log_topic.php

<?php
/**
 * Date: 2020/4/21 下午11:14
 */

require_once __DIR__ . '/../../vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();

$channel->exchange_declare('topic_logs', 'topic', false, false, false);

$routing_key = isset($argv[1]) && !empty($argv[1]) ? $argv[1] : 'anonymous.info';
$data = implode(' ', array_slice($argv, 2));
if (empty($data)) {
    $data = "Hello World!";
}

$msg = new AMQPMessage($data);

$channel->basic_publish($msg, 'topic_logs', $routing_key);

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

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

消费者代码:receive_logs_topic.php

<?php
/**
 * Date: 2020/4/21 下午11:21
 */

require_once __DIR__ . '/../../vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;

$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();

$channel->exchange_declare('topic_logs', 'topic', false, false, false);

list($queue_name, ,) = $channel->queue_declare("", false, false, true, false);

$binding_keys = array_slice($argv, 1);
if (empty($binding_keys)) {
    file_put_contents('php://stderr', "Usage: $argv[0] [binding_key]\n");
    exit(1);
}

foreach ($binding_keys as $binding_key) {
    $channel->queue_bind($queue_name, 'topic_logs', $binding_key);
}

echo " [*] Waiting for logs. To exit press CTRL+C\n";

$callback = function ($msg) {
    echo ' [x] ', $msg->delivery_info['routing_key'], ':', $msg->body, "\n";
};

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

while ($channel->is_consuming()) {
    $channel->wait();
}

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

6、 RPC  

如果 我想在 远端的 计算机上 运行 一个 函数 并等待函数的 返回结果 。 这种模式 通常叫做 RPC

例子 : service 只返回 数字。

Callback queue  

消息的属性:

AMQP   0-9-1 协议预定义了 14个属性,大多数 很少用,有几个还是能用到的 如:

delivery_mode  //标记消息是否是持久的

content_type   //编码格式,通常使用json编码,如: application/json

reply_to   //通常用来命名 一个 callback queue

correlation_id  // 用于 关联 Rpc  response  和  requests

correlation id

如果我们看到一个 unknown 的 correlation_id 值,我们可以安全地删除 这个消息。

你可能会问,为什么我们是忽略 unknown 消息,而不是 发出一个error 错误呢。

这是由于 在服务端存在 罕见的情况。

rpc server 发送完answer 后,还没来得及发送 ack message  给 request,这时候 rpc server 死掉了。

如果这样的事发生了,重启rpc server后 将再次处理request。 

这就是为什么 client 必须能优雅地处理 重复的 响应,并且 RPC server的处理 也应该是幂等性的。

 

client 代码:rpc_client.php

<?php
/**
 * Email: zhiliang.zhao@360che.com
 * Date: 2020/4/23 下午10:48
 */

require_once __DIR__ . '/../../vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

class FibonacciRpcClient
{
    private $connection;
    private $channel;
    private $callback_queue;
    private $response;
    private $corr_id;
    
    public function __construct()
    {
        $this->connection = new AMQPStreamConnection(
            'localhost',
            5672,
            'guest',
            'guest'
        );
        $this->channel = $this->connection->channel();
        list($this->callback_queue, ,) = $this->channel->queue_declare(
            "",
            false,
            false,
            true,
            false
        );
        
        $this->channel->basic_consume(
            $this->callback_queue,
            '',
            false,
            true,
            false,
            false,
            array(
                $this,
                'onResponse'
            )
        );
    }
    
    public function onResponse($rep)
    {
        if($rep->get('correlation_id') == $this->corr_id) {
            $this->response = $rep->body;
        }
    }
    
    public function call($n)
    {
        $this->response = null;
        $this->corr_id = uniqid();
        
        $msg = new AMQPMessage(
            (string) $n,
            array(
                'correlation_id' => $this->corr_id,
                'reply_to' => $this->callback_queue
            )
        );
        $this->channel->basic_publish($msg, '', 'rpc_queue');
        while(!$this->response) {
            $this->channel->wait();
        }
        return intval($this->response);
        
    }
}

$fibonacci_rpc = new FibonacciRpcClient();
$response = $fibonacci_rpc->call(30);
echo ' [.] Got ', $response, "\n";

 

server代码:

<?php
/**
 * Date: 2020/4/22 下午11:50
 */

require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();

$channel->queue_declare('rpc_queue', false, false, false, false);

function fib($n)
{
    if ($n == 0) {
        return 0;
    }
    if ($n == 1) {
        return 1;
    }
    return fib($n-1) + fib($n-2);
}

echo " [x] Awaiting RPC requests\n";

$callback = function($req) {
    $n = intval($req->body);
    echo '[.] fib(', $n, ")\n";
    
    $msg = new AMQPMessage(
        (string) fib($n),
        array('correlation_id' => $req->get('correlation_id'))
    );
    
    $req->delivery_info['channel']->basic_public(
        $msg,
        '',
        $req->get('reply_to')
    );
    
    $req->delivery_info['channel']->basic_ack(
        $req->delivery_info['delivery_tag']
    );
    
};

$channel->basic_qos(null, 1, null);
$channel->basic_consume('rpc_queue',
    '',
    false,
    false,
    false,
    false,
    $callback
    );

while($channel->is_consuming()) {
    $channel->wait();
}

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

 

如果 RPC server 太慢,你可以仅仅 再运行 另外一个 server就可以了

对于client ,client 需要一个 网络来回 对于一个 单独的RPC request.

 

 

rabbitmq命令:

sudo rabbitmqctl list_queues

sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged

列出 exchanges

sudo rabbitmqctl list_exchanges

列出存在的绑定。

rabbitmqctl list_bindings

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值