Rocket MQ入门教程(MQ简介、集群搭建、收发消息)

一、MQ简介

为什么使用MQ

消息队列是一种先进先出的数据结构

其应用场景主要包含以下3个方面

  • 应用解耦

    比如下图的订单系统,在没有使用MQ的时候,订单系统需要调用支付系统库存系统物流系统,如果在调用支付系统的时候,支付系统挂掉了。那么就会影响库存系统物流系统的调用。影响用户的体验。

使用消息队列解耦合,系统的耦合性就会提高了。比如物流系统发生故障,需要几分钟才能来修复,在这段时间内,物流系统要处理的数据被缓存到消息队列中,用户的下单操作正常完成。当物流系统回复后,补充处理存在消息队列中的订单消息即可,终端系统感知不到物流系统发生过几分钟故障。

  • 流量削峰

应用系统如果遇到系统请求流量的瞬间猛增,有可能会将系统压垮。有了消息队列可以将大量请求缓存起来,分散到很长一段时间处理,这样可以大大提到系统的稳定性和用户体验。

一般情况,为了保证系统的稳定性,如果系统负载超过阈值,就会阻止用户请求,这会影响用户体验,而如果使用消息队列将请求缓存起来,等待系统处理完毕后通知用户下单完毕,这样总比不能下单体验要好。

业务系统正常时段的QPS如果是1000,流量最高峰是10000,为了应对流量高峰配置高性能的服务器显然不划算,这时可以使用消息队列对峰值流量削峰

  • 数据分发

如上图B、C、D、E系统需要使用A系统的数据,A系统就得手动的编写调用代码。如果日后再有F、G系统也需要A系统的数据的话,A系统还得重新编写代码。工作量十分大。

通过消息队列可以让数据在多个系统更加之间进行流通。数据的产生方不需要关心谁来使用数据,只需要将数据发送到消息队列,数据使用方直接在消息队列中直接获取数据即可

MQ的优点和缺点

**优点:**解耦、削峰、数据分发

缺点:

  • 系统可用性降低

    系统引入的外部依赖越多,系统稳定性越差。一旦MQ宕机,就会对业务造成影响。

    如何保证MQ的高可用?

  • 系统复杂度提高

    MQ的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过MQ进行异步调用。

    如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?

  • 一致性问题

    A系统处理完业务,通过MQ给B、C、D三个系统发消息数据,如果B系统、C系统处理成功,D系统处理失败。

    如何保证消息数据处理的一致性?

各种MQ产品的比较

二、RocketMQ快速入门

RocketMQ是阿里巴巴2016年MQ中间件,使用Java语言开发,在阿里内部,RocketMQ承接了例如“双11”等高并发场景的消息流转,能够处理万亿级别的消息。

准备工作

  • 下载RocketMQ

    RocketMQ最新版本:4.5.1

    下载地址

    1602501565295

  • 环境要求

    • Linux64位系统
    • JDK1.8(64位)
    • 源码安装需要安装Maven 3.2.x

安装RocketMQ

  • 安装步骤

    解压安装包

    # 安装解压命令
    yum install -y unzip zip
    # 解压
    unzip rocketmq-all-4.5.1-bin-release.zip
    

    进入安装目录

    cd rocketmq-all-4.5.1-bin-release
    
  • 目录介绍

    • bin:启动脚本,包括shell脚本和CMD脚本
    • conf:实例配置文件 ,包括broker配置文件、logback配置文件等
    • lib:依赖jar包,包括Netty、commons-lang、FastJSON等

启动RocketMQ

启动NameServer

# 1.启动NameServer
nohup sh mqnamesrv &
# 2.查看启动日志
tail -f ~/logs/rocketmqlogs/namesrv.log

启动Broker

# 1.启动Broker
nohup sh mqbroker -n localhost:9876 &
# 2.查看启动日志
tail -f ~/logs/rocketmqlogs/broker.log 
  • 问题描述:

    RocketMQ默认的虚拟机内存较大,启动Broker如果因为内存不足失败,需要编辑如下两个配置文件,修改JVM内存大小

# 编辑runbroker.sh和runserver.sh修改默认JVM大小
vi runbroker.sh
vi runserver.sh

1602502345797

  • 参考设置:

JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"

