系列七、RocketMQ如何保证顺序消费消息

news2024/11/28 14:30:21

一、概述

        所谓顺序消费指的是可以按照消息的发送顺序来进行消费。例如一笔订单产生了3条消息,即下订单==》减库存==》增加订单,消费时要按照顺序消费才有意义,要不然就乱套了(PS:你总不能订单还没下,就开始减库存吧),与此同时多笔订单之间又是可以并行消费的。

二、RocketMQ实现顺序消费的方式

(1)保证同一个订单的消息,一定要发送到同一个队列;

(2)并且该队列只有一个消费者,也就是说同一个队列不能出现多个消费者并行消费的情况。到这里可能会有人产生疑问,一个队列只有一个消费者,那性能岂不是很低?关于这种情况,RocketMQ的解决方法是,虽然同一个队列不能并行消费,但是可以并行消费不同的队列。通俗点讲队列和消费者是一对一的关系,即一个队列只属于某一个消费者,消费者和队列是1对多的关系,即一个消费者可以对应多个队列。进而提升RocketMQ的效率。

三、队列消费的两种模式

3.1、并发消费模式

        当同一类消息被送入不同队列,且这些消息在处理上并不需要按照时序消费时,可以考虑使用并发消费模式。并发消费模式生产者会将消息轮询发送到不同的队列中,这些队列会和消费者建立多个连接(线程)将消息并发的送给不同的消费者。因为消费者处理速度有快有慢,所以并不能保证物流数据会按照1~9的顺序依次消费。并发消费模式处理效率很高,但是无法保证有序性。

 

3.2、有序消费模式

        有序消费是指生产者在生产数据的时候,根据hash规则指定让消息放入哪个队列,在消费者消费时会保证不同消费者针对每一个队列只有唯一一个连接(线程)用于消费指定队列。有序消费模式可以保证消息按队列FIFO顺序依次被消费,但因此失去并发性能,有序消费模式只有在业务要求必须按照顺序消费的场景下才允许使用。

3.2.1、RocketMQ如何实现有序消息

要想实现RocketMQ实现有序消息,只需要做如下两点调整:

(1)生产者端要求按照ID等唯一标识分配消息队列;

(2)消费者端采用专用的监听器保证对队列的单线程应用;

3.2.2、发送顺序消息

/**
 * 发送顺序消息
 * @throws Exception
 */
