在介绍RabbitMQ之前实现要介绍一下MQ,MQ是什么?
MQ全称是Message Queue,可以理解为消息队列的意思,简单来说就是消息以管道的方式进行传递。
RabbitMQ是一个实现了AMQP(Advanced Message Queuing Protocol)高级消息队列协议的消息队列服务,用Erlang语言的。
使用场景
在我们秒杀抢购商品的时候,系统会提醒我们稍等排队中,而不是像几年前一样页面卡死或报错给用户。
像这种排队结算就用到了消息队列机制,放入通道里面一个一个结算处理,而不是某个时间断突然涌入大批量的查询新增把数据库给搞宕机,所以RabbitMQ本质上起到的作用就是削峰填谷,为业务保驾护航。
为什么选择RabbitMQ
现在的市面上有很多MQ可以选择,比如ActiveMQ、ZeroMQ、Appche Qpid,那问题来了为什么要选择RabbitMQ?
- 除了Qpid,RabbitMQ是唯一一个实现了AMQP标准的消息服务器;
- 可靠性,RabbitMQ的持久化支持,保证了消息的稳定性;
- 高并发,RabbitMQ使用了Erlang开发语言,Erlang是为电话交换机开发的语言,天生自带高并发光环,和高可用特性;
- 集群部署简单,正是应为Erlang使得RabbitMQ集群部署变的超级简单;
- 社区活跃度高,根据网上资料来看,RabbitMQ也是首选;
工作机制
生产者、消费者和代理
在了解消息通讯之前首先要了解3个概念:生产者、消费者和代理。
生产者:消息的创建者,负责创建和推送数据到消息服务器;
消费者:消息的接收方,用于处理数据和确认消息;
代理:就是RabbitMQ本身,用于扮演“快递”的角色,本身不生产消息,只是扮演“快递”的角色。
消息发送原理
首先你必须连接到Rabbit才能发布和消费消息,那怎么连接和发送消息的呢?
你的应用程序和Rabbit Server之间会创建一个TCP连接,一旦TCP打开,并通过了认证,认证就是你试图连接Rabbit之前发送的Rabbit服务器连接信息和用户名和密码,有点像程序连接数据库,使用Java有两种连接认证的方式,后面代码会详细介绍,一旦认证通过你的应用程序和Rabbit就创建了一条AMQP信道(Channel)。
信道是创建在“真实”TCP上的虚拟连接,AMQP命令都是通过信道发送出去的,每个信道都会有一个唯一的ID,不论是发布消息,订阅队列或者介绍消息都是通过信道完成的。
为什么不通过TCP直接发送命令?
对于操作系统来说创建和销毁TCP会话是非常昂贵的开销,假设高峰期每秒有成千上万条连接,每个连接都要创建一条TCP会话,这就造成了TCP连接的巨大浪费,而且操作系统每秒能创建的TCP也是有限的,因此很快就会遇到系统瓶颈。
如果我们每个请求都使用一条TCP连接,既满足了性能的需要,又能确保每个连接的私密性,这就是引入信道概念的原因。
你必须知道的Rabbit
想要真正的了解Rabbit有些名词是你必须知道的。
包括:ConnectionFactory(连接管理器)、Channel(信道)、Exchange(交换器)、Queue(队列)、RoutingKey(路由键)、BindingKey(绑定键)。
**ConnectionFactory(连接管理器):**应用程序与Rabbit之间建立连接的管理器,程序代码中使用;
**Channel(信道):**消息推送使用的通道;
**Exchange(交换器):**用于接受、分配消息;
Queue(队列):用于存储生产者的消息;
RoutingKey(路由键):用于把生成者的数据分配到交换器上;
BindingKey(绑定键):用于把交换器的消息绑定到队列上;
消息持久化
Rabbit队列和交换器有一个不可告人的秘密,就是默认情况下重启服务器会导致消息丢失,那么怎么保证Rabbit在重启的时候不丢失呢?答案就是消息持久化。
当你把消息发送到Rabbit服务器的时候,你需要选择你是否要进行持久化,但这并不能保证Rabbit能从崩溃中恢复,想要Rabbit消息能恢复必须满足3个条件:
- 投递消息的时候durable设置为true,消息持久化,代码:channel.queueDeclare(x, true, false, false, null),参数2设置为true持久化;
- 设置投递模式deliveryMode设置为2(持久),代码:channel.basicPublish(x, x, MessageProperties.PERSISTENT_TEXT_PLAIN,x),参数3设置为存储纯文本到磁盘;
- 消息已经到达持久化交换器上;
- 消息已经到达持久化的队列;
持久化工作原理
Rabbit会将你的持久化消息写入磁盘上的持久化日志文件,等消息被消费之后,Rabbit会把这条消息标识为等待垃圾回收。
持久化的缺点
消息持久化的优点显而易见,但缺点也很明显,那就是性能,因为要写入硬盘要比写入内存性能较低很多,从而降低了服务器的吞吐量,尽管使用SSD硬盘可以使事情得到缓解,但他仍然吸干了Rabbit的性能,当消息成千上万条要写入磁盘的时候,性能是很低的。
虚拟主机
每个Rabbit都能创建很多vhost,我们称之为虚拟主机,每个虚拟主机其实都是mini版的RabbitMQ,拥有自己的队列,交换器和绑定,拥有自己的权限机制。
vhost特性
- RabbitMQ默认的vhost是“/”开箱即用;
- 多个vhost是隔离的,多个vhost无法通讯,并且不用担心命名冲突(队列和交换器和绑定),实现了多层分离;
- 创建用户的时候必须指定vhost;
环境搭建
安装wget命令
#rpm -ivh http://mirrors.163.com/centos/7/os/x86_64/Packages/wget-1.14-18.el7.x86_64.rpm
#yum -y install wget
下载erlang solution
rabbitMQ是erlang语言进行开发,所以安装RabbitMQ前需要安装erlang的依赖
#wget https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm
安装erlang
#yum install epel-release
#rpm -Uvh erlang-solutions-1.0-1.noarch.rpm
#yum install erlang
下载rabbitMQ
#wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.12/rabbitmq-server-3.7.12-1.el6.noarch.rpm
安装rabbitMQ
#yum install rabbitmq-server-3.7.12-1.el6.noarch.rpm
开启web端管理访问(非必须,如果要开启,需要先开启允许远程访问)
#rabbitmq-plugins enable rabbitmq_management rabbitmq_management_agent rabbitmq_web_dispatch
安装消息延迟插件
#cd /usr/lib/rabbitmq/lib/rabbitmq_server-3.7.12/plugins
#wget wget https://dl.bintray.com/rabbitmq/community-plugins/rabbitmq_delayed_message_exchange-0.0.1.ez
#rabbitmq-plugins enable rabbitmq_delayed_message_exchange
注:查看插件情况
rabbitmq-plugins list
启动rabbitMQ
service rabbitmq-server start ##启动rabbitMQ
service rabbitmq-server status ##查看rabbitMQ状态
service rabbitmq-server restart ##重启rabbitMQ
service rabbitmq-server stop ##停止rabbitMQ
使用web访问
http://xxx.xxx.xxx.xxx:15672
RabbitMQ命令
创建VirtualHost
rabbitmqctl add_vhost 虚拟服务器名称
rabbitmqctl add_vhost my_test
创建用户并配置权限
rabbitmqctl add_user 用户名 密码
rabbitmqctl add_user testuser testuser
rabbitmqctl set_permissions -p / 用户名 ".*" ".*" ".*"
rabbitmqctl set_permissions -p my_test testuser ".*" ".*" ".*"
配置角色
rabbitmqctl set_user_tags 用户名 角色类型
rabbitmqctl set_user_tags testuser policymaker
角色类型有:none、management、policymaker、monitoring、administrator
Springboot应用
pom配置文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<!--<version>1.5.2.RELEASE</version>-->
<!--排除这个slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
application配置文件
rabbitmq.host=192.168.163.131,192.168.163.130
rabbitmq.port=5672,5672
rabbitmq.username=haoweiGuest,haowei
rabbitmq.password=hw123456,1Qaz2wsx
rabbitmq.virtual-host=/vasky,/radap
rabbitmq.exchangeName=/realtime/face
rabbitmq.topic=haowei.face,haowei-lanlingyuan
启动注入bean(注入两个MQ)
package intellif.custom.mqtt.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* @author weijinzhi
* @project IFaceServer-shanghaiputuo
* @datetime 2019/7/22 13:34
*/
@Configuration
public class RabbitConfig {
@Value("${rabbitmq.host}")
private String mqHost;
@Value("${rabbitmq.port}")
private String mqPort;
@Value("${rabbitmq.username}")
private String mqUserName;
@Value("${rabbitmq.password}")
private String mqPassword;
@Value("${rabbitmq.virtual-host}")
private String mqVirtualHost;
@Bean(name = "firstConnectionFactory")
@Primary
public ConnectionFactory firstConnectionFactory() {
String[] host = mqHost.split(",");
String[] port = mqPort.split(",");
String[] username = mqUserName.split(",");
String[] password = mqPassword.split(",");
String[] virtualHost = mqVirtualHost.split(",");
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost(host[0]);
connectionFactory.setPort(Integer.valueOf(port[0]));
connectionFactory.setUsername(username[0]);
connectionFactory.setPassword(password[0]);
connectionFactory.setVirtualHost(virtualHost[0]);
return connectionFactory;
}
@Bean(name = "firstRabbitTemplate")
@Primary
public RabbitTemplate firstRabbitTemplate(@Qualifier("firstConnectionFactory") ConnectionFactory connectionFactory) {
RabbitTemplate firstRabbitTemplate = new RabbitTemplate(connectionFactory);
firstRabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return firstRabbitTemplate;
}
@Bean(name = "secondConnectionFactory")
public ConnectionFactory secondConnectionFactory() {
String[] host = mqHost.split(",");
String[] port = mqPort.split(",");
String[] username = mqUserName.split(",");
String[] password = mqPassword.split(",");
String[] virtualHost = mqVirtualHost.split(",");
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost(host[1]);
connectionFactory.setPort(Integer.valueOf(port[1]));
connectionFactory.setUsername(username[1]);
connectionFactory.setPassword(password[1]);
connectionFactory.setVirtualHost(virtualHost[1]);
return connectionFactory;
}
@Bean(name = "secondRabbitTemplate")
public RabbitTemplate secondRabbitTemplate(@Qualifier("secondConnectionFactory") ConnectionFactory connectionFactory) {
RabbitTemplate secondRabbitTemplate = new RabbitTemplate(connectionFactory);
secondRabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return secondRabbitTemplate;
}
@Bean(name="firstFactory")
public SimpleRabbitListenerContainerFactory firstFactory(@Qualifier("firstConnectionFactory") ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// factory.setMessageConverter(new Jackson2JsonMessageConverter());
return factory;
}
@Bean(name="secondFactory")
public SimpleRabbitListenerContainerFactory secondFactory(@Qualifier("secondConnectionFactory") ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// factory.setMessageConverter(new Jackson2JsonMessageConverter());
return factory;
}
}
监听
@Component
public class QueneConsemer {
private final Logger logger = LogManager.getLogger(QueneConsemer.class);
@RabbitListener(queues = "haowei.face", containerFactory = "firstFactory")
public void process(Object content) {
logger.info("接收处理队列A当中的消息: " + JSONObject.toJSONString(content));
}
}