一、 VMware
1. 重置VMware虚拟网络
VMware 虚拟网络不稳定,经常出现问题,可以通过重置网络环境来修复重置时,会删除所有虚拟网络,再重新创建
打开编辑 -- 虚拟网络编辑器 -- 还原默认设置。
2. 设置 NAT 网络网段
选择 VMnet8,修改左下角子网ip:192.168.64.0
3. 虚拟机
Network 网络服务和 NetworkManager 网络服务可能会发生冲突,把 NetworkManager 服务禁用
systemctl stop NetworkManager
systemctl disable NetworkManager
二、 RabbitMQ
RabbitMQ是一种消息中间件,用于处理来自客户端的异步消息。服务端将要发送的消息放入到队列池中。接收端可以根据RabbitMQ配置的转发机制接收服务端发来的消息。RabbitMQ依据指定的转发规则进行消息的转发、缓冲和持久化操作,主要用在多服务器间或单服务器的子系统间进行通信,是分布式系统标准的配置。
1. RabbitMQ 使用场景
1.1 服务解耦
1.2 流量削峰
1.3 异步调用
2. rabbitmq安装
2.1 下载离线安装包文件
上传离线安装包
rabbitmq-install` 目录上传到 `/root`
切换到rabbitmq-install
目录
cd rabbitmq-install
安装
rpm -ivh *.rpm
启动rabbitmq服务器
# 设置服务,开机自动启动
systemctl enable rabbitmq-server
# 启动服务
systemctl start rabbitmq-server
2.2 rabbitmq管理界面
启用管理界面
# 开启管理界面插件
rabbitmq-plugins enable rabbitmq_management
# 防火墙打开 15672 管理端口
firewall-cmd --zone=public --add-port=15672/tcp --permanent
firewall-cmd --reload
重启RabbitMQ服务
systemctl restart rabbitmq-server`
访问
访问服务器的15672
端口,例如:
http://192.168.64.140:15672
2.3 添加用户
# 添加用户
rabbitmqctl add_user admin admin
# 新用户设置用户为超级管理员
rabbitmqctl set_user_tags admin administrator`
2.4 设置访问权限
2.5 开放客户端连接端口
# 打开客户端连接端口
firewall-cmd --zone=public --add-port=5672/tcp --permanent
firewall-cmd --reload
主要端口介绍
- 4369 – erlang发现口
- 5672 – client端通信口
- 15672 – 管理界面ui端口
- 25672 – server间内部通信口
3. RabbitMQ六种工作模式
1. 简单模式
1.1 创建 rabbitmq 项目
1.2 添加 rabbitmq amqp 依赖
4.0.0
com.tedu
rabbitmq
0.0.1-SNAPSHOT
com.rabbitmq
amqp-client
5.4.3
org.slf4j
slf4j-api
1.8.0-alpha2
org.slf4j
slf4j-log4j12
1.8.0-alpha2
org.apache.maven.plugins
maven-compiler-plugin
3.8.0
1.8
1.3 创建生产者发送消息 Producer
package m1;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
public static void main(String[] args) throws Exception {
//1.建立连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
f.setPort(5672);
f.setUsername("admin");
f.setPassword("admin");
Connection con = f.newConnection();
//创建通信的通道
Channel c = con.createChannel();
//2.在服务器上创建队列
//告诉服务器,需要一个"hello"队列,如果服务器上不存在这个队列,服务器会新建队列
c.queueDeclare(
"hello",//队列名
false, //是否是持久队列
false, //是否是排他队列、独占队列
false, //是否自动删除
null); //其他属性
//3.发送消息
//发送的消息数据,必须是 byte[] 数组,不管是什么类型的数据,都要转成 byte[] 数组再发送
// 参数:1. 默认的交换机 2. 队列 3. 其他消息属性 4. 消息
c.basicPublish("", "hello", null, "Hello world!".getBytes());
c.close();//断开通信
con.close();//断开连接
}
}
1.4 创建消费者接收消息 Consumer
package m1;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws Exception {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
//f.setPort(5672);//默认端口可以不写
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
//定义队列
c.queueDeclare("hello",false,false,false,null);
DeliverCallback deliverCallback=new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
byte[] a= message.getBody();
String msg=new String(a);
System.out.println("收到:"+msg);
}
};
CancelCallback cancelCallback=new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
//接收消息
c.basicConsume("hello", true, deliverCallback,cancelCallback);
}
}
2. 工作模式
2.1 生产者发送消息
这里模拟耗时任务,发送的消息中,每个点使工作进程暂停一秒钟,例如"Hello…"将花费3秒钟来处理
package m2;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Producer {
public static void main(String[] args) throws Exception {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
// f.setPort(5672); //默认端口可以不设置
f.setUsername("admin");
f.setPassword("admin");
//创建通信的通道
Channel c = f.newConnection().createChannel();
//定义队列
c.queueDeclare("hello2", false, false, null);
//发送消息
//模拟延迟消息
while (true){
System.out.println("输入消息");
String msg=new Scanner(System.in).nextLine();
c.basicPublish("","hello", null, msg.getBytes(StandardCharsets.UTF_8));
}
}
}
2.2 消费者接收消息
package m2;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws Exception {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
//f.setPort(5672);//默认端口可以不写
f.setUsername("admin");
f.setPassword("admin");
//创建通信的通道
Channel c = f.newConnection().createChannel();
//定义队列
c.queueDeclare("hello2", false,false, false, null);
//创建回调对象
DeliverCallback deliverCallback=new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg=new String(message.getBody());
System.out.println("收到:"+msg);
//遍历字符串,每个点暂停1秒
for (int i=0;i
2.3 运行测试
运行:
- 一个生产者
- 两个消费者
生产者发送多条消息,
如: 1,2,3,4,5. 两个消费者分别收到:
- 消费者一: 1,3,5
- 消费者二: 2,4
rabbitmq在所有消费者中轮询分发消息,把消息均匀地发送给所有消费者
2.4 消息确认
为了确保消息不会丢失,rabbitmq支持消息确认(回执)。当一个消息被消费者接收到并且执行完成后,消费者会发送一个ack (acknowledgment) 给rabbitmq服务器, 告诉他我已经执行完成了,你可以把这条消息删除了。如果一个消费者没有返回消息确认就挂掉了(信道关闭,连接关闭或者TCP链接丢失),rabbitmq就会明白,这个消息没有被处理完成,rebbitmq就会把这条消息重新放入队列,如果在这时有其他的消费者在线,那么rabbitmq就会迅速的把这条消息传递给其他的消费者,这样就确保了没有消息会丢失。这里不存在消息超时, rabbitmq只在消费者挂掉时重新分派消息, 即使消费者花非常久的时间来处理消息也可以手动消息确认默认是开启的,前面的例子我们通过autoAck=ture把它关闭了。我们现在要把它设置为false,然后工作进程处理完意向任务时,发送一个消息确认(回执)。
修改:
//手动发送回执
c.basicAck(message.getEnvelope().getDeliveryTag(),false );**
//第二个参数:true-自动确认,自动Ack,false-手动确认,手动Ack
c.basicConsume("hello2", false, deliverCallback,cancelCallback);
package m2;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws Exception {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
//f.setPort(5672);//默认端口可以不写
f.setUsername("admin");
f.setPassword("admin");
//创建通信的通道
Channel c = f.newConnection().createChannel();
//定义队列
c.queueDeclare("hello2", false,false, false, null);
//创建回调对象
DeliverCallback deliverCallback=new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg=new String(message.getBody());
System.out.println("收到:"+msg);
//遍历字符串,每个点暂停1秒
for (int i=0;i
2.5 合理地分发
rabbitmq会一次把多个消息分发给消费者, 这样可能造成有的消费者非常繁忙, 而其它消费者空闲. 而rabbitmq对此一无所知, 仍然会均匀的分发消息,我们可以使用 basicQos(1)
方法, 这告诉rabbitmq一次只向消费者发送一条消息, 在返回确认回执前, 不要向消费者发送新消息. 而是把消息发给下一个空闲的消费者.
修改:
//每次只抓取一条消息
c.basicQos(1);
package m2;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws Exception {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
//f.setPort(5672);//默认端口可以不写
f.setUsername("admin");
f.setPassword("admin");
//创建通信的通道
Channel c = f.newConnection().createChannel();
//定义队列
c.queueDeclare("hello2", false,false, false, null);
//创建回调对象
DeliverCallback deliverCallback=new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg=new String(message.getBody());
System.out.println("收到:"+msg);
//遍历字符串,每个点暂停1秒
for (int i=0;i
2.6 消息持久化
当rabbitmq关闭时, 我们队列中的消息仍然会丢失, 除非明确要求它不要丢失数据要求rabbitmq不丢失数据要做如下两点: 把队列和消息都设置为可持久化(durable)队列设置为可持久化, 可以在定义队列时指定参数durable为true
//第二个参数是持久化参数durable
ch.queueDeclare("helloworld", true, false, false, null);
由于之前我们已经定义过队列"hello"是不可持久化的, 对已存在的队列, rabbitmq不允许对其定义不同的参数, 否则会出错, 所以这里我们定义一个不同名字的队列"task_queue"
//定义一个新的队列,名为 task_queue
//第二个参数是持久化参数 durable
ch.queueDeclare("task_queue", true, false, false, null);
生产者和消费者代码都要修改
这样即使rabbitmq重新启动, 队列也不会丢失. 现在我们再设置队列中消息的持久化, 使用MessageProperties.PERSISTENT_TEXT_PLAIN
参数
//第三个参数设置消息持久化
ch.basicPublish("", "task_queue", MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes());
生产者代码
package m2;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Producer {
public static void main(String[] args) throws Exception {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
// f.setPort(5672); //默认端口可以不设置
f.setUsername("admin");
f.setPassword("admin");
//创建通信的通道
Channel c = f.newConnection().createChannel();
//定义队列
c.queueDeclare("task_queue", true,false, false, null);
//发送消息
//模拟延迟消息
while (true){
System.out.println("输入消息");
String msg=new Scanner(System.in).nextLine();
c.basicPublish("","task_queue", MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes(StandardCharsets.UTF_8));
}
}
}
消费者代码
package m2;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws Exception {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
//f.setPort(5672);//默认端口可以不写
f.setUsername("admin");
f.setPassword("admin");
//创建通信的通道
Channel c = f.newConnection().createChannel();
//定义队列
c.queueDeclare("task_queue", true,false, false, null);
//创建回调对象
DeliverCallback deliverCallback=new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg=new String(message.getBody());
System.out.println("收到:"+msg);
//遍历字符串,每个点暂停1秒
for (int i=0;i
3. 发布订阅模式
在前面的例子中,我们任务消息只交付给一个工作进程。在这部分,我们将做一些完全不同的事情——我们将向多个消费者传递同一条消息。这种模式称为“发布/订阅”。
3.1 Exchanges 交换机
RabbitMQ消息传递模型的核心思想是,生产者永远不会将任何消息直接发送到队列。实际上,通常生产者甚至不知道消息是否会被传递到任何队列。相反,生产者只能向交换机(Exchange)发送消息。交换机是一个非常简单的东西。一边接收来自生产者的消息,另一边将消息推送到队列。交换器必须确切地知道如何处理它接收到的消息。
有几种可用的交换类型:direct、topic、header 和 fanout。
fanout交换机非常简单。它只是将接收到的所有消息广播给它所知道的所有队列。这正是我们的日志系统所需要的。首先,每当我们连接到Rabbitmq时,我们需要一个新的空队列。为此,我们可以创建一个具有随机名称的队列,或者,更好的方法是让服务器为我们选择一个随机队列名称。其次,一旦断开与使用者的连接,队列就会自动删除。在Java客户端中,当我们不向queueDeclare()提供任何参数时,会创建一个具有生成名称的、非持久的、独占的、自动删除队列。
//自动生成队列名
//非持久,独占,自动删除
String queueName = ch.queueDeclare().getQueue();
3.2 绑定 Bindings
我们已经创建了一个fanout交换机和一个队列。现在我们需要告诉exchange向指定队列发送消息。exchange和队列之间的关系称为绑定。
//指定的队列,与指定的交换机关联起来
//成为绑定 -- binding
//第三个参数时 routingKey, 由于是fanout交换机, 这里忽略 routingKey
ch.queueBind(queueName, "logs", "");
现在, logs交换机将会向我们指定的队列添加消息
3.3 定义生产者
package m3;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Producer {
public static void main(String[] args) throws Exception {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
// f.setPort(5672); //默认端口可以不设置
f.setUsername("admin");
f.setPassword("admin");
//创建通信的通道
Channel c = f.newConnection().createChannel();
//定义交换机-命名:logs
//c.exchangeDeclare("logs", "fanout"); c.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
//向交换机发送消息
while (true){
System.out.println("输入:");
String msg=new Scanner(System.in).nextLine();
//第二个参数对fanout交换机无效
c.basicPublish("logs", "",null, msg.getBytes(StandardCharsets.UTF_8));
}
}
}
3.4 定义消费者
package m3;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.UUID;
public class Consumer {
public static void main(String[] args) throws Exception {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
// f.setPort(5672); //默认端口可以不设置
f.setUsername("admin");
f.setPassword("admin");
//创建通信的通道
Channel c = f.newConnection().createChannel();
//1.定义随机队列,2.定义交换机,3.绑定
String queue = UUID.randomUUID().toString();
c.queueDeclare(queue,false,true,true,null);
//第三个参数对fanout交换机无效
c.queueBind(queue,"logs", "");
//从随机队列正常的消费数据
DeliverCallback deliverCallback=new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg=new String(message.getBody());
System.out.println("收到:"+msg);
}
};
CancelCallback cancelCallback=new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
c.basicConsume(queue,true, deliverCallback,cancelCallback);
}
}
4. 路由模式
4.1直连交换机 Direct exchange
上一节中的日志系统向所有消费者广播所有消息。我们希望扩展它,允许根据消息的严重性过滤消息。例如,我们希望将日志消息写入磁盘的程序只接收关键error,而不是在warning或info日志消息上浪费磁盘空间。
前面我们使用的是fanout交换机,这并没有给我们太多的灵活性——它只能进行简单的广播。
我们将用直连交换机(Direct exchange)代替。它背后的路由算法很简单——消息传递到bindingKey与routingKey完全匹配的队列。为了说明这一点,请考虑以下设置
4.2 绑定 Bindings
c.queueBind(queue, "direct-logs", key);
4.3 定义生产者
package m4;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Producer {
public static void main(String[] args) throws Exception {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
// f.setPort(5672); //默认端口可以不设置
f.setUsername("admin");
f.setPassword("admin");
//创建通信的通道
Channel c = f.newConnection().createChannel();
//定义direct交换机:direct-logs
c.exchangeDeclare("direct-logs", BuiltinExchangeType.DIRECT);
while (true){
System.out.println("输入消息:");
String msg = new Scanner(System.in).nextLine();
System.out.println("输入路由键:");
String key = new Scanner(System.in).nextLine();
//发送消息,并携带路由键
c.basicPublish("direct-logs",key,null, msg.getBytes(StandardCharsets.UTF_8));
}
}
}
4.4 定义消费者
package m4;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
public class Consumer {
public static void main(String[] args)throws Exception {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
// f.setPort(5672); //默认端口可以不设置
f.setUsername("admin");
f.setPassword("admin");
//创建通信的通道
Channel c = f.newConnection().createChannel();
//1.定义独占的随机队列,2.定义交换机,3.绑定
//方法一:String queue = UUID.randomUUID().toString();
//c.queueDeclare(queue,false,true,true,null);
//方法二:服务器随机命名,false,true,true--非持久,独占,自定删除
String queue = c.queueDeclare().getQueue();
c.exchangeDeclare("direct-logs", BuiltinExchangeType.DIRECT);
System.out.println("输入绑定键,用空格隔开:");
String s = new Scanner(System.in).nextLine();
String[] a = s.split("s+");
for (String key:a){
c.queueBind(queue, "direct-logs", key);
}
//从随机队列 queue 接收消息
//收到消息后用来处理消息的回调对象
DeliverCallback deliverCallback=new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody());
String key = message.getEnvelope().getRoutingKey();
System.out.println(key+"-"+msg);
}
};
//消费者取消时的回调对象
CancelCallback cancelCallback=new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
c.basicConsume(queue, true, deliverCallback,cancelCallback);
}
}
5. 主题模式
5.1 主题交换机 Topic exchange
发送到Topic交换机的消息,它的routingKey,必须是由点分隔的多个单词。单词可以是任何东西,但通常是与消息相关的一些特性。几个有效的routingKey示例:“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”。routingKey可以有任意多的单词,最多255个字节。bindingKey也必须采用相同的形式。Topic交换机的逻辑与直连交换机类似——使用特定routingKey发送的消息将被传递到所有使用匹配bindingKey绑定的队列。bindingKey有两个重要的特殊点:
*
可以通配单个单词。#
可以通配零个或多个单词。
4.3 定义生产者
package m5;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Producer {
public static void main(String[] args) throws Exception{
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
// f.setPort(5672); //默认端口可以不设置
f.setUsername("admin");
f.setPassword("admin");
//创建通信的通道
Channel c = f.newConnection().createChannel();
//定义交换机
c.exchangeDeclare("topic-logs", BuiltinExchangeType.TOPIC);
//发送消息,并携带路由键
while (true){
System.out.println("输入消息:");
String msg = new Scanner(System.in).nextLine();
System.out.println("输出路由键:");
String key = new Scanner(System.in).nextLine();
c.basicPublish("topic-logs", key,null,msg.getBytes(StandardCharsets.UTF_8));
}
}
}
4.4 定义消费者
package m5;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
public class Consumer {
public static void main(String[] args) throws Exception {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
// f.setPort(5672); //默认端口可以不设置
f.setUsername("admin");
f.setPassword("admin");
//创建通信的通道
Channel c = f.newConnection().createChannel();
//1.定义独占的随机队列,2.定义交换机,3.绑定
//方法一:String queue = UUID.randomUUID().toString();
//c.queueDeclare(queue,false,true,true,null);
//方法二:服务器随机命名,false,true,true--非持久,独占,自定删除
String queue = c.queueDeclare().getQueue();
c.exchangeDeclare("topic-logs", BuiltinExchangeType.DIRECT);
System.out.println("输入绑定键,用空格隔开:");
String s = new Scanner(System.in).nextLine();
String[] a = s.split("s+");
for (String key:a){
c.queueBind(queue, "topic-logs", key);
}
//从随机队列 queue 接收消息
//收到消息后用来处理消息的回调对象
DeliverCallback deliverCallback=new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody());
String key = message.getEnvelope().getRoutingKey();
System.out.println(key+"-"+msg);
}
};
//消费者取消时的回调对象
CancelCallback cancelCallback=new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
c.basicConsume(queue, true, deliverCallback,cancelCallback);
}
}