@Test
public void sendOrderlyMessage() {
	// 1、创建生产者,指定组名
	DefaultMQProducer producer = new DefaultMQProducer("GROUP_ORDERLY");
	// 2、指定Namesrv地址
	producer.setNamesrvAddr(RocketMQConstant.NAME_SERVER_ADDR);
	try {
		// 3、启动生产者
		producer.start();
		// 模拟10笔订单
		for(Integer orderId = 1; orderId <= 10; orderId++) {
			// 每笔订单要发送3条消息,即:创建订单==》扣减库存==》增加积分
			for (int i = 0; i < 3; i++) {
				String data = "";
				switch (i % 3) {
					case 0:
						data = orderId + "号创建订单";
						break;
					case 1:
						data = orderId + "号扣减库存";
						break;
					case 2:
						data = orderId + "号增加积分";
						break;
				}
				// 创建消息对象 topic:TOPIC_ORDER、tags:TAG_ORDER、key:orderId
				Message message = new Message("TOPIC_ORDERLY","TAG_ORDERLY",orderId.toString(),data.getBytes(RemotingHelper.DEFAULT_CHARSET));
				// 发送消息,实现[MessageQueueSelector]接口
				SendResult sendResult = producer.send(message, new MessageQueueSelector() {
					@Override
					public MessageQueue select(List<MessageQueue> messageQueues, Message message, Object o) {
						int orderId = Integer.parseInt(message.getKeys());
						int size = messageQueues.size();    // 队列的数量
						int index = orderId % size;         // 队列的索引
						MessageQueue messageQueue = messageQueues.get(index);   // 选择的队列
						log.info("订单id:{},队列数量:{},队列索引:{},发送内容:{},队列id:{}",orderId,size,index,new String(message.getBody()),messageQueue.getQueueId());
						return messageQueue;
					}
				}, null);
			}
		}

	} catch (Exception e) {
		log.error("error:{}",e.getMessage());
	}
}
// 控制台打印结果
"C:\Program Files\Java\jdk1.8.0_202\bin\java.exe" -ea -Didea.test.cyclic.buffer.size=1048576 -javaagent:D:\Programs\ideaIU-2018.3.6\lib\idea_rt.jar=51556:D:\Programs\ideaIU-2018.3.6\bin -Dfile.encoding=UTF-8 -classpath "D:\Programs\ideaIU-2018.3.6\lib\idea_rt.jar;D:\Programs\ideaIU-2018.3.6\plugins\junit\lib\junit-rt.jar;D:\Programs\ideaIU-2018.3.6\plugins\junit\lib\junit5-rt.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\rt.jar;D:\augenstemn@gitee\rocketmq\rocketmq-example\target\test-classes;D:\augenstemn@gitee\rocketmq\rocketmq-example\target\classes;D:\mavenRepository\com\alibaba\fastjson\1.2.83\fastjson-1.2.83.jar;D:\mavenRepository\org\apache\rocketmq\rocketmq-client\4.9.2\rocketmq-client-4.9.2.jar;D:\mavenRepository\org\apache\rocketmq\rocketmq-common\4.9.2\rocketmq-common-4.9.2.jar;D:\mavenRepository\org\apache\rocketmq\rocketmq-remoting\4.9.2\rocketmq-remoting-4.9.2.jar;D:\mavenRepository\io\netty\netty-all\4.1.65.Final\netty-all-4.1.65.Final.jar;D:\mavenRepository\org\apache\rocketmq\rocketmq-logging\4.9.2\rocketmq-logging-4.9.2.jar;D:\mavenRepository\commons-validator\commons-validator\1.7\commons-validator-1.7.jar;D:\mavenRepository\commons-beanutils\commons-beanutils\1.9.4\commons-beanutils-1.9.4.jar;D:\mavenRepository\commons-digester\commons-digester\2.1\commons-digester-2.1.jar;D:\mavenRepository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;D:\mavenRepository\commons-collections\commons-collections\3.2.2\commons-collections-3.2.2.jar;D:\mavenRepository\org\projectlombok\lombok\1.18.22\lombok-1.18.22.jar;D:\mavenRepository\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar;D:\mavenRepository\ch\qos\logback\logback-classic\1.2.10\logback-classic-1.2.10.jar;D:\mavenRepository\ch\qos\logback\logback-core\1.2.10\logback-core-1.2.10.jar;D:\mavenRepository\junit\junit\4.13.2\junit-4.13.2.jar;D:\mavenRepository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\mavenRepository\org\apache\commons\commons-lang3\3.11\commons-lang3-3.11.jar;D:\mavenRepository\org\apache\commons\commons-collections4\4.4\commons-collections4-4.4.jar" com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4 org.star.OrderMessageProducer,sendOrderlyMessage
RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.InternalThreadLocalMap).
RocketMQLog:WARN Please initialize the logger system properly.
11:04:01.604 [main] INFO org.star.OrderMessageProducer - 订单id:1,队列数量:4,队列索引:1,发送内容:1号创建订单,队列id:1
11:04:01.803 [main] INFO org.star.OrderMessageProducer - 订单id:1,队列数量:4,队列索引:1,发送内容:1号扣减库存,队列id:1
11:04:01.804 [main] INFO org.star.OrderMessageProducer - 订单id:1,队列数量:4,队列索引:1,发送内容:1号增加积分,队列id:1
11:04:01.807 [main] INFO org.star.OrderMessageProducer - 订单id:2,队列数量:4,队列索引:2,发送内容:2号创建订单,队列id:2
11:04:01.808 [main] INFO org.star.OrderMessageProducer - 订单id:2,队列数量:4,队列索引:2,发送内容:2号扣减库存,队列id:2
11:04:01.810 [main] INFO org.star.OrderMessageProducer - 订单id:2,队列数量:4,队列索引:2,发送内容:2号增加积分,队列id:2
11:04:01.811 [main] INFO org.star.OrderMessageProducer - 订单id:3,队列数量:4,队列索引:3,发送内容:3号创建订单,队列id:3
11:04:01.813 [main] INFO org.star.OrderMessageProducer - 订单id:3,队列数量:4,队列索引:3,发送内容:3号扣减库存,队列id:3
11:04:01.816 [main] INFO org.star.OrderMessageProducer - 订单id:3,队列数量:4,队列索引:3,发送内容:3号增加积分,队列id:3
11:04:01.818 [main] INFO org.star.OrderMessageProducer - 订单id:4,队列数量:4,队列索引:0,发送内容:4号创建订单,队列id:0
11:04:01.820 [main] INFO org.star.OrderMessageProducer - 订单id:4,队列数量:4,队列索引:0,发送内容:4号扣减库存,队列id:0
11:04:01.822 [main] INFO org.star.OrderMessageProducer - 订单id:4,队列数量:4,队列索引:0,发送内容:4号增加积分,队列id:0
11:04:01.826 [main] INFO org.star.OrderMessageProducer - 订单id:5,队列数量:4,队列索引:1,发送内容:5号创建订单,队列id:1
11:04:01.831 [main] INFO org.star.OrderMessageProducer - 订单id:5,队列数量:4,队列索引:1,发送内容:5号扣减库存,队列id:1
11:04:01.834 [main] INFO org.star.OrderMessageProducer - 订单id:5,队列数量:4,队列索引:1,发送内容:5号增加积分,队列id:1
11:04:01.836 [main] INFO org.star.OrderMessageProducer - 订单id:6,队列数量:4,队列索引:2,发送内容:6号创建订单,队列id:2
11:04:01.838 [main] INFO org.star.OrderMessageProducer - 订单id:6,队列数量:4,队列索引:2,发送内容:6号扣减库存,队列id:2
11:04:01.840 [main] INFO org.star.OrderMessageProducer - 订单id:6,队列数量:4,队列索引:2,发送内容:6号增加积分,队列id:2
11:04:01.841 [main] INFO org.star.OrderMessageProducer - 订单id:7,队列数量:4,队列索引:3,发送内容:7号创建订单,队列id:3
11:04:01.844 [main] INFO org.star.OrderMessageProducer - 订单id:7,队列数量:4,队列索引:3,发送内容:7号扣减库存,队列id:3
11:04:01.846 [main] INFO org.star.OrderMessageProducer - 订单id:7,队列数量:4,队列索引:3,发送内容:7号增加积分,队列id:3
11:04:01.850 [main] INFO org.star.OrderMessageProducer - 订单id:8,队列数量:4,队列索引:0,发送内容:8号创建订单,队列id:0
11:04:01.852 [main] INFO org.star.OrderMessageProducer - 订单id:8,队列数量:4,队列索引:0,发送内容:8号扣减库存,队列id:0
11:04:01.853 [main] INFO org.star.OrderMessageProducer - 订单id:8,队列数量:4,队列索引:0,发送内容:8号增加积分,队列id:0
11:04:01.856 [main] INFO org.star.OrderMessageProducer - 订单id:9,队列数量:4,队列索引:1,发送内容:9号创建订单,队列id:1
11:04:01.858 [main] INFO org.star.OrderMessageProducer - 订单id:9,队列数量:4,队列索引:1,发送内容:9号扣减库存,队列id:1
11:04:01.860 [main] INFO org.star.OrderMessageProducer - 订单id:9,队列数量:4,队列索引:1,发送内容:9号增加积分,队列id:1
11:04:01.860 [main] INFO org.star.OrderMessageProducer - 订单id:10,队列数量:4,队列索引:2,发送内容:10号创建订单,队列id:2
11:04:01.864 [main] INFO org.star.OrderMessageProducer - 订单id:10,队列数量:4,队列索引:2,发送内容:10号扣减库存,队列id:2
11:04:01.866 [main] INFO org.star.OrderMessageProducer - 订单id:10,队列数量:4,队列索引:2,发送内容:10号增加积分,队列id:2

