【RocketMQ】RocketMQ发送不同类型消息

🎯 导读:本文介绍了RocketMQ消息队列系统中的几种消息发送模式及其应用场景,包括同步消息、异步消息以及事务消息。同步消息确保了消息的安全性,但牺牲了一定的性能;异步消息提高了响应速度,适用于对响应时间敏感的场景;事务消息则保证了消息与本地事务的一致性,适用于需要预执行业务逻辑以决定消息是否发送的场景。此外,文章还探讨了Topic与Tag的应用策略,以及如何利用自定义Key来方便消息的查询和去重,提供了丰富的代码示例帮助理解。

文章目录

  • RocketMQ发送同步消息*
  • RocketMQ发送异步消息*
    • 异步消息生产者
    • 异步消息消费者
  • RocketMQ发送单向消息
    • 单向消息生产者
    • 单向消息消费者
  • RocketMQ发送延迟消息*
    • 延迟消息生产者
    • 延迟消息消费者
  • RocketMQ发送顺序消息
    • 场景分析
    • 定义消息实体
    • 顺序消息生产者
    • 顺序消息消费者
  • RocketMQ发送批量消息
    • 批量消息生产者
    • 批量消息消费者
  • RocketMQ发送事务消息(不够Seata方便)
    • 事务消息的发送流程
    • 事务消息生产者
    • 事务消息消费者
    • 测试结果
  • RocketMQ发送带标签的消息*(消息过滤)
    • 订阅关系一致
    • 标签消息生产者
    • 标签消息消费者
    • Topic 和 Tag 的应用推荐(官方推荐)
  • 发送消息携带自定义Key
    • 携带Key好处
    • 携带 key 消息生产者
    • 携带 key 消息消费者

使用*标注的为常用的消息类型

RocketMQ发送同步消息*

在这里插入图片描述

方法有返回Result的是同步消息,可以参考快速入门案例的实现

  • 同步消息发送过后会有一个返回值(MQ 服务器接收到消息后返回的一个确认),这种方式非常安全,但是性能没有那么高
  • 在 MQ 集群中,要等到所有的从机都复制了消息以后才会返回(要等很久)
  • 应用场景:重要的消息可以选择这种方式

在这里插入图片描述

RocketMQ发送异步消息*

  • 异步消息用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker响应的场景
  • 发送完以后会有一个异步消息通知告诉生产者消息是否发送成功

异步消息生产者

