前言
- 在何种场景下使用了消息中间件?
- 为什么要在系统里引入消息中间件?
微服务分布式架构后,链式调用是我们在写程序时候的一般流程,为了完成一个整体功能会将其拆分成多个函数(或子模块),比如模块A调用模块B、模块B调用模块C、模块C调用模块D。但在大型分布式应用中,系统间的RPC交互繁杂,耦合性很大。
面对大流量并发时,容易被冲垮。
RPC接口上基本都是同步调用,整体的服务性能遵循“木桶理论”,即整体系统的耗时取决于链路中最慢的那个接口。比如A调用B/C/D都是50ms,但此时B又调用了B1,花费2000ms,那么直接就拖累了整个服务性能。
MQ作用
- 能够解耦
要做到系统解耦,当新的模块接进来时,可以做到代码改动最小
- 能够消峰
设置流量缓冲池,可以让后端系统按照自身吞吐量能力进行消费,不被冲垮
- 能够异步
强弱依赖梳理能将非关键调用链路的操作异步化并提升整体系统的吞吐能力
处理过程
发送者把消息发送给消息服务器,消息服务器将消息存放在若干队列/主题中,在合适的时候,消息服务器会把消息转发给接受者。在这个过程中,发送和接受是异步的,也就是发送无需等待,而且发送者和接受者的生命周期也没有必然的联系。尤其在发布pub/订阅sub模式下,也可以完成一对多的通信,既让一个信息有多个接收者。
安装ActiveMQ
官网地址: http://activemq.apache.org/
启动
./activemq start
查看是否启动`
ps -ef|grep activemq|grep -v grep
客户端访问(本机)
IP地址:8161
用户名,密码都为admin
这里如果可以互相ping通,但是访问不了的,可以尝试修改conf/jetty.xml
把127.0.0.1改成0.0.0.0
新建Maven导入依赖
<!-- activemq 所需要的jar 包-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.12.0</version>
</dependency>
<!-- activemq 和 spring 整合的基础包 -->
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
JMS编码总体架构
Destination分为两种:队列(点对点)和主题(一对多)。
队列(Queue)
编写生产者代码
public class jmsProduce {
public static final String ACTIVEMQ_URL="tcp://192.168.10.100:61616";
public static final String QUEUE_NAME="queue01";
public static void main(String[] args) throws JMSException {
//1.创建连接工厂,采用默认的用户名密码,连接给定的url
ActiveMQConnectionFactory connectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂,获得Connection并启动
Connection connection = connectionFactory.createConnection();
connection.start();
//3创建session
//两个参数,第一个:事务,第二个:签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4创建目的地(队列还是主题)
Queue queue = session.createQueue(QUEUE_NAME);
//5创建消息的生产者
MessageProducer producer = session.createProducer(queue);
//6.通过使用MessageProducers生产3条消息发送到mq的队列里
for (int i = 1; i <=3; i++) {
TextMessage textMessage = session.createTextMessage("msg---" + i);//理解为一个字符串
//发送
producer.send(textMessage);
}
//关闭资源
producer.close();
session.close();
connection.close();
System.out.println("******消息发布完成");
}
}
Number Of Pending Messages :待处理的消息数量
Number Of Consumers :消费者数量
Messages Enqueued:消息排队
Messages Dequeued:消息已出列
当有一个消息进入这个队列时,等待消费的消息是1,进入队列的消费是1
当消息消费后,等待消费的消息是0,进入队列的消息是1,出队列的消息是1
再来一条消息时,等待消费的消息是1,进入队列的消息是2
方法一:消费者编码
public class jmsConsumer {
public static final String ACTIVEMQ_URL="tcp://192.168.10.100:61616";
public static final String QUEUE_NAME="queue01";
public static void main(String[] args) throws JMSException {
//1.创建连接工厂,采用默认的用户名密码,连接给定的url
ActiveMQConnectionFactory connectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂,获得Connection并启动
Connection connection = connectionFactory.createConnection();
connection.start();
//3创建session
//两个参数,第一个:事务,第二个:签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4创建目的地(队列还是主题)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消费者
MessageConsumer consumer = session.createConsumer(queue);
while(true){
TextMessage receive = (TextMessage)consumer.receive();
if(receive!=null){
System.out.println("消费者接收到消息"+receive.getText());
}else{
break;
}
}
consumer.close();
session.close();
connection.close();
}
}
receive方法有两种,一种是不带参数:表示一直等待,一种带时间参数,延时等待。
方法二:通过监听的方式消费消息MessageListener
public class jmsConsumer {
public static final String ACTIVEMQ_URL="tcp://192.168.10.100:61616";
public static final String QUEUE_NAME="queue01";
public static void main(String[] args) throws JMSException, IOException {
//1.创建连接工厂,采用默认的用户名密码,连接给定的url
ActiveMQConnectionFactory connectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂,获得Connection并启动
Connection connection = connectionFactory.createConnection();
connection.start();
//3创建session
//两个参数,第一个:事务,第二个:签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4创建目的地(队列还是主题)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消费者
MessageConsumer consumer = session.createConsumer(queue);
//通过监听方式来消费消息
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message!=null&&message instanceof TextMessage){
TextMessage receive = (TextMessage)message;
try {
System.out.println("消费者接收到消息"+receive.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read();//保证控制台不灭
consumer.close();
session.close();
connection.close();
}
}
消费者3大消费情况
- 先生产,只启动一号消费者;可以消费消息
- 先生产,先启动一号消费者,再启动二号消费者;一号消费者可以消费,二号消费者不能消费,因为一号消费者已经把消息消费完了
- 先启动两个消费者,再启动生产;负载均衡,平均分配消费
JMS基本开发步骤
两种消费方法
同步阻塞方式(receive())
订阅者或接收者调用MessageConsumer的receive()来接收消息,receive方法能够在接收到消息之前(或超时之前)将一直阻塞
异步非阻塞方式(监听器onMessage())
订阅者或接收者通过MessageConsumer的setMessageListener(MessageListener
listener)注册一个消息监听器,当消息到达之后,系统自动调用监听器MessageListener的onMessage(Message message)方法
点对点消息传递域的特点:
- 每个消息只能有一个消费者,类似1对1的关系。
- 消息的生产者和消费者之间没有时间上的相关性。无论消费者在生产者发送消息的时候是否处于运行状态,消费者都可以提取消息。好比发手机消息,发送者发送后,接收者不一定立即看
- 消息被消费后队列中不会再存储,所以消费者不会消费到已经被消费掉的消息
主题(Topic)
生产者代码:
public class jmsProduce_Topic {
public static final String ACTIVEMQ_URL="tcp://192.168.10.100:61616";
public static final String TOPIC_NAME="topic01";
public static void main(String[] args) throws JMSException {
//1.创建连接工厂,采用默认的用户名密码,连接给定的url
ActiveMQConnectionFactory connectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂,获得Connection并启动
Connection connection = connectionFactory.createConnection();
connection.start();
//3创建session
//两个参数,第一个:事务,第二个:签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4创建目的地(队列还是主题)
Topic topic = session.createTopic(TOPIC_NAME);
//5创建消息的生产者
MessageProducer producer = session.createProducer(topic);
//6.通过使用MessageProducers生产3条消息发送到mq的队列里
for (int i = 1; i <=3; i++) {
TextMessage textMessage = session.createTextMessage("TOPIC_NAME---" + i);//理解为一个字符串
//发送
producer.send(textMessage);
}
//关闭资源
producer.close();
session.close();
connection.close();
System.out.println("******TOPIC_NAME消息发布完成");
}
}
消费者代码:
public class jmsConsumer_Topic {
public static final String ACTIVEMQ_URL="tcp://192.168.10.100:61616";
public static final String TOPIC_NAME="topic01";
public static void main(String[] args) throws JMSException, IOException {
//1.创建连接工厂,采用默认的用户名密码,连接给定的url
ActiveMQConnectionFactory connectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工厂,获得Connection并启动
Connection connection = connectionFactory.createConnection();
connection.start();
//3创建session
//两个参数,第一个:事务,第二个:签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4创建目的地(队列还是主题)
Topic topic = session.createTopic(TOPIC_NAME);
//5.创建消费者
MessageConsumer consumer = session.createConsumer(topic);
/*while(true){
TextMessage receive = (TextMessage)consumer.receive();
if(receive!=null){
System.out.println("消费者接收到消息"+receive.getText());
}else{
break;
}
}*/
//通过监听方式来消费消息
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message!=null&&message instanceof TextMessage){
TextMessage receive = (TextMessage)message;
try {
System.out.println("消费者接收到Topic消息"+receive.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read();//保证控制台不灭
consumer.close();
session.close();
connection.close();
}
}
消费者1和消费者2都收到了3条消息
主题消息传递域的特点:(好比于微信公众号)
- 生产者将消息发布到topic中,每个消息可以有多个消费者,属于1:N的关系
- 生产者和消费者之间有时间上的相关性。订阅某个主题的消费者只能消费自它订阅之后发布的消息
- 生产者生产时,topic不保存消息它是无状态的不落地,假如无人订阅就去生产,那就是一条废消息,所以,一般先启动消费者,再启动生产者
Queue和Topic的对比总结