1. 消息中间件产生的背景
在客户端与服务器进行通讯时.客户端调用后,必须等待服务对象完成处理返回结果才能继续执行。
客户与服务器对象的生命周期紧密耦合,客户进程和服务对象进程都都必须正常运行;如果由于服务对象崩溃或者网络故障导致用户的请求不可达,客户会受到异常。
2. 消息中间件概述
① 什么是消息中间件
面向消息的中间件(MessageOrlented MiddlewareMOM)较好的解决了以上问题。
发送者将消息发送给消息服务器,消息服务器将消感存放在若千队列中,在合适的时候再将消息转发给接收者。
这种模式下,发送和接收是异步的,发送者无需等待; 二者的生命周期未必相同: 发送消息的时候接收者不一定运行,接收的时候,发送者也不一定运行,一对多通信: 对于一个消息可以有多个接收者。
② 什么是JMS
JMS是java的消息服务,JMS的客户端之间可以通过JMS服务进行异步的消息传输。
JMS通讯模式:发布订阅和消息队列
发布订阅:一对多(消息模型),主题topic,如果消费者集群,每个消费者都会消费。
消息队列:点对点(消息模型),队列quque,如果消费者集群,均摊消息
③ 消息中间件的好处
流量削峰(秒杀系统),异步解耦
④ 目前常用mq对比
ActiveMQ | RabbitMQ | RocketMQ | ZeroMQ | |
关注度 | 高 | 高 | 中 | 中 |
成熟度 | 成熟 | 成熟 | 比较成熟 | 不成熟 |
所属社区/公司 | Apache | Mozilla Public License | Alibaba | |
社区活跃度 | 高 | 高 | 中 | 低 |
文档 | 多 | 多 | 中 | 中 |
特点 | 功能齐全,被大量开源项目使用 | 由于Erlang 语言的并发能力,性能很好 | 各个环节分布式扩展设计,主从 HA;支持上万个队列;多种消费模式;性能很好 | 低延时,高性能,最高 43万条消息每秒 |
授权方式 | 开源 | 开源 | 开源 | 开源 |
开发语言 | Java | Erlang | Java | C |
支持的协议 | OpenWire、 STOMP、 REST、XMPP、 AMQP | AMQP | 自己定义的一 套(社区提供 JMS--不成熟) | TCP、UDP |
客户端支持语言 | Java、C、 C++、 Python、 PHP、 Perl、.net 等 | Java、C、 C++、 Python、 PHP、Perl 等 | Java C++(不成熟) | python、 java、 php、.net 等 |
持久化 | 内存、文件、数据库 | 内存、文件 | 磁盘文件 | 在消息发送端保存 |
事务 | 支持 | 不支持 | 支持 | 不支持 |
集群 | 支持 | 支持 | 支持 | 不支持 |
负载均衡 | 支持 | 支持 | 支持 | 不支持 |
管理界面 | 一般 | 好 | 无社区有 web console 实现 | 无 |
部署方式 | 独立、嵌入 | 独立 | 独立 | 独立 |
评价 | 优点: 成熟的产品,已经在很多公司得到应用(非大规模场景)。有较多的文档。各种协议支持较好,有多重语言的成熟的客户端; 缺点: 根据其他用户反馈,会出莫名其妙的问题,切会丢失消息。 其重心放到activemq6.0 产品—apollo 上去了,目前社区不活跃,且对 5.x 维护较少; Activemq 不适合用于上千个队列的应用场景 | 优点: 由于erlang语言的特性,mq 性能较好;管理界面较丰富,在互联网公司也有较大规模的应用;支持amqp系诶,有多中语言且支持 amqp 的客户端可用 缺点: erlang语言难度较 大。集群不支持动态扩展。 | 优点: 模型简单,接口易用(JMS 的接口很多场合并不太实用)。在阿里大规模应用。目前支付宝中的余额宝等新兴产 品均使用rocketmq。集群规模大概在50 台左右,单日处理消息上百亿;性能非常好,可以大量堆 积消息在broker 中;支持多种消费,包括集群消费、广播消费等。开发度较活跃,版本更新很快。 缺点: 没有在 mq 核心中去实现JMS 等接口 |
3. SpringBoot整合ActiveMQ
① maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>activemqdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ActiveMqDemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
② application.yml配置
server:
port: 8080
spring:
activemq:
broker-url: tcp://127.0.0.1:61616
user: admin
password: admin
queue: springboot-activemq-queue
③ 编写生产者代码
ActiveMqProducer.java
package com.example.producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Component;
import javax.jms.Destination;
@Component
public class ActiveMqProducer {
// 也可以注入JmsTemplate,JmsMessagingTemplate对JmsTemplate进行了封装
// JmsMessagingTemplate底层默认集成了持久化,用JmsTemplate的话需要写如下代码:
// jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
// 发送消息,destination是发送到的队列,message是待发送的消息
public void sendMessage(Destination destination, final String message) {
jmsMessagingTemplate.convertAndSend(destination, message);
}
}
ActiveMqController.java
package com.example.controller;
import com.example.producer.ActiveMqProducer;
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.jms.Destination;
import java.util.HashMap;
import java.util.Map;
@RestController
public class ActiveMqController {
@Autowired
private ActiveMqProducer activeMqProducer;
@Value("${queue}")
private String queue;
/**
* 生产者发送消息
* @return
*/
@GetMapping("/sendMessage")
public String sendMessage(){
Map<String,String> map = new HashMap<>();
map.put("id","1");
map.put("name","zhangsan");
map.put("phone","123456");
Destination destination = new ActiveMQQueue("queue");
activeMqProducer.sendMessage(destination,map.toString());
return "消息发送成功";
}
}
此时启动ActiveMQ服务端,可以看到多了一个队列,并且为未消费状态。
此时,重启服务端,会发现之前的队列消息还在,即默认做了持久化。
④ 编写消费者代码
ActiveMqConsumer.java
package com.example.consumer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class ActiveMqConsumer {
@Value("${queue}")
private String queue;
@JmsListener(destination = "queue")
public void receiveQueue(String message) {
System.out.println("消费端监听到消息:" + message);
}
}
启动消费端(这里我为了测试分了两个项目,实际开发中肯定在一个项目中),会发现消息已经被消费成功。
4. Topic模式消息的接收
由于ActiveMQ默认只支持Queue模式,所以要想订阅Topic模式需要手动开启发布订阅域(PubSubDomain)。
注意开启PubSubDomain后将无法接收Queue模式的消息,所以我们需要对Topic的接收者进行单独的配置.
Topic的消息订阅模式分为以下两种:非持久订阅和持久订阅
① 配置:以下是持久订阅,非持久订阅只需将factory.setSubscriptionDurable(true)去掉或者改为false即可
@Bean
public JmsListenerContainerFactory topicListenerFactory(ConnectionFactory connectionFactory){
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// pubSubDomain 表示开启发布订阅域
factory.setPubSubDomain(true);
// 开启持久订阅。即下线后重新上线依然能继续接收Topic消息
factory.setSubscriptionDurable(true);
// 持久订阅的Client端标识(多个端持久订阅需要保证唯一性,否则可能会出现问题)
factory.setClientId("test_client1");
factory.setConnectionFactory(connectionFactory);
return factory;
}
② 使用:(只需要在监听器加入一个属性containerFactory)
@JmsListener(destination = "linebus.publish.status.topic",containerFactory = "topicListenerFactory")
public void receiveLinebusPublishStatusTopic(String message){
try {
Map<String, Object> map = JsonUtil.fromJson(message,Map.class);
boolean status = (boolean) map.get("publishStatus");
if(status){
LOGGER.info("网站更新,业务暂停");
}else{
LOGGER.info("可以正常访问");
}
} catch (IOException e) {
e.printStackTrace();
}
}
挺简单的一个demo,做一个笔记,希望也对有需要的人提供到帮助!