测试RocketMQ

  • 发送消息

    # 1.设置环境变量
    export NAMESRV_ADDR=localhost:9876
    # 2.使用安装包的Demo发送消息
    sh tools.sh org.apache.rocketmq.example.quickstart.Producer
    
  • 接收消息

    # 1.设置环境变量
    export NAMESRV_ADDR=localhost:9876
    # 2.接收消息
    sh tools.sh org.apache.rocketmq.example.quickstart.Consumer
    
  • 错误解决

    # 如果发生错误,先查看当前目录下的nohup.out日志文件,再查看~/logs/下面的日志文件
    # 如果启动失败,很有可能是Java环境变量的问题,还有就是内存大小的问题
    # 如果在测试的时候出现错误,很有可能是内存不足导致的,可以使用命令free -h来查看剩余的可用内存
    
  • 测试效果

a

关闭RocketMQ

# 1.关闭NameServer
sh mqshutdown namesrv
# 2.关闭Broker
sh mqshutdown broker

三、RocketMQ集群搭建

各种角色的简介

  • Producer:消息的发送者;举例:发信者
  • Consumer:消息接收者;举例:收信者
  • Broker:暂存和传输消息;举例:邮局
  • NameServer:管理Broker;举例:各个邮局的管理机构
  • Topic:区分消息的种类;一个发送者可以发送消息给一个或者多个Topic;一个消息的接收者可以订阅一个或者多个Topic消息
  • Message Queue:相当于是Topic的分区;用于并行发送和接收消息

1602554275946

集群搭建的方式

  • 集群的特点

    • Name Server是无状态的,因为每一个Broker都会向所有的Name Server发送消息,因此Name Server之间的数据是一致的,不需要做数据同步,因此搭建Name Server的集群只需要启动多个Name Server即可。

    • Broker集群通常是按组出现的,一个组内有一个主节点和多个从节点,主节点负责写入数据,从节点负责读取数据。Broker通过Broker Name来区分是否为一组,如果Broker Name相同,则为一组。通过Broker ID来区分主从节点,如果Broker ID为0,则为主节点。

    • Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。

    • Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。

  • 集群的模式

    ① 单Master模式

    这种方式风险较大,一旦Broker重启或者宕机时,会导致整个服务不可用。不建议线上环境使用,可以用于本地测试。

    ② 多Master模式

    一个集群无Slave,全是Master,例如2个Master或者3个Master,这种模式的优缺点如下:

    • 优点:配置简单,单个Master宕机或重启维护对应用无影响,在磁盘配置为RAID10时,即使机器宕机不可恢复情况下,由于RAID10磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢),性能最高;
    • 缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到影响。

    ③ 多Master多Slave模式(异步)

    每个Master配置一个Slave,有多对Master-Slave,HA采用异步复制方式,主备有短暂消息延迟(毫秒级),这种模式的优缺点如下:

    • 优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,同时Master宕机后,消费者仍然可以从Slave消费,而且此过程对应用透明,不需要人工干预,性能同多Master模式几乎一样;
    • 缺点:Master宕机,磁盘损坏情况下会丢失少量消息。

    ④ 多Master多Slave模式(同步)

    每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,即只有主备都写成功,才向应用返回成功,这种模式的优缺点如下:

    • 优点:数据与服务都无单点故障,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高;
    • 缺点:性能比异步复制模式略低(大约低10%左右),发送单个消息的RT会略高,且目前版本在主节点宕机后,备机不能自动切换为主机。

双主双从集群搭建

工作流程

  1. 启动NameServer,NameServer起来后监听端口,等待Broker、Producer、Consumer连上来,相当于一个路由控制中心。
  2. Broker启动,跟所有的NameServer保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。
  3. 收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。
  4. Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,轮询从队列列表中选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。
  5. Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息。

准备工作

