“消息队列”是在消息的传输过程中保存消息的容器,当我们需要使用消息的时候可以取出消息供自己使用。
消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ。
(1) 通过异步处理提高系统性能(削峰、减少响应所需时间)
如上图,在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。
(2) 降低系统耦合性
我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。
我们最常见的事件驱动架构类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示:
消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。 从上图可以看到消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计。
消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。
另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。
RocketMq是一个由阿里巴巴开源的消息中间件,脱胎去阿里每部使用的MetaQ,在设计上借鉴了Kafka。2012年开源,2017年成为apache顶级项目
安装 Erlang
yum install erlang
#等待安装完成
#检查是否安装成功
erl -version
安装 Rabbitmq-server
yum install rabbitmq-server
修改配置文件,修改rabbitmq.config文件(yum安装放在/etc/rabbitmq/)
将配置文件中"%% {loopback_users, []},",这一行的逗号去掉,目的是为了开启远程访问
rabbitmq-plugins enable rabbitmq_management
# 添加新用户
rabbitmqctl add_user username password
# 设置用户tag
rabbitmqctl set_user_tags username administrator
# 赋予用户默认vhost的全部操作权限
rabbitmqctl set_permissions -p / username ".*" ".*" ".*"
#查看现有的用户
rabbitmqctl user_list
# 由于RabbitMQ默认的账号用户名和密码都是guest。为了安全起见, 先删掉默认用户
rabbitmqctl delete_user guest
操作
# 添加开机启动RabbitMQ服务
chkconfig rabbitmq-server on
# 启动服务
service rabbitmq-server start
# 查看服务状态
$service rabbitmq-server status
# 停止服务
service rabbitmq-server stop
查看web
AMQP 中的消息路由过程和Java 开发者熟悉的JMS 存在一些差别,在AMQP 中增加了Exchan ge 和B inding 的角色。生产者需要把消息发布到Exchange 上,消息最终到达队列并被消费者接收,而Binding 决定交换器上的消息应该被发送到哪个队列中
不同类型的交换器分发消息的策略也不同,目前交换器有4 种类型: Direct 、Fanout、Topic 、Headers 。其中Headers 交换器匹配AMQP 消息的Header 而不是路由键。此外, Headers 交换器和Direct 交换器完全一致,但性能相差很多, 目前几乎不用了,所以下面我们看另外三种类型。
Provier实现:
4.0.0
com.whut
spring-boot-direct-provider
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.0.5.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
org.springframework.boot
spring-boot-starter-amqp
server:
port: 8080 #配置端口号
#port: ${random.int[1024,9999]} #生成随机端口号
spring:
mvc:
view:
prefix: /
suffix: .html
servlet: #配置文件大小
multipart:
max-file-size: 20Mb
max-request-size: 20Mb
#rabbitmq配置
rabbitmq:
host: yor_port
port: 5672
username: root
password: 123456
#配置交换器名字
mq:
config:
exchange: log.direct
routing:
info:
key: log.info.routing.key
error:
key: log.error.routing.key
@Component
public class Sender {
@Autowired
private AmqpTemplate amqpTemplate;
@Value("${mq.config.exchange}")
private String exchangeName;
@Value("${mq.routing.info.key}")
private String routingKey;
public void send(String msg){
/**
* 转发消息
*/
amqpTemplate.convertAndSend(this.exchangeName,this.routingKey,msg);
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringBootServerApplication.class)
public class QueueTest {
@Autowired
private Sender sender;
@Test
public void test1() throws InterruptedException {
int i = 0;
while (true){
Thread.sleep(1000);
System.out.println("i ="+ i);
this.sender.send("sender "+ i++);
}
}
}
Reciver实现:
server:
port: 8080 #配置端口号
#port: ${random.int[1024,9999]} #生成随机端口号
spring:
mvc:
view:
prefix: /
suffix: .html
servlet: #配置文件大小
multipart:
max-file-size: 20Mb
max-request-size: 20Mb
#rabbitmq配置
rabbitmq:
host: *************
port: 5672
username: root
password: 123456
#配置交换器名字
mq:
config:
exchange: log.direct
#配置队列信息
queue:
info: log.info
error: log.error
routing:
info:
key: log.info.routing.key
error:
key: log.error.routing.key
package com.whut.receiver;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.lang.model.type.ExecutableType;
@Component
/**
* bindings:绑定队列
* @QueueBinding
* value:配置队列名称
* exchange:配置交换器
* key:配置路由键
* @Queue autoDelete:配置是否为临时队列,当配置为true时候为临时队列,即当消费者蹦的时候,再次启动时候会
* 丢失数据
* value:配置队列名称
*
* @Exchange value:配置交换器名字
* type 配置交换器的模式
*
*/
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "${mq.queue.info}",autoDelete = "true"),
exchange = @Exchange(value = "${mq.config.exchange}",type = ExchangeTypes.DIRECT),
key = "${mq.routing.info.key}"
)
)
public class InfoReceiver {
@RabbitHandler
public void receiver(String msg){
System.out.println("InfoReceiver ----> " + msg);
}
}
Provider实现:
@Component
public class UserSender {
@Autowired
private AmqpTemplate amqpTemplate;
@Value("${mq.config.exchange}")
private String exchangeName;
public void send(String msg){
/**
* 转发消息
*/
amqpTemplate.convertAndSend(this.exchangeName,"user.log.debug","user.log.debug = " + msg);
amqpTemplate.convertAndSend(this.exchangeName,"user.log.info","user.log.info = " + msg);
amqpTemplate.convertAndSend(this.exchangeName,"user.log.warn","user.log.warn =" +msg);
amqpTemplate.convertAndSend(this.exchangeName,"user.log.error","user.log.error =" + msg);
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringBootServerApplication.class)
public class QueueTest {
@Autowired
private UserSender userSender;
@Autowired
private ProductSender productSender;
@Autowired
private OrderSender orderSender;
@Test
public void test1() throws InterruptedException {
while (true){
Thread.sleep(1000);
this.userSender.send("UserSender ...............");
this.orderSender.send("orderSender ...............");
this.productSender.send("productSender ...............");
}
}
}
Consumer实现:
@Component
/**
* bindings:绑定队列
* @QueueBinding
* value:配置队列名称
* exchange:配置交换器
* key:配置路由键
* @Queue autoDelete:配置是否为临时队列,当配置为true时候为临时队列,即当消费者蹦的时候,再次启动时候会
* 丢失数据
* value:配置队列名称
*
* @Exchange value:配置交换器名字
* type 配置交换器的模式
*
*/
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "${mq.queue.info}",autoDelete = "true"),
exchange = @Exchange(value = "${mq.config.exchange}",type = ExchangeTypes.TOPIC),
key = "*.log.info"
)
)
public class InfoReceiver {
@RabbitHandler
public void receiver(String msg){
System.out.println("InfoReceiver ----> " + msg);
}
}
实现效果:
实现略。