RabbitMQ 消息队列入门
前言
中间件
消息队列
- 异步处理,注册完发短信
- 应用解耦,订单接口调用扣库存接口,失败了怎么办?
- 流量削峰,大量请求到达业务接口,这不行!
- 日志处理,每个业务代码都调用一下写日志的方法吗?结合AOP思想,业务程序为什么要关心写日志的事情?
- 消息通讯等,ABC处在聊天室里面,一起聊天?foreach吗?
官网有7个入门教程,过了一遍,做个笔记。
正文
HelloWorld
概述
RabbitMQ,是个消息代理人message broker。它接收,存储,转发消息。
几个常用的术语:
- 生产者Producer,生产发送消息。
- 消费者Consumer,接收消息。
- 队列Queue,只受系统内存和硬盘大小限制。存储消息,生产者往队列里面发送,消费者监听读取。
这几个对象可以分布在不同的机器。
使用Client
P和C的角色。maven仓库包为amqp-client
和slf4j-nop
<dependencies>
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-nop -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.30</version>
</dependency>
<endencies>
发送
也就是Producer.java
public class Send {
private static final String QUEUE_NAME = "hello1";
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("xxx.xxx.xxx.xxx");
factory.setPort(5672);
factory.setUsername("full_access");
factory.setPassword("111111");
factory.setVirtualHost("test_host1");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello world";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(Charset.forName("utf-8")));
System.out.println(" [x] Sent '" + message + "'");
} catch (TimeoutException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Connection
和Channel
都实现了ICloseable接口,所以可以使用try(...)接口自动释放资源。Channel
是我们要经常使用的API对象。channel.queueDeclare
是幂等的,只有在没有的情况下才会创建。然后调用basicPublish
方法,往队列发送字节数组消息。
接收
Consumer,Receiver.java
public class Receiver {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("xxx.xxx.xxx.xxx");
factory.setPort(5672);
factory.setUsername("full_access");
factory.setPassword("111111");
factory.setVirtualHost("test_host1");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback = ((consumerTag, message) -> {
String msg = new String(message.getBody(), "UTF-8");
System.out.println(" [x] Received [" + msg + "]");
});
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> {
});
}
}
connection
和 channel
对象没有使用try-with-resource自动释放,factory.newConnection()
之后程序就会保持运行,调用basicConsume
方法来消费得到的消息。该方法第二个autoAck参数写了false,这样,消息就属于未确认的状态,每次启动都会重复收到。
Work Queue 工作队列
消息产生的速度大于消费的速度,该怎么办?
每个http请求的时间不宜过长,所以可以把内部耗时的方法做成异步,然后用回调callback的方式实现。换个角度说就是consumer里面有比较耗时的任务,可以用thread.sleep()
模拟一下。
DeliverCallback deliverCallback = ((consumerTag, message) -> {
String msg = new String(message.getBody(), "UTF-8");
int i = new Random().nextInt(5);
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(msg + "休眠了" + i + "秒");
});
不过这不是这节的重点,这里的重点是几个参数。
消息确认
首先设置一下每次接收的消息数,每次一个channel.basicQos(1);
。在客户端没有确认之前不会接收新的消息。channel.basicConsume
方法的第二个参数autoAck表示自动确认。消息有两种状态,ready和unacked的。消息发送到queue→ready→consumer消费,但不确认→unacked→确认→结束,等待下一个。
public class NewTask {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("x.x.x.x");
factory.setPort(5672);
factory.setUsername("full_access");
factory.setPassword("111111");
factory.setVirtualHost("test_host1");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
channel.basicQos(1);//一次接收一个消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String msg = new String(message.getBody(), "UTF-8");
int i = new Random().nextInt(2);
try {
Thread.sleep(i * 1000);
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);//只确认这个tag对应的消息
System.out.println(msg + "执行了" + i + "秒,consumerTag=" + consumerTag + "并发送了确认");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
boolean autoAck = false;//不自动确认
String str = channel.basicConsume(QUEUE_NAME, autoAck, deliverCallback, consumerTag -> {
System.out.println(consumerTag);
});
}
}
Message Durablity
Redis类似。
确保消息不丢。也就是确保未消费的消息在服务器意外宕机重启之后消息不丢。RabbitMQ会以一定间隔把消息写入磁盘,但不是实时【所以还是有一个短的时间间隔会产生消息的丢失情况】。为了解决这个问题,需要两个配置
定义queue的时候设置durable参数为true。rabbitmq不允许queue name相同其他参数不同的两个队列,所以可以先删以前的。
boolean durable = true;
channel.queueDeclare("hello", durable, false, false, null);
发送的时候设置
MessageProperties
属性。channel.basicPublish("", "hello",
MessageProperties.PERSISTENT_TEXT_PLAIN,//持久化为文本
message.getBytes());
Fair Dispatch 公平分发
RabbitMQ的默认推送策略是把第N个消息推送给第N个客户端,他不会管一个客户端是否还有没确认的消息,所以可能会导致某个客户端非常的忙。解决方案:
调用basicOps设置prefetchCount
为1,这样一个客户端在没有确认当前消息之前不会收到下一个消息。
Publish/Subscribe 发布/订阅
一次性给所有的Consumer发送消息
回顾一下前面的例子,基本的代码流程是
- 创建ConnectionFactory→设置参数→创建Connection→创建Channel
- Producer声明QueueName,往Exchanges=”“发送消息
- Consumer指定相同的QueueName,设置消息处理函数,读取数据,发送确认。
Exchanges
RabbitMQ中有Exchange的概念。消息实际上不会直接发送给Queue,而是给Exchange,然后通过exchange转发给queue,然后给Consumer消费。exchange为空字符表示系统内部默认的exchange。
[root@test]~# rabbitmqctl list_exchanges -p test_host1
Listing exchanges for vhost test_host1 ...
name type
amq.fanout fanout
amq.direct direct
amq.match headers
amq.rabbitmq.trace topic direct
amq.headers headers
amq.topic topic
amp.*
为系统自带的exchange。
管理界面查看
ExchangeType
Type决定一个Exchange怎么处理接收到的消息,广播到所有队列或者推送到特定的队列或者直接丢弃消息。
内置的ExchangeType枚举
public enum BuiltinExchangeType {
DIRECT("direct"), FANOUT("fanout"), TOPIC("topic"), HEADERS("headers");
//...省略其他
}
fanout
顾名思义,是一种广播的处理方式,会发送到所有的queue。看个demo。
先看Send
//Send.java
public class Send {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("x.x.x.x");
factory.setPort(5672);
factory.setUsername("full_access");
factory.setPassword("111111");
factory.setVirtualHost("test_host1");
try (final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel()) {
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);//Exchange声明
final String msg = String.valueOf(LocalDateTime.now());
channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes(Charset.defaultCharset()));//第二个routingKey留空待定
System.out.println("发送" + msg);
} catch (TimeoutException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
定义一个FANOUT
类型的Exchange,没有定义Queue。调用basicPublish发送消息。
再看Receiver.java
//Receiver.java
public class Receiver {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("x.x.x.x");
factory.setPort(5672);
factory.setUsername("full_access");
factory.setPassword("111111");
factory.setVirtualHost("test_host1");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
final String queueName = channel.queueDeclare().getQueue();//获取自动生成的queue,
channel.queueBind(queueName, EXCHANGE_NAME, "");//绑定,最后一个参数待定
channel.basicConsume(queueName, true, (consumerTag, message) -> {
final String s = new String(message.getBody(), Charset.defaultCharset());
System.out.println("收到" + s);
}, consumerTag -> { });
}
}
定义一个和Send相同的Exchange。获取创建的channel对应的系统自动生成的queue(结束之后会自动删除,避免系统有太多队列)。绑定queue和exchange。RabbitMQ会丢弃消息如果这个exchange下面没有绑定queue的话。
运行多个Receiver实例。因为ExchangeType是fanout,所以,每个实例都会收到广播的消息。
对比前面例子中的默认Exchange,一个消息,发送到一个Exchange(默认的空字符串),因为queuename指定了是同一个,所以,只会有一个client收到消息。
而这个例子中,queue是自动生成的,所以会有多个自动删除的queue,一个queue对应一个client。ExchangeType是fanout,所以,每个client都会收到。
Routing
有选择性的接收消息
前面例子使用了fanout
广播的方式来发布消息,一条消息会被推送到所有的队列,又因为队列是自动生成的,一个队列对应一个consumer,所以所有的consumer都会收到所有的消息。这无法实现某个consumer只关心某种类型的消息的需求。所以,这里引入exchangetype=direct的例子。
name 相同,type不同的exchange不合法,可以先在rabbitmq的管理平台界面删除原先的exchange。
Binding
回顾前面的代码。publish和queue绑定的时候都留空了routingKey参数。
Send.java
Consumer.java
Consumer的queueBind 和 Producer的basicPublish中routingKey需要匹配。fanout
类型的exchange会忽略routingKey参数,所以我们直接留空。
direct Exchange
fanout的消息分发不太灵活,所以这里使用direct的Exchange。看下图,如果Producer产生的routingKey为orange,那么只会发送给Q1,那么只有C1会收到消息。如果routingKey为black或者green,那么C2会收到消息。
Multiple Bindings
多个队列绑定同一个routingKey也是合理。下面的例子Q1和Q2都会收到black的消息,这种绑定本质上就退化成了一种前面的fanout Exchange。
Demo
场景:Producer
产生3种routingKey的message,Info,Error,Fault。定义两个Consumer,C1接收Info的message,C2接收Error和Fault。
//Send.java
public class Send {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("x.x.x.x");//省略其他设置
try (final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel()) {
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);//指定exchange的类型
String messageType = args[0];//传入routingKey
String msg = messageType + " message" + LocalDateTime.now();
channel.basicPublish(EXCHANGE_NAME, messageType, null, msg.getBytes(StandardCharsets.UTF_8));
System.out.println("发送了" + msg);
} catch (TimeoutException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//Receiver.java
public class Receive {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws IOException, TimeoutException {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("x.x.x.x");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
final String queueName = channel.queueDeclare().getQueue();
for (String arg : args) {//遍历所有的routingKey,绑定所有当前queue
System.out.println("绑定routingKey:" + arg);
channel.queueBind(queueName, EXCHANGE_NAME, arg);
}
channel.basicConsume(queueName, true, ((consumerTag, message) -> {
final String msg = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println("收到" + msg);
}), consumerTag -> {
});
}
}
创建5个启动配置,3个为Send,分别发送Info,Fault,Error消息;2个Receiv,第一个接收Info,第二个接收Error和Fault。
最终效果
Topic
通过pattern模式来指定接收的消息。
前面例子使用的ExchangeType为direct
,相对于fanout
,是灵活了一些,但是还是有一些缺点,比如无法组合条件。比如有个consumer关心所有的error
消息以及和a相关的info
消息。这里就可以使用Topic的Exchange。然后都是通过routingKey参数来指定。
通配符
* 星号,代表一个词
# 井号,代表0个或多个词(包括一个)
以点分隔,组成routingKey。比如*.a.b.#
如果设置BuiltinExchangeType.TOPIC
的exchangeType,但是没有使用通配符,那么就和BuiltinExchangeType.DIRECT
是一样的。
未匹配任何模式的消息会被丢弃。
关键代码
声明exchange
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
发送消息
if (args[0].equals("info")) {
s = "a's info message";
channel.basicPublish(EXCHANGE_NAME, "a.info", null, s.getBytes(StandardCharsets.UTF_8));
} else {
s = "xxx.yyy's error message";
channel.basicPublish(EXCHANGE_NAME, "xxx.yyy.error", null, s.getBytes(StandardCharsets.UTF_8));
}
使用通配符接收消息
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
final String queueName = channel.queueDeclare().getQueue();//临时的queue
String routingKey = args[0];//传入的参数 比如*.info 或 #.error来匹配
channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
System.out.println("绑定" + routingKey + "的队列");
channel.basicConsume(queueName, true, ((consumerTag, message) -> {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println("收到消息" + msg);
}), consumerTag -> {});
RPC 远程调用
远程过程调用。
Client 调用 Server的服务。Client发送消息,Server消费消息。Server计算结果,发布一个消息到对应的队列。Client消费队列里面的消息。这一个过程Client和Server都是双重身份。这个是和其他最主要的区别。
关于RPC
RPC是一种常见的模式,但也存在一些争议,这主要体现在如果开发者有意或无意的不去注意这是一个本地的方法还是比较耗时的远程方法。RPC也增加了系统的调试复杂度。
开发RPC的几个建议:
- 确保方法容易辨识是远程还是本地
- 做好文档
- 处理调用时候的异常
回调队列
Client需要Server的计算结果,所以需要在消息里面带上CallbackQueueName。根据AMQP 0-9-1
协议,定义了14个属性,除了4个比较常用,其他都很少用。
- deliveryMode 设置消息的持久化,第二个例子中用过。
- contentType 设置内容的mime-type ,建议application/json
- replyTo 回复队列名
- correlationId 关联id 因为消息是异步的,所以可以给每个消息带上个id,用来关联发送的消息。
final AMQP.BasicProperties basicProperties = new AMQP.BasicProperties.Builder()
.correlationId("uuid")
.replyTo("xxx")
.build();
channel.basicPublish("", "rpc_queue", props, message.getBytes());
简易版Client
public class Client {
private static final String RPC_QUEUE_NAME = "rpc_queue";//rpc调用的queue,往里面发rpc调用参数
public static void main(String[] args) throws IOException, TimeoutException {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("x.xx.x.x");
factory.setPort(5672);
factory.setUsername("full_access");
factory.setPassword("111111");
factory.setVirtualHost("test_host1");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
String msg = String.valueOf(3);//模拟调用参数
final String replyQueueName = channel.queueDeclare().getQueue();
String corrId = UUID.randomUUID().toString();
final AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.replyTo(replyQueueName)//回复的队列
.correlationId(corrId)//当前消息的uuid
.build();
channel.basicPublish("",
RPC_QUEUE_NAME,
properties,
msg.getBytes(StandardCharsets.UTF_8));//广播的方式往rpc queue发布消息
System.out.println("发送计算[" + msg + "]的消息");
//等待消息回复
channel.basicConsume(replyQueueName, true, (consumerTag, message) -> {
String revCorrId = message.getProperties().getCorrelationId();
if (corrId.equals(revCorrId)) {//拿到了回复
final String result = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println("发送" + msg + "得到回复" + result);
} else {
System.out.println("收到correlationId:" + revCorrId);
}
}, consumerTag -> {
});
}
}
channel.queueDeclare()
用来声明一个临时队列,为接收返回结果的队列。代码中只发布了一个计算请求,所以basicConsume
中corrId判断其实没有必要。正常情况下可以在当前临时队列发布多个计算请求,每个的计算结果都传入到当前的临时队列,所以需要判断corrId的匹配情况。
简易版Server
public class Server {
private static final String RPC_QUEUE_NAME = "rpc_queue";
public static void main(String[] args) throws IOException, TimeoutException {
System.out.println("hello from server");
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("x.x.x.x");
factory.setPort(5672);
factory.setUsername("full_access");
factory.setPassword("111111");
factory.setVirtualHost("test_host1");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(RPC_QUEUE_NAME,false,false,false,null);//声明非排他的队列,用来消费rpc_queue里面的计算请求。
channel.basicConsume(RPC_QUEUE_NAME,
true,//自动回复,后面就不需要手动ack
(consumerTag, message) -> {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
String replyMsg = new String(msg + "Result");//简单模拟计算结果。
System.out.println("收到" + msg + "开始计算,计算完成结果为:[" + replyMsg + "]");
//拿到需要回复properties
String replyQueueName = message.getProperties().getReplyTo();
String correlationId = message.getProperties().getCorrelationId();
AMQP.BasicProperties replyProps = new AMQP.BasicProperties.Builder()
.correlationId(correlationId)//correlationId返回去。
.build();
// 把计算结果发回去
channel.basicPublish("", replyQueueName, replyProps, replyMsg.getBytes(StandardCharsets.UTF_8));
}, consumerTag -> {
});
}
}
消费RPC_QUEUE_NAME
的计算请求,然后根据消息里面带的getReplyTo()
的值返回给客户端。
Publisher Confirm 发布确认
可靠发布
启用生产者确认
根据AMQP 0.9.1
协议,这个确认默认是没有启用的,可以通过confirmSelect
方法启用。
Channel channel = connection.createChannel();
channel.confirmSelect();
这个方法是针对channel的,不是针对每个消息,所以,只要 在开启channel之后调用一次就好。
确认每个消息
先是一个简单的例子,每发完一个消息,都让系统确认等待一下。
while (thereAreMessagesToPublish()) {
byte[] body = ...;
BasicProperties properties = ...;
channel.basicPublish(exchange, queue, properties, body);
// 5秒超时
channel.waitForConfirmsOrDie(5_000);
}
每次发完一个消息,都等待最多5秒钟的一遍确认。这个很明显会极大的影响系统的吞吐率。
批量确认
发送一个确认一个明显会比较low,所以这里引入一种批量确认的方式。不过这只是一种自己业务代码的确认机制,不是rabbitmq提供的。
int batchSize = 100;//
int outstandingMessageCount = 0;
while (thereAreMessagesToPublish()) {
byte[] body = ...;
BasicProperties properties = ...;
channel.basicPublish(exchange, queue, properties, body);
outstandingMessageCount++;//发送一个加1
if (outstandingMessageCount == batchSize) {
ch.waitForConfirmsOrDie(5_000);//到达batchSize之后确认
outstandingMessageCount = 0;
}
}
if (outstandingMessageCount > 0) {
ch.waitForConfirmsOrDie(5_000);//确认剩下的
}
这种确认吞吐量是上来了,不过最大的问题是当confirm
出问题了之后是无法定位到具体哪个有问题。
ConcurrentSkipListMap 和 channel.getNextPublishSeqNo()
channel.getNextPublishSeqNo
可以获取发布的消息的下一个序号,有序递增。ConcurrentSkipListMap有一个heapMap
方法,可以返回key小于等于param的map子集。在发布消息之前先获取序号,作为key放到map里面。
map.put(nextPublishSeqNo, byteMsg);
channel.basicPublish("", queueName, null, msgStr.substring(i, i + 1).getBytes(StandardCharsets.UTF_8));
异步确认
Producer只管发消息,然后注册一个异步回调函数。rabbitmq提供了两个回调函数。一个是发送成功的回调,一个是发送失败的回调。两个函数的参数是一样的,两个。
- sequence number 序号。表示成功/失败的消息编号
- multiple 布尔值。false表示只有一个被确认。true表示小于等于当前序号的消息发送成功/失败
channel.confirmSelect();//启用消息确认
channel.addConfirmListener(
(deliveryTag, multiple) -> {
if (multiple) {
System.out.println("序号" + deliveryTag + "的信息发送成功");
map.remove(deliveryTag);
} else {
System.out.println("序号小于" + deliveryTag + "的信息发送成功");
final ConcurrentNavigableMap<Long, Byte> confirmed = map.headMap(deliveryTag, true);
confirmed.clear();
}
},
(deliveryTag, multiple) -> {
if (!multiple) {
System.out.println("发送失败的信息sequence number:" + deliveryTag);
} else {
System.out.println("序号小于" + deliveryTag + "的消息发送失败");
}
});
RabbitMQ 消息队列入门的更多相关文章
- RabbitMQ消息队列入门篇(环境配置+Java实例+基础概念)
一.消息队列使用场景或者其好处 消息队列一般是在项目中,将一些无需即时返回且耗时的操作提取出来,进行了异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量. 在项目启 ...
- RabbitMQ消息队列入门(一)——RabbitMQ消息队列的安装(Windows环境下)
一.RabbitMQ介绍1.RabbitMQ简介RabbitMQ是一个消息代理:它接受和转发消息.你可以把它想象成一个邮局:当你把你想要发布的邮件放在邮箱中时,你可以确定邮差先生最终将邮件发送给你的收 ...
- RabbitMQ 消息队列 入门 第二章(交换类型fanout)
1.安装完 RabbitMQ 之后,我们可以点击 http://localhost:15672/#/ 默认账号:guest 密码: guest 在这上面我们可以查看执行情况.管理连接.管理队列 ...
- RabbitMQ 消息队列 入门 第一章
RabbitMQ : 官网:https://www.rabbitmq.com/ GitHub:https://github.com/rabbitmq?q=rabbitmq 第一步安装: 点击 htt ...
- (六)RabbitMQ消息队列-消息任务分发与消息ACK确认机制(PHP版)
原文:(六)RabbitMQ消息队列-消息任务分发与消息ACK确认机制(PHP版) 在前面一章介绍了在PHP中如何使用RabbitMQ,至此入门的的部分就完成了,我们内心中一定还有很多疑问:如果多个消 ...
- RabbitMQ消息队列(一): Detailed Introduction 详细介绍
http://blog.csdn.net/anzhsoft/article/details/19563091 RabbitMQ消息队列(一): Detailed Introduction 详细介绍 ...
- RabbitMQ消息队列1: Detailed Introduction 详细介绍
1. 历史 RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现.AMQP 的出现其实也是应了广大人民群众的需求,虽然在同步消息通讯的世界里有 ...
- (转)RabbitMQ消息队列(九):Publisher的消息确认机制
在前面的文章中提到了queue和consumer之间的消息确认机制:通过设置ack.那么Publisher能不到知道他post的Message有没有到达queue,甚至更近一步,是否被某个Consum ...
- (转)RabbitMQ消息队列(七):适用于云计算集群的远程调用(RPC)
在云计算环境中,很多时候需要用它其他机器的计算资源,我们有可能会在接收到Message进行处理时,会把一部分计算任务分配到其他节点来完成.那么,RabbitMQ如何使用RPC呢?在本篇文章中,我们将会 ...
随机推荐
- 详解firewalld 和iptables
在RHEL7里有几种防火墙共存:firewalld.iptables.ebtables,默认是使用firewalld来管理netfilter子系统,不过底层调用的命令仍然是iptables等. fir ...
- 关于js中iframe 中 location.href的用法
关于js中"window.location.href"."location.href"."parent.location.href".&qu ...
- JSP+Servlet+C3P0+Mysql实现的简单新闻系统
项目简介 项目来源于:https://gitee.com/glotion/servlet-jsp_news 本系统基于JSP+Servlet+C3P0+Mysql.涉及技术少,易于理解,适合JavaW ...
- 安装arcgis server时提示“应用程序无法启动,因为应用程序......或使用命令行sxstrace.exe”
说一下这个原因:有几个条件不满足会产生这样的问题: 1.软件的发布是不需要安装的,直接在vs里编译好release版就发布了,而发布的时候如果缺少一些库文件,就会产生这样的问题. 一版都是目 ...
- mabatis入门五 高级结果映射
一.创建测试的表和数据 1.创建表 1CREATE TABLE items ( 2 id INT NOT NULL AUTO_INCREMENT, 3 itemsname VARCHAR(32) NO ...
- python之xlrd和xlwt模块读写excel使用详解
一.xlrd模块和xlwt模块是什么 xlrd模块是python第三方工具包,用于读取excel中的数据: xlwt模块是python第三方工具包,用于往excel中写入数据: 二 ...
- warning: directory not found for option“XXXXXX” 解决方案
从项目中删除了某个目录.文件以后,编译出现警告信息: ld: warning: directory not found for option"XXXXXX" 很奇怪,为什么已经 ...
- 一分钟 Get 时序数据库 InfluxDB 的技能
1. 通过上期分享<实践指路明灯,源码剖析flink-metrics>,对当下较火的流式处理框架 flink 的指标监控体系有了全局的认识,并结合 flink-metrics-xxxx 模 ...
- MySQL(Linux)编码问题——网站刚刚上线就被光速打脸
MySQL(Linux)编码问题--刚刚上线就被光速打脸 MySql默认编码问题 总结了一下,大致是这样的 修改数据库配置 在URL上加载参数 MySql默认编码问题 说到这里真的想哭,改了无数bug ...
- 使用appium框架测试安卓app时,获取toast弹框文字时,前一步千万不要加time.sleep等等待时间。
使用appium框架测试安卓app时,如果需要获取toast弹框的文案内容,那么再点击弹框按钮之前,一定记得千万不要加time.sleep()等待时间,否则有延迟,一直获取不到: 获取弹框的代码: m ...