@Test
public void asyncProducer() throws Exception {DefaultMQProducer producer = new DefaultMQProducer("async-producer-group");producer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);producer.start();Message message = new Message("asyncTopic", "我是一个异步消息".getBytes());producer.send(message, new SendCallback() {// 异步回调方法@Overridepublic void onSuccess(SendResult sendResult) {System.out.println("发送成功");}@Overridepublic void onException(Throwable e) {System.err.println("发送失败:" + e.getMessage());}});System.out.println("我先执行");// 挂起jvmSystem.in.read();
}

在这里插入图片描述

异步消息消费者

@Test
public void testAsyncConsumer() throws Exception {// 创建默认消费者组DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer-group");// 设置nameServer地址consumer.setNamesrvAddr("localhost:9876");// 订阅一个主题来消费   *表示没有过滤参数 表示这个主题的任何消息consumer.subscribe("TopicTest", "*");// 注册一个消费监听 MessageListenerConcurrently是并发消费// 默认是20个线程一起消费,可以参看 consumer.setConsumeThreadMax()consumer.registerMessageListener(new MessageListenerConcurrently() {@Overridepublic ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,ConsumeConcurrentlyContext context) {// 这里执行消费的代码 默认是多线程消费System.out.println(Thread.currentThread().getName() + "----" + msgs);return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}});consumer.start();System.in.read();
}

RocketMQ发送单向消息

  • 这种方式主要用在不关心发送结果的场景(没有同步或者异步回调,不在乎消息是否发送成功),例如日志信息的发送
  • 这种方式吞吐量很大,但是存在消息丢失的风险

单向消息生产者

@Test
public void testOnewayProducer() throws Exception {// 创建默认的生产者DefaultMQProducer producer = new DefaultMQProducer("test-group");// 设置nameServer地址producer.setNamesrvAddr("localhost:9876");// 启动实例producer.start();Message msg = new Message("TopicTest", ("单向消息").getBytes());// 发送单向消息producer.sendOneway(msg);// 关闭实例producer.shutdown();
}

单向消息消费者

消费者和上面一样

RocketMQ发送延迟消息*

  • 消息放入 MQ 后,过一段时间,才会被消费者监听到
  • 在下单业务中,提交一个订单后,发送一个延时消息,30min后去检查这个订单的状态,如果还是未付款就取消订单,释放库存。类似场景还有 7 天自动收货

延迟消息生产者

RocketMQ 4.x 版本不支持任意时间的延时,只支持以下几个固定的延时等级,等级1就对应1s,以此类推,最高支持2h延迟

在这里插入图片描述

@Test
public void testDelayProducer() throws Exception {// 创建默认的生产者DefaultMQProducer producer = new DefaultMQProducer("ms-consumer-group");// 设置nameServer地址producer.setNamesrvAddr("localhost:9876");// 启动实例producer.start();Message msg = new Message("TopicTest", ("延迟消息").getBytes());// 给这个消息设定一个延迟级别,每个级别对应一个时间// messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2hmsg.setDelayTimeLevel(3);// 发送单向消息producer.send(msg);// 打印时间System.out.println(new Date());// 关闭实例producer.shutdown();
}

RocketMQ 5.x 版本支持任意时间的延时(使用时间轮算法)

Message message = new Message("orderMsTopic", "订单号,座位号".getBytes());
// 直接设置延迟多少秒
message.setDelayTimeSec(500);
// 设置延迟多少毫秒
// message.setDelayTimeMs(100);
// 发延迟消息
producer.send(message);

延迟消息消费者

延时消息会有一点小误差,不完全准时

/*** 发送时间Fri Apr 21 16:19:54 CST 2023* 收到消息了Fri Apr 21 16:20:20 CST 2023* --------------* 发送时间Fri Apr 21 16:21:08 CST 2023* 收到消息Fri Apr 21 16:21:18 CST 2023** @throws Exception*/
@Test
public void msConsumer() throws Exception {DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ms-consumer-group");consumer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);consumer.subscribe("orderMsTopic", "*");consumer.registerMessageListener(new MessageListenerConcurrently() {@Overridepublic ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {System.out.println("收到消息了" + new Date());System.out.println(new String(msgs.get(0).getBody()));return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}});consumer.start();System.in.read();
}

RocketMQ发送顺序消息

  • 消息有序指的是可以按照消息的发送顺序来消费(FIFO)
  • RocketMQ 可严格保证消息有序,例如下单之后,先发送短信、再发货

【有序类型】

  • 分区有序(可以直接通过MessageListenerOrderly实现)
  • 全局有序(MessageListenerOrderly + 设置Broker队列数量为1)

可能大家会有疑问,mq本身不就是FIFO吗?

答:一个broker中有四个queue,消费默认是并发的,线程之间有竞争关系,不能确保顺序

【顺序消费的原理解析】

  • 默认消息发送时采取Round Robin轮询方式把消息发送到不同的queue(分区队列)
  • 消费消息的时候,从多个queue上拉取消息,消费不能保证全局有序,只能保证分区有序(每个queue的消息都是有序的)

在这里插入图片描述

  • 但如果控制消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,就保证了全局有序(发送和消费参与的队列只有一个)

场景分析

模拟一个订单的发送流程,创建两个订单,发送的消息分别是

  • 订单号1000111 发消息流程 下订单->物流->签收
  • 订单号1000222 发消息流程 下订单->物流->拒收

即实现分区有序

定义消息实体

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MsgModel {private String orderSn;private Integer userId;private String desc; // 下单 短信 物流
}

顺序消息生产者

private List<MsgModel> msgModels = Arrays.asList(new MsgModel("1000111", 1, "下单"),new MsgModel("1000111", 1, "短信"),new MsgModel("1000111", 1, "物流"),new MsgModel("1000222", 2, "下单"),new MsgModel("1000222", 2, "短信"),new MsgModel("1000222", 2, "物流")
);@Test
public void orderlyProducer() throws Exception {DefaultMQProducer producer = new DefaultMQProducer("orderly-producer-group");producer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);producer.start();// 发送顺序消息  发送时要确保有序 并且要发到同一个队列下面去msgModels.forEach(msgModel -> {String msgModelString = msgModel.toString();Message message = new Message("orderlyTopic", msgModelString.getBytes());try {producer.send(message, new MessageQueueSelector() {@Overridepublic MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {// arg就是接收到的订单号 orderSn// 在这里 选择队列int hashCode = arg.toString().hashCode();// 根据订单号hashCode,取模放到队列中,订单号一样,放到的队列一样return mqs.get(hashCode % mqs.size());}},// 传递订单号进去msgModel.getOrderSn());} catch (Exception e) {e.printStackTrace();}});producer.shutdown();System.out.println("发送完成");
}

