异步消息
消息队列
点对点模式, 消息队列中的每一条消息只能有一个接收者接收. 如果有多个接收者监听队列, 那么一条消息由哪个接收者接收到是不确定的. 例如排队机.
主题
发布/订阅模式, 发布者发布消息到主题, 主题的订阅者都会收到消息的副本. 例如期刊订阅.
异步消息和同步调用
发送消息完成后不等待消息处理结果. 调用者不需要等待被调用者完成操作. 同步的本质是把被调用者纳入到调用者的时间序列中.
消息的发送者接收者位置独立. 调用者不需要知道被调用者的位置, 例如URL, IP/端口等等. 但是需要知道消息代理(消息队列/主题)的地址.
消息不依赖于接收者的实现方式. 调用者只需要发送消息(具有一定的格式), 无序依赖被调用者采用的具体的技术协议, 如HTTP, TCP, RPC, REST等等. 但是需要符合消息代理(消息队列/主题)的协议.
消息代理确保消息不丢失. 被调用者的可用性基本不影响调用者, 但是消息代理(消息队列/主题)的可用性直接影响调用者.
总之异步消息是以数据格式为规范的系统协作范式.
同步调用是以过程调用为范式的系统协作范式. 同步调用可以确保调用已经完成, 异步消息不保证消息何时被处理.
RabbitMQ
RabbitMQ实现了AMQP. RabbitMQ采用erlang语言实现. 消息的发送者和接收者可以采用不同的语言实现.
安装
下载安装erlang_win64_20.3
下载安装RabbitMQ
或者在百度网盘https://pan.baidu.com/s/18D_48VjCQUPzTZIWWofCrA
安装完成后启动RabbitMQ服务
编程
参考http://www.rabbitmq.com/getstarted.html编写程序.
下载
java-amqp-client
SLF4J API
SLF4J Simple
后, 把上述3个jar文件加入要编写的项目中.
编写发送端文件Send.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Send {
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
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("UTF-8"));
System.out.println(" [x] Sent '" + message + "'");
channel.close();
connection.close();
}
}
编写接收端文件Recv .java
import com.rabbitmq.client.*;
import java.io.IOException;
public class Recv {
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received '" + message + "'");
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
项目文件结构
运行结果
运行Recv.java
运行Send.java
Recv接收到信息
使用在其它主机上部署的RabbitMQ
RabbitMQ的默认用户guest只能使用localhost上的RabbitMQ服务器. 如果要使用其它主机上的RabbitMQ服务器, 需要在RabbitMQ服务器上配置用户名和口令, 完成用户的认证. 配置用户的权限(访问控制列表), 完成用户的授权. 这样, 在客户端使用(用IP地址或域名)指定的RabbitMQ服务器时, 需要提供用户名和口令, RabbitMQ完成用户身份认证, 根据用户的权限, 判断用户是否可以访问和可以如何访问这个RabbitMQ服务器, 完成访问控制.
在RabbitMQ服务器上增加用户
例如在一个部署在Linux(Ubuntu18.4)主机上的RabbitMQ服务器上增加用户u00, 口令为p00
sudo rabbitmqctl add_user u00 p00
执行结果:
~$ sudo rabbitmqctl add_user u00 p00
Adding user "u00" ...
查看RabbitMQ服务器的用户列表
sudo rabbitmqctl list_users
执行结果:
~$ sudo rabbitmqctl list_users
Listing users ...
u00 []
guest [administrator]
配置用户的访问权限
例如在一个部署在Linux主机上的RabbitMQ服务器上为用户u00授予全部权限
sudo rabbitmqctl set_permissions -p / u00 '.*' '.*' '.*'
执行结果:
~$ sudo rabbitmqctl set_permissions -p / u00 '.*' '.*' '.*'
Setting permissions for user "u00" in vhost "/" ...
查看RabbitMQ服务器的用户权限列表
sudo rabbitmqctl list_permissions
执行结果:
~$ sudo rabbitmqctl list_permissions
Listing permissions for vhost "/" ...
guest .* .* .*
u00 .* .* .*
程序的变化
在程序中设置主机的IP地址, 用于连接RabbitMQ服务器的用户名和口令。
把Send.java和Recv.java中的
factory.setHost("localhost");
替换为
factory.setHost("10.0.2.15");
factory.setUsername("u00");
factory.setPassword("p00");
重新运行Send.java和Recv.java, 可以得到和原来同样的运行结果.
在windows上部署RabbitMQ的注意事项
- 注意事项1:
对于Windows10, erlang_20.3, rabbitmq-server-3.7.5需要手工复制erlang的cookie文件到用户目录下, 以保持cookie一致. 否则rabbitmqctl命令不能正确执行. 也就是需要把C:\Windows\System32\config\systemprofile.erlang.cookie文件复制到当前用户目录下, 即目录C:\Users\当前用户名\ - 注意事项2:
在window上执行以管理员权限执行设置用户u00具有全部权限的命令为 rabbitmqctl set_permissions -p / u00 .* .* .*
注意不要加’号.