rabbitmq消息队列
什么是异步调用,什么是同步调用?
同步调用?
-
A服务调用B服务,需要等待B服务执行完毕之后才能继续执行,才能往后执行
-
同步调用有RestTimplate,ribbon,Feign,Dubbo
异步调用?
- A服务调用服务B,b在执行的过程中可以往下执行,无需等待服务B的执行结果
- 通过消息队列可以实现异步调用
消息队列概念?
- MQ全称为Message Queue,消息队列(MQ)是一种
应用程序对应用程序的通信方法
。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。 消息传递
指的是程序之间通过在消息中发送数据进行通信
,而不是通过直接调用彼此来通信
,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。
其他的mq介绍
- RabbitMQ
稳定可靠
,数据一致
,支持多协议,有消息确认,基于erlang语言 - Kafka
高吞吐
,高性能
,快速持久化,无消息确认,无消息遗漏,可能会有有重复消息,依赖于zookeeper,成本高. - ActiveMQ
不够灵活轻巧
,对队列较多情况支持不好. - RocketMQ
性能好
,高吞吐
,高可用性
,支持大规模分布式,协议支持单一
RabbitMQ安装及配置
基于 CentOS 安装 RabbitMQ 3.7
说明:推荐使用本地安装,减少网络依赖
1.安装前准备
-
如果之前安装过erlang,先删除
yum remove erlang*
-
安装C++编译环境
# yum -y install make gcc gcc-c++ yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel unixODBC unixODBC-devel httpd python-simplejson
-
下载erlang和rabbitMQ
# 下载erlang wget http://www.erlang.org/download/otp_src_20.1.tar.gz # 下载rabbitMQ wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.0/rabbitmq-server-generic-unix-3.7.0.tar.xz
2.安装erlang
-
解压erlang安装包
tar -xvf otp_src_20.1.tar.gz
-
进入解压文件夹
cd otp_src_20.1
-
指定安装目录及安装配置(需要先安装并配置JDK)
# erlang指定安装在/usr/local/erlang目录 ./configure --prefix=/usr/local/erlang --enable-smp-support --enable-threads --enable-sctp --enable-kernel-poll --enable-hipe --with-ssl --without-javac
-
编译与安装
make && make install
-
配置erlang环境变量
vi /etc/profile
- 将 export PATH=$PATH:/usr/local/erlang/bin 添加到文件末尾
-
重新加载profile文件
source /etc/profile
3.安装RabbitMQ
-
解压RabbitMQ安装包
- 由于下载的安装包为xz文件,先将xz解压为tar
xz -d rabbitmq-server-generic-unix-3.7.0.tar.xz
- 再解压缩tar文件
tar -xvf rabbitmq-server-generic-unix-3.7.0.tar
-
启动RabbitMQ
- 进入到解压的RabbitMQ的sbin目录
cd rabbitmq_server-3.7.0/sbin
- 启动
./rabbitmq-server -detached
- 查看进程
ps aux|grep rabbit #ps a 显示现行终端机下的所有程序,包括其他用户的程序。 #ps u 以用户为主的格式来显示程序状况。 #ps x 显示所有程序,不以终端机来区分。
4.启动管理界面
-
启动RabbitMQ的管理系统插件(需进入sbin目录)
./rabbitmq-plugins enable rabbitmq_management
5.放行端口
如果没有网络指令需要先安装:yum install net-tools
-
查看并放行端口
-
也可以直接关闭防火墙
-
CentOS7
#关闭防火墙 systemctl stop firewalld #开机禁用 systemctl disable firewalld #查看状态 systemctl status firewalld
-
CentOS6
#1.永久性生效,重启后不会复原 #开启: chkconfig iptables on #关闭: chkconfig iptables off #2.即时生效,重启后复原 #开启: service iptables start #关闭: service iptables stop #3.查询TCP连接情况: netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' #4.查询端口占用情况: netstat -anp | grep portno(例如:netstat –apn | grep 80)
-
-
云服务器需要在控制台添加“安全组设置”
-
阿里云服务器
)
-
华为云服务器
-
rabbitMQ的逻辑结构
rabbitMQ的用户管理
创建用户
- 命令创建
# 到/usr/local/rabbitmq_server-3.7.0/sbin目录下执行
./rabbitmqctl add_user username password
- 通过web端的管理工具创建
用户权限
用户级别
- 超级管理员administrator,可以登录控制台,查看所有信息,可以对用户和策略进行操作
- 监控者monitoring,可以登录控制台,可以查看节点的相关信息,比如进程数,内存磁盘使用情况
- 策略制定者policymaker ,可以登录控制台,制定策略,但是无法查看节点信息
- 普通管理员 management 仅能登录控制台
- 其他, 无法登录控制台,一般指的是提供者和消费者
设置用户权限
./rabbitmqctl set_user_tags ktkt administrator
rabbitMQ的工作模式
rabbitMQ提供了各种消息的通信方式-工作模式
消息通信由两个角色完成:消息生产者(producer)和消息消费者(Consumer)
参考官方文档:RabbitMQ Tutorials — RabbitMQ
简单模式
生产者将消息发到队列,消费者从队列中取消息 ,一条消息对应一个消费者
工作模式
Work模式就是一条消息可以被多个消费者尝试接收,但是最终只能有一个消费者能获取
订阅模式
多个消息队列,每个消息队列有一个消费者监听
通过交换机对生产者传过来的消息进行操作,根据交换机的模式传入到对应的queue里面去
fanout模式:每个队列里面都复制一份
路由模式
生产者将消息发送到了type为direct模式的交换机,消费者的队列在将自己绑定到路由的时候会给自己绑定一个key
,只有消费者发送对应key格式的消息
时候队列才会收到消息
总概念图
案例演示(maven)
简单模式
-
第一步:先把简单模式的项目创建好,由于创建maven项目过于简单 我就不截图了
- 创建一个普通的maven项目为mq_demo1
- 创建一个子模块消息提供者poducer
- 在创建一个子模块消息消费者consumer
- 都要导入依赖
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.7.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.3.2</version> </dependency>
- 在资源目录创建log4j.properties
log4j.rootLogger=DEBUG,A1 log4j.logger.com.kt = DEBUG log4j.logger.org.mybatis = DEBUG log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
-
第二步:分别在consumer和poducer创建连接工具类
![](https://gitee.com/biexianwocai/blog_images/raw/master/img/20201206224846.png)
/**
* Created by IntelliJ IDEA.
* User: kt
* Date: 2020/12/4 10:36
*
* @author kt
*/
public class MQUtil {
public static Connection getConnection() {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//在工厂对象中设置mq的连接信息 ip port 虚拟主机 username password
factory.setHost("填本机或者服务器ip");
factory.setPort(5672);
//factory.setVirtualHost("host1");
factory.setUsername("ktkt");
factory.setPassword("ktkt");
//通过工厂获得与mq的连接对象
Connection connection = null;
try {
connection = factory.newConnection();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return connection;
}
}
- 在consumer创建业务类,因为是消息消费者(接收者),所以我们要写接收消息的业务方法
/**
* Created by IntelliJ IDEA.
* User: kt
* Date: 2020/12/6 0:21
* @author kt
*/
public class ReceiveMsg {
public static void main(String[] args) {
Connection connection = MQUtil.getConnection();
try {
Channel channel = connection.createChannel();
//通过consumer来处理数据
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//body就是从队列中获取的数据
String msg = new String(body);
System.out.println("接收:"+msg);
}
};
//参数1:接收哪个队列的数据
//参数2:消息确认 是否应答,收到消息是否回复
//参数3:
channel.basicConsume("queue1",true,consumer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 在poducer创建业务类,因为是消息提供者,所以我们要写发送消息的业务方法
/**
* Created by IntelliJ IDEA.
* User: kt
* Date: 2020/12/5 23:54
* @author kt
*/
public class SendMsg {
public static void main(String[] args) throws TimeoutException {
String msg = "Hello,rabbit";
//获取连接
Connection connection = MQUtil.getConnection();
try {
Channel channel = connection.createChannel();
//定义队列---用Java代码创建队列
//param1:定义队列的名称
//param2:队列中的数据是否提供持久化(选择了就停服务不会丢失数据)
//param3:是否排外 当连接关闭时是否删除当前队列 ture:删除 是否为连接私有
//param4:自动删除 当此队列的连接数为0 此队列销毁 无论是否有数据
//param5:设置当前队列的参数
//channel.queueDeclare("queue7",false,false,false,null);
//发送消息
// 参数1:交换机名称 如果直接发送到队列 则交换机名称为 ""
// 参数2: 队列名称
// 参数3: 设置当前这条消息的属性 比如:有效时间
// 参数4: 消息的内容
channel.basicPublish("","queue1",null,msg.getBytes());
System.out.println("发送"+msg);
channel.close();
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 可以开始测试了,同时启动,该发送的发送!!
工作模式
工作模式无非就是在简单模式上多加了一个消费者,就是多个消费者消费同一条消息
-
所以,再创建一个消费者consumer1,依赖和结构和consumer一样
-
在consumer创建业务类,因为是消息消费者(接收者),所以我们要写接收消息的业务方法 /** * Created by IntelliJ IDEA. * User: kt * Date: 2020/12/6 0:21 * @author kt */ public class ReceiveMsg1 { public static void main(String[] args) { Connection connection = MQUtil.getConnection(); try { Channel channel = connection.createChannel(); //通过consumer来处理数据 Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { //body就是从队列中获取的数据 String msg = new String(body); System.out.println("consumer1接收:"+msg); } }; //参数1:接收哪个队列的数据 //参数2:消息确认 是否应答,收到消息是否回复 //参数3: channel.basicConsume("queue1",true,consumer); } catch (IOException e) { e.printStackTrace(); } } }
-
测试,会发现是轮询的一个但错,但是也不能说是全轮询,如果只有两个消费者的时候,当一个消费者消息还没消费完时,另一方消费完接着接收
订阅模式
多个队列多个消费者监听,通过交换机的fanout模式来对每个队列都进行复制消息 fanout:是对绑定的每个队列都发送所有的消息
同样使用工作模式的案例
在发送的时候指定交换机,而不是发送到队列里面去,因为这个时候我们是订阅模式,自动复制进队列
//发送方只需要更改为
channel.basicPublish("ex1","",null,msg.getBytes());
//接收方可以从交换机绑定的队列中获取消息,比如我交换机ex1又queue3和queue4,那么我consumer和consumer1分别从queue3和queue4中接收消息其实是一样的消息
//consumer
channel.basicConsume("queue3",true,consumer);
//consumer1
channel.basicConsume("queue4",true,consumer);
![](https://gitee.com/biexianwocai/blog_images/raw/master/img/20201206231248.png)
路由模式
发送方通过指定的key然后通过direct交换机发送到指定的队列
这一个其实也是在基于工作模式
的,在发送和接收有些许地方要更改,多余的代码我不写第二遍啦,需要的可以私信我要源码或者自己手动cv哦
我们在交换机和队列的绑定也有要注意的
![](https://gitee.com/biexianwocai/blog_images/raw/master/img/20201206235733.png)
当key为a的时候就发送到key为的队列中,我这里做个简单的测试,
//prodicer的sendMsg的方法
/**
* 如果第一个参数写了交换机 参数二就是key
* 这里我简单的发送一下,后面我们实际的开发中肯定不是这样的了
* */
if (msg.startsWith("a")){
channel.basicPublish("ex2","a",null,msg.getBytes());
}else if(msg.startsWith("b")){
channel.basicPublish("ex2","b",null,msg.getBytes());
}
//consumer的接收方法 因为我该交换机绑定了queue5和queue6
channel.basicConsume("queue5",true,consumer);
//consumer1的接收方法
channel.basicConsume("queue6",true,consumer);
测试一下:
基本没啥问题了,这就是路由模式,执行流程没说太多,因为在前面对于工作模式的介绍以及画图,都应该能看的很明白,多看多想okok