Process finished with exit code 0

3.2.2、接收顺序消息(先启动监听)

/**
 * 接收顺序消息:消费者1
 * @throws Exception
 */
@Test
public void receiveOrderlyMessageConsumer1() throws Exception {
	// 1、创建消费者
	DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("GROUP_ORDERLY");
	// 2、连接Namesrv
	consumer.setNamesrvAddr(RocketMQConstant.NAME_SERVER_ADDR);
	/**
	 * 3、订阅主题
	 *      参数1:主题名称
	 *      参数2:主题中消息类型,*表示订阅所有
	 */
	consumer.subscribe("TOPIC_ORDERLY","*");
	// 4、注册监听器,要想实现顺序消费,消费者端要增加[MessageListenerOrderly]监听器,用于实现有序队列
	consumer.registerMessageListener(new MessageListenerOrderly() {
		@Override
		public ConsumeOrderlyStatus consumeMessage(List<MessageExt> messageExts, ConsumeOrderlyContext context) {
			// 遍历输出
			messageExts.forEach(e -> {
				log.info("我是消费者1,我正在消费消息,消息内容:{},队列id:{}",new String(e.getBody()),context.getMessageQueue().getQueueId());
			});
			return ConsumeOrderlyStatus.SUCCESS;
		}
	});
	// 5、启动消费者
	consumer.start();
	// 6、挂起当前JVM
	System.in.read();
}

