文章目录
什么是消息中间?
消息中间件,此处指的是面向消息的中间件。
消息中间件主要作为消息通讯使用,你可以简单理解为两个程序即时通讯的一种技术,是用于两个程序之间交互的一种方式。
消息队列中间件中的“消息队列
”是什么意思?
消息队列,是消息中间件的一种实现方式,就是一个用于存放消息的队列(容器)
消息队列中间件中的“中间件
”是什么意思?
百度百科介绍:中间件(Middleware
)是处于操作系统和应用程序之间的软件。
中间件又能细分成如下:
- 基于远程过程调用 (
Remote Procedure Call
,RPC
)的中间件 - 基于对象请求代理 (
Object Request Broker
,ORB
) 的中间件 - 面向消息的中间件或基于
MOM
的中间件 (本文就是这种)。
消息中间件的主要作用
消息(队列)中间件是分布式系统中重要的组件,主要解决应用耦合、异步消息、流量削锋等问题。实现高性能、高可用、可伸缩和最终一致性架构。
消息中间件的产生的背景
由于客户端在于服务端进行通讯的时候,客户端调用某些(API
)业务后,必须等待服务端处理完成后返回结果才能继续执行。这个过程中,代码是同步执行的。
JMS(Java Message Service)
百度百科介绍:JMS
即Java
消息服务(Java Message Service
)应用程序接口,是一个Java
平台中关于面向消息中间件(MOM
)的API
,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java
消息服务是一个与具体平台无关的API,绝大多数MOM
提供商都对JMS
提供支持。
你可以简单理解为JMS
是java
的消息服务,JMS
的客户端之间可以通过JMS
服务进行异步的消息传输。
应用场景
系统解耦、异步处理、流量削峰
举例:用户购物下单,下单需要生成订单并且减少商品的库存数量。
传统是同步方式处理,非常明显的缺点就是:商品订单的逻辑和商品库存的逻辑都是耦合在一起处理的。
使用消息中间件后:
商品订单系统仅处理自己的业务逻辑,然后通过消息中间件,异步的方式去处理商品库存系统的逻辑。
举例(短信注册新用户):新用户注册就送积分/礼包/限时VIP
使用消息中间件后:
还有流量削峰等等应用场景等场景就不画图了(毕竟都是瞎画,没正经学过,凑合看)
消息模型介绍
-
Point to Point
(P2P
) 点对点
P2P
有以下几个概念:
1.消息队列(Queue
)存放消息的队列(容器)
2.发送者(Sender
) 消息的生产者
3.接收者(Receiver
) 消息的消费者每个消息都会被发送到一个特定的队列,接收者 (
Receiver
) 从队列中获取(消费)消息,直到队列的消息被消费或者超时。P2P
特点:
1.每个消息只能被一个消费者消费(被消费的消息就不会保留在消息队列)
2.发送者和接收者,在时间上没有依赖性,即:发送者发送了消息之后,不管接收者有没有在线,都没有影响。(发送者:你接收者宕机了都不管我事,我只管发)
3.接收者成功接收消息后需要向消息队列应答(告诉消息队列,我消费了消息)
P2P
模式:保证每个消息都被成功消费(如果你是这么希望的,请使用它)
Publish
/Subscribe
(Pub
/Sub
) 发布/订阅
Pub
/Sub
的几个概念:
1.主题(Topic
)
2.发布者(Publisher
)
3.订阅者(Subscriber
)
客户端将消息发送到主题,多个发布者将消息发送到Topic
,系统将这些消息传递给多个订阅者。
Pub
/Sub
的特点:
1.每个消息可以有多个消费者
发布者和订阅者之间有时间上的依赖性。针对某个主题(Topic
)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息,而且为了消费消息,订阅者必须保持运行的状态。
为了缓和这样严格的时间相关性,JMS
允许订阅者创建一个可持久化的订阅。这样,即使订阅者没有被激活(运行),它也能接收到发布者的消息。
如果你希望发送的消息可以不被做任何处理、或者被一个消息者处理、或者可以被多个消费者处理的话,那么可以采用Pub
/Sub
模型
2.消息的消费
在JMS
中,消息的产生和消息是异步的。对于消费来说,JMS
的消息者可以通过两种方式来消费消息。
- 同步
订阅者或接收者调用receive
方法来接收消息,receive
方法在能够接收到消息之前(或超时之前)将一直阻塞 ,直到消息的到来。 - 异步
订阅者或接收者可以注册为一个消息监听器。当消息到达之后,系统自动调用监听器的onMessage
方法。
常见的消息队列中间件
RabbitMQ、Redis(Pub/Sub)、ZeroMQ、RocketMQ、ActiveMQ、Jafka/Kafka 等…
Java中使用ActiveMQ
我是以Docker for Windows
(Docker Desktop
)方式使用,非常的方便,不会Docker
的朋友,推荐你学习Docker
哦,对你学习开发的帮助将会很大。
ActiveMQ启动后,浏览器访问 localhost:8161/admin
(默认账户为admin密码admin)
页面和模块就不多进行介绍了,“面向百度学习”就能了解清楚,或者右键“翻译成(中文)”
P2P
模式 Java 代码:
Maven
依赖
<!--只是activemq的依赖,不是springboot整合的依赖,所以要标明版本号,不能省略-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.7.0</version>
</dependency>
消息生产者
public class Producter {
public static void main(String[] args) throws JMSException {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD, "tcp://127.0.0.1:61616");
Connection connection = connectionFactory.createConnection();
connection.start();
// false 表示不开启事务 、Session.CLIENT_ACKNOWLEDGE 表示手动应答模式 / AUTO_ACKNOWLEDGE 自动应答
Session session = connection.createSession(Boolean.FALSE, Session.CLIENT_ACKNOWLEDGE);
// 参数值darian-queue是Query的名字
Queue queue = session.createQueue("darian-queue");
// 消息生产者
MessageProducer producer = session.createProducer(queue);
// 设置不持久化
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
for (int i = 1; i <= 5; i++) {
sendMsg(session, producer, i);
System.out.println("发送消息第 "+i+" 条");
}
connection.close();
}
public static void sendMsg(Session session, MessageProducer producer, int i) throws JMSException {
TextMessage message = session.createTextMessage("Hello ActiveMQ!" + i);
producer.send(message);
}
}
消息消费者
public class Consumer {
public static void main(String[] args) throws JMSException {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD, "tcp://127.0.0.1:61616");
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(Boolean.FALSE, Session.CLIENT_ACKNOWLEDGE);
// 获取session注意参数值darian-queue是一个服务器的queue,须在在ActiveMq的console配置
Destination destination = session.createQueue("darian-queue");
// 消费者
MessageConsumer consumer = session.createConsumer(destination);
while (true) {
TextMessage message = (TextMessage) consumer.receive();
if (null != message) {
System.out.println("收到消息:" + message.getText());
// 手动应答
message.acknowledge();
} else {
break;
}
}
session.close();
connection.close();
}
}
Pub/Sub
模式 Java 代码:
发布者代码
public class Publishe {
private static String BROKER_URL = "tcp://127.0.0.1:61616";
private static String TOPIC = "darian-topic";
public static void main(String[] args) throws JMSException {
start();
}
static public void start() throws JMSException {
System.out.println("生产者启动!");
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, BROKER_URL);
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(null);
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
send(producer, session);
System.out.println("消息发送成功!");
connection.close();
}
static public void send(MessageProducer producer, Session session) throws JMSException {
for (int i = 1; i <= 5; i++) {
System.out.println("消息" + i);
TextMessage textMessage = session.createTextMessage("消息" + i);
Destination destination = session.createTopic(TOPIC);
producer.send(destination, textMessage);
}
}
}
订阅者代码
public class Subscribe {
private static String BROKER_URL = "tcp://127.0.0.1:61616";
private static String TOPIC = "darian-topic";
public static void main(String[] args) throws JMSException {
start();
}
static public void start() throws JMSException {
System.out.println("消费者启动!");
// 创建ActiveMQConnectionFactory 会话工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, BROKER_URL);
Connection connection = activeMQConnectionFactory.createConnection();
// 启动JMS 连接
connection.start();
// 不开启消息事物,消息主要发送消费者,则表示消息已经签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 创建一个队列
Topic topic = session.createTopic(TOPIC);
MessageConsumer consumer = session.createConsumer(topic);
// consumer.setMessageListener(new MsgListener());
while (true) {
TextMessage textMessage = (TextMessage) consumer.receive();
if (textMessage != null) {
System.out.println("接受到消息:" + textMessage.getText());
// textMessage.acknowledge();// 手动签收
// session.commit();
} else {
break;
}
}
connection.close();
}
先启动消费者1-N个,再启动生产者发送消息,再看控制台。
页面面板属性解释:
Number Of Consumers
消费者数量
Number Of Pending Messages
等待消费的消息数量
Messages Enqueued
进入队列的消息 [ 队列消息数量(进过队列的消息数量)]
Messages Dequeued
出了队列的消息 [ 可以理解为是消费这消费掉的数量 ]
JMS消息一致性(消息可靠机制)
ActiveMQ
消息签收机制:客戶端成功接收一条消息的标志是一条消息被签收,成功应答。
消息的签收情形分两种:
- 带事务的
Session
// Session 带事务,自动签收
// false表示不带事务,Session.AUTO_ACKNOWLEDGE表示自动签收
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
如果session
带有事务,并且事务成功提交,则消息被自动签收。如果事务回滚,则消息会被再次传送。
- 不带事务的
Session
// Session 不带事务,手动签收
// false表示不带事务,Session.CLIENT_ACKNOWLEDGE 表示手动签收
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
不带事务的session
的签收方式,取决于session
的配置。
ActiveMQ
支持一下三種模式:
Session.AUTO_ACKNOWLEDGE
消息自动签收
Session.CLIENT_ACKNOWLEDGE
消息手动签收
(客戶端调用 acknowledge
方法手动签收)
// 消费者
textMessage.acknowledge();//手动签收
Session.DUPS_OK_ACKNOWLEDGE
非必须签收,消息可能会重复发送。
在第二次重新传送消息的时候,消息只有在被确认之后,才认为已经被成功地消费了。
消息的成功消费通常包含三个阶段:
1.客户接收消息
2.客户处理消息
3.消息被确认
在事务性会话中,当一个事务被提交的时候,确认自动发生。在非事务性会话中,消息何时被确认取决于创建会话时的应答模式(acknowledgement mode
)
SpringBoot整合ActiveMQ
下面我用两个项目的案例示范(P2P
(点对点)方式):
示范案例模拟情况:项目B 需要 项目A 的某某数据
两个项目分别都引入 ActiveMQ
的Maven
依赖和配置Yml
文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
yml
配置
spring:
activemq:
broker-url: tcp://127.0.0.1:61616
user: admin
password: admin
# 自定义参数Queue名
myqueue: darian-queue
项目A(生产者)
队列配置
@Configuration
public class QueueConfig {
@Value("${myqueue}")
private String queue;
// 创建ActiveMQ队列
@Bean
public Queue logQueue() {
return new ActiveMQQueue(queue);
}
}
生产者
@Component
@EnableScheduling // 开启定时任务,模拟生产者实际情况
public class Producer {
@Autowired
private JmsMessagingTemplate jsm;
// JmsTemplate 和 JmsMessagingTemplate 选一个就行 差不多
// private JmsTemplate jmsTemplate;
@Autowired
private Queue queue;
private int count;
// 定时生产消息
@Scheduled(fixedDelay = 5000)
public void sendMsg(){
String nowMsg = "test message:" + System.currentTimeMillis();
System.out.println("生产消息:" + nowMsg);
// 懒得用对象,喜欢传对象的传对象,传JSON的传JSON 我这偷个懒
jsm.convertAndSend(queue, nowMsg);
}
}
项目B(消费者)
消费者
@Component
public class Consumer {
@JmsListener(destination = "${myqueue}")
public void receive(String msg) {
System.out.println("收到消息:"+ msg);
}
}
然后启动项目B(消费者),再启动项目A(生产者),效果如下:
这样就Over了!
MQ原理
没有仔细去了解,但大概应该是:消息队列作为消费者获取消息的一个入口,生产者/消费者基于 Socket TCP 协议一直保持和消息队列的连接,生产者往队列中添加消息,消息队列给消费者推送消息。
有点多线程Wati/Notifly的感觉。
tips:虽然都是一些比较基础知识,但我们也不能停止学习的脚步,活到老,学到老嘛。