假设你有个系统A,这个系统A会产出一个核心数据,现在下游有系统B和系统C需要这个数据。
那简单,系统A就是直接调用系统B和系统C的接口发送数据给他们就好了。
整个过程,如下图所示:
但是现在要是来了系统D、系统E、系统F、系统G,等等,十来个其他系统慢慢的都需要这份核心数据呢?如下图所示:
大家可别以为这是开玩笑,一个大规模系统,往往会拆分为几十个甚至上百个子系统,每个子系统又对应N多个服务,这些系统与系统之间有着错综复杂的关系网络。
如果某个系统产出一份核心数据,可能下游无数的其他系统都需要这份数据来实现各种业务逻辑。此时如果你要是采取上面那种模式来设计系统架构,那么绝对你负责系统A的同学要被烦死了。
先是来一个人找他要求发送数据给一个新的系统H,系统A的同学要修改代码然后在那个代码里加入调用新系统H的流程。一会那个系统B是个陈旧老系统要下线了,告诉系统A的同学:别给我发送数据了,接着系统A再次修改代码不再给这个系统B。
然后如果要是某个上游或者下游系统突然宕机了呢?
系统A的调用代码里是不是会抛异常?那系统A的同学会收到报警说异常了,结果他还要去care是下游哪个系统宕机了。所以在实际的系统架构设计中,如果全部采取这种系统耦合的方式,在某些场景下绝对是不合适的,系统耦合度太严重。并且互相耦合起来并不是核心链路的调用,而是一些非核心的场景(比如上述的数据消费)导致了系统耦合,这样会严重的影响上下游系统的开发和维护效率。
因此在上述系统架构中,就可以采用MQ中间件来实现系统解耦。
系统A就把自己的一份核心数据发到MQ里,下游哪个系统感兴趣自己去消费即可,不需要了就取消数据的消费,如下图所示:
假设你有一个系统调用链路,是系统A调用系统B,一般耗时20ms;系统B调用系统C,一般耗时200ms;系统C调用系统D,一般耗时2s,如下图所示。
现在最大的问题就是:
用户一个请求过来巨慢无比,因为走完一个链路,需要耗费:
20ms + 200ms + 2000ms(2s) = 2220ms,
也就是2秒多的时间。但是实际上,链路中的系统A调用系统B,系统B调用系统C,这两个步骤起来也就220ms。就因为引入了系统C调用系统D这个步骤,导致最终链路执行时间是2秒多,直接将链路调用性能降低了10倍,这就是导致链路执行过慢的罪魁祸首。
那此时我们可以思考一下,是不是可以将系统D从链路中抽离出去做成异步调用呢?其实很多的业务场景是可以允许异步调用的。
这样,实现思路就是系统A -> 系统B -> 系统C,直接就耗费220ms后直接成功了。然后系统C就是发送个消息到MQ中间件里,由系统D消费到消息之后慢慢的异步来执行这个耗时2s的业务处理。通过这种方式直接将核心链路的执行性能提升了10倍。整个过程,如下图所示:
假设你有一个系统,平时正常的时候每秒可能就几百个请求,系统部署在8核16G的机器的上,正常处理都是OK的,每秒几百请求是可以轻松抗住的,但是如下图所示,在高峰期一下子来了每秒钟几千请求,弹指一挥间出现了流量高峰,此时你的选择是要搞10台机器,抗住这个瞬时高峰吗?
假设瞬时高峰每天就那么半个小时,其它时间基本为每秒就几百请求,如果你线上部署了很多台机器,那么每台机器就处理每秒几十个请求就可以了,这不是有点浪费机器资源吗?大部分时候,每秒几百请求,一台机器就足够了,但是为了抗那每天瞬时的高峰,硬是部署了10台机器,每天就那半个小时有用,别的时候都是浪费资源的。
1、采用异步处理模式
2、应用系统间解耦
发送者和接受者无需了解对方,只需发送或者接收消息即可。并且发送者和接受者无需同时在线。
去官网http://activemq.apache.org/,下载最新安装包,我使用的是5.15.10版本。
将安装包apache-activemq-5.15.10-bin.tar下载到根目录的/opt下。
使用
tar -zxvf apache-activemq-5.15.10-bin.tar
对其进行解压,并把挤压后的文件放到根目录的/myactivemq/文件夹下。
cd /myactivemq/apache-activemq-5.15.10/bin
进入activemq目录下的bin目录,使用
./activemq start
启动activeMQ。
也可以
./activemq status
查看activeMQ启动状态。
./activemq stop
关闭。
在启动ActiveMQ之后,ActiveMQ会开放两个端口,一个是后台端口61616,另一个是前台端口8161.我们可以使用ip+端口号进行访问,如http://192.168.0.119:8161进行访问。能看到如下画面,即启动成功。
<?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>
<groupId>com.rocket</groupId>
<artifactId>test_activemq</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.targer>1.8</maven.compiler.targer>
</properties>
<dependencies>
<!--activemq需要的jav包-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.9</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
<!--下面是junit/log4等通用配置-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
ActiveMQ中的目的地有两种:queue和topic,其中队列是点对点的传输,只能有一个接收方,而topic可以有多个接收方,只要再发布消息之前订阅了该topic就可以接收到,并且实现了“负载均衡”,即多个接受者间隔接受。
在点对点传输下,目的地为队列queue。
package com.atguigu.activemq.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* @ClassName JmsProduce
**/
public class JmsProduce {
public static final String URL = "tcp://192.168.0.119:61616";
public static final String queue_name = "queue01";
public static void main(String[] args) throws JMSException {
//创建连接工厂
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(URL);
//利用工厂获取connection,并开始连接
Connection connection = factory.createConnection();
connection.start();
//创建会话session,两个参数:事务和签收
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
//创建目的地Destination
Queue queue = session.createQueue(queue_name);
//创建生产者
MessageProducer producer = session.createProducer(queue);
//生产者发送消息
for (int i = 1; i < 3; i++) {
TextMessage textMessage = session.createTextMessage("mag..." + i);
producer.send(textMessage);
}
//关闭资源
producer.close();
session.close();
connection.close();
System.out.println("生产者生产数据成功!");
}
}
package com.atguigu;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
/**
* ActiveMQ
* JMS(Java Message Service)的生产者编码
* @author xxy
* @date 2019/12/16
*/
public class JmsConsumer {
private static final String ACTIVE_MQ = "tcp://192.168.0.119:61616";
private static final String QUEUE_NAME = "queue01"
public static void main(String[] args) throws JMSException, IOException {
System.out.println("消费者01");
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(ACTIVE_MQ);
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageConsumer consumer = session.createConsumer(queue);
/*
同步阻塞方式receive()
while (true) {
TextMessage message = (TextMessage) consumer.receive();
if (null != message) {
System.out.println("消费者受到消息,内容是:" + message.getText());
} else {
break;
}
}
consumer.close();
session.close();
connection.close();*/
//监听器方式
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if (null != message && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("msg-----" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read();
consumer.close();
session.close();
connection.close();
}
}
消费者消息的接受存在两种方式,一种是同步阻塞方式,另一种是监听器方式。
同步阻塞方式中有receive()和receive(long time)两个方法,没有消息阻塞在这里或者没有消息超过一定时间之后退出。监听器方式为设置一个监听器,监听是否有消息传送过来。
package com.atguigu;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* ActiveMQ
* JMS(Java Message Service)的生产者编码
* @author xxy
* @date 2019/12/16
*/
public class JmsProduceTopic {
private static final String ACTIVE_URL = "tcp://192.168.0.119:61616";
private static final String TOPIC_NAME = "topic-01";
public static void main(String[] args) throws JMSException {
//创建连接工厂,按照指定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
//通过连接工厂创建连接,并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//创建回话session,两个参数:事务和签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//常见目的地(队列或queue者主题topic)
Topic topic = session.createTopic(TOPIC_NAME);
//创建生产者
MessageProducer producer = session.createProducer(topic);
//使用MessageProducer生产3条消息发送到MQ的队列里
for (int i = 0; i < 3; i++) {
//创建消息
TextMessage textMessage = session.createTextMessage("msg------" + i);
//通过生产者MessageProducer发送给MQ
producer.send(textMessage);
}
//关闭资源
producer.close();
session.close();
connection.close();
}
}
package com.atguigu;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
/**
* ActiveMQ
* JMS(Java Message Service)的消费者编码
* @author xxy
* @date 2019/12/16
*/
public class JmsConsumerTopic {
private static final String ACTIVEMQ_URL = "tcp://192.168.0.119:61616";
private static final String TOPIC_NAME = "topic-01";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("消费者TOPIC-3");
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
MessageConsumer consumer = session.createConsumer(topic);
//监听器方式
consumer.setMessageListener((message) -> {
if (null != message && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("msg-----" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
System.in.read();
consumer.close();
session.close();
connection.close();
}
}