目录
1、同步和异步的区别
同步通信:同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
同步通信具有高效的时效性。但遇到耦合度高、性能下降、资源浪费、级联失败等问题。
异步通信:异步方法通常会在另外一个线程中"真实"地执行。整个过程,不会阻碍调用者的工作。对于调用者来说,异步调用似乎是一瞬间就完成的。 如果异步调用需要返回结果,那么当这个异步调用真实完成时,则会通知调用者。
异步通信解决同步通信的吞吐量、耦合度低、服务之间的强依赖、流量削峰等问题。
2、消息规范
JMS:JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。通过Apache ActiveMQ、JBoss 社区所研发的 HornetQ、OpenJMS实现JMS消息服务。
AMQP:即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
MQTT协议:(消息队列遥测传输)(Message Queuing Telemetry Transport)是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。它工作在 TCP/IP协议族上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议,为此,它需要一个消息中间件。
Kafka:是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。
3、通过案例感知异步消息
案例场景:购物多件商品一个主要订单多个子订单,支付后商家提示确认订单信息是否正确,作为消费方确认信息正确。商家给消费方联系运输。
1. controller/OrderController
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("{id}")
public List<String> getOrder(@PathVariable String id){
return orderService.getOrder(id);
}
}
2. service/OrderServiceImpl实现类
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private MessageService messageService;
@Override
public List<String> getOrder(String id) {
System.out.println("--------1.业务开始处理订单--------");
// 消息通知
System.out.println("-------2. 生产者发送消息---------");
messageService.publisherSendMsgs(id);
System.out.println("---------订单业务已经处理完毕提交支付---------");
return null;
}
}
3. controller/MessageController
@RestController
@RequestMapping("/msgs")
public class MessageController {
@Autowired
private MessageService messageService;
@GetMapping
public String consumerGetMsgs(){
String id = messageService.consumerGetMsgs();
return id;
}
}
4.service/MessageServiceImpl实现类
@Service
public class MessageServiceImpl implements MessageService {
// 集合模拟就是消息队列
private List<String> listMsg = new ArrayList<>();
@Override
public void publisherSendMsgs(String id) {
System.out.println("------3.待处理的订单加入到消息队列中------" + id);
listMsg.add(id);
}
@Override
public String consumerGetMsgs() {
String id = listMsg.remove(0);
System.out.println("----4.消费者确认消息队列订单已收到------"+ id);
return id;
}
}
5. 测试业务执行效果
4、在linux环境下的RabbitMQ
MQ(Message Queue)消息队列,是基础数据结构中“先进先出”的一种数据结构。指把要传输的数据(消息)放在队列中,用队列机制来实现消息传递——生产者产生消息并把消息放入队列,然后由消费者去处理。消费者可以到指定队列拉取消息,或者订阅相应的队列,由MQ服务端给其推送消息。
下载rabbitmq-server在linux环境下docker容器中安装
# 下载rabbitmq-server
https://github.com/rabbitmq/rabbitmq-server/releases
# 上传mq.tar到指定路径
cd /tmp
# docker服务
systemctl start docker # 启动docker服务
systemctl stop docker # 停止docker服务
systemctl restart docker # 重启docker服务
# 导入rabbitmq消息队列镜像
docker load -i mq.tar
# 查看mq镜像
docker images
# 运行mq
docker run \
-e RABBITMQ_DEFAULT_USER=Husp \
-e RABBITMQ_DEFAULT_PASS=123456 \
--name mq \
--hostname mq1 \
-p 15672:15672 \ # 管理平台的端口
-p 5672:5672 \ # 通信端口
-d \ # 后台运行
rabbitmq:3-management
通过浏览器外部访问rabbitmq的管理端口:15672
5、rabbitmq的主要理解的概念
- Connections(连接):消息的发送方和接收方通过MQ进行通信连接。
- Channels(通道):消息发送方和接收建立连接后生成的通信区域。
- Exchanges(交换机):发送方将消息通过指定的路由规则和发送到指定的路由队列绑定起来。
- Queues(队列):将发送方和接收方的消息存储到一种数据结构中。
- Admin(管理用户信息)
- virtual hosts(虚拟主机):虚拟主机(vhost)提供逻辑分组和资源分离。每一个vhost本质上是一个mini版的RabbitMQ服务器,拥有自己的connection、exchange、queue、binding等,拥有自己的权限。
- Broker(RocketMQ的核心):提供了消息的接收,存储,拉取等功能。
6、rabbitmq的运行机制
7、RabbitMQ的消息模型
- SimpleQueues(简单消息队列)
- Work Queues(工作消息队列)
- Publish/Subscribe(发布/订阅消息):交换机类型:direct(路由), topic(主题), headers (标头)and fanout(广播)
8、消息队列发送和接收流程
发送消息
建立连接
建立channel通道
发送消息到队列
接收消息
// 4.订阅消息
channel.basicConsume(queueName, true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
// 5.处理消息
String message = new String(body);
System.out.println("接收到消息:【" + message + "】");
}
});
将消费方和队列通过channel绑定在一起,接收方获取队列中的消息。
重点理解:
在消息队列模型中,publisher设置连接参数,分别是:主机名host、通信端口号port、虚拟主机vhost、用户名username、密码password建立Connection。创建通道channel。利用通道channel声明队列。向队列中发送消息。consumer的消费行为handleDelivery利用声明的队列将消费者和队列绑定,消费者接收消息。
9、SpringBoot整合RabbitMQ并测试
引入pom文件的消息队列spring amqp依赖
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
发送方和接收方配置文件
spring:
rabbitmq:
host: 192.168.171.134 # 主机名
port: 5672 # 通信端口号
virtual-host: / # 虚拟主机
username: Husp # 用户名
password: 123456 # 密码
测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
//发送消息
@Test
public void testSimpleMessage1(){
String queueName = "simple.queue";
String msg = "zhangsan";
rabbitTemplate.convertAndSend(queueName, msg);
}
}
@Component
public class SpringRabbitListener {
//接收消息
@RabbitListener(queues = "simple.queue")
public void receiveSimpleMessage(String msg){
System.out.println("消费者接收到的消息:" + msg);
}
}
多个消费者绑定到一个队列,同一条消息只能被一个消费者处理,处理完成再处理下一条消息。
接收方配置文件
spring:
rabbitmq:
host: 192.168.171.134
port: 5672
username: Husp
password: 123456
virtual-host: /
listener:
simple:
prefetch: 1 # 每次取1条消息,取完后再进行下一条消息
绑定交换机和队列
发送消息队列
测试
//接收消息
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.orders1"),
exchange = @Exchange(name = "exchange.direct", type = ExchangeTypes.DIRECT),
key = {"zhangsan", "lisi"}))
public void receiveDirectQueueMessage1(String msg) throws InterruptedException {
System.out.println("消费者1接收到的消息:" + msg + LocalTime.now());
Thread.sleep(200);
}
//接收消息
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.orders2"),
exchange = @Exchange(name = "exchange.direct", type = ExchangeTypes.DIRECT),
key = {"xiaomeng"}))
public void receiveDirectQueueMessage2(String msg) throws InterruptedException {
System.err.println("消费者2接收到的消息:" + msg + LocalTime.now());
Thread.sleep(200);
}
//发送消息
@Test
public void testDirectMessage(){
//交换机名称
String exchangeName = "exchange.direct";
//消息
String msg = "zhangsan-20";
rabbitTemplate.convertAndSend(exchangeName, "xiaomeng", msg);
}
该交换机类型direct,向队列direct.orders和direct.orders2发送消息,将交换机的Routing key和队列绑定。其他类型交换机以同样的方式操作发送和接收消息,加深理解!