顺序消息消费者

@Test
public void orderlyConsumer() throws Exception {DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("orderly-consumer-group");consumer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);consumer.subscribe("orderlyTopic", "*");// MessageListenerConcurrently 并发模式 多线程的  重试16次// MessageListenerOrderly 顺序模式 单线程的 无限重试Integer.Max_Valueconsumer.registerMessageListener(new MessageListenerOrderly() {@Overridepublic ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {System.out.println("线程id:" + Thread.currentThread().getId());System.out.println(new String(msgs.get(0).getBody()));return ConsumeOrderlyStatus.SUCCESS;}});consumer.start();System.in.read();
}

下面的结果就是分区有序,同一个订单(同一队列)的消息会按照顺序消费,但是不同订单的消息没有顺序约束

在这里插入图片描述

RocketMQ发送批量消息

RocketMQ可以一次性发送一组消息,这一组消息被投递到同一个队列中,会被当做一条消息进行消费

批量消息生产者

@Test
public void testBatchProducer() throws Exception {// 创建默认的生产者DefaultMQProducer producer = new DefaultMQProducer("test-group");// 设置nameServer地址producer.setNamesrvAddr("localhost:9876");// 启动实例producer.start();// 消息实际上还是分开发的,接收的时候还是一条一条接收就行List<Message> msgs = Arrays.asList(new Message("TopicTest", "我是一组消息的A消息".getBytes()),new Message("TopicTest", "我是一组消息的B消息".getBytes()),new Message("TopicTest", "我是一组消息的C消息".getBytes()));SendResult send = producer.send(msgs);System.out.println(send);// 关闭实例producer.shutdown();
}

三个消息放在一个队列中

在这里插入图片描述

批量消息消费者

消费者还是一条一条来消费

@Test
public void testBatchConsumer() throws Exception {// 创建默认消费者组DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer-group");// 设置nameServer地址consumer.setNamesrvAddr("localhost:9876");// 订阅一个主题来消费   表达式,默认是*consumer.subscribe("TopicTest", "*");// 注册一个消费监听 MessageListenerConcurrently是并发消费// 默认是20个线程一起消费,可以参看 consumer.setConsumeThreadMax()consumer.registerMessageListener(new MessageListenerConcurrently() {@Overridepublic ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,ConsumeConcurrentlyContext context) {// 这里执行消费的代码 默认是多线程消费System.out.println(Thread.currentThread().getName() + "----" + new String(msgs.get(0).getBody()));return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}});consumer.start();System.in.read();
}

RocketMQ发送事务消息(不够Seata方便)

事务消息的发送流程

它允许发送方在发送消息之前执行某些业务逻辑,并根据这些业务逻辑的结果来决定消息是否应该被发送。

  • 如果业务逻辑执行成功,则消息会被提交并最终被消费者消费;
  • 如果业务逻辑执行失败或不确定,则消息的状态将保持未知,直到通过回查机制确认其状态为止。

在这里插入图片描述

下图说明了事务消息的大致方案,其中分为两个流程:正常事务消息的发送及提交、事务消息的补偿流程。

在这里插入图片描述

事务消息发送及提交

1、发送消息(half消息)

2、服务端响应消息写入结果(如果写入失败,此时half消息对业务不可见,本地事务不执行)

3、根据发送结果执行本地事务

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

事务补偿

补偿阶段用于解决消息UNKNOW或者Rollback发生超时或者失败的情况

1、对没有Commit/Rollback的事务消息(pending状态的消息),发起一次“回查”

2、Producer收到回查消息,检查回查消息对应的本地事务的状态

3、根据本地事务状态,重新 Commit 或者 Rollback