以下的操作需要在两台服务器上都要执行

  • 服务器

    序号IP角色架构模式
    1192.168.25.135nameserver、brokerserverMaster1、Slave2
    2192.168.25.138nameserver、brokerserverMaster2、Slave1
  • Host添加信息

    vim /etc/hosts
    

    配置如下:

    # nameserver
    10.180.150.56 rocketmq-nameserver1
    116.62.119.24 rocketmq-nameserver2
    # broker
    10.180.150.56 rocketmq-master1
    10.180.150.56 rocketmq-slave2
    116.62.119.24 rocketmq-master2
    116.62.119.24 rocketmq-slave1
    

    配置完成后, 重启网卡

    systemctl restart network
    
  • 防火墙配置

    # 关闭防火墙
    systemctl stop firewalld.service 
    # 查看防火墙的状态
    firewall-cmd --state 
    # 禁止firewall开机启动
    systemctl disable firewalld.service
    

    RocketMQ默认使用3个端口:9876 、10911 、11011 。如果防火墙没有关闭的话,那么防火墙就必须开放这些端口:

    nameserver 默认使用 9876 端口

    master 默认使用 10911 端口

    slave 默认使用11011 端口

  • 环境变量配置

    vim /etc/profile
    

    在profile文件的末尾加入如下命令

    #set rocketmq
    ROCKETMQ_HOME=/usr/local/rocketmq/rocketmq-all-4.4.0-bin-release
    PATH=$PATH:$ROCKETMQ_HOME/bin
    export ROCKETMQ_HOME PATH
    

    输入:wq! 保存并退出, 并使得配置立刻生效:

    source /etc/profile
    
  • 创建消息存储路径

    mkdir -p /usr/local/rocketmq/store2
    mkdir -p /usr/local/rocketmq/store2/commitlog
    mkdir -p /usr/local/rocketmq/store2/consumequeue
    mkdir -p /usr/local/rocketmq/store2/index
    

broker配置文件

服务器:10.180.150.56

vi /usr/soft/rocketmq/conf/2m-2s-sync/broker-a.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=0
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
vi /usr/soft/rocketmq/conf/2m-2s-sync/broker-b-s.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=1
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=11011
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SLAVE
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128

服务器:116.62.119.24

vi /usr/soft/rocketmq/conf/2m-2s-sync/broker-b.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=0
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
vi /usr/soft/rocketmq/conf/2m-2s-sync/broker-a-s.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=1
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=11011
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SLAVE
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128

修改启动脚本

# 下面两个文件分别进行加载
vi /usr/local/rocketmq/bin/runbroker.sh
vim /usr/local/rocketmq/bin/runserver.sh

需要根据内存大小进行适当的对JVM参数进行调整:

#===================================================
# 开发环境配置 JVM Configuration
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"

