跟随尚硅谷雷老师的讲解,迈入RocketMQ的学习中。
MQ,Message Queue,是一种提供消息队列服务的中间件,也称为消息中间件,是一套提供了消息生产、存储、消费全过程API的软件系统。消息即数据。一般消息的体量不会很大。
从网上可以查看到很多的关于MQ用途的叙述,但总结起来其实就以下三点。
异步解耦
数据收集
分布式系统会产生海量级数据流,如:业务日志、监控数据、用户行为等。针对这些数据流进行实时或批量采集汇总,然后对这些数据流进行大数据分析,这是当前互联网平台的必备技术。通过MQ完成此类数据收集是最好的选择。
一般情况下MQ的实现是要遵循一些常规性协议的。常见的协议如下:
JMS
JMS,Java Messaging Service(Java消息服务)。是Java平台上有关MOM(Message OrientedMiddleware,面向消息的中间件 PO/OO/AO)的技术规范,它便于消息系统中的Java应用程序进行消息交换,并且通过提供标准的产生、发送、接收消息的接口,简化企业应用的开发。ActiveMQ是该协议的典型实现。
STOMP
STOMP,Streaming Text Orientated Message Protocol(面向流文本的消息协议),是一种MOM设计的简单文本协议。STOMP提供一个可互操作的连接格式,允许客户端与任意STOMP消息代理(Broker)进行交互。ActiveMQ是该协议的典型实现,RabbitMQ通过插件可以支持该协议。
AMQP
AMQP,Advanced Message Queuing Protocol(高级消息队列协议),一个提供统一消息服务的应用层标准,是应用层协议的一个开放标准,是一种MOM设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制。 RabbitMQ是该协议的典型实现。
MQTT
MQTT,Message Queuing Telemetry Transport(消息队列遥测传输),是IBM开发的一个即时通讯协议,是一种二进制协议,主要用于服务器和低功耗IoT(物联网)设备间的通信。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和致动器的通信协议。 RabbitMQ通过插件可以支持该协议。
消息是指,消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题。
Topic表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。 topic:message 1:n message:topic 1:1。
一个生产者可以同时发送多种Topic的消息;而一个消费者只对某种特定的Topic感兴趣,即只可以订阅和消费一种Topic的消息。 producer:topic 1:n consumer:topic 1:1。
为消息设置的标签,用于同一主题下区分不同类型的消息。来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑,实现更好的扩展性。
Topic是消息的一级分类,Tag是消息的二级分类。
存储消息的物理实体。一个Topic中可以包含多个Queue,每个Queue中存放的就是该Topic的消息。一个Topic的Queue也被称为一个Topic中消息的分区(Partition)。
一个Topic的Queue中的消息只能被一个消费者组中的一个消费者消费。一个Queue中的消息不允许同一个消费者组中的多个消费者同时费。
在学习参考其它相关资料时,还会看到一个概念:分片(Sharding)。分片不同于分区。在RocketMQ中,分片指的是存放相应Topic的Broker。每个分片中会创建出相应数量的分区,即Queue,每个Queue的大小都是相同的。
修改runserver.sh
使用vim命令打开bin/runserver.sh文件。现将这些值修改为如下:
> nohup sh bin/mqnamesrv &
> tail -f ~/logs/rocketmqlogs/namesrv.log
The Name Server boot success...
启动broker
> nohup sh bin/mqbroker -n localhost:9876 &
> tail -f ~/logs/rocketmqlogs/broker.log
The broker[%s, 172.30.30.233:10911] boot success...
发送消息
export NAMESRV_ADDR=localhost:9876
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
接收消息
sh bin/tools.sh org.apache.rocketmq.example.1 quickstart.Consumer
无论是关闭name server还是broker,都是使用bin/mqshutdown命令。
[root@mqOS rocketmq]# sh bin/mqshutdown broker
The mqbroker(1740) is running...
Send shutdown request to mqbroker(1740) OK
[root@mqOS rocketmq]# sh bin/mqshutdown namesrv
The mqnamesrv(1692) is running...
Send shutdown request to mqnamesrv(1692) OK
[2]+ 退出 143 nohup sh bin/mqbroker -n localhost:98
RocketMQ有一个可视化的dashboard,通过该控制台可以直观的查看到很多数据。
console项目取消了,从这里下载最新的。https://github.com/apache/rocketmq-dashboard
注意上面的namesrvAddr地址如果没有配置映射的话,需要写ip地址。
javax.xml.bind
jaxb-api
2.3.0
com.sun.xml.bind
jaxb-impl
2.3.0
com.sun.xml.bind
jaxb-core
2.3.0
javax.activation
activation
1.1.1
mvn clean package -Dmaven.test.skip=true
# 指定整个broker集群的名称,或者说是RocketMQ集群的名称
brokerClusterName=DefaultCluster
# 指定master-slave集群的名称。一个RocketMQ集群可以包含多个master-slave集群
brokerName=broker-a
# master的brokerId为0
brokerId=0
# 指定删除消息存储过期文件的时间为凌晨4点
deleteWhen=04
# 指定未发生更新的消息存储文件的保留时长为48小时,48小时后过期,将会被删除
fileReservedTime=48
# 指定当前broker为异步复制master
brokerRole=ASYNC_MASTER
# 指定刷盘策略为异步刷盘
flushDiskType=ASYNC_FLUSH
# 指定Name Server的地址
namesrvAddr=192.168.119.150:9876;192.168.119.160:9876
修改broker-b-s.properties
将该配置文件内容修改为如下:
brokerClusterName=DefaultCluster
# 指定这是另外一个master-slave集群
brokerName=broker-b
# slave的brokerId为非0
brokerId=1
deleteWhen=04
fileReservedTime=48
# 指定当前broker为slave
brokerRole=SLAVE
flushDiskType=ASYNC_FLUSH
namesrvAddr=192.168.119.150:9876;192.168.119.160:9876
# 指定Broker对外提供服务的端口,即Broker与producer与consumer通信的端口。默认
10911。由于当前主机同时充当着master1与slave2,而前面的master1使用的是默认端口。这
里需要将这两个端口加以区分,以区分出master1与slave2
listenPort=11911
# 指定消息存储相关的路径。默认路径为~/store目录。由于当前主机同时充当着master1与
slave2,master1使用的是默认路径,这里就需要再指定一个不同路径
storePathRootDir=~/store-s
storePathCommitLog=~/store-s/commitlog
storePathConsumeQueue=~/store-s/consumequeue
storePathIndex=~/store-s/index
storeCheckpoint=~/store-s/checkpoint
abortFile=~/store-s/abort
其他配置
除了以上配置外,这些配置文件中还可以设置其它属性。
#指定整个broker集群的名称,或者说是RocketMQ集群的名称
brokerClusterName=rocket-MS
#指定master-slave集群的名称。一个RocketMQ集群可以包含多个master-slave集群
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=0
#nameServer地址,分号分割
namesrvAddr=nameserver1:9876;nameserver2:9876
#默认为新建Topic所创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议生产环境中关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议生产环境中关闭
autoCreateSubscriptionGroup=true
#Broker对外提供服务的端口,即Broker与producer与consumer通信的端口
listenPort=10911
#HA高可用监听端口,即Master与Slave间通信的端口,默认值为listenPort+1
haListenPort=10912
#指定删除消息存储过期文件的时间为凌晨4点
deleteWhen=04
#指定未发生更新的消息存储文件的保留时长为48小时,48小时后过期,将会被删除
fileReservedTime=48
#指定commitLog目录中每个文件的大小,默认1G
mapedFileSizeCommitLog=1073741824
#指定ConsumeQueue的每个Topic的每个Queue文件中可以存放的消息数量,默认30w条
mapedFileSizeConsumeQueue=300000
#在清除过期文件时,如果该文件被其他线程所占用(引用数大于0,比如读取消息),此时会阻止
#此次删除任务,同时在第一次试图删除该文件时记录当前时间戳。该属性则表示从第一次拒绝删除
#后开始计时,该文件最多可以保留的时长。在此时间内若引用数仍不为0,则删除仍会被拒绝。不过
#时间到后,文件将被强制删除
destroyMapedFileIntervalForcibly=120000
#指定commitlog、consumequeue所在磁盘分区的最大使用率,超过该值,则需立即清除过期文
件
diskMaxUsedSpaceRatio=88
#指定store目录的路径,默认在当前用户主目录中
storePathRootDir=/usr/local/rocketmq-all-4.5.0/store
#commitLog目录路径
storePathCommitLog=/usr/local/rocketmq-all-4.5.0/store/commitlog
#consumeueue目录路径
storePathConsumeQueue=/usr/local/rocketmq-all-4.5.0/store/consumequeue
#index目录路径
storePathIndex=/usr/local/rocketmq-all-4.5.0/store/index
#checkpoint文件路径
storeCheckpoint=/usr/local/rocketmq-all-4.5.0/store/checkpoint
#abort文件路径
abortFile=/usr/local/rocketmq-all-4.5.0/store/abort
#指定消息的最大大小
maxMessageSize=65536
#Broker的角色
# - ASYNC_MASTER 异步复制Master
# - SYNC_MASTER 同步双写Master
# - SLAVE
brokerRole=SYNC_MASTER
#刷盘策略
# - ASYNC_FLUSH 异步刷盘
# - SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#发消息线程池数量
sendMessageThreadPoolNums=128
#拉消息线程池数量
pullMessageThreadPoolNums=128
#强制指定本机IP,需要根据每台机器进行修改。官方介绍可为空,系统默认自动识别,但多网卡
时IP地址可能读取错误
brokerIP1=192.168.3.105
brokerClusterName=DefaultCluster
brokerName=broker-b
brokerId=0
deleteWhen=04
fileReservedTime=48
brokerRole=ASYNC_MASTER
flushDiskType=ASYNC_FLUSH
namesrvAddr=192.168.59.164:9876;192.168.59.165:9876
修改broker-a-s.properties
将该配置文件内容修改为如下:
brokerClusterName=DefaultCluster
brokerName=broker-a
brokerId=1
deleteWhen=04
fileReservedTime=48
brokerRole=SLAVE
flushDiskType=ASYNC_FLUSH
namesrvAddr=192.168.59.164:9876;192.168.59.165:9876
listenPort=11911
storePathRootDir=~/store-s
storePathCommitLog=~/store-s/commitlog
storePathConsumeQueue=~/store-s/consumequeue
storePathIndex=~/store-s/index
storeCheckpoint=~/store-s/checkpoint
abortFile=~/store-s/abort
6 启动服务器
启动NameServer集群
分别启动rocketmqOS1与rocketmqOS2两个主机中的NameServer。启动命令完全相同。
nohup sh bin/mqnamesrv &
tail -f ~/logs/rocketmqlogs/namesrv.log
启动两个Master
分别启动rocketmqOS1与rocketmqOS2两个主机中的broker master。注意,它们指定所要加载的配置文件是不同的。
nohup sh bin/mqbroker -c conf/2m-2s-async/broker-a.properties &
tail -f ~/logs/rocketmqlogs/broker.log
nohup sh bin/mqbroker -c conf/2m-2s-async/broker-b.properties &
tail -f ~/logs/rocketmqlogs/broker.log
启动两个Slave
分别启动rocketmqOS1与rocketmqOS2两个主机中的broker slave。注意,它们指定所要加载的配置文件是不同的。
nohup sh bin/mqbroker -c conf/2m-2s-async/broker-b-s.properties &
tail -f ~/logs/rocketmqlogs/broker.log
nohup sh bin/mqbroker -c conf/2m-2s-async/broker-a-s.properties &
tail -f ~/logs/rocketmqlogs/broker.log
在mq解压目录的bin目录下有一个mqadmin命令,该命令是一个运维指令,用于对mq的主题,集群,broker 等信息进行管理。
在运行mqadmin命令之前,先要修改mq解压目录下bin/tools.sh配置的JDK的ext目录位置。本机的ext
目录在/usr/java/jdk1.8.0_161/jre/lib/ext
。
使用vim命令打开tools.sh文件,并在JAVA_OPT配置的-Djava.ext.dirs这一行的后面添加ext的路径。
JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn256m -
XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m"
JAVA_OPT="${JAVA_OPT} -
Djava.ext.dirs=${BASE_DIR}/lib:${JAVA_HOME}/jre/lib/ext:${JAVA_HOME}/lib/
ext:/usr/java/jdk1.8.0_161/jre/lib/ext"
JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}"
直接运行该命令,可以看到其可以添加的commands。通过这些commands可以完成很多的功能。
[root@mqOS rocketmq-all-4.8.0-bin-release]# ./bin/mqadmin
The most commonly used mqadmin commands are:
updateTopic Update or create topic
deleteTopic Delete topic from broker and NameServer.
updateSubGroup Update or create subscription group
deleteSubGroup Delete subscription group from broker.
updateBrokerConfig Update broker's config
updateTopicPerm Update topic perm
topicRoute Examine topic route info
topicStatus Examine topic Status info
topicClusterList get cluster info for topic
brokerStatus Fetch broker runtime status data
queryMsgById Query Message by Id
queryMsgByKey Query Message by Key
queryMsgByUniqueKey Query Message by Unique key
queryMsgByOffset Query Message by offset
QueryMsgTraceById query a message trace
printMsg Print Message Detail
printMsgByQueue Print Message Detail
sendMsgStatus send msg to broker.
brokerConsumeStats Fetch broker consume stats data
producerConnection Query producer's socket connection and client
version
consumerConnection Query consumer's socket connection, client
version and subscription
consumerProgress Query consumers's progress, speed
consumerStatus Query consumer's internal data structure
cloneGroupOffset clone offset from other group.
clusterList List all of clusters
topicList Fetch all topic list from name server
updateKvConfig Create or update KV config.
deleteKvConfig Delete KV config.
wipeWritePerm Wipe write perm of broker in all name server
resetOffsetByTime Reset consumer offset by timestamp(without
client restart).
updateOrderConf Create or update or delete order conf
cleanExpiredCQ Clean expired ConsumeQueue on broker.
cleanUnusedTopic Clean unused topic on broker.
startMonitoring Start Monitoring
statsAll Topic and Consumer tps stats
allocateMQ Allocate MQ
checkMsgSendRT check message send response time
clusterRT List All clusters Message Send RT
getNamesrvConfig Get configs of name server.
updateNamesrvConfig Update configs of name server.
getBrokerConfig Get broker config by cluster or special broker!
queryCq Query cq command.
sendMessage Send a message
consumeMessage Consume message
updateAclConfig Update acl config yaml file in broker
deleteAccessConfig Delete Acl Config Account in broker
clusterAclConfigVersion List all of acl config version information in
cluster
updateGlobalWhiteAddr Update global white address for acl Config File
in broker
getAccessConfigSubCommand List all of acl config information in
cluster
该命令在官网中有详细的用法解释。
https://github.com/apache/rocketmq/blob/master/docs/cn/operation.md
<dependencies>
<dependency>
<groupId>org.apache.rocketmqgroupId>
<artifactId>rocketmq-clientartifactId>
<version>4.9.0version>
dependency>
dependencies>
定义同步消息发送生产者
/**
* 定义同步发送消息生产者
*/
public class SyncProducer {
public static void main(String[] args) throws Exception {
//创建一个producer,参数为producer group名称
DefaultMQProducer producer = new DefaultMQProducer("pg");
//指定nameserver地址
producer.setNamesrvAddr("192.168.119.100:9876");
//设置当发送失败时重试发送的次数,默认为2次
producer.setRetryTimesWhenSendFailed(3);
//设置发送超时时限为5s,默认为3s
producer.setSendMsgTimeout(5000);
//开启生产者
producer.start();
//生产者发送100条消息
for (int i = 0; i < 100; i++) {
byte[] body = ("hi" + i).getBytes();
Message msg = new Message("someTopic", "someTag", body);
//为消息指定key
msg.setKeys("key-"+i);
//发送消息
SendResult sendResult = producer.send(msg);
System.out.println(sendResult);
}
//关闭producer
producer.shutdown();
}
}
/**
* 定义异步发送消息生产者
*/
public class AsyncProducer {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("pg");
producer.setNamesrvAddr("192.168.119.100:9876");
//指定异步发送消息失败后不进行重试发送
producer.setRetryTimesWhenSendAsyncFailed(0);
//指定新创建的Topic的Queue数量为2,默认为4
producer.setDefaultTopicQueueNums(2);
//启动生产者
producer.start();
//生产消息
try {
for (int i = 0; i < 100; i++) {
byte[] body = ("hi"+i).getBytes();
Message msg = new Message("myTopic", "myTag", body);
//异步发送,指定回调
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println(sendResult);
}
@Override
public void onException(Throwable throwable) {
throwable.printStackTrace();
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
//适当将线程睡眠,否则消息都还没发送完就直接关闭生产了。
TimeUnit.SECONDS.sleep(3);
producer.shutdown();
}
}
定义单向消息发送生产者
/**
* 单向消息发送者
*/
public class OnewayProducer {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("pg");
producer.setNamesrvAddr("192.168.119.100:9876");
producer.start();
for (int i = 0; i < 100; i++) {
byte[] body = ("hi" + i).getBytes();
Message message = new Message("single", "someTag", body);
//单向发送
producer.sendOneway(message);
}
producer.shutdown();
System.out.println("producer shudown");
}
}
定义消息消费者
/**
* 定义消息消费者
*/
public class MessageConsumer {
public static void main(String[] args) throws Exception{
//定义一个pull消费者
// DefaultLitePullConsumer consumer = new DefaultLitePullConsumer("cg");
//定义一个push消费者(发布-订阅模式)
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("cg");
//指定nameServer
consumer.setNamesrvAddr("192.168.119.100:9876");
//指定从第一条消息开始消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//指定消费Topic与Tag
consumer.subscribe("someTopic","*");
//指定采用广播模式进行消费,默认为集群模式
// consumer.setMessageModel(MessageModel.BROADCASTING);
//注册消息监听器
//一旦broker中有了其订阅的消息就会触发该方法的执行
//其返回值为当前consumer消费的状态
//写法1:lambda表达式
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
//逐条消费消息
for(MessageExt msg:msgs){
System.out.println(msg);
}
//返回消费状态,消费成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
//写法2:匿名内部类方式
// consumer.registerMessageListener(new MessageListenerConcurrently() {
// //一旦broker中有了其订阅的消息就会触发该方法的执行
// //其返回值为当前consumer消费的状态
// @Override
// public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) {
// //逐条消费消息
// for(MessageExt msg:msgs){
// System.out.println(msg);
//
// }
// //返回消费状态,消费成功
// return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
// }
// });
//开启消费者消费
consumer.start();
System.out.println("Consumer started");
}
}
/**
* 定时投递
*/
public class OrderProducer {
public static void main(String[] args) throws Exception{
DefaultMQProducer producer = new DefaultMQProducer("pg");
producer.setNamesrvAddr("192.168.119.100:9876");
producer.start();
for (int i = 0; i < 100; i++) {
//使用整形数作为orderId
Integer orderId = i ;
byte[] body = ("hi" +i ).getBytes();
Message msg = new Message("TopicA", "TagA", body);
//将orderId作为消息key
msg.setKeys(orderId.toString());
//send()的第三个参数值会传递给选择器的select()的第三个参数
//使用同步发送
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
//使用消息key作为选择的选择算法
String keys = msg.getKeys();
Integer id = Integer.valueOf(keys);
//使用arg作为选择算法
// Integer id = (Integer) arg;
int index = id % mqs.size();
return mqs.get(index);
}
}, orderId);
System.out.println(sendResult);
}
producer.shutdown();
}
}
DelayProducer
public class DelayProducer {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("pg");
producer.setNamesrvAddr("192.168.119.100:9876");
producer.start();
for (int i = 0; i < 100 ; i++) {
byte[] body = ("hi"+i).getBytes();
Message msg = new Message("TopicB", "someTag", body);
//指定消息延迟等级为3 级,即延迟10s
SendResult sendResult = producer.send(msg);
//输出消息被发送的时间
System.out.println(new SimpleDateFormat("mm:ss").format(new Date()));
System.out.println(","+sendResult);
}
producer.shutdown();
}
}
OtherConsumer
public class OtherConsumer {
public static void main(String[] args) throws Exception{
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("cg");
consumer.setNamesrvAddr("192.168.119.100:9876");
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.subscribe("TopicB","*");
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
for (MessageExt msg : msgs) {
//输出消息被消费的时间
System.out.println(new SimpleDateFormat("mm:ss").format(new Date()));
System.out.println("," + msg);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
System.out.println("consumer started");
}
}
定义工行事务监听器
//定义工行事务监听器
public class ICBCTransactionListener implements TransactionListener {
//回调操作方法
//消息预提交成功就会触发该方法的执行,用于完成本地事务
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
System.out.println("预提交消息成功:"+message);
//假设接收到TAGA的消息就表示扣款成功,TAGB的消息表示扣款失败
//TAGC表示扣款结果不清楚,需要执行消息回查
if(StringUtils.equals("TAGA",message.getTags())){
return LocalTransactionState.COMMIT_MESSAGE;
}else if (StringUtils.equals("TAGB",message.getTags())){
return LocalTransactionState.ROLLBACK_MESSAGE;
}else if (StringUtils.equals("TAGC",message.getTags())){
return LocalTransactionState.UNKNOW;
}
return LocalTransactionState.UNKNOW;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
System.out.println("执行消息回查"+messageExt.getTags());
return LocalTransactionState.COMMIT_MESSAGE;
}
}
定义事务消息生产者
//定义事务消息生产者
public class TransactionProducer {
public static void main(String[] args) throws MQClientException {
TransactionMQProducer producer = new TransactionMQProducer("tpg");
producer.setNamesrvAddr("192.168.119.100:9876");
/**
* 定义一个线程池
corePoolSize 线程池中核心线程数量
maximumPoolSize 线程池中最多线程数
keepAliveTime 当线程池中线程数量大于核心线程数量时,多余空闲线程的存活时长
unit 时间单位
workQueue 临时存放任务的队列,其参数就是队列的长度
threadFactory 线程工厂
*/
ThreadPoolExecutor executorService = new ThreadPoolExecutor(2, 5,
100, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("client-transaction-msg-check-thread");
return thread;
}
});
//为生产者指定一个线程池
producer.setExecutorService(executorService);
//为生产者添加事务监听器
producer.setTransactionListener(new ICBCTransactionListener());
producer.start();
String[] tags = {"TAGA","TAGB","TAGC"};
for (int i = 0; i < 3; i++) {
byte[] body = ("hi"+i).getBytes();
Message message = new Message("TTopic", tags[i], body);
SendResult sendResult = producer.sendMessageInTransaction(message, null);
System.out.println("发送结果为:"+sendResult.getSendStatus());
}
}
}
定义事务消息消费者
/**
* 定义消息消费者
*/
public class Consumer {
public static void main(String[] args) throws Exception{
//定义一个pull消费者
// DefaultLitePullConsumer consumer = new DefaultLitePullConsumer("cg");
//定义一个push消费者(发布-订阅模式)
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("cg");
//指定nameServer
consumer.setNamesrvAddr("192.168.119.100:9876");
//指定从第一条消息开始消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//指定消费Topic与Tag
consumer.subscribe("TTopic","*");
//指定采用广播模式进行消费,默认为集群模式
// consumer.setMessageModel(MessageModel.BROADCASTING);
//注册消息监听器
//一旦broker中有了其订阅的消息就会触发该方法的执行
//其返回值为当前consumer消费的状态
//写法1:lambda表达式
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
//逐条消费消息
for(MessageExt msg:msgs){
System.out.println(msg);
}
//返回消费状态,消费成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
//写法2:匿名内部类方式
// consumer.registerMessageListener(new MessageListenerConcurrently() {
// //一旦broker中有了其订阅的消息就会触发该方法的执行
// //其返回值为当前consumer消费的状态
// @Override
// public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) {
// //逐条消费消息
// for(MessageExt msg:msgs){
// System.out.println(msg);
//
// }
// //返回消费状态,消费成功
// return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
// }
// });
//开启消费者消费
consumer.start();
System.out.println("Consumer started");
}
}
该批量发送的需求是,不修改最大发送4M的默认值,但要防止发送的批量消息超出4M的限制。
定义消息列表分割器
/**
* @author hxld
* @create 2022-09-07 16:12
*/
/**
*
* 消息列表分割器:其只会处理每条消息的大小不超4M的情况,若存在某条消息,其本身大小大于4M,这个分割器就无法进行处理
* 会直接将这条消息构成一个子列表返回。
*/
public class MessageListSplitter implements Iterator<List<Message>> {
//指定极限值为4M
private final int SIZE_LIMIT = 4 * 1024 * 1024;
//存放所有要发送的消息
private final List<Message> messages;
//要进行批量发送消息的小集合起始索引
private int currIndex;
public MessageListSplitter(List<Message> messages) {
this.messages = messages;
}
@Override
public boolean hasNext() {
//判断当前开始遍历的消息索引要小于消息总数
return currIndex < messages.size();
}
@Override
public List<Message> next() {
int nextIndex = currIndex;
//记录当前要发送的这一批次消息列表的大小
int totalSize = 0 ;
for(; nextIndex < messages.size(); nextIndex ++){
//获取当前遍历的消息
Message message = messages.get(nextIndex);
//统计当前遍历的message的大小
int tmpSize = message.getTopic().length() + message.getBody().length;
Map<String, String> properties = message.getProperties();
for (Map.Entry<String,String> entry : properties.entrySet()){
tmpSize += entry.getKey().length() + entry.getValue().length();
}
tmpSize = tmpSize + 20 ;
//判断当前消息本身是否大于4M
if(tmpSize > SIZE_LIMIT) {
if(nextIndex - currIndex == 0){
nextIndex ++ ;
}
break;
}
if (tmpSize + totalSize > SIZE_LIMIT){
break;
}else{
totalSize += tmpSize;
}
}
//获取当前messages列表的子集合[currIndex,nextIndex)
List<Message> subList = this.messages.subList(currIndex, nextIndex);
//下次遍历的开始索引
currIndex = nextIndex;
return subList;
}
}
定义批量消息生产者
public class BatchProducer {
public static void main(String[] args) throws MQClientException {
DefaultMQProducer producer = new DefaultMQProducer("pg");
producer.setNamesrvAddr("192.168.119.100:9876");
//指定要发送的消息的最大大小,默认是4m
producer.start();
//定义要发送的消息集合
List<Message> messages = new ArrayList<>();
for (int i = 0; i < 100; i++) {
byte[] body = ("hi" + i).getBytes();
Message msg = new Message("someTopic", "someTag", body);
messages.add(msg);
}
//定义消息列表分割器,将消息列表分割为多个不超出4M大小的小列表
MessageListSplitter splitter = new MessageListSplitter(messages);
while (splitter.hasNext()) {
try {
List<Message> listItem = splitter.next();
producer.send(listItem);
} catch (Exception e) {
e.printStackTrace();
}
}
producer.shutdown();
}
}
定义批量消息消费者
public class BatchConsumer {
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("cg");
consumer.setNamesrvAddr("192.168.119.100:9876");
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.subscribe("someTopicA","*");
//指定每次可以消费10调消息,默认为1
consumer.setConsumeMessageBatchMaxSize(10);
//指定每次可以从Broker拉取40条消息,默认为32
consumer.setPullBatchSize(40);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs){
System.out.println(msg);
}
//消费成功的返回结果
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("Consumer Started");
}
}
定义Tag过滤生产者
public class FilterByTagProducer {
public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
DefaultMQProducer producer = new DefaultMQProducer("pg");
producer.setNamesrvAddr("192.168.119.100:9876");
producer.start();
String[] tags = {"myTagA","myTagB","myTagC"};
for (int i = 0; i < 10; i++) {
byte[] body = ("hi" + i ).getBytes();
String tag = tags[i % tags.length];
Message msg = new Message("myTopic", tag, body);
SendResult sendResult = producer.send(msg);
System.out.println(sendResult);
}
producer.shutdown();
}
}
定义Tag过滤消费者
public class FilterByTagConsumer {
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("cg");
consumer.setNamesrvAddr("192.168.119.100:9876");
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.subscribe("myTopic","myTopiA || myTagB");
consumer.registerMessageListener((MessageListenerConcurrently) (list, consumeConcurrentlyContext) -> {
for (MessageExt mg:list){
System.out.println(mg);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
System.out.println("Consumer Started");
}
}
定义SQL过滤生产者
public class FilterBySQLProducer {
public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
DefaultMQProducer producer = new DefaultMQProducer("pg");
producer.setNamesrvAddr("192.168.119.100:9876");
producer.start();
// String[] tags = {"myTagA","myTagB","myTagC"};
for (int i = 0; i < 10; i++) {
byte[] body = ("hi" + i ).getBytes();
// String tag = tags[i % tags.length];
Message msg = new Message("myTopic", "myTag", body);
msg.putUserProperty("age",i + " ");
SendResult sendResult = producer.send(msg);
System.out.println(sendResult);
}
producer.shutdown();
}
}
定义SQL过滤消费者
public class FilterBySQLConsumer {
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("cg");
consumer.setNamesrvAddr("192.168.119.100:9876");
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
// consumer.subscribe("myTopic","myTopiA || myTagB");
consumer.subscribe("myTopic", MessageSelector.bySql("age between 0 and 6"));
consumer.registerMessageListener((MessageListenerConcurrently) (list, consumeConcurrentlyContext) -> {
for (MessageExt mg:list){
System.out.println(mg);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
System.out.println("Consumer Started");
}
}
这个课程真的是一言难尽,期待后续进行重新更新吧。