事务消息状态

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

  • TransactionStatus.CommitTransaction: 提交事务,允许消费者消费此消息
  • TransactionStatus.RollbackTransaction: 回滚事务,该消息将被删除,不允许被消费
  • TransactionStatus.Unknown: 中间状态,需要检查消息队列来确定状态

事务消息生产者

通过在broker.conf文件中设置如下参数

  • transactionCheckInterval:Broker检查事务消息状态的默认间隔时间(单位为毫秒)。默认Broker每1分钟(60000毫秒)会对未确认的事务消息进行一次状态检查
  • transactionTimeOut:事务消息的有效期,即事务消息在未得到确认前能存在的最长时间
  • transactionCheckMax:事务消息的最大检测次数(默认回查15次)。如果在达到最大检测次数后事务消息的状态仍未得到确认,Broker会默认认为事务已失败,并对消息进行回滚
private Random random = new Random();
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");/*** 创建订单,扣减库存** @throws Exception*/
@Test
public void createOrderAndDeductStock() throws Exception {// 构建消息体TransactionMQProducer producer = new TransactionMQProducer("async-producer-group");producer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);// 设置事务消息的监听器producer.setTransactionListener(new TransactionListener() {@Overridepublic LocalTransactionState executeLocalTransaction(Message msg, Object arg) {System.out.println(sdf.format(new Date()) + "-->执行本地事务:" + new String(msg.getBody()) + ";事务参数:" + arg);// 获取订单信息String orderInfo = new String(msg.getBody());// 做一些业务操作...// 检查订单状态if (isOrderValid(orderInfo)) {System.out.println("订单状态没有问题,消息发送给消费者处理");return LocalTransactionState.COMMIT_MESSAGE;} else {System.out.println("订单状态有问题,等会重新 检查本地事务");// 返回 UNKNOW ,后面会调用checkLocalTransaction(msg)方法return LocalTransactionState.UNKNOW;}}/*** 回查,确认上面的业务是否有结果* 触发条件:* 1、当上面执行本地事务返回结果 UNKNOW 时,或者回查方法也返回 UNKNOW 时,会触发* 2、上面操作超过 20s 没有做出一个结果,也就是超时或者卡住了,也会进行回查* @param messageExt* @return*/@Overridepublic LocalTransactionState checkLocalTransaction(MessageExt messageExt) {System.out.println(sdf.format(new Date()) + "-->检查本地事务:" + new String(messageExt.getBody()));// 检查订单状态String orderInfo = new String(messageExt.getBody());if (isOrderValid(orderInfo)) {System.out.println("订单状态没有问题,消息发送给消费者处理");return LocalTransactionState.COMMIT_MESSAGE;} else {System.out.println("订单状态还是有问题,这个消息不要了");return LocalTransactionState.ROLLBACK_MESSAGE;}}});producer.start();for (int i = 0; i < 3; i++) {String orderId = UUID.randomUUID().toString().replace("-", "");int amount = random.nextInt(10) + 1;// 构建订单信息String orderInfo = "订单号:" + orderId + " 数量:" + amount;// 尝试保存订单信息boolean saveOrderSuccess = saveOrder(orderInfo);// 发送事务消息Message message = new Message("Affair_Topic", orderInfo.getBytes());System.out.println(sdf.format(new Date()) + "-->发送事务消息:" + orderInfo);producer.sendMessageInTransaction(message, "hahaha");}System.in.read();
}private boolean saveOrder(String orderInfo) {// 模拟保存订单信息到数据库// 假设保存成功return true;
}private boolean isOrderValid(String orderInfo) {// 模拟检查订单状态// 假设订单有效int random = this.random.nextInt(2);return random == 1; // 应该替换为实际的订单状态检查逻辑
}

事务消息消费者

@Test
public void testTransactionConsumer() throws Exception {// 创建默认消费者组DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer-group");// 设置nameServer地址consumer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);// 订阅一个主题来消费   *表示没有过滤参数 表示这个主题的任何消息consumer.subscribe("Affair_Topic", "*");// 注册一个消费监听 MessageListenerConcurrently是并发消费// 默认是20个线程一起消费,可以参看 consumer.setConsumeThreadMax()consumer.registerMessageListener(new MessageListenerConcurrently() {@Overridepublic ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {// 这里执行消费的代码 默认是多线程消费System.out.println(sdf.format(new Date()) + "-->" + Thread.currentThread().getName() + " 执行消费:" + new String(msgs.get(0).getBody()));return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}});consumer.start();System.in.read();
}

