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

一、概述

        所谓顺序消费指的是可以按照消息的发送顺序来进行消费。例如一笔订单产生了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.mfbz.cn/a/68325.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue-2.nodejs的介绍和安装

nodejs简介 ► 创建 Node.js 应用:package.json 首先&#xff0c;创建一个新文件夹以便于容纳需要的所有文件&#xff0c;并且在此其中创建一个 package.json 文件&#xff0c;描述你应用程序以及需要的依赖&#xff1a; 配合着你的 package.json 请运行 npm install。如果你…

基于ffmpeg与SDL的视频播放库

由于工作需要&#xff0c;自己封装的基于ffmpeg的视频编解码库&#xff0c;显示采用了SDL库。可以播放本地文件或网络流&#xff0c;支持多端口播放&#xff0c;支持文字叠加&#xff0c;截图、视频录制等等。 头文件代码&#xff1a; #pragma once #ifdef __DLLEXPORT #defin…

SpringCloud实用篇5——elasticsearch基础

目录 1.初识elasticsearch1.1 了解ES1.1.1 elasticsearch的作用1.1.2 ELK技术栈1.1.3 elasticsearch和lucene1.1.4 总结 1.2.倒排索引1.2.1.正向索引1.2.2.倒排索引1.2.3.正向和倒排 1.3 es的一些概念1.3.1 文档和字段1.3.2 索引和映射1.3.3 mysql与elasticsearch 1.4 部署单点…

flutter开发实战-TextPainter计算文本内容的宽度

flutter开发实战-TextPainter计算文本内容的宽度 最近开发过程中根据Text文本的大小判断是否需要进行显示跑马灯效果&#xff0c;获取文本的大小&#xff0c;需要TextPainter来获取Size 一、TextPainter TextPainter主要用于实现文本的绘制。TextPainter类可以将TextSpan渲染…

浏览器多管闲事之跨域

年少时的梦想就是买一台小霸王游戏机 当时的宣传语就是小霸王其乐无穷~。 大些了&#xff0c;攒够了零花钱&#xff0c;在家长的带领下终于买到了 那一刻我感觉就是最幸福的人 风都是甜的&#xff01; 哪成想... 刚到家就被家长扣下了 “”禁止未成年人玩游戏机 (问过卖家了&a…

Window下安装MinGW64

欢迎来到我的酒馆 介绍Windows下&#xff0c;安装MinGW64。 目录 欢迎来到我的酒馆二.MinGW64三.配置系统环境变量 二.MinGW64 从sourceforge下载mingw64&#xff0c; sourceforge下载MinGW https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/ 下…

香港站群服务器为什么适合seo优化?

​  香港站群为什么适合seo优化?本文主要从以下四点出发进行原因阐述。 1.香港站群服务器的优势 2.香港站群服务器与国内服务器的对比 3.多IP站群服务器的优势 4.香港站群服务器在SEO优化中的注意事项 1.香港站群服务器的优势 香港站群服务器是为了满足企业SEO优化需求而提供…

实践分享:Vue 项目如何迁移小程序

最近我们小组刚经历了将成熟的 HTML5 项目转换成小程序&#xff0c;并在app中运行的操作&#xff01;记录下来分享给各位。 项目&#xff1a;将已有的 Vue 项目转为小程序&#xff0c; 在集成了FinClip SDK 的 App 中运行。 技术&#xff1a;uni-app、FinClip 两个注意事项&…

[每周一更]-(第57期):用Docker、Docker-compose部署一个完整的前后端go+vue分离项目

文章目录 1.参考项目2.技能点3.GO的Dockerfile配置后端的结构如图Dockerfile先手动docker调试服务是否可以启动报错 4.Vue的Dockerfile配置前端的结构如图nginx_docker.confDockerfile构建 5.docker-compose 整合前后端docker-compose.yml错误记录&#xff08;1&#xff09;ip端…

PS AI版本安装教程

好久没写博客了&#xff0c;今天更新一下子吧&#xff01; 随着chatGPT的提出&#xff0c;各种软件逐渐开始镶嵌人工智能&#xff0c;为我们的生活带来了极大的便利&#xff01;话不多说&#xff0c;开始介绍今天的主角&#xff0c;PS的AI版本。 安装教程&#xff1a; 1.安装…

