声明:本文是《RabbitMQ实战指南》(朱忠华 著)学习笔记,仅供本人学习研究之用,如若喜欢请购买正版书籍。如有侵权,请联系删除。
什么是分布式系统
分布式:一个业务拆分为多个子业务,部署在不同的服务器上。集群:同一个业务,部署在多个服务器上。
分布式系统的 CAP 理论:在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性)三者不可兼得。
一致性(C):如果系统对一个写操作返回成功,那么之后的读请求都必须读到这个新数据;如果返回失败,那么所有读操作都不能读到这个数据,对调用者而言数据具有强一致性(Strong Consistency)。
可用性(A):只要收到用户的请求,服务器就必须给出回应。换句话说,系统 7 * 24 小时都能对外提供服务。
分区容错性(P):大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。比如,一台服务器 G1 放在中国,另一台服务器 G2 放在美国,这就是两个区,它们之间可能无法通信。G1 向 G2 发送一条消息,G2 可能无法收到。系统设计的时候,必须考虑到这种情况。一般来说,分区容错无法避免,因此可以认为 CAP 的 P 总是成立,剩下的 C 和 A 无法同时做到。
什么是消息中间件
对于分布式系统而言,它们之间可以通过 RPC(比如 Dubbo)、RMI、消息消息中间件等进行通信。
消息中间件是指利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,可以在分布式环境下提供应用解耦、弹性伸缩、冗余存储、流量削峰、异步通信、数据同步等等功能,其作为分布式系统架构中的一个重要组件,有着举足轻重的地位。
当今市面上有很多主流的消息中间件,如老牌的 ActiveMQ、RabbitMQ,炙手可热的 Kafka,以及阿里巴巴自主研发的 RocketMQ 等。
消息中间件的应用
消息中间件的主要作用是:异步、解耦和流量削峰。
RabbitMQ简介
RabbitMQ 是采用 Erlang 语言编写的,实现了 AMQP(Advanced Message Queuing Protocol ,高级消息队列协议)的消息中间件,它具有以下特点:
可靠性:RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认及发布确认等。
灵活的路由:在消息进入队列之前,通过交换器来路由消息。
扩展性:多个 RabbitMQ 节点可以组成一个集群,也可以根据实际业务情况动态地扩展集群中节点。
高可用性:队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用。
支持多种协议:RabbitMQ 除了原生支持 AMQP 协议,还支持 STOMP、MQTT 等多种消息中间件协议。
多语言:RabbitMQ 几乎支持所有常用语言,比如 Java、Python、Ruby、PHP、C#、JavaScript 等。
管理界面:RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。
插件机制:RabbitMQ 提供了许多插件,方便进行扩展,也可以编写自己的插件。
安装RabbitMQ
由于 Rabbit 是使用 Erlang 语言编写的,在安装 RabbitMQ 之前需要安装 Erlang 。
去 Erlang 官网下载 Erlang,我下载的版本是 20.0。下载完成之后,上传到 Ubuntu,解压安装包,并配置安装目录,这里将 Erlang 安装到 /opt/apps/erlang 目录下,依次执行如下命令:
tar xvf otp_src_20.0.tar.gz
cd otp_src_20.0/
./configure --prefix=/opt/apps/erlang
make
make install
在安装过程中,可能会遇到各种问题,比如缺乏依赖等,可以自行网上搜索解决。
安装完成后,需要配置环境变量。编辑/etc/profile,加入如下配置:
ERLANG_HOME=/opt/apps/erlang
export PATH=$PATH:$ERLANG_HOME/bin
export ERLANG_HOME
最后执行命令让配置生效:
source /etc/profile
输入如下命令验证 Erlang 是否安装成功:
erl
出现如下图所示即表示安装成功:
Erlang/OTP 20 [erts-9.0] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1]
Eshell V9.0 (abort with ^G)
1>
下面开始安装 RabbitMQ。在 RabbitMQ 的官网下载安装包,上传到 Ubuntu。我这里下载的是rabbitmq-server-generic-unix-3.6.15.tar.xz,注意是xz格式,解压解包如下:
xz -d rabbitmq-server-generic-unix-3.6.15.tar.xz
tar -xvf rabbitmq-server-generic-unix-3.6.15.tar
为了方便,改个名字:
mv rabbitmq_server-3.6.15/ rabbitmq
修改 /etc/profile 文件 ,配置环境变量:
export PATH=$PATH:/opt/apps/rabbitmq/sbin
export RABBITMQ_HOME=/opt/apps/rabbitmq
之后执行如下命令让配置生效:
source /etc/profile
到这里,RabbitMQ安装成功了。执行如下命令运行RabbitMQ:
rabbitmq-server -detached
在 rabbitmq-server 命令后面添加一个 "-detached" 参数是为了能够让 RabbitMQ 服务以守护进程的方式在后台运行,这样就不会因为当前 Shell 窗口的关闭而影响服务。
运行 rabbitmqctl status 命令查看 RabbitMQ 是否正常启动,示例如下:
rabbitmqctl status
如果 RabbitMQ 正常启动 ,会输出如下所示的信息:
Status of node rabbit@wuychn
[{pid,4639},
{running_applications,
[{rabbit,"RabbitMQ","3.6.15"},
{mnesia,"MNESIA CXC 138 12","4.15.3"},
{ranch,"Socket acceptor pool for TCP protocols.","1.3.2"},
{ssl,"Erlang/OTP SSL application","8.2.3"},
{public_key,"Public key infrastructure","1.5.2"},
{asn1,"The Erlang ASN1 compiler version 5.0.4","5.0.4"},
{crypto,"CRYPTO","4.2"},
{rabbit_common,
"Modules shared by rabbitmq-server and rabbitmq-erlang-client",
"3.6.15"},
{xmerl,"XML parser","1.3.16"},
{compiler,"ERTS CXC 138 10","7.1.4"},
{os_mon,"CPO CXC 138 46","2.4.4"},
{recon,"Diagnostic tools for production use","2.3.2"},
{syntax_tools,"Syntax tools","2.1.4"},
{sasl,"SASL CXC 138 11","3.1.1"},
{stdlib,"ERTS CXC 138 10","3.4.3"},
{kernel,"ERTS CXC 138 10","5.4.1"}]},
{os,{unix,linux}},
{erlang_version,
"Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:64] [kernel-poll:true]\n"},
{memory,
[{connection_readers,0},
{connection_writers,0},
{connection_channels,0},
{connection_other,0},
{queue_procs,2744},
{queue_slave_procs,0},
{plugins,0},
{other_proc,18771664},
{metrics,184440},
{mgmt_db,0},
{mnesia,61584},
{other_ets,1504480},
{binary,53896},
{msg_index,43864},
{code,21603929},
{atom,951465},
{other_system,9933494},
{allocated_unused,17868024},
{reserved_unallocated,0},
{total,63893504}]},
{alarms,[]},
{listeners,[{clustering,25672,"::"},{amqp,5672,"::"}]},
{vm_memory_calculation_strategy,rss},
{vm_memory_high_watermark,0.4},
{vm_memory_limit,826294272},
{disk_free_limit,50000000},
{disk_free,11650199552},
{file_descriptors,
[{total_limit,924},{total_used,2},{sockets_limit,829},{sockets_used,0}]},
{processes,[{limit,1048576},{used,156}]},
{run_queue,0},
{uptime,1163},
{kernel,{net_ticktime,60}}]
也可以通过 rabbitmqctl cluster_status 命令来查看集群信息,目前只有一个 RabbitMQ 服务节点,可以看作单节点的集群:
Cluster status of node rabbit@wuychn
[{nodes,[{disc,[rabbit@wuychn]}]},
{running_nodes,[rabbit@wuychn]},
{cluster_name,<<"rabbit@wuychn">>},
{partitions,[]},
{alarms,[{rabbit@wuychn,[]}]}]
生产和消费消息
先从一个 Hello World 开始,看看如何使用 RabbitMQ Java 客户端生产和消费消息。
默认情况下,访问 RabbitMQ 服务的用户名和密码都是 guest,这个账户有限制,只能通过本地网络(localhost)访问,远程网络访问受限,所以在实现生产和消费消息之前,需要另外添加一个用户,并设置相应的访问权限。下面的命令添加一个新的用户,用户名为 root,密码为 123456:
rabbitmqctl add_user root 123456
为 root 用户设置所有权限:
rabbitmqctl set_permissions -p / root ".*" ".*" ".*"
设置 root 用户为管理员角色:
rabbitmqctl set_user_tags root administrator
这样就成功添加了一个用户。下面开始编写代码。
添加依赖:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.2.2</version>
</dependency>
生产者代码如下,它将发送一条消息 “Hello World!” 到 RabbitMQ 中:
public class RabbitProducer {
private static final String EXCHANGE_NAME = "exchange_demo";
private static final String ROUTING_KEY = " routingkey_demo";
private static final String QUEUE_NAME = "queue_demo";
private static final String IP_ADDRESS = "192.168.115.128";
private static final int PORT = 5672; //RabbitMQ服务端默认端口号为5672
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(IP_ADDRESS);
factory.setPort(PORT);
factory.setUsername("root");
factory.setPassword("123456");
Connection connection = factory.newConnection(); // 创建连接
Channel channel = connection.createChannel(); // 创建信道
// 创建一个type为direct、持久化的、非自动删除的交换器
channel.exchangeDeclare(EXCHANGE_NAME, "direct", true, false, null);
// 创建一个持久化的、非排他的、非自动删除的队列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 将交换器与队列通过路由键绑定
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
// 发送一条持久化消息:Hello World!
String message = "Hello World!";
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
// 关闭资源
channel.close();
connection.close();
}
}
消费者代码如下:
public class RabbitConsumer {
private static final String QUEUE_NAME = "queue_demo";
private static final String IP_ADDRESS = "192.168.115.128";
private static final int PORT = 5672; //RabbitMQ服务端默认端口号为5672
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Address[] addresses = new Address[]{new Address(IP_ADDRESS, PORT)};
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("root");
factory.setPassword("123456");
Connection connection = factory.newConnection(addresses); // 创建连接
final Channel channel = connection.createChannel(); // 创建信道
channel.basicQos(64); // 设置客户端最多接收未被 ack 的消息的个数
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("recv message: " + new String(body));
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(QUEUE_NAME, consumer);
// 等待回调函数执行完毕之后,关闭资源
TimeUnit.SECONDS.sleep(5);
channel.close();
connection.close();
}
}
运行生产者,然后运行消费者(也可以交换启动顺序),可以看到消费者打印出了一句:recv message: Hello World!。