/**
 * 接收顺序消息:消费者2
 * @throws Exception
 */
@Test
public void receiveOrderlyMessageConsumer2() throws Exception {
	// 1、创建消费者
	DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("GROUP_ORDERLY");
	// 2、连接Namesrv
	consumer.setNamesrvAddr(RocketMQConstant.NAME_SERVER_ADDR);
	/**
	 * 3、订阅主题
	 *      参数1:主题名称
	 *      参数2:主题中消息类型,*表示订阅所有
	 */
	consumer.subscribe("TOPIC_ORDERLY","*");
	// 4、注册监听器,要想实现顺序消费,消费者端要增加[MessageListenerOrderly]监听器,用于实现有序队列
	consumer.registerMessageListener(new MessageListenerOrderly() {
		@Override
		public ConsumeOrderlyStatus consumeMessage(List<MessageExt> messageExts, ConsumeOrderlyContext context) {
			// 遍历输出
			messageExts.forEach(e -> {
				log.info("我是消费者2,我正在消费消息,消息内容:{},消息队列:{}",new String(e.getBody()),context.getMessageQueue().getQueueId());
			});
			return ConsumeOrderlyStatus.SUCCESS;
		}
	});
	// 5、启动消费者
	consumer.start();
	// 6、挂起当前JVM
	System.in.read();
}

消费者1控制台打印结果


11:04:01.802 [ConsumeMessageThread_5] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:1号创建订单,队列id:1
11:04:01.804 [ConsumeMessageThread_6] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:1号扣减库存,队列id:1
11:04:01.808 [ConsumeMessageThread_7] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:1号增加积分,队列id:1
11:04:01.829 [ConsumeMessageThread_8] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:4号创建订单,队列id:0
11:04:01.831 [ConsumeMessageThread_9] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:5号创建订单,队列id:1
11:04:01.831 [ConsumeMessageThread_10] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:4号扣减库存,队列id:0
11:04:01.833 [ConsumeMessageThread_10] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:4号增加积分,队列id:0
11:04:01.836 [ConsumeMessageThread_11] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:5号扣减库存,队列id:1
11:04:01.838 [ConsumeMessageThread_12] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:5号增加积分,队列id:1
11:04:01.853 [ConsumeMessageThread_13] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:8号创建订单,队列id:0
11:04:01.856 [ConsumeMessageThread_14] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:8号扣减库存,队列id:0
11:04:01.858 [ConsumeMessageThread_15] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:8号增加积分,队列id:0
11:04:01.858 [ConsumeMessageThread_16] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:9号创建订单,队列id:1
11:04:01.860 [ConsumeMessageThread_17] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:9号扣减库存,队列id:1
11:04:01.865 [ConsumeMessageThread_18] INFO org.star.OrderMessageConsumer - 我是消费者1,我正在消费消息,消息内容:9号增加积分,队列id:1

消费者2控制台打印结果

11:04:01.815 [ConsumeMessageThread_1] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:2号创建订单,消息队列:2
11:04:01.817 [ConsumeMessageThread_2] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:3号创建订单,消息队列:3
11:04:01.820 [ConsumeMessageThread_2] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:3号扣减库存,消息队列:3
11:04:01.821 [ConsumeMessageThread_2] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:3号增加积分,消息队列:3
11:04:01.820 [ConsumeMessageThread_1] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:2号扣减库存,消息队列:2
11:04:01.821 [ConsumeMessageThread_1] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:2号增加积分,消息队列:2
11:04:01.840 [ConsumeMessageThread_3] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:6号创建订单,消息队列:2
11:04:01.843 [ConsumeMessageThread_4] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:6号扣减库存,消息队列:2
11:04:01.844 [ConsumeMessageThread_5] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:6号增加积分,消息队列:2
11:04:01.845 [ConsumeMessageThread_6] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:7号创建订单,消息队列:3
11:04:01.848 [ConsumeMessageThread_7] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:7号扣减库存,消息队列:3
11:04:01.859 [ConsumeMessageThread_8] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:7号增加积分,消息队列:3
11:04:01.866 [ConsumeMessageThread_9] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:10号创建订单,消息队列:2
11:04:01.868 [ConsumeMessageThread_10] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:10号扣减库存,消息队列:2
11:04:01.868 [ConsumeMessageThread_10] INFO org.star.OrderMessageConsumer - 我是消费者2,我正在消费消息,消息内容:10号增加积分,消息队列:2