Docker一键部署项目,无需登录XShell

文章目录 一键部署项目Docker手动部署SpringBoot项目编写docker部署的脚本文件script.sh 脚本内容 特别注意&#xff01;编写dockerfiledockerfile 文件内容 上传后端服务的jar包到服务器中执行 script 脚本部署后端服务 自动部署SpringBoot项目引入jsch依赖编写jsch工具类执行…

工业4.0:欢迎来到智能制造

制造业正在经历一场被称为“工业4.0”的全新技术革命&#xff0c;这场革命将数字化、网络化、智能化和自动化技术融合在一起&#xff0c;旨在打造高质、高效、高产且可持续的智能工厂。工业4.0将彻底改变产品制造的方式&#xff0c;颠覆我们对制造业的传统认知。 什么是工业4.…

【Java】try|catch|throws 具体详解+应用

目录 tryCatch 基本介绍 使用细节 throws异常处理 基本介绍 ​ 使用细节 自定义异常 基本概念 步骤 throw和throws的区别 tryCatch 基本介绍 使用细节 throws异常处理 基本介绍 使用细节 自定义异常 基本概念 步骤 throw和throws的区别

【数据结构】Disruptor环形数组无锁并发框架阅读

Disruptor 是苹国外厂本易公司LMAX开发的一个高件能列&#xff0c;研发的初夷是解决内存队列的延识问顾在性能测试中发现竟然与10操作处于同样的数量级)&#xff0c;基于Disruptor开发的系统单线程能支撑每秒600万订单&#xff0c;2010年在QCn演讲后&#xff0c;获得了业界关注…

MinGW-W64 下载、安装与配置(支持最新版的GCC,目前 GCC 13.2.0)

文章目录 一、简介1. MinGW 和 MinGW-W64 区别和联系2. MSVCRT 和 UCRT 介绍 二、下载1. 从 sourceforge.net 下载2. 从 github 下载3. 从 镜像站点 下载4. 自己编译 三、安装与配置1. 在线安装2. 离线安装3. 环境配置 四、总结 一、简介 1. MinGW 和 MinGW-W64 区别和联系 M…

数组slice、splice字符串substr、split

一、定义 这篇文章主要对数组操作的两种方法进行介绍和使用&#xff0c;包括&#xff1a;slice、splice。对字符串操作的两种方法进行介绍和使用&#xff0c;包括&#xff1a;substr、split (一)、数组 slice:可以操作的数据类型有&#xff1a;数组字符串 splice:数组 操作数组…

【go语言学习笔记】04 Go 语言工程管理

文章目录 一、质量保证1. 单元测试1.1 定义1.2 Go 语言的单元测试1.3 单元测试覆盖率 2. 基准测试2.1 定义2.2 Go 语言的基准测试2.3 计时方法2.4 内存统计2.5 并发基准测试2.6 基准测试实战 3. 特别注意 二、性能优化1. 代码规范检查1.1 定义1.2 golangci-lint1.2.1 安装1.2.2…

vue3 + ts+element-plus学习笔记

子组件通过defineProps方法接收父组件传递过来的数据&#xff0c;是vue3中提供的方法&#xff0c;不需要引入&#xff0c;直接使用 方法的写法&#xff1a; const onClick (){... }自定义事件&#xff1a; 子组件点击事件 全局事件总线 mitt 兄弟组件之间的事件&#x…

【福建事业单位-资料分析】03 比重和平均数

【福建事业单位-资料分析】03 比重和平均数 一、比重&#xff08;现期比重&#xff0c;基期比重、两期比重&#xff09;1.1 现期比重增长贡献量&#xff0c;利润率 1.2基期比重&#xff08;用现期和增长率逆求&#xff09;1.3两期比重&#xff08;难点重点&#xff09;——比较…

基础实验篇 | QGC实时调整控制器参数实验

PART 1 实验名称及目的 QGC实时调整控制器参数实验&#xff1a;在进行硬件在环仿真和真机实验时&#xff0c;常常需要在QGC地面站中观察飞行状态&#xff0c;并对控制器参数进行实时调整&#xff0c;以使得飞机达到最佳的控制效果&#xff0c;但是&#xff0c;在Simulink中设…
最新文章