RocketMQ
一、MQ简介
1、什么是MQ
MQ(Message Queue)是一种跨进程的通信机制,用于传递消息。通俗点说,就是一个**先进先出(FIFO)的数据结构**
本质:一发一存一消费
简单的描述:
- 消息:消息就是指要传送或者转发的数据,可以是简单的文字也可以是很复杂的数据(Object)
- 队列:是一种先进先出数据结构。它是存放消息的容器,消息从队尾入队,从队头出队,入队即发消息的过程,出队即收消息的过程。
生产者发送所要发送的消息放到队列,也就是MQ,先进先出,以一种队伍的形式排列先到队列的先发给消费者
2、MQ的应用场景
1)异步解耦
最常见的一个场景是用户注册后,需要发送注册邮件和短信通知,以告知用户注册成功
此架构下注册、邮件、短信三个任务全部完成后,才返回注册结果到客户端(在此其间用户一直在等待),用户才能使用账号登录。但是对于用户来说,注册功能实际只需要注册系统存储用户的账户信息后,该用户便可以登录,而后续的注册短信和邮件不是即时需要关注的步骤。
所以实际当数据写入注册系统后,注册系统就可以把其他的操作放入对应的消息队列 MQ 中然后马上返回用户结果,由消息队列 MQ 异步地进行这些操作。
异步解耦是消息队列 MQ 的主要特点,主要目的是减少请求响应时间和解耦。主要的使用场景就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同时,由于使用了消息队列
MQ,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦合。
(只有满足比较耗时且不需要即时返回结果这两种条件,才用MQ)
2)流量削峰
流量削峰也是消息队列 MQ 的常用场景,一般在秒杀或团队抢购(高并发)活动中使用广泛。在秒杀或团队抢购活动中,由于用户请求量较大,导致流量暴增,秒杀的应用在处理如此大量的访问流量后,下游的通知系统无法承载海量的调用量,甚至会导致系统崩溃等问题而发生漏通知的情况。为解决这些问题,可在应用和下游通知系统之间加入消息队列 MQ。
(即通过MQ的缓冲,来削弱秒杀时带来的高并发。当秒杀成功,将成功消息放入MQ(先进先出),由下游的通知系统进行处理,通知系统并非像秒杀那样需要实时处理。)
秒杀处理流程如下所述:
- 用户发起海量秒杀请求到秒杀业务处理系统。
- 秒杀处理系统按照秒杀处理逻辑将满足秒杀条件的请求发送至消息队列 MQ。
- 下游的通知系统订阅消息队列 MQ 的秒杀相关消息,再将秒杀成功的消息发送到相应用户。
- 用户收到秒杀成功的通知。
3、常见的MQ产品
- ZeroMQ
号称最快的消息队列系统,尤其针对大吞吐量的需求场景。扩展性好,开发比较灵活,采用C语言实现,实际上只是一个Socket库的重新封装,如果做为消息队列使用,需要开发大量的代码。
ZeroMQ仅提供非持久性的队列,也就是说如果down机,数据将会丢失。 - RabbitMQ
使用erlang语言开发,性能较好,适合于企业级的开发。但是不利于做二次开发和维护。 - ActiveMQ
历史悠久的Apache开源项目。已经在很多产品中得到应用,实现了JMS1.1规范,可以和springjms轻松融合,实现了多种协议,支持持久化到数据库,对队列数较多的情况支持不好。
- RocketMQ
阿里巴巴的MQ中间件,由java语言开发,性能非常好,能够撑住双十一的大流量,而且使用起来很简单。
- Kafka
Kafka是Apache下的一个子项目,是一个高性能跨语言分布式Publish/Subscribe消息队列系统, 相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统。
二、RocketMQ入门
1、RocketMQ环境搭建
1)环境准备
- 下载RocketMQ安装包
http://rocketmq.apache.org/release_notes/release-notes-4.4.0/ - 环境要求
- Linux 64位操作系统
- 64bit JDK 1.8+
2)安装RocketMQ
- 使用FTP工具上传文件到Linux系统的/usr/local/src/路径下
# ls /usr/local/src/
- 解压到安装目录
# unzip rocketmq-all-4.4.0-bin-release.zip
如果没有unzip
先安装zip命令
将解压的文件夹移到上一级的rocketmq目录中
# mv rocketmq-all-4.4.0-bin-release ../rocketmq
3)设置RocketMQ的环镜变量
- 编辑profile文件
# vim /etc/profile
- 将下面的配置添加到文件最底部
export PATH = $PATH:/usr/local/rocketmq/bin
export NAMESRV_ADDR=localhost:9876
- 保存并退出
- 更新profile
# source /etc/profile
4)启动前设置
切换到安装目录
cd /usr/local/rocketmq/bin
修改参数:编辑bin/runbroker.sh 和 bin/runserver.sh文件,修改里面的
# vim ./runbroker.sh
在insert的状态下用 # 注释
保存退出:esc+:wq
# vim ./runserver.sh
和上面一样要注释掉。
5)开放9876,10909,10911
# 开放80端口
firewall-cmd --permanent --add-port=9876/tcp
firewall-cmd --permanent --add-port=10909/tcp
firewall-cmd --permanent --add-port=10911/tcp
# 移除端口
firewall-cmd --permanent --remove-port=8080/tcp
# 查询端口是否开放
firewall-cmd --query-port=9876/tcp
# 查看防火墙开放的端口及一些规则
firewall-cmd --list-all
#重启防火墙(修改配置后要重启防火墙)
firewall-cmd --reload
6)启动RocketMQ
- 切换到安装目录
cd /usr/local/rocketmq/bin
- 启动NameServer(必须启动的组件)
nohup sh ./mqnamesrv -n 192.168.23.128:9876 & // & 表示后台运行 192.168.127.134表示公网ip
端口检查是否启动成功
# netstat -an | grep 9876
只要进程不报错,就应该是启动成功了,可以查看一下日志
# tail -f ~/logs/rocketmqlogs/namesrv.log
Ctrl+C退出log日志文件
- 启动 Broker(必须启动的组件)
// & 表示后台运行 192.168.23.128表示公网ip
nohup sh mqbroker -n 192.168.23.128:9876 autoCreateTopicEnable=true &
查看日志
tail -f ~/logs/rocketmqlogs/broker.log
7)测试RocketMQ
(如果能发送成功,接收成功,表示配置成功)
RocketMQ官方给了我们两个测试文件,只要测试一端发送,一端接收就可以了。
再开两个Xshell窗口,都将目录切换到RocketMQ的bin目录下,一个用于发送消息,一个用于接收消息:
- 测试消息发送
声明发送地址:
\# export NAMESRV_ADDR=localhost:9876
\# ./tools.sh org.apache.rocketmq.example.quickstart.Producer
- 测试消息接收
声明接收地址:
# export NAMESRV_ADDR=localhost:9876
# ./tools.sh org.apache.rocketmq.example.quickstart.Consumer
注意:消息接收端在接收完后仍处于待接收的状态,并没有关闭。
8)关闭 RocketMQ
注意:一定要先关闭broker 再关闭namesrv
# ./mqshutdown broker
# ./mqshutdown namesrv
2、RocketMQ的架构及概念
如上图所示,整体可以分成4个角色,分别是:NameServer、Broker、Producer、Consumer。
- Broker (邮递员)“不弱可”
Broker是RocketMQ的核心,负责消息的接收,存储,投递等功能。
- NameServer (邮局)
消息队列的协调者,Broker向它注册路由信息,同时Producer和Consumer向其获取路由信息。 - Producer (寄件人) “破若丢涩”
消息的生产者,需要从NameServer获取Broker信息,然后与Broker建立连接,向Broker发送消息。 - Consumer (收件人)“康修猫”
消息的消费者,需要从NameServer获取Broker信息,然后与Broker建立连接,从Broker获取消息。
- Topic (地区)
用来区分不同类型的消息,发送和接收消息前都需要先创建Topic,针对Topic来发送和接收消息。 - Message Queue (邮件)
为了提高性能和吞吐量,引入了Message Queue,一个Topic可以设置一个或多个Message Queue,这样消息就可以并行往各个Message Queue发送消息,消费者也可以并行的从多个 Message Queue读取消息。 - Message
Message 是消息的载体。 - Producer Group
生产者组,简单来说就是多个发送同一类消息的生产者称之为一个生产者组。 - Consumer Group
消费者组,消费同一类消息的多个 consumer 实例组成一个消费者组。
3、RocketMQ控制台安装
网上博客:
三、SpringCloud+RecketMQ
简单流程讲解:Nacos作为注册中心
创建一个简单的springcloud分布式微服务项目一个生产者微服务和一个消费者微服务
然后生产者添加一条数据时通过recketmq发送通知,然后在消费者微服务查看接收的通知信息
1、创建简单的数据库表
2、创建SpringCloud工程
总工程pom
<?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>cn.zhibang</groupId>
<artifactId>sb_rocketmq</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>sc-commons</module>
<module>sc-producer-8001</module>
<module>sc-user-9001</module>
</modules>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<springcloudalibaba.version>2.3.0.RELEASE</springcloudalibaba.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<!-- spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${springcloud.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${springcloudalibaba.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
3、sc-commons
1)改pom
<?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">
<parent>
<artifactId>sb_rocketmq</artifactId>
<groupId>cn.zhibang</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sc-commons</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
</project>
2)写yml
因为是api微服务模块不需要写yml
3)创建所需Model
Shop
package cn.zhibang.common.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Shop implements Serializable {
private static final long serialVersionUID = 7885999085160310238L;
@TableId(value = "sid",type = IdType.AUTO)
private Integer sid;
private String sname;
private Double price;
private String describe;
}
CommonResult
package cn.zhibang.common.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResult<T> implements Serializable {
private static final long serialVersionUID = 5991358332938309635L;
private Long code; //响应结果码
private String message; //提示消息
private T data; //响应数据
}
4、sc-producer-8001
1)改pom
<?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">
<parent>
<artifactId>sb_rocketmq</artifactId>
<groupId>cn.zhibang</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
<artifactId>sc-producer-8001</artifactId>
<dependencies>
<dependency>
<groupId>cn.zhibang</groupId>
<artifactId>sc-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入druid数据库连接 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.18</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.6.0</version>
</dependency>
</dependencies>
</project>
2)写yml
server:
port: 8001
spring:
application:
name: service-producer
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sb_recketmq?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: root
thymeleaf:
cache: false
cloud:
nacos:
discovery:
server-addr: localhost:8848
rocketmq:
name-server: 192.168.127.134:9876 #rocketMQ服务的地址
producer:
group: shop-price # 生产者组
mybatis-plus:
# mapper-locations: classpath*:mapper/*.xml #扫描映射文件
type-aliases-package: cn.zhibang.common.model #实体类别名
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3)写主启动类
package cn.zhibang.producer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableDiscoveryClient
@ComponentScan("cn.zhibang")
public class ProducerApplication8001 {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication8001.class,args);
}
}
3)写mapper,service,serviceimpl
这里是创建关于Shop实体类的接口及实现类省去
4)写controller
package cn.zhibang.producer.controler;
import cn.zhibang.common.model.CommonResult;
import cn.zhibang.common.model.Shop;
import cn.zhibang.producer.service.ShopService;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/shop")
public class ShopController {
@Resource
private ShopService shopService;
@Autowired
private RocketMQTemplate rocketMQTemplate;
@GetMapping("/addshop")
public CommonResult createOrder(String sname,Double price){
CommonResult<Object> result = new CommonResult<>();
Shop s =new Shop();
s.setSname(sname);
s.setPrice(price);
//商品存入数据库中
boolean save = shopService.save(s);
if (save){
result.setCode(200L);
result.setMessage("添加成功");
result.setData(save);
//下单成功之后,将消息放到MQ中,向某一个主题发送消息
//参数一:指定topic
//参数二:指定消息体
rocketMQTemplate.convertAndSend("shop-topic", s);
return result;
}else{
return new CommonResult(500L, "添加失败", null);
}
}
}
5、sc-user-9001
1)改pom
<?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">
<parent>
<artifactId>sb_rocketmq</artifactId>
<groupId>cn.zhibang</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sc-user-9001</artifactId>
<dependencies>
<dependency>
<groupId>cn.zhibang</groupId>
<artifactId>sc-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入druid数据库连接 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.18</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.6.0</version>
</dependency>
</dependencies>
</project>
2)写yml
server:
port: 9001
spring:
application:
name: service-user
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sb_recketmq?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: root
thymeleaf:
cache: false
cloud:
nacos:
discovery:
server-addr: localhost:8848
rocketmq:
name-server: 192.168.127.134:9876
mybatis-plus:
# mapper-locations: classpath*:mapper/*.xml #扫描映射文件
type-aliases-package: cn.zhibang.common.model #实体类别名
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3)写主启动类
package cn.zhibang.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableDiscoveryClient
@ComponentScan("cn.zhibang")
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class,args);
}
}
3)写Service接收消息类
package cn.zhibang.user.service;
import cn.zhibang.common.model.Shop;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
@Service
//consumerGroup消息者组名
//topic主题名,和生产者的主题名一致
@RocketMQMessageListener(consumerGroup = "shop-price", topic = "shop-topic")
public class SmsService implements RocketMQListener<Shop> {
@Override
public void onMessage(Shop shop) {
System.out.println("收到一个商品通知信息,接下来发送短信:"+shop);
}
}
6、案例测试
- 启动RroketMQ
- 启动Nacos注册中心
- 启动生产者微服务 sc-producer-8001
- 启动消费者微服务 sc-user-9001
- 往数据库添加数据并发送消息:http://localhost:8001/shop/addshop?sname=%E8%8D%89%E8%8E%93&price=5.44
- 查看消费者是否收到消息
此时会发现RocketMQ控制台主题一栏会添加有