https://www.rabbitmq.com/
发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式
同步调用的时效性强,可以立即获取结果
我们以前在使用Feign或OpenFeign时,就是使用的同步调用
代码耦合度高:每次加入新的需求,都要修改原来的代码
性能低:调用者需要等待服务提供者响应,如果调用链过长则响应时间等于每次调用的时间之和。
资源浪费:调用链中的每个服务在等待响应过程中,不能释放请求占用的资源,高并发场景下会极度浪费系统资源
级联失败:如果服务提供者出现问题,所有调用方都会跟着出问题,如同多米诺骨牌一样,迅速导致整个微服务群故障
发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式
其常见的实线就是事件驱动模式
可以实现服务解耦的问题,性能得到提升,吞吐量提高,服务没有强依赖性,不必担心级联失败问题,实现服务削峰
N (Message Quene):翻译为j消息队列,通过典型的生产者和消费者模型生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入轻松的实现系统间解耦。别名为消息中间件,通过利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。
ActiveM 是A4pache出品,最流行的,能力强劲的开源消息总线。它是一个完全支持/8规范的的消息中间件。丰富的API,多种集群架构模式让认kctiveMA在业界成为老牌的消息中间件,在中小型企业颇受欢迎!
Kafka是LinkedIn开源的分布式发布-订阅消息系统,目前归属于Apache顶级项目。Kafka主要特点是基于Pu11的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输。8.8版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,适合产生大量数据的互联网服务的数据收集业务。
RocketNQ是阿里开源的消息中间件,它是纯Java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。RocketNO思路起源于Kafka,但并不是Kafka的一个Copy,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景。
RabbitNQ是使用Erlang语言开发的开源消息队列系统,基于ANQP协议来实现。ANQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。ANQP协议更多用在企业系统内对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。
docker pull rabbitmq
//启动
docker run \
-e RABBITMQ_DEFAULT_USER=syf20020816 \
-e RABBITMQ_DEFAULT_PASS=20020816 \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3.10-management
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.15.0</version>
</dependency>
/**
* 直连式连接
*/
public class Publisher {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接mq的连接工厂对象
final ConnectionFactory connectionFactory = new ConnectionFactory();
//设置主机
connectionFactory.setHost("192.168.112.101");
//设置端口号
connectionFactory.setPort(5672);
//设置连接虚拟主机
connectionFactory.setVirtualHost("/test");
//设置虚拟主机的用户名密码
connectionFactory.setUsername("ssf2asdas6");
connectionFactory.setPassword("204545454");
//获取连接对象
final Connection connection = connectionFactory.newConnection();
//获取连接中的通道
final Channel channel = connection.createChannel();
//通道绑定对应的消息队列
//参数1:队列名称,不存在则创建
//参数2:定义队列特性是否需要持久化,true:持久化,false:非持久化
//参数3:是否独占队列
//参数4:是否在消费完成后删除队列
//参数5:拓展附加参数
channel.queueDeclare("demo1",false,false,false,null);
//发布消息
//参数1:交换机名称(exchange)
//参数2:队列名称
//参数3:传递消息额外设置
//参数4:消息的具体内容
channel.basicPublish("","demo1",null,"hello world".getBytes());
//关闭资源
channel.close();
connection.close();
}
}
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建链接工厂
final ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.112.101");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/test");
connectionFactory.setUsername("ssf2asdas6");
connectionFactory.setPassword("204545454");
//创建连接对象
final Connection connection = connectionFactory.newConnection();
//创建通道
final Channel channel = connection.createChannel();
channel.queueDeclare("demo1",false,false,false,null);
//消费消息
//参数1:消费队列的名称
//参数2:开启消息的自动确认机制
//参数3:消费时的回调接口
channel.basicConsume("demo1",true, new DefaultConsumer(channel){
//最后一个参数:消息队列中取出的消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
}
});
//关闭资源,若不关闭则一直进行监听
channel.close();
connection.close();
}
}
Spring AMQP是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息。包含两部分,其中spring-amqp是基础抽象,spring-rabbit是底层的默认实现。
https://spring.io/projects/spring-amqp
无论你的消息发布者还是消息消费者都需要使用以下yaml配置
spring:
rabbitmq:
host: 192.168.112.101
port: 5672
virtual-host: /test
username: sysdaa6
password: 20asdsa16
这里注意的是,你的队列一定要是存在的
@SpringBootTest
class PublisherApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
rabbitTemplate.convertAndSend("demo1","hello springAMQP!");
}
}
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "demo1")
public void listenQueue(String msg){
System.out.println(msg);
}
}
使用工作队列模型
工作队列,可以提高消息处理速度,避免队列消息堆积
spring:
rabbitmq:
host: 192.168.112.101
port: 5672
virtual-host: /test
username: your username
password: your password
listener:
simple:
prefetch: 1 #表示每次只能获取一条消息,处理完才能获取下一条
@SpringBootTest
class PublisherApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void testWorkQueue() throws InterruptedException {
String queueName = "demo1";
String msg = "this is the msg:";
for (int i = 0; i <= 50; i++) {
rabbitTemplate.convertAndSend(queueName,msg+i);
Thread.sleep(50);
}
}
}
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "demo1")
public void listenWorkQueue(String msg) throws InterruptedException {
System.out.println("=====consumer 1:=====|"+ LocalDateTime.now());
System.out.println(msg);
Thread.sleep(50);
}
@RabbitListener(queues = "demo1")
public void listenWorkQueue2(String msg) throws InterruptedException {
System.err.println("=====consumer 2:=====|"+ LocalDateTime.now());
System.err.println(msg);
Thread.sleep(500);
}
}
发布订阅模式与之前案例的区别就是允许将同一消息发送给多个消费者。实现方式是加入了exchange(交换机)
exchange负责消息路由,而不是存储,路由失败则消息丢失
定义一个FanoutConfig
配置类进行交换机和队列的绑定
@Configuration
public class FanoutConfig {
//交换机
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("test.fanout");
}
//队列1
@Bean
public Queue fanoutQueue1() {
return new Queue("fanout.queue1");
}
//绑定队列1到交换机
@Bean
public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
//队列2
@Bean
public Queue fanoutQueue2() {
return new Queue("fanout.queue2");
}
//绑定队列2到交换机
@Bean
public Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
@Test
void testSendFanoutExchange(){
//交换机名称
String exchangeName = "test.fanout";
//消息
String msg = "test for send FanoutExchange";
//发送消息
rabbitTemplate.convertAndSend(exchangeName,"",msg);
}
Direct Exchange 会将接收到的消息根据规则路由到指定的Queue,因此称为路由模式(routes)。
消息发送者中指明routerKey
@Test
void testSendDirectExchange(){
//交换机名称
String exchangeName = "test.direct";
//消息
String msg = "test for router to black queue";
//发送消息
rabbitTemplate.convertAndSend(exchangeName,"black",msg);
}
@Test
void testSendDirectExchange2(){
//交换机名称
String exchangeName = "test.direct";
//消息
String msg = "test for router to white queue";
//发送消息
rabbitTemplate.convertAndSend(exchangeName,"white",msg);
}
定义一个监听组件使用注解形式指定队列名称,交换机名称和类型(默认direct),以及路由通道
@Component
public class SpringRabbitListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "test.direct",type = ExchangeTypes.DIRECT),
key = {"black","white"}
))
public void listenDirectQueue1(String msg){
System.out.println("direct queue1:"+msg);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "test.direct",type = ExchangeTypes.DIRECT),
key = {"black","green"}
))
public void listenDirectQueue2(String msg){
System.out.println("direct queue2:"+msg);
}
}
TopicExchange与DirectExchange类似,区别在于routingKey必须是多个单词的列表,并且以.
分割。
如:person.zhangsan
Queue与Exchange指定BindingKey时可以使用通配符:
#
:代指0个或多个单词*
:代指一个单词 @Test
public void testTopicExchange(){
//交换机名称
String exchangeName = "test.topic";
//消息
String msg = "test for topic in china shanghai";
//发送消息
rabbitTemplate.convertAndSend(exchangeName,"china.shanghai",msg);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name ="test.topic",type = ExchangeTypes.TOPIC),
key = "china.shanghai"
))
public void listenTopicQueue1(String msg){
System.out.println("topic:china.shanghai:"+msg);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name ="test.topic",type = ExchangeTypes.TOPIC),
key = "american.newyork"
))
public void listenTopicQueue2(String msg){
System.out.println("topic:american.newyork:"+msg);
}