测试结果

在这里插入图片描述

RocketMQ发送带标签的消息*(消息过滤)

  • RocketMQ提供消息过滤功能,通过tag或者key进行区分。同一个主题对应多个tag。我们往一个主题里面发送消息的时候,根据业务逻辑,可能需要区分,比如带有tagA标签的被A消费,带有tagB标签的被B消费,通过过滤来区别对待
  • 场景:订单主题中。有的消息是关于生鲜,需要及时配送;有的消息是关于服装,可以慢一点发货。不同标签的处理逻辑不同

在这里插入图片描述

订阅关系一致

  • 订阅关系:一个消费者组订阅一个 Topic 的某一个 Tag(个人实测,一个标签最好对应一个标签,不然信息会被过滤掉,不被正常消费),这种记录被称为订阅关系。
  • 订阅关系一致:同一个消费者组下所有消费者实例所订阅的 Topic、Tag 必须完全一致。如果订阅关系(消费者组名 - Topic - Tag)不一致,会导致消费消息紊乱,甚至消息丢失。

在这里插入图片描述

标签消息生产者

@Test
public void tagProducer() throws Exception {DefaultMQProducer producer = new DefaultMQProducer("tag-producer-group");producer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);producer.start();Message message = new Message("tagTopic", "vip1", "我是vip1的文章".getBytes());Message message2 = new Message("tagTopic", "vip2", "我是vip2的文章".getBytes());producer.send(message);producer.send(message2);System.out.println("发送成功");producer.shutdown();
}

标签消息消费者

/*** vip1** @throws Exception*/
@Test
public void tagConsumer1() throws Exception {DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("tag-consumer-group-a");consumer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);// 主题,标签(写 * 是所有标签)consumer.subscribe("tagTopic", "vip1");consumer.registerMessageListener(new MessageListenerConcurrently() {@Overridepublic ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {System.out.println("我是vip1的消费者,我正在消费消息" + new String(msgs.get(0).getBody()));return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}});consumer.start();System.in.read();
}/*** vip1 || vip2** @throws Exception*/
@Test
public void tagConsumer2() throws Exception {DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("tag-consumer-group-b");consumer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);consumer.subscribe("tagTopic", "vip1 || vip2");consumer.registerMessageListener(new MessageListenerConcurrently() {@Overridepublic ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {System.out.println("我是vip2的消费者,我正在消费消息" + new String(msgs.get(0).getBody()));return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}});consumer.start();System.in.read();
}

Topic 和 Tag 的应用推荐(官方推荐)

总结:不同的业务应该使用不同的Topic,如果是相同的业务里面有不同的表现形式,我们要使用tag进行区分,可以从以下几个方面进行判断:

1、消息类型是否一致:如普通消息、事务消息、定时(延时)消息、顺序消息,不同的消息类型使用不同的 Topic,无法通过 Tag 进行区分

2、业务是否相关联:淘宝交易消息、京东物流消息使用不同的 Topic 进行区分;同样是天猫交易消息,电器类订单、女装类订单、化妆品类订单的消息可以用 Tag 进行区分

3、消息优先级是否一致:如同样是物流消息,盒马必须小时内送达,天猫超市 24 小时内送达,淘宝物流则相对会慢一些,不同优先级的消息用不同的 Topic 进行区分

4、消息量级是否相当:有些业务消息虽然量小但是实时性要求高(如生鲜订单),如果跟某些万亿量级的消息使用同一个 Topic,可能会因为过长的等待时间而“饿死”,此时需要将不同量级的消息进行拆分,使用不同的 Topic

总的来说,针对消息分类,您可以选择创建多个 Topic,或者在同一个 Topic 下创建多个 Tag。但通常情况下,不同的 Topic 之间的消息没有必然的联系,而 Tag 则用来区分同一个 Topic 下相互关联的消息,例如全集和子集的关系、流程先后的关系。

发送消息携带自定义Key

在 RocketMQ 中的消息,默认会有一个messageId当做消息的唯一标识,我们也可以给消息携带一个key,用作唯一标识或者业务标识,包括在控制面板查询的时候也可以使用messageId或key来进行查询

