命令则可以关联下一个行为动作
,这样就可以理解为基于接收的消息相当于得到了一个行为动作,使用这些行为动作就可以组织成一个业务逻辑,进行进一步的操作。同步消息
就是生产者发送完消息,等待消费者处理,消费者处理完将结果告知生产者,然后生产者继续向下执行业务。这种模式过于卡生产者的业务执行连续性,在现在的企业级开发中,上述这种业务场景通常不会采用消息的形式进行处理。异步消息
就是生产者发送完消息,无需等待消费者处理完毕,生产者继续向下执行其他动作。
保存消息的容器
中,也是使用队列模型来保存。但是消息可以被多个消费者消费,生产者和消费者完全独立,相互不需要感知对方的存在。JMS的问世为消息中间件提供了很强大的规范性支撑,但是使用的过程中就开始被人发现诟病:比如JMS设置的极其复杂的多种类消息处理机制。
AMQP(advanced message queuing protocol):一种协议(高级消息队列协议,也是消息代理规范),规范了网络交换的数据格式,兼容JMS操作。
AMQP在JMS的消息模型基础上又进行了进一步的扩展,除了点对点和发布订阅的模型,开发了几种全新的消息模型,适应各种各样的消息发送。
目前实现了AMQP协议的消息中间件技术也很多,而且都是较为流行的技术,例如:RabbitMQ、StormMQ、RocketMQ
Kafka,一种高吞吐量的分布式发布订阅消息系统,提供实时消息功能。Kafka技术并不是作为消息中间件为主要功能的产品,但是其拥有发布订阅的工作模式,也可以充当消息中间件来使用,而且目前企业级开发中其身影也不少见。
springboot整合各种各样的消息中间件:各种消息中间件必须先安装再使用。
http://127.0.0.1:8161/
web管理服务默认端口8161,访问后可以打开ActiveMQ的管理界面,首先输入访问用户名和密码,初始化用户名和密码相同,均为:admin,成功登录后进入管理后台界面
ActiveMQ运行后占用的端口有:61616、5672、61613、1883、61614,如果启动失败,请先管理对应端口即可。
整合步骤;加坐标,做配置,调接口
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-activemqartifactId>
dependency>
spring:
activemq:
broker-url: tcp://localhost:61616
MessageService
public interface MessageService {
// 自定义方法
// 把消息放入消息队列中
void sendMessage(String id);
// 把消息从消息队列中取出来
String doMessage();
}
@Service
public class MessageServiceActivemqImpl implements MessageService {
@Autowired
private JmsMessagingTemplate messagingTemplate;
@Override
public void sendMessage(String id) {
System.out.println("待发送短信的订单已纳入处理队列,id:"+id);
//第一个参数是消息队列名
messagingTemplate.convertAndSend("order.queue.id",id);
}
@Override
public String doMessage() {
//第一个参数是消息队列名
String id = messagingTemplate.receiveAndConvert("order.queue.id",String.class);
System.out.println("已完成短信发送业务,id:"+id);
return id;
}
}
MessageController
@RestController
@RequestMapping("/msgs")
public class MessageController {
@Autowired
private MessageService messageService;
@GetMapping
public String doMessage(){
String id = messageService.doMessage();
return id;
}
}
orderService
public interface OrderService {
void order(String id);
}
orderServiceImpl
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private MessageService messageService;
// 处理消息
@Override
public void order(String id) {
//一系列操作,包含各种服务调用,处理各种业务
System.out.println("订单处理开始");
//短信消息处理
messageService.sendMessage(id);
System.out.println("订单处理结束");
System.out.println();
}
}
OrderController
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("{id}")
public void order(@PathVariable String id){
orderService.order(id);
}
}
@Component
public class MessageListener {
@JmsListener(destination = "order.queue.id") //监听ActiveMQ中指定名称的消息队列
@SendTo("order.other.queue.id")//将下面方法的返回值传递到另一个消息队列中,参数就是消息队列的名称。
public String receive(String id){
System.out.println("已完成短信发送业务,id:"+id);
return "new:"+id;
}
}
使用注解@JmsListener定义当前方法监听ActiveMQ中指定名称的消息队列。
如果当前消息队列处理完还需要继续向下传递当前消息到另一个队列中使用注解@SendTo即可,这样即可构造连续执行的顺序消息队列。
spring:
activemq:
broker-url: tcp://localhost:61616
jms:
pub-sub-domain: true
pub-sub-domain默认值为false,即点对点模型,修改为true后就是发布订阅模型。
windows版安装包下载地址:https://rabbitmq.com/install-windows.html
启动服务器的命令:
rabbitmq-service.bat start # 启动服务
rabbitmq-service.bat stop # 停止服务
rabbitmqctl status # 查看服务状态
rabbitmq-plugins.bat list # 查看当前所有插件的运行状态
rabbitmq-plugins.bat enable rabbitmq_management # 启动rabbitmq_management插件
启动插件后可以在插件运行状态中查看是否运行,运行后通过浏览器即可打开服务后台管理界面
http://localhost:15672
rabbitmq的web管理服务默认端口15672,访问后可以打开RabbitMQ的管理界面,如下:
首先输入访问用户名和密码,初始化用户名和密码相同,均为:guest,成功登录后进入管理后台界面:
RabbitMQ满足AMQP协议,因此不同的消息模型对应的制作不同,先使用最简单的direct模型开发。
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
dependencies>
spring:
rabbitmq:
host: localhost
port: 5672 # 配置端口号(默认为5672)
由于RabbitMQ不同模型要使用不同的交换机,因此需要先初始化RabbitMQ相关的对象,例如队列,交换机等
package com.knife.service.impl.rabbitmq.direct.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //作为配置类
public class RabbitConfigDirect {
// Queue的包选择 : import org.springframework.amqp.core.Queue;
// 定义一个存储消息的消息队列对象
@Bean
public Queue directQueue(){
// return new Queue("direct_queue",true,false,false);
// 第一个参数 指定消息队列对象的名称
// 第二个参数 durable:是否持久化,默认为true
// 第三个参数 exclusive:是否当前连接专用,默认为false,连接关闭后队列即被删除
// 第四个参数 autoDelete:是否自动删除,当生产者活消费者不适应此队列,自动删除,默认为false
return new Queue("direct_queue");
}
// 定义一个交换机对象
@Bean
public DirectExchange directExchange(){
// 参数是自定义的交换机名称
return new DirectExchange("directExchange");
}
// 把队列和交换机绑定在一起,绑定关系
@Bean
public Binding bindingDirect(){
// direct为绑定的名称
return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct");
}
// 可以定义多个消息队列对象,
@Bean
public Queue directQueue2(){
return new Queue("direct_queue2");
}
// 多个消息队列对象可以用同一台交换机绑定
@Bean
public Binding bindingDirect2(){
return BindingBuilder.bind(directQueue2()).to(directExchange()).with("direct2");
}
}
队列Queue与直连交换机DirectExchange创建后,还需要绑定他们之间的关系Binding,这样就可以通过交换机操作对应队列。
@Service
public class MessageServiceRabbitmqDirectImpl implements MessageService {
@Autowired
private AmqpTemplate amqpTemplate;
@Override
public void sendMessage(String id) {
System.out.println("待发送短信的订单已纳入处理队列(rabbitmq direct),id:"+id);
// 第一参数:要求传入一个交换机的名称
// 第二个参数:是routingKey的名称,即绑定消息队列对象与交换机关系的名称
// 第三个参数:要传的消息
amqpTemplate.convertAndSend("directExchange","direct",id);
// 采用默认的交换机与默认的绑定关系
// amqpTemplate.convertAndSend(id);
}
// 一般消息都是自动执行的,所以不需要手动获取消息,直接使用listener
@Override
public String doMessage() {
return null;
}
}
amqp协议中的操作API接口名称看上去和jms规范的操作API接口很相似,但是传递参数差异很大。
@Component
public class MessageListener {
// 定义指定监听的消息队列的名称
@RabbitListener(queues = "direct_queue")
public void receive(String id){
System.out.println("已完成短信发送业务(rabbitmq direct),id:"+id);
}
}
@Component
public class MessageListener2 {
// 可以配置多个监听器
// 如果有两组监听器,并且都监听同一个消息队列,它们会轮询处理
@RabbitListener(queues = "direct_queue")
public void receive(String id){
System.out.println("已完成短信发送业务(rabbitmq direct two),id:"+id);
}
}
使用注解@RabbitListener定义当前方法监听RabbitMQ中指定名称的消息队列。
@Configuration //作为配置类
public class RabbitConfigTopic {
// Queue的包选择 : import org.springframework.amqp.core.Queue;
// 定义一个存储消息的消息队列对象
@Bean
public Queue topicQueue(){
// return new Queue("topic_queue",true,false,false);
// 第一个参数 指定消息队列对象的名称
// 第二个参数 durable:是否持久化,默认为true
// 第三个参数 exclusive:是否当前连接专用,默认为false,连接关闭后队列即被删除
// 第四个参数 autoDelete:是否自动删除,当生产者活消费者不适应此队列,自动删除,默认为false
return new Queue("topic_queue");
}
// 定义一个交换机对象
@Bean
public TopicExchange topicExchange(){
// 参数是自定义的交换机名称
return new TopicExchange("topicExchange");
}
// 把队列和交换机绑定在一起,绑定关系
@Bean
public Binding bindingTopic(){
//主题模式支持routingKey匹配模式,
// *表示匹配一个单词,#表示匹配任意内容,这样就可以通过主题交换机将消息分发到不同的队列中
return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("topic.*.id");
}
// 可以定义多个消息队列对象,
@Bean
public Queue topicQueue2(){
return new Queue("topic_queue2");
}
// 多个消息队列对象可以用同一台交换机绑定
@Bean
public Binding bindingTopic2(){
// 主题模式支持routingKey匹配模式,*表示匹配一个单词,
// #表示匹配任意内容,这样就可以通过主题交换机将消息分发到不同的队列中
return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("topic.orders.*");
}
}
主题模式支持routingKey匹配模式,*表示匹配一个单词,#表示匹配任意内容,这样就可以通过主题交换机将消息分发到不同的队列中,详细内容请参看RabbitMQ系列课程。
匹配键 | topic.*.* | topic.# |
---|---|---|
topic.order.id | true | true |
order.topic.id | false | false |
topic.sm.order.id | false | true |
topic.sm.id | false | true |
topic.id.order | true | true |
topic.id | false | true |
topic.order | false | true |
步骤④:使用AmqpTemplate操作RabbitMQ
@Service
public class MessageServiceRabbitmqTopicImpl implements MessageService {
@Autowired
private AmqpTemplate amqpTemplate;
@Override
public void sendMessage(String id) {
System.out.println("待发送短信的订单已纳入处理队列(rabbitmq topic),id:"+id);
// 第一参数:要求传入一个交换机的名称
// 第二个参数:是routingKey的名称,即绑定消息队列对象与交换机关系的名称
// 第三个参数:要传的消息
amqpTemplate.convertAndSend("topicExchange","topic.orders.id",id);
// 采用默认的交换机与默认的绑定关系
// amqpTemplate.convertAndSend(id);
}
// 一般消息都是自动执行的,所以不需要手动获取消息,直接使用listener
@Override
public String doMessage() {
return null;
}
}
发送消息后,根据当前提供的routingKey与绑定交换机时设定的routingKey进行匹配,规则匹配成功消息才会进入到对应的队列中。
步骤⑤:使用消息监听器在服务器启动后,监听指定队列
@Component
public class MessageListener {
// 定义指定监听的消息队列的名称
@RabbitListener(queues = "topic_queue")
public void receive(String id){
System.out.println("已完成短信发送业务(rabbitmq topic),id:"+id);
}
// 可以配置多个监听器
// 如果有两组监听器,并且都监听同一个消息队列,它们会轮询处理
@RabbitListener(queues = "topic_queue2")
public void receive2(String id){
System.out.println("已完成短信发送业务(rabbitmq topic two),id:"+id);
}
}
使用注解@RabbitListener定义当前方法监听RabbitMQ中指定名称的消息队列。
关于NAMESRV_ADDR对于初学者来说建议配置此项,也可以通过命令设置对应值,操作略显繁琐,建议配置。系统学习RocketMQ知识后即可灵活控制该项。
mqnamesrv # 启动命名服务器
mqbroker # 启动broker
应该选择对应的.cmd启动
运行bin目录下的mqnamesrv命令即可启动命名服务器,默认对外服务端口9876。
运行bin目录下的mqbroker命令即可启动broker服务器,如果环境变量中没有设置NAMESRV_ADDR则需要在运行mqbroker指令前通过set指令设置NAMESRV_ADDR的值,并且每次开启均需要设置此项。
tools org.apache.rocketmq.example.quickstart.Producer # 生产消息
tools org.apache.rocketmq.example.quickstart.Consumer # 消费消息
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.rocketmqgroupId>
<artifactId>rocketmq-spring-boot-starterartifactId>
<version>2.2.1version>
dependency>
dependencies>
rocketmq:
name-server: localhost:9876
producer:
group: group_rocketmq # 设置默认的生产消费者所属组(自定义的名称)
设置默认的生产者消费者所属组group。
@Service
public class MessageServiceRocketmqImpl implements MessageService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Override
public void sendMessage(String id) {
System.out.println("待发送短信的订单已纳入处理队列(rocketmq),id:"+id);
// rocketMQTemplate.convertAndSend("order_id",id); //同步消息
// 异步生产消息以后的回调方法
SendCallback callback = new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
// 消息发送成功要做什么
System.out.println("消息发送成功");
}
@Override
public void onException(Throwable e) {
// 消息发送失败要做什么
System.out.println("消息发送失败!!!!!");
}
};
rocketMQTemplate.asyncSend("order_id",id,callback);//发送异步消息
}
}
使用asyncSend方法发送异步消息。
@Component
// topic参数:你消费的信息来源与哪里(消息的生产者)
//consumerGroup参数:指定你是哪一个消费者组对应的信息(要和生产组名一致(yml文件配置的))
@RocketMQMessageListener(topic = "order_id",consumerGroup = "group_rocketmq")
public class MessageListener implements RocketMQListener<String> {//这里的泛型是指定消息的种类
// 后面接收到的消息都会传到参数id里面去
@Override
public void onMessage(String id) {
// 这里面对消息进行处理
System.out.println("已完成短信发送业务(rocketmq),id:"+id);
}
}
zookeeper-server-start.bat ..\..\config\zookeeper.properties # 启动zookeeper
kafka-server-start.bat ..\..\config\server.properties # 启动kafka
# 创建topic
kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic itheima
# 查询topic
kafka-topics.bat --zookeeper 127.0.0.1:2181 --list
# 删除topic
kafka-topics.bat --delete --zookeeper localhost:2181 --topic itheima
kafka-console-producer.bat --broker-list localhost:9092 --topic itheima # 测试生产消息
kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic itheima --from-beginning # 测试消息消费
<dependency>
<groupId>org.springframework.kafkagroupId>
<artifactId>spring-kafkaartifactId>
dependency>
spring:
kafka:
bootstrap-servers: localhost:9092
consumer:
group-id: order
设置默认的生产者消费者所属组id。
@Service
public class MessageServiceKafkaImpl implements MessageService {
@Autowired
private KafkaTemplate<String,String> kafkaTemplate;
@Override
public void sendMessage(String id) {
System.out.println("待发送短信的订单已纳入处理队列(kafka),id:"+id);
kafkaTemplate.send("itheima2022",id);
}
}
使用send方法发送消息,需要传入topic名称。
@Component
public class MessageListener {
@KafkaListener(topics = "itheima2022")
public void onMessage(ConsumerRecord<String,String> record){
System.out.println("已完成短信发送业务(kafka),id:"+record.value());
}
}
使用注解@KafkaListener定义当前方法监听Kafka中指定topic的消息,接收到的消息封装在对象ConsumerRecord中,获取数据从ConsumerRecord对象中获取即可。