1、MQ
1、mq?
所谓mq(message queue)是一种跨进程的通信机制,用于传递消息,就是一个
先进先出的数据结构,俗称消息队列。
2、mq的主要作用
mq并不是凭空出来的,但凡一个技术的出现肯定是为了解决某些实际问题而产生的,
同样的道理,mq也不例外。比如早期用户注册或者下订单发送短信、发送邮件业务,众所周知发送一条邮件是十分
耗时的,如果并发情况下都在访问这个接口,那么会有大量的线程阻塞在此,很容易压垮这个服务,微服务中任何
一个服务的不可用都有可能引发雪崩效应,所以说为了避免这种情况发生,引入了消息队列概念,吧消息先发送
到mq中,然后慢慢消费,也不会影响主流程。
2.1 异步解耦
下图所示,同时发送短信和邮件通知,告诉用户注册成功(注册系统),传统模式下需要邮件和短信发送成功并返回结果给客户端,用户才能使用账号登录。
但是实际情况下,用户只需要输入用户名和密码,发送短信和邮件通知,用户根本不需要关心。所以实际当数据写入注册服务后,注册服务(注册系统)就可以吧其他操作放入消息队列mq中,然后马上返回用户结果,由消息队列MQ异步进行操作,如下图
异步解耦主要是减少请求响应时间和解耦,主要使用场景就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列,同时,由于使用了消息队列MQ,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不受对方的影响,即解耦合。
2.2 流量削峰
流量削峰是MQ中常用场景,一般在秒杀或者团队抢购(高并发)中,因为此业务的用户请求量大,导致流量暴增,秒杀的应用在处理如此大量的访问流量后,下游的通知系统无法承受海量的调用量,甚至会导致系统崩溃等问题的发生漏通知的情况,为解决这些问题,可在应用和下游通知系统之间加入消息队列MQ.
秒杀处理流程
1、用户发起海量秒杀请求到秒杀业务处理系统。
2、秒杀处理系统按照秒杀处理逻辑将满足秒杀条件的请求发送至消息队列MQ
3、下游的通知系统订阅消息队列MQ的秒杀相关消息,再将秒杀成功的消息发送到响应用户。
4、用户收到秒杀成功的通知。
3、RocketMQ入门
3.1 环境准备
http://rocketmq.apache.org/docs/quick-start/
下载二进制版本
3.2 解压到本地
3.3 配置环境变量
path下添加刚才配置好的ROCKET_HOME,放置在bin目录下
3.4 启动
进入到rocketmq的安装目录bin下,选择此处打开命令窗口
执行 start mqnamesrv.cmd
进入到bin目录,打开cmd窗口 输入
start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
此时服务已经启动成功,当前启动后的窗口不要关闭。
3.5 关闭
直接关闭窗口即可。
4 RocketMQ 架构及概念
如上图所示,整体划分四个角色,分别是NameServer,Broker,Producer,Consumer。
1、Broker(邮递员):是mq的核心,负责消息的接收、存储、发布等功能
2、NameServer(邮局):是mq的协调者,Broker向他注册路由信息,
同时Producer和Consumer向其获取路由信息
3、Producer(寄件人):消息生产者,需要从NameServer获取Broker信息,然后与Broker建立连接,
向Broker发送消息
4、Consumer(收件人):消息消费者,需要从NameServer获取Broker信息,然后与Broker建立连接,
从Broker获取消息
5、Topic(地区、区域):用来区分不同类型的消息,发送和接收消息前都需要先创建Topic,针对Topic来发送
和接收消息。
6、Message: 消息的载体
7、Producer Group:生产者组,多个发送同一类的生产者称之为一个生产者组
8、Consumer Group:消费者组,消费同一类消息的多个Consumer实例组成一个消费者组。
5、RocketMQ控制台安装
为了更好查看消息,我们安装可视化工具rocketmq-externals
5.1 官网下载
下载地址:https://github.com/apache/rocketmq-externals/releases
5.2 修改配置文件
打开src\main\resources 下的application.properties配置文件 port/namesrvAddr
# 管理后台访问上下文路径,默认为空,
#如果填写,格式为:/xxx,例:/rocketmq-console
server.contextPath=
#控制台端口,选择一个不用的端口,本次暂时不做改动
server.port=8080
#spring.application.index=true
# 当前应用名称
spring.application.name=rocketmq-console
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
logging.config=classpath:logback.xml
#if this value is empty,use env value rocketmq.config.namesrvAddr NAMESRV_ADDR | now, you can set it in ops page.default localhost:9876
# name server地址,此时选择了本地服务,监听在9876端口上
rocketmq.config.namesrvAddr=localhost:9876
#if you use rocketmq version < 3.5.8, rocketmq.config.isVIPChannel should be false.default true
rocketmq.config.isVIPChannel=
#rocketmq-console's data path:dashboard/monitor
rocketmq.config.dataPath=/tmp/rocketmq-console/data
#set it false if you don't want use dashboard.default true
rocketmq.config.enableDashBoardCollect=true
5.3 打jar包,启动
进入到rocketmq-console目录,跳过测试打包
mvn clean package -DskipTests
然后进入到target目录,直接启动
java -jar rocketmq-console-ng-1.0.0.jar
5.4 访问控制台
http://localhost:8999
6 SpringBoot集成RocketMQ步骤
6.1 导入启动依赖
<!--rocketMQ-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
6.2 application.yml配置
#rocketmq 配置
rocketmq:
name-server: 127.0.0.1:9876 #rocketmq服务器地址
producer:
group: shop-order #消息生产者:当前项目的名称,其实这个无所谓的,选择当前项目名尽可能的由意义。
6.4 发送消息使用spring给我们提供的模板RocketMQTemplate,里面封装了大量的方法,拿来即用十分方便
6.5 消费者消费消息步骤
6.5.1 导入rocketmq依赖以及rocketmq客户端依赖
<!--rocketMQ-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<!--版本号切记要和生产者版本号一致,要不然可能会出现杂七杂八的版本问题,得不偿失-->
<version>2.0.2</version>
</dependency>
<!--rocketMQ 客户端-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
</dependency>
6.5.2 application.yml配置
rocketmq:
name-server: 127.0.0.1:9876 #rocketmq服务器地址
6.5.3 消费消息需要实现一个接口RocketMQListener
@Service
//consumerGroup:消费者组名,同一个服务中组名不可以重复
//topic:消费的主题,其实就是监听这个主题下的消息
@RocketMQMessageListener(consumerGroup = "shop-user1", topic = "order-tpopic-num1")
@Slf4j
public class SmsService implements RocketMQListener<Order> {
@Override
public void onMessage(Order order) {
String s = JSON.toJSONString(order);
//log.info("获取到订单信息:{}", s);
System.out.println("获取到订单信息:{}"+s);
}
}
6.6 生产者发送消息
/**
* 模拟下订单,发送消息
*/
@RestController
@Slf4j
public class OrderController6 {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**************************************************************************/
/**
* @return
*/
@RequestMapping("/order/message6")
public Order message6(@RequestParam(value = "name", required = false) String name, @RequestParam(value = "phone", required = false) String phone) {
Order order = new Order();
order.setUsername("chenchen");
order.setPprice(8888.8);
order.setNumber(2);
//param1:指定topic主题,向哪个topic发送消息
//param2:指定消息内容
rocketMQTemplate.convertAndSend("order-tpopic-num1", order);
log.info("调用orderService3热点规则方法:{}", "message2");
return order;
}
@RequestMapping("/order/message7")
public Order message7(@RequestParam(value = "name", required = false) String name, @RequestParam(value = "phone", required = false) String phone) {
Order order = new Order();
order.setUsername("chenchenis huahua");
order.setPprice(7777.8);
order.setNumber(89);
//发送普通消息
rocketMQTemplate.syncSend("order-tpopic-syncSend",order);
log.info("调用orderService3热点规则方法:{}", "message2");
return order;
}
}
浏览器发送请求下单
RocketMQ控制台,查看发送的消息
6.7 消费者消费消息
@Service
/**
@RocketMQMessageListener消费者参数细节
1、consumerGroup:消费者分组
2、topic:消费的主题,和生产者的topic保持一致
3、consumMode:ComsumeMode.CONCURRENTLY消费者模式 有序和无序,默认无序
4、messageModel:MessageModel.CLUSTERING 消息模式:广播和集群,默认集群
集群:一条消息只能被一个消费者实例消费(相当于点对点)
广播:每个消费者都会收到消息,每条消息可以被多个消费者实例处理(相当于发布订阅)
**/
@RocketMQMessageListener(consumerGroup = "shop-user", topic = "order-tpopic-syncSend")
@Slf4j
public class SmsService2 implements RocketMQListener<Order> {
@Override
public void onMessage(Order order) {
String s = JSON.toJSONString(order);
System.out.println("获取到订单信息:{}"+s);
}
}
控制台显示,监听了两个主题消息,都已经收到
7、消息类型
刚才已经生产者已经成功的将消息投递发送出去,而消费者确实也成功的拿到了消息,不过实际开发中mq会针对不同的业务场景,发送不同的消息类型,简单来说RocketMQ可以提供三种方式来发送消息,分别是可靠同步发送、可靠异步发送以及单向发送。
7.1 可靠同步发送
同步发送是指消息发送方发出数据后,会在收到接收方返回响应之后才会发下一个数据(消息)的通讯方式,
典型的应用如:重要邮件通知、报名短信通知、营销短信系统等。
7.2 可靠异步消息
1、异步发送是指发送方发出数据后,不等接收方返回响应,接着发送下个数据(消息)的通讯方式,
发送方通过回调的接口接收服务器响应,并对响应结果进行处理。
2、异步发送一般用于链路耗时比较长,对RT响应时间较为敏感的业务场景,如:大文件解析、上传、下载后通知转码服务,转码成功后通知通知推送转码结果。
7.3 单向消息
单向发送是指发送方只负责发送消息,不用等待服务器 响应且没有回调函数触发,即只发送请求不等待应答,
典型应用于某些耗时非常短,但对可靠性要求并不高的场景,如 日志收集。
7.4 下面分别测试可靠同步、可靠异步、以及单向消息
7.4.1 准备工作导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
测试可靠单向消息
/**
* 普通消息测试
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = OrderApplication.class)
@Slf4j
public class MessageType {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 同步消息
*/
@Test
public void syncMessage() {
for (int i = 0; i < 5; i++) {
//param1:topic 消息主题,
//param2:消息体,具体发送的内容
//param3:long timeout 指定超时时间,超过指定时间不在发送消息
SendResult sendResult = rocketMQTemplate.syncSend("sync-message-00" + i, "测试同步消息",50);
System.out.println(sendResult);
}
}
测试异步消息
/**
* 异步消息
*/
@Test
public void assyncMessage() {
// param1:topic 消息主题
//param2:消息体,具体发送的内容
//param3:SendCallback: 消息回调,包含两个回调方法,一个成功时onSuccess,一个异常时onException
rocketMQTemplate.asyncSend("async-message-chenchen", "异步消息测试", new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("异步消息:{}", sendResult.toString());
}
@Override
public void onException(Throwable throwable) {
log.error("异常信息:{}", throwable.toString());
}
});
log.info("普通消息先执行");
try {
Thread.sleep(500000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
异步最大的区别就是不管你是成功还是失败与否,都先会往下执行,其次才是回调,如图所示 控制台先打印出 “普通消息先执行”,再回调了返回结果集。
测试单向消息
/**
* 单向消息:不管对方收不收得到,自己不关心,只关注发送
*/
@Test
public void sendOneWay() {
// param1:topic 消息主题
//param2:消息体,具体发送的内容
rocketMQTemplate.sendOneWay("sendOneWay-messagetype", "单向消息测试");
}
8、 消息顺序
mq虽然说一个队列,但是如果向刚才那样的api是无法保证消息的顺序性质的,一个Topic默认有4个message queue,当生产者发送普通消息时,会随机选择一个message queue发送,每次都不同,当然消费者读取的消息也是无序的。
测试:接连发送消息
/**
* 单向同步
*/
@Test
public void sendOneWay() {
// param1:topic 消息主题
//param2:消息体,具体发送的内容
rocketMQTemplate.sendOneWay("sendOneWay-message-type", "单向消息测试");
}
控制台查看消息随机落在不同的message queue上。
那么有没有顺序的呢?答案是有的
/**
* 单向顺序消息
* 同步顺序以及异步顺序都是一样,只不过api不同
*/
@Test
public void sendOneWayOrderly() {
// param1:topic 消息主题
//param2:消息体,具体发送的内容
//param3:哈希值,只要唯一即可。。
rocketMQTemplate.sendOneWayOrderly("sendonway-message-order","生产单向顺序消息","hk");
}
此时查看消息控制台消息均匀落在了message queue3上面,那么消息者消费时,就可以实现顺序消费