3.2.3、结果分析

        通过对上述发送消息和接收消息控制台日志的分析,发现订单id相同的消息,都发送到了同一个队列中,也即保证了同一个订单的消息,发送到了同一个队列中。与此同时观察消费者的消费日志,发现队列id相同的消息队列中被分配给同一个消费者了,也即保证了一个队列唯一分配给了一个消费者,保证了顺序消费。另外观察日志发现,对于同一个消费者,同一个组的消息能够保证顺序,而不同组的消息无法保证顺序,也即局部有序。

3.2.4、如何实现全局顺序消费

只需要在生产者端固定将所有消息发送到0号队列即可保证全局有序,这也意味着全局采用单线程消费,这种方式执行效率极差。

@Override
public MessageQueue select(List<MessageQueue> messageQueues, Message message, Object o) {
	return messageQueues.get(0);
}

3.2.5、顺序消费的使用限制

        有序消费模式只支持集群模式(messageModel = MessageModel.CLUSTERING),不支持广播模式(messageModel = MessageModel.BROADCASTING),采用广播模式将无法接收到数据。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

备战金九银十之 5分钟带你掌握 Linux 的三种搜索方式

1.find 命令 find 命令是用来在给定的目录下查找符合给定条件的文件 语法格式&#xff1a;find [查找起始路径] [查找条件] [处理动作] &#xff08;1&#xff09;根据名称查找&#xff1a;find [查找起始路径] -name 文件名 或者 find [查找起始路径] -iname 文件名 -name …

超低功耗在智能门锁行业的应用

1. 名词解释 在本体上以电子方式识别、处理人体生物特征信息、电子信息、网络通讯信息等并控制机械执行机构实施启闭的门锁”叫电子智能门锁。通俗地理解&#xff0c;智能门锁是电子信息技术与机械技术相结合的全新的锁具品类&#xff0c;是在传统机械锁基础上升级改进的&…

C++学习| MFC简单入门

前言&#xff1a;因为接手了CMFC的程序&#xff0c;所以需要对MFC编程方面有所了解。 C之MFC简单入门 MFC相关的概念MFCWIN32QT MFC项目基本操作MFC项目创建MFC项目文件解读界面和代码数据交互——加法器 MFC相关的概念 MFC MFC&#xff08;Microsoft Foundation Classes微软…

【独立版】多商家+供应链云仓系统源码

详情介绍【独立版】多商家供应链云仓系统源码

刑侦科技前沿:IP地址定位技术打击网络犯罪

刑事侦查科技突飞猛进&#xff0c;IP地址定位技术正成为网络犯罪打击的有力利器。 近期&#xff0c;警方通过精准的IP地址定位技术成功破获一起大规模网络犯罪案件。此案件涉及多个地区&#xff0c;且作案手法极其隐蔽。得益于IP地址定位技术&#xff0c;警方得以及时介入将罪…

APP开发者如何有效利用广告位资源,提升平台变现收益?

不同的广告场景能为开发者带来不同的收益&#xff0c;开发者可以对广告位进行合理开发和设计。 在广告位设计上&#xff0c;有三大策略可以参考&#xff1a; 需要尊重用户体验&#xff0c;基于原生、APP类型&#xff0c;设计适合自己的广告位样式&#xff1b; 可以选择性引入…

【JavaEE进阶】Spring 更简单的读取和存储对象

文章目录 一. 存储Bean对象1. 配置扫描路径2. 添加注解存储 Bean 对象2.1 使用五大类注解存储Bean2.2 为什么要有五大类注解&#xff1f;2.3 有关获取Bean参数的命名规则 3. 使用方法注解储存 Bean 对象3.1 方法注解储存对象的用法3.2 Bean的重命名3.3 同⼀类型多个 Bean 报错 …

mysql获取第一个逗号前面的字符串

字符串内容如下&#xff1a; 统编版&#xff08;2019&#xff09;,必修下册,第五单元 ,第10课,10-2 在马克思墓前的讲话 /恩格斯, 想获取&#xff0c;第一个逗号前面的字符串&#xff0c;即&#xff1a;统编版&#xff08;2019&#xff09; 需要第一获取逗号的下标位置&…

用AI工具生成短视频大片,1分钟详细教程教会你

