同步
优点:时效性较强,可以立即得到结果
缺点:
耦合度高-服务之间都是直接调用
性能和吞吐能力下降-响应时长为几个服务处理的后的总和
有额外的资源消耗-上游服务需要等下游服务的结果才能往后执行
有级联失败问题-下游服务如果出错,会造成上游服务也失败
总结
调用目标服务时,需要等待目标服务给出响应,才能继续执行下一步操作
使用场景
微服务:订单服务调用用户服务,获取用户信息
异步
优点:
耦合度低-只跟Broker耦合
吞吐量提升-整个调用响应时长变短,提升吞吐量
故障隔离-下游服务故障,不会影响上游服务
流量削峰-高并发的请求数,由Broker来抗,不会直接打到具体的服务,后面可以根据服务的消费能力来慢慢消费
缺点:
依赖于Broker,对其要求高
引入了其他组件,架构变得复杂
总结
主业务执行后,不需要等后续的其他服务执行的结果,就可以给出响应。相当于开了一个线程去执行其他业务
使用场景
医院APP挂号业务:
1、用户在医院APP挂号,插入挂号记录到数据库
2、发送挂号成功的信息给用户
3、记录用户挂号的日志记录到日志文件中
以上三个步骤,在第一步成功后,2、3两步的执行先后顺序是没有要求的
RabbitMQ
RabbitMQ由Rabbit公司基于Erlang语言开发的,支持多协议的、性能优秀的消息队列产品。一般消息队列,俗称消息中间件
安装
第一步:
获取 rabbitmq 的镜像
第二步:
加载资料的 mq.tar
第三步:
docker load -i mq.tar
第四步:
启动容器
5672:MQ的通信端口
15672:管控台的端口
docker run \
-e RABBITMQ_DEFAULT_USER=itheima \
-e RABBITMQ_DEFAULT_PASS=itheima \
-v mq-plugins:/plugins \
--name mq \
--hostname mq \
--restart=always \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
结构:
角色:
1、publisher:生产者-用于生产消息的
2、consumer:消费者-用于消费消息的
3、exchange:交换机-按照指定的规则把消息路由到具体的队列
4、queue:队列-存储消息
5、virtualHost:虚拟主机-对不同用户的 exchange、queue、消息 的进行隔离
测试RabbitMQ
父工程依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath/>
</parent><properties>
<java.version>11</java.version>
</properties><dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
模仿生产者发送消息
@Test
public void testSendMessage() throws IOException, TimeoutException {
//1.创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.138.100");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("itheima");
factory.setPassword("itheima");
Connection connection = factory.newConnection();
//2.创建通道
Channel channel = connection.createChannel();
//3.声明交换机与队列
/**
* @param queue 队列名称
* @param durable 是否持久化
* @param exclusive 是否排它
* @param autoDelete 是否自动删除
* @param arguments 队列其他参数
* @return a declaration-confirm method to indicate the queue was successfully declared
* @throws java.io.IOException if an error is encountered
*/
channel.queueDeclare("simple.queue", true, false, false, null);
//4.发送消息
/**
* exchange 交换机,默认的交换机是""
* routingKey 路由Key
* props 消费其他参数
* body 消息内容
*/
channel.basicPublish("", "simple.queue", null, "Hello,RabbitMQ!! Two".getBytes());
//5.释放资源
channel.close();
connection.close();
}
模仿消费者消费消息
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.138.100");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("itheima");
factory.setPassword("itheima");
Connection connection = factory.newConnection();
//2.创建通道
Channel channel = connection.createChannel();
//3.声明交换机与队列
/**
* 如果队列已经存在,则不会重复创建
*/
channel.queueDeclare("simple.queue", true, false, false, null);
//4.消费消息
channel.basicConsume("simple.queue", new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息标签
* Envelope envelope 封装了消息的对象(没有内容)
* AMQP.BasicProperties properties 其他数据
* byte[] body 消息内容
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(consumerTag);
System.out.println(envelope.getDeliveryTag());
System.out.println(envelope.getExchange());
System.out.println(envelope.getRoutingKey());
System.out.println("消息内容:" + new String(body));
}
});
//5.卡住
System.in.read();
}
Spring AMQP的五种模式:
Simple-简单模式
模型
一个生产者,一个消费者
单发: null
配置文件:
spring:
rabbitmq:
host: 192.168.138.100 # 主机名
port: 5672 # 端口
virtual-host: / # 虚拟主机
username: itheima # 用户名
password: itheima # 密码
生产者:
@Autowired
private RabbitTemplate rabbitTemplate;/**
*
*@throws
*/
@Test
public void simpleTest() throws Exception{
//这个API,必须先提前创建好队列,才能够发送
//转换并发送
rabbitTemplate.convertAndSend("","simple.queue","SpringAMQP");
}
消费者:
@Component
public class MessageListener {
//消费完后会自动签收
@RabbitListener(queues = "simple.queue")
public void simpleConsume(String message){
System.out.println("收到的消息" + message);
}
}
#这段代码是一个bean所以要启动引导类才会生效
WorkQueue-工作队列模式
模型
一个生产者,多个消息者
单发:多个消费者之间是竞争关系
为了测试WorkQueue我们先要RabbitMQ页面重新创建一个名为mysimple.queue的消息队列
生产者
/**
* @throws Exception
*/
@Test
public void workQueueTest() throws Exception {
for (int i = 0; i < 100; i++) {
//转换并发送
//这个API,必须先提前创建好队列,才能够发送
//amqpTemplate和rabbitTemplate是一样的
amqpTemplate.convertAndSend("", "mysimple.queue", "WorkQueue Message !!" + i);
}}
消费者
#根据不同的消费能力,竞争消费的数量
//消费完后会自动签收
@RabbitListener(queues = "mysimple.queue")
public void workQueue1(String message){
System.out.println("消费者1:" + message);
}//消费完后会自动签收
@RabbitListener(queues = "mysimple.queue")
public void workQueue2(String message){
System.out.println("消费者2:" + message);
}
配置文件
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
这里有一个机制,就是RabbitMQ默认的机制是,根据就是根据消费者的数量平均分,而加了这个配置文件的话两个消费者才根据消费能确定竞争。
Fanout-配置队列交换机及绑定关系
群发
一个生产者,多个消息者
通过交换机,把消息路由到队列,实现群发 #无条件群发
创建队列(Queue)、交换机(FanoutExchange)、指定队列与交换机的绑定关系(Binding)
值得注意的一点:配置类里面的Queue、FanoutExchange、Binding以上的类库都是 amqp.core 包下并且都是写在消费者应用
方式一: 创建队列和交换机使用的是new方式
@Configuration
public class RabbitConfig {
//1.声明队列
@Bean
public Queue fanoutQueue1(){
//创建队列,默认是持久化
return new Queue("fanout.queue1");
}@Bean
public Queue fanoutQueue2(){
//创建队列,默认是持久化
return new Queue("fanout.queue2");
}
//2.声明交换机
@Bean
public Exchange itcastFanout(){
//创建Fanout交换机,默认是持久化
return new FanoutExchange("itcast.fanout");
}
//3.绑定队列到交换机
@Bean
public Binding queue1Exchange(Queue fanoutQueue1,FanoutExchange itcastFanout){
return BindingBuilder.bind(fanoutQueue1).to(itcastFanout);
}@Bean
public Binding queue2Exchange(Queue fanoutQueue2,FanoutExchange itcastFanout){
return BindingBuilder.bind(fanoutQueue2).to(itcastFanout);
}
}
首先,通过 fanoutQueue1() 和 fanoutQueue2() 两个方法声明了两个队列,并返回相应的 Queue 对象。
然后,通过 itcastFanout() 方法声明了一个 Fanout 类型的交换机,并返回一个 Exchange 对象。
最后,通过 queue1Exchange() 和 queue2Exchange() 方法分别将队列 fanoutQueue1 和 fanoutQueue2 绑定到交换机 itcastFanout 上,并返回一个 Binding 对象。
这样就完成了队列、交换机和绑定的配置。在 RabbitMQ 中,生产者发送消息到交换机,交换机根据绑定关系将消息分发到相应的队列中。
方法二:使用建造者的方式,创建队列和交换机。
@Configuration
public class RabbitFanoutConfig {@Bean
public Queue itheimaFanoutQueue1(){
return QueueBuilder.durable("itheima.fanout.queue1").build();
}@Bean
public Queue itheimaFanoutQueue2(){
return QueueBuilder.durable("itheima.fanout.queue2").build();
}@Bean
public FanoutExchange itheimaFanout(){
return ExchangeBuilder.fanoutExchange("itheima.fanout").durable(true).build();
}@Bean
public Binding itheimaFanoutQueue1ItheimaFanout(Queue itheimaFanoutQueue1, FanoutExchange itheimaFanout){
return BindingBuilder.bind(itheimaFanoutQueue1).to(itheimaFanout);
}@Bean
public Binding itheimaFanoutQueue2ItheimaFanout(Queue itheimaFanoutQueue2, FanoutExchange itheimaFanout){
return BindingBuilder.bind(itheimaFanoutQueue2).to(itheimaFanout);
}
}
队列的创建方式:使用了 QueueBuilder.durable() 方法来创建队列,并将 durable 设置为 true,表示队列是持久化的。
交换机的创建方式:使用了 ExchangeBuilder.fanoutExchange().durable(true).build() 方法来创建 Fanout 类型的交换机,并设置为持久化的。
配置Direct交换机
模型
群发
一个生产者,多个消息者
通过交换机,通过路由key的精确名称把消息路由到队列,实现群发
路由key【Routingkey】→一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
消费者
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue(name = "direct.queue2"),//队列
exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),//交换机
key = {"yellow","red"}
)
})
public void handleDirectQueue2(String message){
System.out.println("direct.queue2 : " + message);
}
}
-
@RabbitListener
: 该注解标识该方法是一个消息监听器,用于监听指定队列中的消息。 -
@QueueBinding
: 该注解定义了队列与交换机之间的绑定关系。
value: 指定队列的相关属性,@Queue(name = "direct.queue2")表示声明了一个名为"direct.queue2"的队列。
exchange: 指定交换机的相关属性,@Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT)表示声明了一个名为"itcast.direct"的直连类型交换机。
key: 绑定键,表示某个消息将被路由到指定的队列,key = {"yellow","red"}表示将绑定键为"yellow"和"red"的消息路由到该队列。
生产者
@Test
public void handleDirectTest() throws Exception{
//转换并发送
//这个API,必须先提前创建好队列,才能够发送
rabbitTemplate.convertAndSend("itcast.direct","blue" , "Hello,blue");
rabbitTemplate.convertAndSend("itcast.direct","yellow" , "Hello,yellow");
rabbitTemplate.convertAndSend("itcast.direct","red" , "Hello,red");
}
配置Topic交换机
群发
一个生产者,多个消息者
通过交换机,通过路由key的部分名称把消息路由到队列,实现群发
1.*:匹配单个字符
2.#:匹配一个或多个字符
消费者
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue(name = "topic.queue1",durable = "true"),//队列
exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),//交换机
key = {"china.#"}
)
})
public void handleTopicQueue1(String message){
System.out.println("topic.queue1 : " + message);
}@RabbitListener(bindings = {
@QueueBinding(
value = @Queue(name = "topic.queue2",durable = "true"),//队列
exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),//交换机
key = {"#.news"}
)
})
public void handleTopicQueue2(String message){
System.out.println("topic.queue2 : " + message);
}
其中
value = @Queue(name = "topic.queue1",durable = "true"),//队列
这段代码中的注解配置对队列的名称和持久化属性进行了设置,具体解析如下:
value = @Queue(name = "topic.queue1", durable = "true")
name = "topic.queue1": 声明了一个名为 “topic.queue1” 的队列,名称为 “topic.queue1”。
durable = "true": 设置队列的持久化属性为 true,表示该队列在消息代理重启后仍然存在。
exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),//交换机
name = "itcast.topic": 声明了一个名为 “itcast.topic” 的交换机,名称为 “itcast.topic”。
type = ExchangeTypes.TOPIC: 设置交换机的类型为主题类型,表示该交换机将使用主题匹配的方式来路由消息。
key = {"china.#"}
key = {"china.#"}`: 设置绑定键为 “china.#”,这里使用了主题匹配的通配符 "#”。“china.#” 表示匹配以 “china.” 开头的所有路由键。
key = {"#.news"}
key = {"#.news"}: 设置绑定键为 “#.news”,这里使用了主题匹配的通配符 “#”。”#.news" 表示匹配以 “.news” 结尾的所有路由键,并且可以包含任意数量的字词作为前缀。
测试
@Test
public void topicTest() throws Exception{
//转换并发送
//这个API,必须先提前创建好队列,才能够发送
rabbitTemplate.convertAndSend("itcast.topic","china.big" , "Hello,china.big");
rabbitTemplate.convertAndSend("itcast.topic","china.big.news" , "Hello,china.big.news");
rabbitTemplate.convertAndSend("itcast.topic","usa.news" , "Hello,usa.news");
rabbitTemplate.convertAndSend("itcast.topic","china.gs.usa.news" , "Hello,china.gs.usa.news");
}
消息转换器
消息转换器,首先消息有神多种形式比如:加密、序列化、josh字符串、或者普通字符串。
一般很多时候我们以传输Josh字符串居多。
@Test
public void testSendMap() throws InterruptedException {
// 准备消息
Map<String,Object> msg = new HashMap<>();
msg.put("name", "Jack");
msg.put("age", 21);
// 发送消息
rabbitTemplate.convertAndSend("simple.queue","", msg);
}
这样未经过序列化和转Josh处理的代码,使用的默认的序列化器,就是jdk序列化器所以说最终的结果一定加密的字符串,它的占用的内存大。
JSON转换器
第一步:
导入依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.5</version>
</dependency>
第二步:
配置bean
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}