在这里插入图片描述

携带Key好处

  • 方便查阅
  • 方便去重

携带 key 消息生产者

@Test
public void keyProducer() throws Exception {DefaultMQProducer producer = new DefaultMQProducer("key-producer-group");producer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);producer.start();String key = UUID.randomUUID().toString();System.out.println(key);Message message = new Message("keyTopic", "vip1", key, "我是vip1的文章".getBytes());producer.send(message);System.out.println("发送成功");producer.shutdown();
}

携带 key 消息消费者

@Test
public void keyConsumer() throws Exception {DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("key-consumer-group");consumer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);consumer.subscribe("keyTopic", "*");consumer.registerMessageListener(new MessageListenerConcurrently() {@Overridepublic ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {MessageExt messageExt = msgs.get(0);System.out.println("我是vip1的消费者,我正在消费消息" + new String(messageExt.getBody()));// 获取keySystem.out.println("我们业务的标识:" + messageExt.getKeys());return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}});consumer.start();System.in.read();
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/434621.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

演示:基于WPF的DrawingVisual开发的频谱图和律动图

一、目的&#xff1a;基于WPF的DrawingVisual开发的频谱图和律动图 二、效果演示 波形图 极坐标 律动图极坐标图 律动图柱状图 Dock布局组合效果 三、环境 VS2022,Net7,Win10&#xff0c;NVIDIA RTX A2000 四、主要功能 支持设置起始频率&#xff0c;终止频率&#xff0c;中心…

【HTTP 和 HTTPS详解】3

HTTP 状态代码 HTTP 状态代码是服务器发送给客户端的三位数字&#xff0c;用于指示客户端请求的结果。它们分为五类&#xff1a;信息性&#xff08;100-199&#xff09;、成功&#xff08;200-299&#xff09;、重定向&#xff08;300-399&#xff09;、客户端错误&#xff08…

【移植】Combo解决方案之W800芯片移植案例

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 本方案基于 OpenHarmony LiteOS-M 内核&#xff0c;使用联盛德 W80…

[论文精读]Membership Inference Attacks Against Machine Learning Models

中文译名&#xff1a;针对机器学习模型的成员推理攻击 会议名称&#xff1a;2017 IEEE Symposium on Security and Privacy (SP) 发布链接&#xff1a;Membership Inference Attacks Against Machine Learning Models | IEEE Conference Publication | IEEE Xplore CODE:Git…

【计算机网络】Tcp报文的组成,Tcp是如何实现可靠传输的?

Tcp的报文组成 TCP&#xff08;传输控制协议&#xff09;是计算机网络中一种重要的传输协议&#xff0c;其报文组成包括多个字段&#xff0c;每个字段具有特定的含义。以下是TCP报文头的主要组成部分&#xff1a; 源端口号&#xff08;Source Port&#xff09;&#xff1a;占用…

C语言 | Leetcode C语言题解之第445题两数相加II

题目&#xff1a; 题解&#xff1a; struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){int stack1[100];int stack2[100];int top1 0;int top2 0;int carry 0;int sum 0;struct ListNode* temp NULL;struct ListNode* head NULL;while (l1) {…

有关若依菜单管理的改造

导言&#xff1a; 搞了个后端对接若依前端&#xff0c;对接菜单管理时候懵懵的就搞完了&#xff0c;也是搞了很久。记一下逻辑和要注意的东西&#xff0c;以后做想似的能有个改造思路。 逻辑&#xff1a; 主要是要把后端传过的数组列表做成类似 这样的&#xff0c;所以要转格式…

git工具指令

下面是常用的Git命令清单&#xff0c;几个专用名称的译名如下&#xff1a; Workspace &#xff1a;工作区 Index /Stage&#xff1a;暂存区 Repository&#xff1a;仓库区&#xff08;或本地仓库&#xff09; Remote&#xff1a;远程仓库新建代码库 在当前目录新建一个Git代…

如何在银河麒麟操作系统中查看内存页大小

如何在银河麒麟操作系统中查看内存页大小 1、操作步骤2、注意事项 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在操作系统中&#xff0c;内存页大小&#xff08;Page Size&#xff09;是一个重要的概念&#xff0c;它决定了操作系统如何…

GPT理论

1.GPT发展 Transformer是一个用作翻译任务的模型&#xff0c;谷歌出品。 GPT全称 lmproving Language Understanding by Generative Pre-Training&#xff0c;用预训练语言理解模型。OPENAI出品。 BERT全称Pre-training of Deep BidirectionalTransformers for Language Unde…

深度学习反向传播-过程举例

深度学习中&#xff0c;一般的参数更新方式都是梯度下降法&#xff0c;在使用梯度下降法时&#xff0c;涉及到梯度反向传播的过程&#xff0c;那么在反向传播过程中梯度到底是怎么传递的&#xff1f;结合自己最近的一点理解&#xff0c;下面举个例子简单说明&#xff01; 一、…

828华为云征文|部署个人知识管理系统 SiyuanNote

828华为云征文&#xff5c;部署个人知识管理系统 SiyuanNote 一、Flexus云服务器X实例介绍二、Flexus云服务器X实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置2.4 Docker 环境搭建 三、Flexus云服务器X实例部署 SiyuanNote3.1 SiyuanNote 介绍3.2 SiyuanNote 部署3.3 Siyua…

WebSocket实现在线聊天室

项目实现源码&#xff1a; 前端源码 后端源码 1.常见的消息推送方式 1.1 轮询 1.1.1 轮询的概念 客户端以固定的事件间隔&#xff08;例如每秒或几分钟&#xff09;向服务器发送HTTP请求&#xff0c;服务器收到请求后&#xff0c;处理请求并返回数据给客户端 轮询具体实现htt…

element-plus中日历组件设置起始为周一

问题描述 element-plus中的日历组件默认是周日到周六&#xff0c;因业务需求&#xff0c;需要实现从周一到周日的顺序。 解决方式 引入dayjs及本地语言包&#xff0c;使用本地时区即可。 import dayjs from dayjs import dayjs/locale/zh-cn ... // 这一句是为了让日历使用本地…

Android 利用OSMdroid开发GIS

1、地址 Github地址&#xff1a;https://gitee.com/mirrors/osmdroid Git地址&#xff1a; GitCode - 全球开发者的开源社区,开源代码托管平台 Git下载包地址&#xff1a;Releases osmdroid/osmdroid GitHub 新建项目 osmdroid在线&#xff1a; &#xff08;1&#xff09…

基于Hive和Hadoop的图书分析系统

本项目是一个基于大数据技术的图书分析系统&#xff0c;旨在为用户提供全面的图书信息和深入的图书销售及阅读行为分析。系统采用 Hadoop 平台进行大规模数据存储和处理&#xff0c;利用 MapReduce 进行数据分析和处理&#xff0c;通过 Sqoop 实现数据的导入导出&#xff0c;以…

基于SSM+微信小程序的校园二手数码交易平台系统(二手3)(源码+sql脚本+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于ssm微信小程序的校园二手数码交易平台满足了不同用户的功能需求&#xff0c;包括用户、卖家以及管理员&#xff0c;下面对这不同用户的功能需求进行简介。 &#xff08;1&#xff09…

正点原子——DS100示波器操作手册

目录 基础按键&#xff1a; 快捷键 主界面&#xff1a; 垂直设置&#xff1a; 通道设置&#xff1a; 探头比列&#xff1a; 垂直档位&#xff1a; 垂直偏移&#xff1a; 幅度单位&#xff1a; 水平设置&#xff1a; 触发方式&#xff1a; 测量和运算: 光标测量&am…

队列及笔试题

队列 先进先出 使用单链表进行队尾插入 队头删除 其中带头结点直接尾插&#xff0c;不带头结点第一次操作要判断一下 但是带头结点需要malloc和free 函数传需要修改的参数方法 1、二级指针 2、带哨兵位的头结点 3、返回值 4、如果有多个值&#xff0c;用结构体封装起来…

深入解析Debian与Ubuntu:技术特点与用户使用指南

深入解析Debian与Ubuntu&#xff1a;技术特点与用户使用指南 引言 Debian和Ubuntu作为两大知名的Linux发行版&#xff0c;不仅在历史和理念上有所不同&#xff0c;在技术特点和用户使用方法上也各具特色。本文将深入解析它们的技术特点&#xff0c;并提供用户使用指南&#x…