Hi! 大家好&#xff0c;我是专注于AI项目实战的赤辰&#xff0c; 今天我要跟大家分享如何用AI工具1分钟内生成一个短视频大片&#xff0c;效果完全不输影视大V。 只需要用一句话就可以生成视频&#xff0c;或者用一张图就能生成视频&#xff0c;这就是最新推出的AI工具Pika L…

生产者消费者模型 C++ 版

基础生产者消费者模型 网上一般教程是使用std::queue,定义消费者 Consumer &#xff0c;定义Producter类&#xff0c;在main函数里面加锁保证线程安全。 本片文章&#xff0c;实现一个线程安全的队列 threadsafe_queue&#xff0c;只在队列内部加锁。如此可适配&#xff0c;多…

taro h5列表拖拽排序 --- sortablejs 和 react-sortable-hoc

描述&#xff1a;列表&#xff0c;拖拽排序&#xff0c;只测试了h5 一、sortablejs 文档&#xff1a;http://www.sortablejs.com/ 1.安装sortablejs 2、引入 import Sortable from sortablejs3、页面 const [list, setList] useState([{id: item-1,content: 选项1 }, {id…

什么情况,听说项目经理已经成为行业的最底层了

大家好&#xff0c;我是老原。 要说现在的职场人最关心什么&#xff0c;那肯定是自己所在的这个行业、工种市场行情如何&#xff0c;会不会慢慢没落了&#xff0c;或者被现在盛行的AI、ChatGpt给替代了。 今天这个标题的问题也是我在知乎上看到很多项目经理都在提问的问题之一…

创新引领城市进化:人工智能和大数据塑造智慧城市新面貌

人工智能和大数据等前沿技术正以惊人的速度融入智慧城市的方方面面&#xff0c;为城市的发展注入了强大的智慧和活力。这些技术的应用不仅令城市管理更高效、居民生活更便捷&#xff0c;还为可持续发展和创新奠定了坚实的基础。 在智慧城市中&#xff0c;人工智能技术正成为城市…

网络安全/黑客技术(经验分享)

一、为什么选择网络安全&#xff1f; 这几年随着我国《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》等一系列政策/法规/标准的持续落地&#xff0c;网络安全行业地位、薪资随之水涨船高。 未来3-5年&#xff0c;是安全行业的黄金发展期&#xff0c;提前踏入…

WebSocket与消息推送

B/S结构的软件项目中有时客户端需要实时的获得服务器消息&#xff0c;但默认HTTP协议只支持请求响应模式&#xff0c;这样做可以简化Web服务器&#xff0c;减少服务器的负担&#xff0c;加快响应速度&#xff0c;因为服务器不需要与客户端长时间建立一个通信链接&#xff0c;但…

pytest结合 allure 打标记之的详细使用

前言 前面我们提到使用allure 可以生成漂亮的测试报告&#xff0c;下面就Allure 标记我们做详细介绍。 allure 标记 包含&#xff1a;epic&#xff0c;feature, story, title, testcase, issue, description, step, serverity, link, attachment 常用的标记 allure.feature…

汽车租赁小程序制作,如何开发一个微信租车平台“

随着移动互联网的快速发展&#xff0c;小程序已经成为了企业和个人开展业务的重要工具之一。微信小程序作为最为常见和使用最为广泛的一种小程序形式&#xff0c;具有便捷、灵活、高效的特点&#xff0c;受到了广大用户的喜爱。在本文中&#xff0c;我们将手把手教你如何创建一…

vue3使用pinia和pinia-plugin-persist做持久化存储

1、安装依赖 pnpm i pinia // 安装 pinia pnpm i pinia-plugin-persist // 安装持久化存储插件2、main.js引入 import App from ./App.vue const app createApp(App)//pinia import { createPinia } from pinia import piniaPersist from pinia-plugin-persist //持久化插件 …

关于selenium 元素定位的浅度解析

一、By类单一属性定位 元素名称 描述 Webdriver API id id属性 driver.find_element(By.ID, "id属性值") name name属性 driver.find_element(By.NAME, "name属性值") class_name class属性 driver.find_element(By.CLASS_NAME, "class_na…

【计算机网络】应用层协议Http

文章目录 前言URL&#xff08;网址&#xff09;urlencode 和 urldecodeHttp格式方法Http状态码重定向Http常见报头会话保持结语 前言 通过前面的学习&#xff0c;我们已经知道了协议其实就是一种约定&#xff0c;要求双方都能理解对方的消息。应用层上的协议不属于操作系统&am…