mq简称消息队列(也叫消息中间件),通过生产者消费者模型实现系统间的解耦。生产者不断的向消息队列中去写一下信息,消息队列接收到消息会依次把消息放到队列中,日后通过消费者去消费生产者往队列中生产的消息。生产者无需关心消息是否被消费,消费者无需关心生产者有没有正常运行,整个过程没有任何api的侵入。整个过程是异步的。跨系统通信时首选消息队列。
老牌的Apache下的ActiveMQ、RabbitMQ、Apache下炙手可热的Kafka,阿里巴巴自主开发RocketMQ等。
ActiveMQ是Apache出品,最流行的,能力强劲的开源消息总线,它是一个完全支持JMS规范的消息中间件。丰富的API,多种集群架构模式让ActiveMQ在业界成为老牌的消息中间件,在中小型企业颇受欢迎!
Kafka是LinkedIn开源的分布式发布-订阅消息系统,目前归属于Apache顶级项目。Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输,0.8版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,追求产生大量数据的互联网服务的数据收集业务。
RocketMQ是阿里开源的消息中间件,它是纯java开发,具有高吞吐量、高可用性,适合大规模分布式系统应用的特点。RocketMQ思路起源于Kafka,但并不是Kafka的一个Copy,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算,消息推送、日志流式处理、binglog分发等场景。
RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP协议更多用在企业系统内对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。
RabbitMQ比Kafka可靠,kafka更适合IO高吞吐的处理,一般应用在大数据日志处理或对实时性(少量延迟),可靠性(少量丢数据)要求稍低的场景使用,比如ELK日志收集。
AMQP(advanced message queuing protocol)在2003年时被提出,最早用于解决金融领域不同平台之间的消息传递交互问题。顾名思义,AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。这使得实现了AMQP的provider天然性就是跨平台的。以下是AMQP协议类型:
------------Server----------------
Virtual host(基本一个应用一个虚拟主机,类似于关系型数据库中的库,虚拟主机需要与用户进行绑定)
----------------------------------
---------------------------------------- Exchange
Publisher application---------生产消息------------> +-----------------------+
---------------------------------------- +-----------------------+
---------------------------------------- Message Queue
Consumer application<--------消费消息----------- +-----------------------+
---------------------------------------- +-----------------------+
以下协议可以理解为生产者先把消息发给Server(可以理解rabbitmq的一个节点,也就是一个rabbitmq服务器),生产者通过虚拟主机把消息发给交换机,交换机与队列之间进行绑定关系(点对点(一对一)、路由、发布订阅),消费者直接从队列中消费消息。
erlang-22.0.7-1.el7.x86_64.rpm
rabbitmq-server-3.7.18-1.el7.noarch.rpm
rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm
yum install -y rabbitmq-server-3.7.18-1.el7.noarch.rpm
注意:默认安装完成后配置文件模板在:/usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.
config.example目录中,需要将配置文件复制到/etc/rabbitmq/目录中,并修改名称为rabbitmq
.config
cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example /etc/rabbitmq/rabbit
mq.config
ls /etc/rabbitmq/rabbitmq.config
vim /etc/rabbitmq/rabbtmq.config
61 行 %%{loopback_users, []},
去掉%%,以及最后的,逗号,修改为{loopback_users, []}
rabbitmq-plugins enable rabbitmq.management
出现如下说明:
Enabling plugins on node rabbit@localhost:
Rabbitmq_management
The following plugins have been configured:
Rabbitmq_management
Rabbitmq_management_agent
Rabbitmq-web_dispatch
Applying plugin configuration to rabbit@localhost...
The following plugins have been enabled:
Rabbitmq_management
Rabbitmq_management_agent
Rabbitmq_web_dispatch
Set 3 plugins.
Offline change: changes will take effect at broker restart.
启动:systemctl start rabbitmq-server
重启:systemctl restart rabbitmq-server
停止:systemctl stop rabbitmq-server
systemctl start rabbitmq-server
从开机启动服务列表移除:systemctl disable firewalld
停止防火墙:systemctl stop firewalld
http://10.15.0.8:15672/
默认用户名/密码:guest/guest
systemctl start|restart|stop|status rabbimq-server
rabbitmqctl help 可以查看更多命令
Rabbitmq-plugins enable|list|disable
AMQP(advanced message queuing protocol)在2003年时被提出,最早用于解决金融领域不同平台之间的消息传递交互问题。顾名思义,AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。这使得实现了AMQP的provider天然性就是跨平台的。以下是AMQP协议类型:
------------Server----------------
Virtual host(基本一个应用一个虚拟主机,类似于关系型数据库中的库,虚拟主机需要与用户进行绑定)
----------------------------------
---------------------------------------- Exchange
Publisher application---------生产消息------------> +-----------------------+
---------------------------------------- +-----------------------+
---------------------------------------- Message Queue
Consumer application<--------消费消息----------- +-----------------------+
---------------------------------------- +-----------------------+
以下协议可以理解为生产者先把消息发给Server(可以理解rabbitmq的一个节点,也就是一个rabbitmq服务器),生产者通过虚拟主机把消息发给交换机,交换机与队列之间进行绑定关系(点对点(一对一)、路由、发布订阅),消费者直接从队列中消费消息。
在rabbitmq页面创建虚拟主机,然后创建相应用户,再把虚拟主机与用户进行绑定。
在上面的模型中,有以下概念:
channel.queueDeclare(“hello”,false,false,false,null);
不好的地方:消费者消费太慢,生产者不停的生产消息,消费者来不及及时处理,会导致队列里面的消息堆积。
//创建连接mq的连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接rabbitmq主机
connectionFactory.setHost(“10.15.0.9”);
//设置端口号
connectionFactory.setPort(5672);
//设置连接哪个虚拟主机
connectionFactory.setVirtualHost(“/ems”);
connectionFactory.setUsername(“ems”);
connectionFactory.setPassword(“123”);
//获取连接对象
Connection connection = connectionFactory.newConnection();
//获取连接中通道
Channel channel = connection .createChannel();
//通道绑定对应消息队列
//参数1:队列名称 如果队列不存在自动创建
//参数2:用来定义队列特性是否要持久化( true 持久化队列 false不持久化,不设置持久化,队列会随着rabbitmq服务的重启而丢失,看公司具体业务需求要不要持久化。但是并不代表队列里面的消息持久化。如果需要将队列里面的消息也持久化,需要在发送消息的时候设置消息的持久化。)
//参数3:exclusive 是否独占队列(独占的意思只允许当前链接使用)true:独占队列; false不独占
//参数4:autoDelete 是否在消费完成后自动删除队列 true自动删除 false不自动删除
//参数5: 额外附加参数
channel.queueDeclare(“hello”,false,false,false,null);
//发布消息
//参数1:交换机名称;参数2:队列名称;
参数3:传递消息额外设置(MessageProperties.PERSISTENT_TEXT_PLAN设置mq消息持久化,服务重启消息不会丢失);
参数4:消息的具体内容
channel.basicPublish(“”,”hello”,MessageProperties.PERSISTENT_TEXT_PLAN,”hello rabbitmq”.getBytes());
channel.close();
connection.close();
//创建连接mq的连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接rabbitmq主机
connectionFactory.setHost(“10.15.0.9”);
//设置端口号
connectionFactory.setPort(5672);
//设置连接哪个虚拟主机
connectionFactory.setVirtualHost(“/ems”);
connectionFactory.setUsername(“ems”);
connectionFactory.setPassword(“123”);
//获取连接对象
Connection connection = connectionFactory.newConnection();
//获取连接中通道
Channel channel = connection .createChannel();
//通道绑定对应消息队列
//参数1:队列名称 如果队列不存在自动创建
//参数2:用来定义队列特性是否要持久化 true 持久化队列 false不持久化
//参数3:exclusive 是否独占队列(独占的意思只允许当前链接使用)true:独占队列; false不独占
//参数4:autoDelete 是否在消费完成后自动删除队列 true自动删除 false不自动删除
//参数5: 额外附加参数
channel.queueDeclare(“hello”,false,false,false,null);
//消费消息
//参数1:消费哪个队列的消息 队列名称
//参数2:开始消息的自动确认机制
//参数3:消费时的回调接口
Channel.basicConsume(“hello”,true,new DefaultConsumer(channel){
@override//最后一个参数:消息队列中取出的消息
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti
Es,byte[] body) throws IOException{
System.out.println(“==============”+new String(body));
}
});
//channel.close();消费端消费时一般不关闭通道及其连接,最好一直消费
//connection.close();
public class RabbitMQUtils{
private static ConnectionFactory connectionFactory;
private static Properties properties;
static{
connectionFactory = new ConnectionFactory();
//设置连接rabbitmq主机
connectionFactory.setHost(“10.15.0.9”);
//设置端口号
connectionFactory.setPort(5672);
//设置连接哪个虚拟主机
connectionFactory.setVirtualHost(“/ems”);
connectionFactory.setUsername(“ems”);
connectionFactory.setPassword(“123”);
}
//定义提供连接对象的方法
public static Connection getConnection(){
try{
return connectionFactory.newConnection();
}catch(Exception e){
e.printStackTrace();
}
return null;
}
//关闭通道和关闭连接工具方法
public static void closeConnectionAndChanel(Channel channel, Connection conn){
try{
if(channel != null) channel.close();
if(conn != null) conn.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
Work queue,也被称为(Task queues),任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。
Work Queues
(using the java Client)
角色:
channel.queueDeclare(“hell0”,true,false,false,null);
for(int i = 0; i < 10; i++){
channel.basicPublish(“”,”hello”,null,(i+”=====>:我是消息”).getBytes())
}
channel.queueDeclare(“hell0”,ture,false,false,null);
channel.basicConsume(“hello”,true,new DefaultConsumer(channel){
@override//最后一个参数:消息队列中取出的消息
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti
Es,byte[] body) throws IOException{
System.out.println(“消费者1:”+new String(body));
}
});
channel.queueDeclare(“hell0”,ture,false,false,null);
channel.basicConsume(“hello”,true,new DefaultConsumer(channel){
@override//最后一个参数:消息队列中取出的消息
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti
Es,byte[] body) throws IOException{
try{
Thread.sleep(1000);//处理消息比较慢,一秒处理一个消息
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(“消费者2:”+new String(body));
}
});
消费者1: 1====>:我是消息
消费者1: 3====>:我是消息
消费者1: 5====>:我是消息
消费者1: 7====>:我是消息
消费者1: 9====>:我是消息
消费者2: 0====>:我是消息
消费者2: 2====>:我是消息
消费者2: 4====>:我是消息
消费者2: 6====>:我是消息
消费者2: 8====>:我是消息
总结:默认情况下,RabbitMQ将按顺序将每个消息发送给下一个使用者。平均而言,每个消费者都会收到相同数量的消息,这种分发消息的方式称为循环。
生产消息
平均分配
消息自动确认开启,假如消息消费者1平均分配了5个完整消息,消费到第3个消息时,自己宕机自己死了,两个消息丢失,所以说消息自动确认开启机制就有问题了。此时需要如下解决方案:
channel.basicQos(1);//一次只接受一条未确认的消息
//参数2:关闭自动确认消息
channel.basicConsume(“hello”,false,new DefaultConsumer(channel){
@override//最后一个参数:消息队列中取出的消息
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti
Es,byte[] body) throws IOException{
System.out.println(“消费者1:”+new String(body));
channel.basicAck(envelope.getDeliveryTag(),false);//手动确认消息
}
});
fanout 扇出 也称为广播
Putting it all together
在广播模式下,消息发送流程是这样的:
//声明交换机
channel.exchangeDeclare(“logs”,”fanout”);//广播 一条消息多个消费者同时消费
//发布消息
channel.basicPublish(“logs”,””,null,”hello”.getBytes());
//声明交换机
channel.exchangeDeclare(“logs”,”fanout”);
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//将临时队列绑定exchange
channel.queueBind(queue,”logs”,””);
//处理消息
channel.basicConsume(queue ,true,new DefaultConsumer(channel){
@override//最后一个参数:消息队列中取出的消息
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti
Es,byte[] body) throws IOException{
System.out.println(“消费者1:”+new String(body));
}
});
//声明交换机
channel.exchangeDeclare(“logs”,”fanout”);
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//将临时队列绑定exchange
channel.queueBind(queue,”logs”,””);
//处理消息
channel.basicConsume(queue ,true,new DefaultConsumer(channel){
@override//最后一个参数:消息队列中取出的消息
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti
Es,byte[] body) throws IOException{
System.out.println(“消费者2:”+new String(body));
}
});
//声明交换机
channel.exchangeDeclare(“logs”,”fanout”);
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//将临时队列绑定exchange
channel.queueBind(queue,”logs”,””);
//处理消息
channel.basicConsume(queue ,true,new DefaultConsumer(channel){
@override//最后一个参数:消息队列中取出的消息
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti
Es,byte[] body) throws IOException{
System.out.println(“消费者3:”+new String(body));
}
});
在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费,这时就要用到Direct模型的Exchange。
在Direct模型下:
图解:
//获取连接对象
Connection connection = RabbitMQUtils.getConnection();
//获取连接对象通道
Channel channel = Connection.createChannel();
String exchangeName = “logs_direct”;
//声明交换机 参数1:交换机名称 参数2:交换机类型 基于指令的Routing key转发
channel.exchangeDeclare(exchangeName ,”direct”);
//发送消息
String routingKey= “error”;//info、warning
//发布消息
channel.basicPublish(exchangeName,routingKey,null,(“指定的route key”+key+”的消息”).getBytes());
//关闭资源
RabbitMQUtils.closeConnectionAndChanel(channel,connection);
Connection connection = RabbitMQUtils.getConnection();
Channel channel = Connection.createChannel();
//通道声明交换机以及交换的类型
channel.exchangeDeclare(“logs_direct”,”direct”);
//创建一个临时队列
String queue = channel.queueDeclare().getQueue();
//基于route key绑定队列和交换机
channel.queueBind(queue,”logs_direct”,”error”);
//获取消费的消息
channel.basicConsume(queue ,true,new DefaultConsumer(channel){
@override//最后一个参数:消息队列中取出的消息
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti
Es,byte[] body) throws IOException{
System.out.println(“消费者1:”+new String(body));
}
});
Connection connection = RabbitMQUtils.getConnection();
Channel channel = Connection.createChannel();
//通道声明交换机以及交换的类型
channel.exchangeDeclare(“logs_direct”,”direct”);
//创建一个临时队列
String queue = channel.queueDeclare().getQueue();
//基于route key绑定队列和交换机
channel.queueBind(queue,”logs_direct”,”info”);
channel.queueBind(queue,”logs_direct”,”error”);
channel.queueBind(queue,”logs_direct”,”warning”);
//获取消费的消息
channel.basicConsume(queue ,true,new DefaultConsumer(channel){
@override//最后一个参数:消息队列中取出的消息
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti
Es,byte[] body) throws IOException{
System.out.println(“消费者2:”+new String(body));
}
});
Topic类型的Exchange与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routingkey的时候使用通配符!这种模型Routingkey一般是由一个或多个单词组成,多个单词之间以“.”分割,例如:item.insert
#通配符
*(star) can substitute for exactly one word. 匹配不多不少恰好1个词
#(hash) can substitute for zero or more words.匹配一个或多个词
#如
audit.# 匹配audit.irs.corporate或者 audit.irs等
audit.* 只能匹配 audit.irs
//获取连接对象
Connection connection = RabbitMQUtils.getConnection();
//获取连接对象通道
Channel channel = Connection.createChannel();
String exchangeName = “topics”;
//声明交换机以及交换机类型
channel.exchangeDeclare(exchangeName ,”topic”);
//发送消息
String routingKey= “user.save”;
//发布消息
channel.basicPublish(exchangeName,routingKey,null,(“这里是topic动态路由模型,routekey”+routingKey+”的消息”).getBytes());
//关闭资源
RabbitMQUtils.closeConnectionAndChanel(channel,connection);
Connection connection = RabbitMQUtils.getConnection();
Channel channel = Connection.createChannel();
String exchangeName = “topics”;
//声明交换机以及交换机类型
channel.exchangeDeclare(exchangeName ,”topic”);
//创建一个临时队列
String queue = channel.queueDeclare().getQueue();
//基于route key绑定队列和交换机
channel.queueBind(queue,exchangeName ,”user.*”);
//获取消费的消息
channel.basicConsume(queue ,true,new DefaultConsumer(channel){
@override//最后一个参数:消息队列中取出的消息
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti
Es,byte[] body) throws IOException{
System.out.println(“消费者1:”+new String(body));
}
});
Connection connection = RabbitMQUtils.getConnection();
Channel channel = Connection.createChannel();
String exchangeName = “topics”;
//声明交换机以及交换机类型
channel.exchangeDeclare(exchangeName ,”topic”);
//创建一个临时队列
String queue = channel.queueDeclare().getQueue();
//基于route key绑定队列和交换机
channel.queueBind(queue,exchangeName ,”user.#”);
//获取消费的消息
channel.basicConsume(queue ,true,new DefaultConsumer(channel){
@override//最后一个参数:消息队列中取出的消息
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti
Es,byte[] body) throws IOException{
System.out.println(“消费者1:”+new String(body));
}
});
记住每种不同类型模式的消费者类名不要相同,不然不知道是给哪个消费者发送消息,即使你的routerkey不同。交换机以及交换机类型由消费者去创建。
spring:
application:
name: springboot_rabbitmq
rabbitmq:
host: 10.15.0.9
port: 5672
username: ems
password: 123
virtual-host: /ems
//注入rabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
//hello world
@Test
public void test(){
rabbitTemplate.convertAndSend(“hello”,””,”hello world”);
}
@Component//默认持久化,非独占
@RabbitListener(queuesToDeclare = @Queue(value=”hello”,durable = false,autoDelete = true))
public class HelloCustomer{
@RabbitHandler
public void receive(String message){
System.out.println(“message = ”+message);
}
}
//注入rabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void test(){
for(int i = 0; i < 10; i++){
rabbitTemplate.convertAndSend(“work”,””,”hello world”);
}
}
@Component
public class HelloCustomer{
//默认持久化,非独占
@RabbitListener(queuesToDeclare = @Queue(value=”work”,durable = false,autoDelete = true))
@RabbitHandler
public void receive1(String message){
System.out.println(“work message1 = ”+message);
}
@RabbitListener(queuesToDeclare = @Queue(value=”work”,durable = false,autoDelete = true))
@RabbitHandler
public void receive2(String message){
System.out.println(“work message2 = ”+message);
}
}
说明:默认在Spring AMQP实现中Work这种方式就是公平调度,如果需要实现能者多劳需要额外配置。
//注入rabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void test(){
rabbitTemplate.convertAndSend(“logs”,””,”这是Fanout的模型发送的消息”);
}
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,//创建临时队列
exchange = @Exchange(value=”logs”,type=”fanout”)//绑定的交换机
)
})
@RabbitHandler
public void receive1(String message){
System.out.println(“message = ”+message);
}
}
//注入rabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testRoute(){
rabbitTemplate.convertAndSend(“directs”,”info”,”发送info的key的路由信息”);
}
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,//创建临时队列
exchange = @Exchange(value=”directs”,type=”direct”)//自定义交换机名称和类型
key = {“info”,”error”,”warn”}
)
})
@RabbitHandler
public void receive1(String message){
System.out.println(“message = ”+message);
}
}
//注入rabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testTopic(){
rabbitTemplate.convertAndSend(“topics”,”user.save”,”user.save路由消息”);
}
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,//创建临时队列
exchange = @Exchange(value=”topics”,type=”topic”)//自定义交换机名称和类型
key = {“user.save”,”user.*”}
)
})
@RabbitHandler
public void receive1(String message){
System.out.println(“message1 = ”+message);
}
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,//创建临时队列
exchange = @Exchange(value=”topics”,type=”topic”)//自定义交换机名称和类型
key = {“order.*”,”user.*”}
)
})
@RabbitHandler
public void receive2(String message){
System.out.println(“message2 = ”+message);
}
Spring的异步处理不能跨jvm,也就是不支持分布式系统,mq就是支付分布式系统的。
我想大家最最最熟悉的就是单机结构,一个系统业务量很小的时候所有的代码都放在一个项目中就好了,然后这个项目部署在一台服务器上就好了。整个项目所有的服务都由这台服务器提供。这就是单机结构。
那么,单机结构有啥缺点呢?我想缺点是显而易见的,单机的处理能力毕竟是有限的,当你的业务增长到一定程度的时候,单机的硬件资源将无法满足你的业务需求。此时便出现了集群模式,往下接着看。
集群模式在程序猿界有各种装逼解释,有的让你根本无法理解,其实就是一个很简单的玩意儿,且听我一一道来。
单机处理到达瓶颈的时候,你就把单机复制几份,这样就构成了一个“集群”。集群中每台服务器就叫做这个集群的一个“节点”,所有节点构成了一个集群。每个节点都提供相同的服务,那么这样系统的处理能力就相当于提升了好几倍(有几个节点就相当于提升了这么多倍)。
但问题是用户的请求究竟由哪个节点来处理呢?最好能够让此时此刻负载较小的节点来处理,这样使得每个节点的压力都比较平均。要实现这个功能,就需要在所有节点之前增加一个“调度者”的角色,用户的所有请求都先交给它,然后它根据当前所有节点的负载情况,决定将这个请求交给哪个节点处理。这个“调度者”有个牛逼了名字——负载均衡服务器。
集群结构的好处就是系统扩展非常容易。如果随着你们系统业务的发展,当前的系统又支撑不住了,那么给这个集群再增加节点就行了。但是,当你的业务发展到一定程度的时候,你会发现一个问题——无论怎么增加节点,貌似整个集群性能的提升效果并不明显了。这时候,你就需要使用微服务结构了。
先来对前面的知识点做个总结。
从单机结构到集群结构,你的代码基本无需要作任何修改,你要做的仅仅是多部署几台服务器,每台服务器上运行相同的代码就行了。但是,当你要从集群结构演进到微服务结构的时候,之前的那套代码就需要发生较大的改动了。所以对于新系统我们建议,系统设计之初就采用微服务架构,这样后期运维的成本更低。但如果一套老系统需要升级成微服务结构的话,那就得对代码大动干戈了。所以,对于老系统而言,究竟是继续保持集群模式,还是升级成微服务架构,这需要你们的架构师深思熟虑、权衡投入产出比。
OK,下面开始介绍所谓的分布式结构。
分布式结构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在分布式结构中,每个子系统就被称为“服务”。这些子系统能够独立运行在web容器中,它们之间通过RPC方式通信。
举个例子,假设需要开发一个在线商城。按照微服务的思想,我们需要按照功能模块拆分成多个独立的服务,如:用户服务、产品服务、订单服务、后台管理服务、数据分析服务等等。这一个个服务都是一个个独立的项目,可以独立运行。如果服务之间有依赖关系,那么通过RPC方式调用。
这样的好处有很多:
系统之间的耦合度大大降低,可以独立开发、独立部署、独立测试,系统与系统之间的边界非常明确,排错也变得相当容易,开发效率大大提升。
系统之间的耦合度降低,从而系统更易于扩展。我们可以针对性地扩展某些服务。假设这个商城要搞一次大促,下单量可能会大大提升,因此我们可以针对性地提升订单系统、产品系统的节点数量,而对于后台管理系统、数据分析系统而言,节点数量维持原有水平即可。
服务的复用性更高。比如,当我们将用户系统作为单独的服务后,该公司所有的产品都可以使用该系统作为用户系统,无需重复开发。
场景说明:用户注册后,需要发送邮件和注册短信,传统的做法有两种 1.串行的方式 2.并行的方式
用户----->注册信息写入数据库 ---->发送邮件------>发送短信
响应150ms<----- 50ms 50ms 50ms
由此可以看出,引入消息队列后,用户的响应时间就等于数据库的时间+写入消息队列的时间(可以忽略不计),引入消息队列后处理后,响应时间是串行的3倍,是并行的2倍。
场景:双11是购物狂欢节,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口。
这种做法有一个缺点:
当库存系统出现故障时,订单就会失败。订单系统和库存系统高耦合,引入消息队列。
场景:秒杀活动,一般因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端假如消息队列。
作用:
默认情况下:RabbitMQ代理操作所需的所有数据/状态都将跨所有节点复制。这方面的一个例外是消息队列。默认情况下,消息队列位于一个节点上,尽管它们可以从所有节点看到和访问。
0.集群规划
node1: 10.15.0.3 mq1 master 主节点
node2: 10.15.0.4 mq2 repl1 副本节点
node3: 10.15.0.5 mq3 repl2 副本节点
1.克隆三台机器主机名和ip映射
vim /etc/hosts加入:
10.15.0.3 mq1
10.15.0.4 mq2
10.15.0.5 mq3
node1: vim /etc/hostname加入: mq1
node2: vim /etc/hostname加入: mq2
Node3: vim /etc/hostname加入: mq3
2.三个机器安装rabbitmq,并同步cookie文件,在node1上执行
scp /var/lib/rabbitmq/.erlang.cookie root@mq2: /var/lib/rabbitmq/
scp /var/lib/rabbitmq/.erlang.cookie root@mq3:/var/lib/rabbitmq/
3.查看cookie是否一致
node1: cat /var/lib/rabbitmq/.erlang.cookie
node2: cat /var/lib/rabbitmq/.erlang.cookie
node3: cat /var/lib/rabbitmq/.erlang.cookie
4.后台启动rabbitmq所有节点执行如下命令,启动成功访问管理界面:
rabbitmq-server -detached
5.在node2和node3执行加入集群命令
1.关闭 rabbitmqctl stop_acp
2.加入集群 rabbitmqctl join_cluster rabbit@mq1
3.启动服务 rabbitmqctl start_app
6.查看集群状态,任意节点执行
rabbitmqctl cluster_status
7.如果出现如下显示,集群搭建成功
8.登录管理界面,展示如下状态
9.测试集群在node1上,创建队列
10.查看node2和node3节点
11.关闭node1节点,执行如下命令,查看node2和node3
执行如下命令:rabbitmqctl stop_app
镜像队列机制就是将队列在三个节点之间设置主从关系,消息会在三个节点之间进行同步,且如果其中一个节点不可用,并不会导致消息丢失或服务不可用的情况,提升MQ集群的整体高可用性。
0.策略说明
1.查看当前策略
rabbitmqctl list_policies
2.添加策略
rabbitmq set_policy ha -all ‘^hello’ ‘{“ha-mode”:”all”,”ha-sync-mode”:”automatic”}’
说明:策略正则表达式为’^’表示匹配所有队列名称 ^hello:匹配hello开头队列
3.删除策略
rabbitmqctl clear_policy
4.测试集群