服务启动

  • 启动NameServer集群

    分别在10.180.150.56和116.62.119.24启动NameServer

    cd /usr/local/rocketmq/bin
    nohup sh mqnamesrv &
    
  • 启动Broker集群

    在10.180.150.56上启动master1slave2`

    master1

    cd /usr/local/rocketmq/bin
    nohup sh mqbroker -c /home/software/rocketmq-all-4.5.1-bin-release/conf/2m-2s-sync/broker-a.properties &
    

    slave2:

    cd /usr/local/rocketmq/bin
    nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-b-s.properties &
    

    在116.62.119.24上启动master2和slave1

    master2

    cd /usr/local/rocketmq/bin
    nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-b.properties &
    

    slave1

    cd /usr/local/rocketmq/bin
    nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-a-s.properties &
    
  • 出现的问题

    java.lang.RuntimeException: Lock failed,MQ already started
    	at org.apache.rocketmq.store.DefaultMessageStore.start(DefaultMessageStore.java:220)
    	at org.apache.rocketmq.broker.BrokerController.start(BrokerController.java:824)
    	at org.apache.rocketmq.broker.BrokerStartup.start(BrokerStartup.java:64)
    	at org.apache.rocketmq.broker.BrokerStartup.main(BrokerStartup.java:58)
    

    解决方法:我们要启动的每一个broker要指定不一样的storePath 路径,也就是在配置文件中修改即可

  • 启动后使用JPS命令查看进程

    1602573883260

集群监控平台搭建

RocketMQ有一个对其扩展的开源项目incubator-rocketmq-externals,这个项目中有一个子模块叫rocketmq-console,这个便是管理控制台项目了,先将incubator-rocketmq-externals拉到本地,因为我们需要自己对rocketmq-console进行编译打包运行。

  • 下载编译并打包

    git clone https://github.com/apache/rocketmq-externals
    cd rocketmq-console
    mvn clean package -Dmaven.test.skip=true
    

    注意:打包前在rocketmq-console中配置namesrv集群地址,需要修改项目的application.yaml文件:

    rocketmq.config.namesrvAddr=192.168.25.135:9876;192.168.25.138:9876
    

    启动rocketmq-console:

    java -jar rocketmq-console-ng-1.0.0.jar
    

    动成功后,我们就可以通过浏览器访问http://localhost:8080进入控制台界面了,如下图:

四、消息发送案例

  • 导入MQ客户端依赖

    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-client</artifactId>
        <version>4.4.0</version>
    </dependency>
    
  • 消息发送者步骤分析

    1.创建消息生产者producer,并制定生产者组名
    2.指定Nameserver地址
    3.启动producer
    4.创建消息对象,指定主题Topic、Tag和消息体
    5.发送消息
    6.关闭生产者producer
    
  • 消息消费者步骤分析

    1.创建消费者Consumer,制定消费者组名
    2.指定Nameserver地址
    3.订阅主题Topic和Tag
    4.设置回调函数,处理消息
    5.启动消费者consumer
    

基本案例

发送同步消息

同步的方式可靠性强,比如重要的消息通知短信通知等

    public static void main(String[] args) throws Exception {
        // 1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        // 2.指定Nameserver地址
        producer.setNamesrvAddr("116.62.119.24:9876");
        // 如果连接阿里云服务器需要添加下面一行代码
        producer.setVipChannelEnabled(false);
        // 3.启动producer
        producer.start();
        Message msg;
        SendResult sendResult;
        for (int i = 0; i < 100; i++) {
            // 4.创建消息对象,指定主题Topic、Tag和消息体
            msg = new Message("TopicTest", "TagA",("Hello RocketMQ " + i).getBytes());
            // 5.发送消息到一个Broker
            sendResult = producer.send(msg);
            // 通过sendResult返回消息是否成功送达
            System.out.printf("%s%n", sendResult);
        }
        // 6.关闭生产者producer
        producer.shutdown();
    }

发送异步消息

异步消息通常用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker的响应。

注意如果虚拟机中安装的RocketMQ报错的话,注意加上Thread.sleep(500);

    public static void main(String[] args) throws Exception {
        // 1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        // 2.指定Nameserver地址
        producer.setNamesrvAddr("116.62.119.24:9876");
        // 如果连接阿里云服务器需要添加下面一行代码
        producer.setVipChannelEnabled(false);
        // 3.启动producer
        producer.start();
        Message msg;
        SendResult sendResult;
        for (int i = 0; i < 100; i++) {
            // 4.创建消息对象,指定主题Topic、Tag和消息体
            msg = new Message("TopicTest", "TagA",("Hello RocketMQ " + i).getBytes());
            // 5.发送消息到一个Broker
            producer.send(msg, new SendCallback() {
                //执行成功的回调函数
                @Override
                public void onSuccess(SendResult sendResult) {
                    System.out.println(sendResult);
                }
                //抛出异常的回调
                @Override
                public void onException(Throwable throwable) {
                    System.out.println("抛出异常" + throwable);
                }
            });
            Thread.sleep(500);
        }
        // 6.关闭生产者producer
        producer.shutdown();
    }

发送单向消息

这种方式主要用在不特别关心发送结果的场景,例如日志发送。

    public static void main(String[] args) throws Exception {
        // 1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        // 2.指定Nameserver地址
        producer.setNamesrvAddr("116.62.119.24:9876");
        // 如果连接阿里云服务器需要添加下面一行代码
        producer.setVipChannelEnabled(false);
        // 3.启动producer
        producer.start();
        Message msg;
        SendResult sendResult;
        for (int i = 0; i < 100; i++) {
            // 4.创建消息对象,指定主题Topic、Tag和消息体
            msg = new Message("TopicTest", "TagA",("Hello RocketMQ " + i).getBytes());
            // 5.发送消息到一个Broker
            producer.sendOneway(msg);
            Thread.sleep(500);
        }
        // 6.关闭生产者producer
        producer.shutdown();
    }

消费消息

负载均衡

消费者采用负载均衡方式消费消息,多个消费者共同消费队列消息,每个消费者处理的消息不同

   public static void main(String[] args) throws Exception {
        // 1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        // 2.指定Nameserver地址
        producer.setNamesrvAddr("116.62.119.24:9876");
        // 如果连接阿里云服务器需要添加下面一行代码
        producer.setVipChannelEnabled(false);
       //负载均衡模式消费
    	consumer.setMessageModel(MessageModel.CLUSTERING);
        // 3.启动producer
        producer.start();
        Message msg;
        SendResult sendResult;
        for (int i = 0; i < 100; i++) {
            // 4.创建消息对象,指定主题Topic、Tag和消息体
            msg = new Message("TopicTest", "TagA",("Hello RocketMQ " + i).getBytes());
            // 5.发送消息到一个Broker
            producer.send(msg, new SendCallback() {
                //执行成功的回调函数
                @Override
                public void onSuccess(SendResult sendResult) {
                    System.out.println(sendResult);
                }
                //抛出异常的回调
                @Override
                public void onException(Throwable throwable) {
                    System.out.println("抛出异常" + throwable);
                }
            });
            Thread.sleep(500);
        }
        // 6.关闭生产者producer
        producer.shutdown();
    }

广播模式

消费者采用广播的方式消费消息,每个消费者消费的消息都是相同的

    public static void main(String[] args) throws Exception {
       // 1.创建消费者Consumer,制定消费者组名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
       // 2.指定Nameserver地址
        consumer.setNamesrvAddr("116.62.119.24:9876");
       // 3.订阅主题Topic和Tag
        consumer.subscribe("TopicTest","TagA");
        //阿里云服务器
        consumer.setVipChannelEnabled(false);
        //负载均衡模式消费
        consumer.setMessageModel(MessageModel.BROADCASTING);
       // 4.设置回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt msg : list) {
                    System.out.println(new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
       // 5.启动消费者consumer
        consumer.start();
    }

顺序消息

  • 原理

    1602637742713

虽然MQ底层使用的是先进先出的队列,但是Broker中有很多个队列。当生产者依次进行发送消息时,消息被放在Broker中的不同队列中,从而提高了消息消费的效率,但是这样操作也会导致消费者消费消息时顺序不一致。

因此提出了全局消息一致局部消息一致

全局消息一致:保证broker中队列顺序的一致性

局部消息一致:将想要保证消息一致的消息全部放到一个队列中,如下图所示

1602638258790

顺序消息生产

public class Producer {

    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        producer.setNamesrvAddr("116.62.119.24:9876");
        producer.setVipChannelEnabled(false);
        producer.start();
        List<OrderStep> orderSteps = OrderStep.buildOrders();
        // 发送消息
        for (int i=0;i<orderSteps.size();i++) {
            String body = orderSteps.get(i)+  "";
            Message message = new Message("TopicA", "orders", "i=" + i, body.getBytes());
            /*
            * 参数一: 消息对象
            * 参数二:消息队列的选择器
            * 参数三:选择队列的业务标识(这里是订单ID)
             */
            SendResult send = producer.send(message, new MessageQueueSelector() {
                /**
                 * @param list 队列集合
                 * @param message 消息对象
                 * @param o 业务标识的参数
                 * @return
                 */
                @Override
                public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
                    long orderId = (long) o;
                    long index = orderId % list.size();
                    return list.get((int) index);
                }
            }, orderSteps.get(i).getOrderId());
            System.out.println("发送结果为:" + send);
        }
        producer.shutdown();
    }
}

public class OrderStep {
    /**
     * 订单的步骤
     */
        private long orderId;
        private String desc;

        public long getOrderId() {
            return orderId;
        }

        public void setOrderId(long orderId) {
            this.orderId = orderId;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        @Override
        public String toString() {
            return "OrderStep{" +
                    "orderId=" + orderId +
                    ", desc='" + desc + '\'' +
                    '}';
        }

    /**
     * 生成模拟订单数据
     */
    public static List<OrderStep> buildOrders() {
        List<OrderStep> orderList = new ArrayList<OrderStep>();

        OrderStep orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111039L);
        orderDemo.setDesc("创建");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111065L);
        orderDemo.setDesc("创建");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111039L);
        orderDemo.setDesc("付款");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103117235L);
        orderDemo.setDesc("创建");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111065L);
        orderDemo.setDesc("付款");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103117235L);
        orderDemo.setDesc("付款");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111065L);
        orderDemo.setDesc("完成");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111039L);
        orderDemo.setDesc("推送");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103117235L);
        orderDemo.setDesc("完成");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111039L);
        orderDemo.setDesc("完成");
        orderList.add(orderDemo);

        return orderList;
    }
}

顺序消息消费

public class ConsumerInOrder {
    public static void main(String[] args) throws Exception {
        // 1.创建消费者Consumer,制定消费者组名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        // 2.指定Nameserver地址
        consumer.setNamesrvAddr("10.180.150.56:9876");
        // 3.订阅主题Topic和Tag
        consumer.subscribe("TopicA","orders");
        //阿里云服务器
        consumer.setVipChannelEnabled(false);
        consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
                for (MessageExt msg : list) {
                    System.out.println("线程:【" + Thread.currentThread().getName() + "】 消费消息:"+ new String(msg.getBody()));
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });
        consumer.start();
        System.out.println("消费者启动");
    }
}

延时消息

比如电商里,提交了一个订单就可以发送一个延时消息,1h后去检查这个订单的状态,如果还是未付款就取消订单释放库存。

public static void main(String[] args) throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        producer.setNamesrvAddr("116.62.119.24:9876");
        // 如果连接阿里云服务器需要添加下面一行代码
        producer.setVipChannelEnabled(false);
        producer.start();
        Message msg;
        SendResult sendResult;
        for (int i = 0; i < 100; i++) {
            // 4.创建消息对象,指定主题Topic、Tag和消息体
            msg = new Message("TopicTest", "TagA",("Hello RocketMQ " + i).getBytes());
            //设置延迟消息级别
            msg.setDelayTimeLevel(2);
            sendResult = producer.send(msg);
            // 通过sendResult返回消息是否成功送达
            System.out.printf("%s%n", sendResult);
        }
        // 6.关闭生产者producer
        producer.shutdown();
    }

上面代码就比普通的发送消息增加了以下代码:

 //设置延迟消息级别
 msg.setDelayTimeLevel(2);

延迟级别为以下几个级别

// org/apache/rocketmq/store/config/MessageStoreConfig.java
private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";

现在RocketMq并不支持任意时间的延时,需要设置几个固定的延时等级,从1s到2h分别对应着等级1到18

批量发送

如果每次只发送不超过4MB的消息,则很容易使用批处理,样例如下:

若果超过4M需要进行消息的截取

    public static void main(String[] args) throws Exception{
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        producer.setNamesrvAddr("116.62.119.24:9876");
        // 如果连接阿里云服务器需要添加下面一行代码
        producer.setVipChannelEnabled(false);
        producer.start();
        List<Message> list = new ArrayList<>();
        Message msg1 = new Message("BatchTopic","Tag1","Hello Word 1".getBytes());
        Message msg2 = new Message("BatchTopic","Tag1","Hello Word 2".getBytes());
        Message msg3 = new Message("BatchTopic","Tag1","Hello Word 3".getBytes());
        list.add(msg1);
        list.add(msg2);
        list.add(msg3);
        SendResult send = producer.send(list);
        System.out.println("发送结果:" + send);
        // 6.关闭生产者producer
        producer.shutdown();
    }

过滤消息

在大多数情况下,TAG是一个简单而有用的设计,其可以来选择您想要的消息。例如:

DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_EXAMPLE");
consumer.subscribe("TOPIC", "TAGA || TAGB || TAGC");

也可以使用SQL表达式筛选消息。SQL特性可以通过发送消息时的属性来进行计算。在RocketMQ定义的语法下,可以实现一些简单的逻辑。下面是一个例子:

------------
| message  |
|----------|  a > 5 AND b = 'abc'
| a = 10   |  --------------------> Gotten
| b = 'abc'|
| c = true |
------------
------------
| message  |
|----------|   a > 5 AND b = 'abc'
| a = 1    |  --------------------> Missed
| b = 'abc'|
| c = true |
------------

生产者中使用:

msg.putUserProperty("i",String.valueOf(i));

消费者中使用:

consumer.subscribe("Fillter1", MessageSelector.bySql("i>2"));

事务消息

执行流程

事务消息指的是一组消息存在着事务,当消息被生产者提交到Broker时,此时是不能被消费的,称为半消息。当生产者发送了提交或者是回滚的时候,这一组消息才能被消费。如果生产者发送的是回滚或者是长时间没有响应的话那么Broker会删除这条消息。

(1) 发送消息(half消息)。

(2) 服务端响应消息写入结果。

(3) 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)。

(4) 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)

事务的三种状态

事务消息共有三种状态,提交状态、回滚状态、中间状态

事务消息生产者

public static void main(String[] args) throws Exception{
        TransactionMQProducer producer = new TransactionMQProducer("group2");
        producer.setNamesrvAddr("116.62.119.24:9876");
        producer.setVipChannelEnabled(false);
        //添加事务的监听器
        producer.setTransactionListener(new TransactionListener() {
            //执行本地事务
            @Override
            public LocalTransactionState executeLocalTransaction(Message message, Object o) {
                if (StringUtils.equals("TAG1", message.getTags())) {
                    return LocalTransactionState.COMMIT_MESSAGE;
                } else if (StringUtils.equals("TAG2", message.getTags())) {
                    return LocalTransactionState.ROLLBACK_MESSAGE;
                } else {
                    return LocalTransactionState.UNKNOW;
                }
            }
            //进行消息事务的状态回查
            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
                System.out.println("MQ检查消息Tag【"+messageExt.getTags()+"】的本地事务执行结果");
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        });
        producer.start();
        String []tags = {"TAG1","TAG2","TAG3"};
        for (int i = 0; i < 2; i++) {
            Message msg = new Message("Transaction", tags[i],("Hello RocketMQ " + i).getBytes());
            //第二个参数为可以将事务控制应用到某一个参数上面,也可以应用到全部消息上  应用到全部上传个null即可
            TransactionSendResult result = producer.sendMessageInTransaction(msg, null);
            System.out.printf("发送结果:" + result);
            Thread.sleep(500);
        }
    }
一、rocketmq入门到精通视频教程目录大纲 001-001_RocketMQ_简介 002-002_RocketMQ_核心概念详解 003-003_RocketMQ_集群构建模型详解(一) 004-004_RocketMQ_集群构建模型详解(二) 005-005_RocketMQ_双主模式集群环境搭建 006-006_RocketMQ_控制台使用讲解 007-007_RocketMQ_Broker配置文件详解 008-008_RocketMQ_helloworld示例讲解 009-009_RocketMQ_整体架构概述详解 010-010_RocketMQ_Producer_API详解 011-011_RocketMQ_Producer_顺序消费机制详解 012-012_RocketMQ_Producer_事务消息机制详解 013-013_RocketMQ_Consumer_Push和Pull模式及使用详解 014-014_RocketMQ_Consumer_配置参数详解 015-015_RocketMQ_Consumer_重试策略详解 016-016_RocketMQ_Consumer_幂等去重策略详解 017-017_RocketMQ_消息模式及使用讲解 018-018_RocketMQ_双主双从集群环境搭建与使用详解 019-019_RocketMQ_FilterServer机制及使用详解 020-020_RocketMQ_管理员命令 二、rocketmq实战视频教程目录大纲 01_rocketmq_实战项目介绍 02_rocketMQ实战项目设计(一) 03_rocketMQ实战项目设计(二) 04_rocketMQ实战-环境搭建(一) 05_rocketMQ实战-环境搭建(二) 06_rocketMQ实战-生产者与spring结合 07_rocketMQ实战-消费者与spring结合 08_rocketMQ实战-数据库模型设计 09_rocketMQ实战-数据库DAO代码生成 10_rocketMQ实战-远程RPC接口设计与实现(一) 11_rocketMQ实战-远程RPC接口设计与实现(二) 12_rocketMQ实战-远程RPC接口设计与实现(三) 13_rocketMQ实战-下单流程(一) 14_rocketMQ实战-下单流程(二) 15_rocketMQ实战-下单流程(三) 16_rocketMQ实战-下单流程(四) 17_rocketMQ实战-下单流程(五) 18_rocketMQ实战-下单流程(六) 19_rocketMQ实战-下单流程(七) 20_rocketMQ实战-下单流程(八)-商品库存 21_rocketMQ实战-下单流程(九)-商品库存 22_rocketMQ实战-下单流程(十)-支付模块 23_rocketMQ实战-整体联调
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超人不会飞aa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值