当前位置: 首页 > news >正文

Kafka - 消息乱序问题的常见解决方案和实现

文章目录

  • 概述
  • 一、MQ消息乱序问题分析
    • 1.1 相同topic内的消息乱序
    • 1.2 不同topic的消息乱序
  • 二、解决方案
    • 方案一: 顺序消息
      • Kafka
        • 1. Kafka 顺序消息的实现
          • 1.1 生产者:确保同一业务主键的消息发送到同一个分区
          • 1.2 消费者:顺序消费消息
        • 2. Kafka 顺序消息实现的局限性
        • 3. 小结
      • RocketMQ
        • 1. 使用 RocketMQ 实现顺序消费
          • 1.1 生产者:发送顺序消息
          • 1.2 消费者:顺序消费消息
        • 2. RocketMQ 顺序消息的局限性
        • 3. 小结
    • 方案二: 前置检测(Pre-check)
      • 前置检测的方案
      • 方案1: 使用辅助表进行前置检测
        • 1.1 方案设计
        • 1.2 数据库表设计
        • 1.3 消费者前置检测代码实现
      • 方案2: 使用序列号/时间戳进行顺序检查
        • 2.1 方案设计
        • 2.2 消费者前置检测代码实现
      • 3. 小结
    • 方案三: 状态机
      • 1. 状态机的设计思路
      • 2. 状态机的实现步骤
      • 3. 设计与实现
      • 3.1 状态机设计
        • 3.1.1 定义状态
        • 3.1.2 定义事件
        • 3.1.3 状态机逻辑
        • 3.1.4 使用状态机处理消息
      • 4. 运行流程
      • 5. 小结
    • 监控与报警
      • 伪实现
  • 总结

在这里插入图片描述


概述

在分布式系统中,消息队列(MQ)作为实现系统解耦和异步通信的重要工具,广泛应用于各种业务场景。然而,消息消费时出现的乱序问题,常常会对业务逻辑的正确执行和系统稳定性产生不良影响。

接下来我们将详细探讨MQ消息乱序问题的根源,并提供一系列在实际应用中可行的解决方案,包括顺序消息、前置检测、状态机等方式

在这里插入图片描述

一、MQ消息乱序问题分析

1.1 相同topic内的消息乱序

  • 并发消费:为了提高消息处理吞吐量,通常会配置多个消费者实例来并发消费同一个队列中的消息。然而,由于消费者实例的性能差异,可能导致消息的消费顺序与发送顺序不一致。
  • 消息分区:MQ系统通常采用分区化设计,当同一业务逻辑的消息分发到不同的分区时,可能出现乱序。
  • 网络延迟与抖动:消息在传输过程中可能会受到网络延迟和抖动的影响,导致消息到达消费者端的顺序与发送顺序不一致。
  • 消息重试与故障恢复:当消费者处理消息失败或出现故障时,重试机制或故障恢复操作不当,也可能导致消息乱序。

1.2 不同topic的消息乱序

例如,系统A在01:00时向TopicA发送了消息msgA-01:00,而系统B在01:01时向TopicB发送了消息msgB-01:01。消费者无法预设msgA-01:00必然先于msgB-01:01被接收。消息系统中的分区策略、消费者的处理能力、网络等因素共同导致无法确保消息遵循严格的先进先出(FIFO)原则。


二、解决方案

为了应对消息乱序问题,有几种常见的解决方案,包括顺序消息、前置检测、状态机等。

方案一: 顺序消息

顺序消息是通过确保同一业务主键的消息发送到同一分区,从而保证消息的顺序性

Kafka

Kafka 为例,虽然它不保证全局消息顺序,但可以通过合理的分区策略和消息键来确保消息的局部顺序性。

下面是使用 Kafka 作为消息队列(MQ)时,如何实现顺序消息的解决方案。通过使用 Kafka 的分区策略和消息键(key),可以确保同一业务主键的消息发送到同一个分区,从而保证消息的顺序性。

1. Kafka 顺序消息的实现
1.1 生产者:确保同一业务主键的消息发送到同一个分区

通过指定消息的 key,Kafka 会确保具有相同 key 的消息发送到同一个分区。这样,即使多个消费者并行消费,也能保证消息在同一个分区内的顺序。

生产者代码实现

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;import java.util.Properties;public class OrderProducer {private final KafkaProducer<String, String> producer;private final String topic;public OrderProducer(String topic) {this.topic = topic;Properties properties = new Properties();properties.put("bootstrap.servers", "localhost:9092");properties.put("key.serializer", StringSerializer.class.getName());properties.put("value.serializer", StringSerializer.class.getName());this.producer = new KafkaProducer<>(properties);}public void sendOrderMessage(String orderId, String orderMessage) {// 使用订单ID作为消息的 key,以确保同一订单的消息发送到同一个分区ProducerRecord<String, String> record = new ProducerRecord<>(topic, orderId, orderMessage);producer.send(record, (metadata, exception) -> {if (exception != null) {exception.printStackTrace();} else {System.out.println("Message sent: " + metadata);}});}public void close() {producer.close();}public static void main(String[] args) {OrderProducer orderProducer = new OrderProducer("order-topic");// 发送顺序消息,确保同一订单的消息被发送到同一分区orderProducer.sendOrderMessage("order123", "Order Created");orderProducer.sendOrderMessage("order123", "Order Paid");orderProducer.sendOrderMessage("order123", "Order Shipped");// 发送另一个订单的消息orderProducer.sendOrderMessage("order456", "Order Created");orderProducer.sendOrderMessage("order456", "Order Paid");orderProducer.close();}
}
  • 在生产者端,通过 ProducerRecord 发送消息时,设置了消息的 key 为订单 ID(orderId)。Kafka 会使用该 key 来确定消息发送到哪个分区,从而确保同一订单的所有消息都会被发送到同一个分区,保证顺序。
  • producer.send() 方法的回调函数用来处理消息发送的异步结果。

1.2 消费者:顺序消费消息

消费者使用 MessageListenerConsumer 来消费消息。Kafka 默认会根据分区消费顺序保证同一分区内消息的顺序。我们只需要保证同一个业务的消息被路由到同一个分区,消费者就能顺序消费这些消息。

消费者代码实现

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;import java.util.Collections;
import java.util.Properties;public class OrderConsumer {private final KafkaConsumer<String, String> consumer;private final String topic;public OrderConsumer(String topic) {this.topic = topic;Properties properties = new Properties();properties.put("bootstrap.servers", "localhost:9092");properties.put("group.id", "order-consumer-group");properties.put("key.deserializer", StringDeserializer.class.getName());properties.put("value.deserializer", StringDeserializer.class.getName());properties.put("auto.offset.reset", "earliest");this.consumer = new KafkaConsumer<>(properties);}public void consumeMessages() {consumer.subscribe(Collections.singletonList(topic));while (true) {consumer.poll(1000).forEach(record -> {// 处理顺序消息System.out.println("Consumed message: " + record.key() + " - " + record.value());});}}public void close() {consumer.close();}public static void main(String[] args) {OrderConsumer orderConsumer = new OrderConsumer("order-topic");// 消费消息,确保同一个订单的消息顺序消费orderConsumer.consumeMessages();}
}
  • 消费者通过 KafkaConsumer 从指定的 topic 中拉取消息。在这种实现中,消息会按照 Kafka 内部的消费机制被顺序消费。

  • consumer.poll() 方法定期从 Kafka 中拉取消息,并根据 key 分配到相应的分区进行消费。

  • Kafka 的分区是顺序消费的,即每个分区内的消息按照生产者发送的顺序消费。因此,通过确保同一订单的消息使用相同的 key,就能保证同一分区内消息的消费顺序。

2. Kafka 顺序消息实现的局限性
  1. 局部顺序保证:Kafka 只能保证同一分区内的消息顺序,对于跨分区的消息并不保证顺序。因此,确保同一业务的消息发送到同一分区非常关键。
  2. 性能与吞吐量:为了提高系统的吞吐量和并发能力,Kafka 会对 topic 进行分区。分区数过多可能影响顺序性,但可以通过合理设计业务键来平衡性能和顺序性要求。
3. 小结

通过使用 Kafka 的分区和消息键机制,我们可以确保同一业务主键的消息在同一分区内顺序消费。这种方法适用于需要保证顺序性的场景,如订单处理等。生产者确保消息按照业务主键路由到同一分区,消费者则按分区顺序消费消息,从而避免消息乱序的问题。


RocketMQ

在使用 RocketMQ 作为消息队列时,确保消息的顺序消费可以通过 顺序消息Ordered Message)的特性来实现。RocketMQ 支持两种类型的顺序消费:局部顺序(确保同一消息队列内的消息顺序)和 全局顺序(通过单一队列保证全局顺序,但在高并发情况下可能会影响性能)。

1. 使用 RocketMQ 实现顺序消费
1.1 生产者:发送顺序消息

生产者通过指定消息的 key 来确保具有相同 key 的消息被发送到同一个消息队列,从而保证顺序性。RocketMQ 支持发送顺序消息的 API,通过 MessageQueueSelector 来指定消息发送到哪个队列。

生产者代码实现

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.common.RemotingHelper;import java.util.List;public class OrderProducer {private DefaultMQProducer producer;public OrderProducer(String groupName) throws Exception {// 创建生产者实例producer = new DefaultMQProducer(groupName);producer.setNamesrvAddr("localhost:9876"); // RocketMQ服务器地址producer.start();}public void sendOrderMessage(String orderId, String orderMessage) throws Exception {// 创建消息实例Message message = new Message("OrderTopic", "OrderTag", orderMessage.getBytes(RemotingHelper.DEFAULT_CHARSET));// 使用订单ID作为消息的key,确保同一订单的消息发送到同一队列SendResult sendResult = producer.send(message, new MessageQueueSelector() {@Overridepublic MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {String orderId = (String) arg;int queueIndex = orderId.hashCode() % mqs.size(); // 根据订单ID选择队列return mqs.get(queueIndex);}}, orderId);System.out.println("Message sent: " + sendResult);}public void close() {producer.shutdown();}public static void main(String[] args) throws Exception {OrderProducer producer = new OrderProducer("order-group");// 发送顺序消息,确保同一订单的消息被发送到同一队列producer.sendOrderMessage("order123", "Order Created");producer.sendOrderMessage("order123", "Order Paid");producer.sendOrderMessage("order123", "Order Shipped");producer.sendOrderMessage("order456", "Order Created");producer.sendOrderMessage("order456", "Order Paid");producer.close();}
}
  • 生产者通过 MessageQueueSelector 来确保相同 key 的消息被发送到相同的队列。这里我们使用 orderId 作为消息的 key,通过计算 orderId.hashCode() 来决定消息发送到哪个队列。确保同一个订单的消息发送到同一个队列,从而在消费时保持顺序性。
  • SendResult 会返回发送结果,包括消息发送的状态。

1.2 消费者:顺序消费消息

在消费者端,RocketMQ 提供了 MessageListenerOrderly 接口来实现顺序消费。该接口保证在同一队列内,消息会按照发送的顺序被消费。

消费者代码实现

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageListenerOrderly;
import org.apache.rocketmq.client.consumer.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.consumer.ConsumeConcurrentlyContext;
import org.apache.rocketmq.common.consumer.ConsumeOrderlyContext;import java.util.List;public class OrderConsumer {private DefaultMQPushConsumer consumer;public OrderConsumer(String groupName) throws Exception {// 创建消费者实例consumer = new DefaultMQPushConsumer(groupName);consumer.setNamesrvAddr("localhost:9876"); // RocketMQ服务器地址consumer.subscribe("OrderTopic", "*"); // 订阅指定的 topic 和 tag}public void consumeMessages() throws Exception {// 设置顺序消费监听器consumer.registerMessageListener(new MessageListenerOrderly() {@Overridepublic ConsumeOrderlyContext consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {for (MessageExt msg : msgs) {// 消费顺序消息System.out.println("Consumed message: " + new String(msg.getBody()));}return ConsumeOrderlyContext.SUCCESS;}});consumer.start();}public void close() {consumer.shutdown();}public static void main(String[] args) throws Exception {OrderConsumer consumer = new OrderConsumer("order-consumer-group");// 开始消费顺序消息consumer.consumeMessages();}
}
  • 消费者使用 MessageListenerOrderly 来实现顺序消费。该接口保证了消费者在同一消息队列内按顺序消费消息。

  • 消费者在接收到消息后,会依次消费并输出消息内容。

  • RocketMQ 是基于消息队列的,每个队列内的消息是顺序消费的,即使有多个消费者,也只会有一个消费者消费某个队列的消息。通过将同一 key 的消息发送到同一个队列,可以确保这些消息按照顺序被消费。

  • 需要注意的是,RocketMQ 保证的是 局部顺序,即同一队列内的消息按照发送顺序消费。对于多个队列和多个消费者,只有同一个队列内的消息顺序是保证的。


2. RocketMQ 顺序消息的局限性
  • 局部顺序保证:RocketMQ 只能保证同一队列内的消息顺序,对于多个队列之间的消息没有顺序保证。
  • 性能影响:如果需要保证全局顺序,可能需要将所有消息都发送到同一个队列,这会影响性能,导致吞吐量下降。通常需要在性能和顺序性之间进行权衡。
3. 小结

通过使用 RocketMQMessageQueueSelectorMessageListenerOrderly,我们可以保证同一业务的消息在同一队列内顺序消费。这种方式适用于需要保证顺序的场景,如订单处理、支付等高可靠性的业务系统。生产者通过业务主键选择队列,消费者则顺序消费消息,确保数据一致性和业务流程的正确执行。


方案二: 前置检测(Pre-check)

前置检测(Pre-check)在消息队列消费中,常用于确保消息消费的顺序性,防止因为消息乱序导致的数据不一致或业务错误。其核心思想是在消息消费之前进行验证,确保前置条件满足才继续消费当前消息

在消费者处理消息之前,进行前置条件检查,确保上一条消息已成功消费。这可以通过消息辅助表来实现,或者在消息中附带序列号、时间戳等信息进行验证。

前置检测的方案

前置检测主要包括以下几种常见方法:

  1. 使用辅助表进行状态检查:通过创建一个辅助表(如状态表或消息表),记录消息的状态,消费者可以通过查询该表来验证上一个消息是否已经成功处理,确保消息按顺序消费。

  2. 使用序列号/时间戳进行顺序检查:在消息中包含序列号或时间戳,消费者根据这些信息判断当前消息是否按预期顺序到达。如果不符合顺序,则将当前消息暂时缓存,等待前一个消息处理完成。

  3. 利用死信队列处理无序消息:当消息的顺序不符合预期时,可以将这些消息暂时放入死信队列(DLQ)中,待前置消息消费成功后再重新消费。

方案1: 使用辅助表进行前置检测

假设在处理订单相关的消息时,我们希望确保订单的状态始终按照正确的顺序处理,比如,Order Created 应该在 Order Paid 前消费。

1.1 方案设计
  • 设计一个 order_status 表,记录订单的处理状态。
  • 消费者在处理消息前,查询这个表,确保订单的前置状态已经处理完毕。
  • 消费失败时,可以将消息放入死信队列或重试。
1.2 数据库表设计
CREATE TABLE order_status (order_id VARCHAR(255) PRIMARY KEY,status VARCHAR(255) NOT NULL,update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);-- 状态示例
-- 订单创建:CREATED
-- 订单支付:PAID
-- 订单完成:COMPLETED
1.3 消费者前置检测代码实现
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageListenerOrderly;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.consumer.ConsumeOrderlyContext;
import java.util.List;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;public class OrderConsumerWithPreCheck {private static final String DB_URL = "jdbc:mysql://localhost:3306/order_db";private static final String DB_USER = "root";private static final String DB_PASSWORD = "password";private DefaultMQPushConsumer consumer;public OrderConsumerWithPreCheck(String groupName) throws Exception {consumer = new DefaultMQPushConsumer(groupName);consumer.setNamesrvAddr("localhost:9876"); // RocketMQ服务器地址consumer.subscribe("OrderTopic", "*"); // 订阅指定的 topic 和 tag}// 检查订单状态public boolean checkOrderStatus(String orderId, String expectedStatus) throws Exception {try (Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD)) {String query = "SELECT status FROM order_status WHERE order_id = ?";try (PreparedStatement statement = connection.prepareStatement(query)) {statement.setString(1, orderId);ResultSet rs = statement.executeQuery();if (rs.next()) {String currentStatus = rs.getString("status");return expectedStatus.equals(currentStatus); // 比对期望状态}}}return false; // 订单未找到,默认返回 false}public void consumeMessages() throws Exception {consumer.registerMessageListener(new MessageListenerOrderly() {@Overridepublic ConsumeOrderlyContext consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {for (MessageExt msg : msgs) {String orderId = msg.getKeys();  // 假设订单ID存储在消息的keys字段String currentStatus = new String(msg.getBody());// 检查前置状态,确保当前状态是顺序的try {if ("OrderCreated".equals(currentStatus)) {if (!checkOrderStatus(orderId, "CREATED")) {System.out.println("Order not created yet, skipping message: " + orderId);continue;  // 如果前置状态不符合,跳过该消息}} else if ("OrderPaid".equals(currentStatus)) {if (!checkOrderStatus(orderId, "PAID")) {System.out.println("Order not paid yet, skipping message: " + orderId);continue;}}// 消费消息逻辑System.out.println("Processing order message: " + orderId + " - " + currentStatus);// 更新状态或其他业务逻辑} catch (Exception e) {e.printStackTrace();}}return ConsumeOrderlyContext.SUCCESS;}});consumer.start();}public void close() {consumer.shutdown();}public static void main(String[] args) throws Exception {OrderConsumerWithPreCheck consumer = new OrderConsumerWithPreCheck("order-consumer-group");consumer.consumeMessages();}
}

方案2: 使用序列号/时间戳进行顺序检查

在这种方法中,我们为每个消息分配一个 序列号时间戳,并通过对比当前消息的序列号和前一条消息的序列号来确保消息按顺序消费。如果序列号不符合预期,消费者会将该消息缓存,等待前置消息的消费完成。

2.1 方案设计
  • 消息中包含一个 sequenceIdtimestamp 字段。
  • 消费者检查当前消息的 sequenceId,如果当前消息的 sequenceId 小于等于上一个已消费消息的 sequenceId,则跳过当前消息。
2.2 消费者前置检测代码实现
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageListenerOrderly;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.consumer.ConsumeOrderlyContext;import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;public class OrderConsumerWithSequenceCheck {private DefaultMQPushConsumer consumer;private AtomicInteger lastSequenceId = new AtomicInteger(0);  // 记录最后处理的序列号public OrderConsumerWithSequenceCheck(String groupName) throws Exception {consumer = new DefaultMQPushConsumer(groupName);consumer.setNamesrvAddr("localhost:9876"); // RocketMQ服务器地址consumer.subscribe("OrderTopic", "*"); // 订阅指定的 topic 和 tag}// 检查消息的序列号,确保顺序性public boolean checkSequenceId(int currentSequenceId) {return currentSequenceId == lastSequenceId.incrementAndGet();}public void consumeMessages() throws Exception {consumer.registerMessageListener(new MessageListenerOrderly() {@Overridepublic ConsumeOrderlyContext consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {for (MessageExt msg : msgs) {int sequenceId = Integer.parseInt(new String(msg.getBody())); // 消息中的序列号if (!checkSequenceId(sequenceId)) {System.out.println("Out of order message, skipping message with sequence: " + sequenceId);continue;  // 如果消息序列号不符合顺序,则跳过}// 消费消息逻辑System.out.println("Processing message with sequence ID: " + sequenceId);// 进行相应的业务处理}return ConsumeOrderlyContext.SUCCESS;}});consumer.start();}public void close() {consumer.shutdown();}public static void main(String[] args) throws Exception {OrderConsumerWithSequenceCheck consumer = new OrderConsumerWithSequenceCheck("order-consumer-group");consumer.consumeMessages();}
}

3. 小结

前置检测方案的核心是通过验证当前消息的处理条件(如订单的状态或消息的序列号),确保前置条件满足后再继续处理当前消息。此方案能有效防止由于消息乱序导致的数据不一致或业务错误,适用于需要严格保证处理顺序的场景。

  • 数据库检查:通过查询数据库记录来验证消息的处理顺序。
  • 序列号检查:通过消息中的序列号或时间戳验证消息是否按顺序到达。

方案三: 状态机

可以利用状态机来管理消息的消费顺序和状态。状态机的核心思想是定义系统的不同状态,以及触发状态变更的事件,从而确保消息在正确的状态下被处理。

通过引入状态机,我们能够:

  • 通过状态转移机制保证消息按顺序消费。
  • 在状态转移过程中,避免非法的状态变更和消息丢失。

1. 状态机的设计思路

在处理消息时,可以将消息消费的过程视为一系列状态的变换。每个消息会根据其当前状态决定是否可以进行处理。

  1. 定义状态

    • 定义消息消费的不同状态,例如 PENDING(待处理)、PROCESSING(处理中)、PROCESSED(已处理)。
    • 每个消息在处理过程中会从一个状态转移到另一个状态。
  2. 定义事件

    • 每个消息可能触发一个事件,事件可以是消息的到达或者某些外部条件的变化。
    • 通过事件来决定状态的转移。
  3. 处理顺序

    • 确保某些消息必须在特定的顺序下处理。比如,某个状态的消息必须先处理完成,才能处理下一个状态的消息。

2. 状态机的实现步骤

  • 状态定义:使用枚举类(enum)定义消息的状态。
  • 事件定义:根据消息到达的顺序或其他外部条件触发不同的事件。
  • 状态机实现:根据当前状态和事件的触发来决定状态转移。

3. 设计与实现

假设我们有一个订单处理系统,订单的状态可能为以下几种:

  • ORDER_CREATED:订单已创建
  • ORDER_PAID:订单已支付
  • ORDER_SHIPPED:订单已发货
  • ORDER_COMPLETED:订单已完成

我们希望确保消息的消费顺序是按顺序进行的,即订单创建 -> 支付 -> 发货 -> 完成。

3.1 状态机设计

3.1.1 定义状态

首先定义订单状态的枚举类型 OrderState

public enum OrderState {ORDER_CREATED,  // 订单已创建ORDER_PAID,     // 订单已支付ORDER_SHIPPED,  // 订单已发货ORDER_COMPLETED // 订单已完成
}
3.1.2 定义事件

根据业务需求,定义事件触发的条件。比如:

  • ORDER_CREATED_EVENT:订单创建事件
  • ORDER_PAID_EVENT:订单支付事件
  • ORDER_SHIPPED_EVENT:订单发货事件
  • ORDER_COMPLETED_EVENT:订单完成事件
3.1.3 状态机逻辑

使用一个状态机类来管理状态的转换。状态机会根据当前状态和触发的事件来进行状态转换。

import java.util.HashMap;
import java.util.Map;public class OrderStateMachine {// 订单状态private OrderState currentState;// 状态转移规则,基于当前状态和事件决定下一个状态private final Map<OrderState, Map<String, OrderState>> transitionTable;public OrderStateMachine() {// 初始化状态为 ORDER_CREATEDthis.currentState = OrderState.ORDER_CREATED;// 初始化状态转移规则this.transitionTable = new HashMap<>();// 设置转移规则// 从 ORDER_CREATED 可以转到 ORDER_PAIDaddTransition(OrderState.ORDER_CREATED, "ORDER_CREATED_EVENT", OrderState.ORDER_PAID);// 从 ORDER_PAID 可以转到 ORDER_SHIPPEDaddTransition(OrderState.ORDER_PAID, "ORDER_PAID_EVENT", OrderState.ORDER_SHIPPED);// 从 ORDER_SHIPPED 可以转到 ORDER_COMPLETEDaddTransition(OrderState.ORDER_SHIPPED, "ORDER_SHIPPED_EVENT", OrderState.ORDER_COMPLETED);}// 添加状态转换规则private void addTransition(OrderState fromState, String event, OrderState toState) {transitionTable.putIfAbsent(fromState, new HashMap<>());transitionTable.get(fromState).put(event, toState);}// 处理事件并转换状态public boolean handleEvent(String event) {Map<String, OrderState> transitions = transitionTable.get(currentState);if (transitions != null && transitions.containsKey(event)) {OrderState nextState = transitions.get(event);System.out.println("State transition: " + currentState + " -> " + nextState);this.currentState = nextState; // 执行状态转移return true;} else {System.out.println("Invalid event for the current state: " + currentState);return false;}}// 获取当前状态public OrderState getCurrentState() {return currentState;}
}
3.1.4 使用状态机处理消息

假设我们在消息队列中有不同的订单消息,需要按顺序消费。我们将消费者与状态机结合使用,确保消息按照正确的顺序消费。

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageListenerOrderly;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.consumer.ConsumeOrderlyContext;import java.util.List;public class OrderConsumerWithStateMachine {private static final String TOPIC = "OrderTopic";private static final String GROUP = "OrderConsumerGroup";private DefaultMQPushConsumer consumer;private OrderStateMachine stateMachine;public OrderConsumerWithStateMachine() {consumer = new DefaultMQPushConsumer(GROUP);stateMachine = new OrderStateMachine();try {consumer.setNamesrvAddr("localhost:9876");consumer.subscribe(TOPIC, "*");consumer.registerMessageListener(new MessageListenerOrderly() {@Overridepublic ConsumeOrderlyContext consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {for (MessageExt msg : msgs) {String event = new String(msg.getBody());System.out.println("Received message: " + event);// 根据消息的内容触发状态机事件if ("ORDER_CREATED_EVENT".equals(event)) {stateMachine.handleEvent("ORDER_CREATED_EVENT");} else if ("ORDER_PAID_EVENT".equals(event)) {stateMachine.handleEvent("ORDER_PAID_EVENT");} else if ("ORDER_SHIPPED_EVENT".equals(event)) {stateMachine.handleEvent("ORDER_SHIPPED_EVENT");} else if ("ORDER_COMPLETED_EVENT".equals(event)) {stateMachine.handleEvent("ORDER_COMPLETED_EVENT");}System.out.println("Current state: " + stateMachine.getCurrentState());}return ConsumeOrderlyContext.SUCCESS;}});consumer.start();System.out.println("Order consumer started");} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) {new OrderConsumerWithStateMachine();}
}

4. 运行流程

  1. 消费者会根据事件(如 ORDER_CREATED_EVENT, ORDER_PAID_EVENT 等)处理消息。
  2. 消费者会触发状态机,状态机会根据当前状态和事件来进行状态转换。
  3. 如果消息的顺序不正确(例如 ORDER_PAID_EVENTORDER_CREATED_EVENT 之前到达),状态机会拒绝处理,并打印 Invalid event for the current state

5. 小结

  • 状态机可以帮助管理消息的消费顺序,确保在处理消息时遵循正确的流程和业务逻辑。
  • 通过定义状态和事件,状态机提供了一个清晰的框架来管理复杂的消息处理过程。
  • 结合消息队列,状态机可以有效地控制消息的顺序消费,避免乱序带来的问题。

监控与报警

建立系统的监控和报警机制,及时发现并处理消息错乱等异常情况。通过设定阈值或检测规则,监控系统的消息流转,确保及时响应并纠正问题。

  • 定期监控消息队列的消费进度,若发现消费滞后或消息顺序异常,自动报警。
  • 通过日志和统计信息,捕获异常并自动触发处理流程。

伪实现

public class MessageMonitor {private static final Logger logger = LoggerFactory.getLogger(MessageMonitor.class);public void monitorMessageQueue() {// 假设有一个队列监控机制boolean isOutOfOrder = checkMessageOrder();if (isOutOfOrder) {logger.error("Message order error detected, triggering alert!");// 触发报警或采取恢复措施}}private boolean checkMessageOrder() {// 检查消息顺序是否正常return false; // 假设没有乱序}
}

总结

MQ消息乱序是分布式系统中常见的挑战,直接影响到系统的稳定性和业务一致性。我们可以通过顺序消息、前置检测、状态机等解决方案, 保证消息的顺序性,提高系统的可靠性和用户体验。

在这里插入图片描述

相关文章:

Kafka - 消息乱序问题的常见解决方案和实现

文章目录 概述一、MQ消息乱序问题分析1.1 相同topic内的消息乱序1.2 不同topic的消息乱序 二、解决方案方案一&#xff1a; 顺序消息Kafka1. Kafka 顺序消息的实现1.1 生产者&#xff1a;确保同一业务主键的消息发送到同一个分区1.2 消费者&#xff1a;顺序消费消息 2. Kafka 顺…...

俏生元,融汇传统智慧与现代科技,解析药食同源健康奥秘

在追求健康生活的今天&#xff0c;药食同源的理念正逐渐成为现代人滋养身心的新选择。俏生元&#xff0c;一直以来注重女性健康和多元需求&#xff0c;正以它独特的视角和匠心产品&#xff0c;助推着药食同源健康滋养的风尚。 俏生元葛根红参丰韵膏 药食同源&#xff0c;匠心独…...

【CUDA】CUBLAS

【CUDA】CUBLAS 在深入了解之前&#xff0c;提前运行预热&#xff08;warmup&#xff09;和基准测试&#xff08;benchmark runs&#xff09; 是获得准确执行时间的关键。如果不进行预热运行&#xff0c;cuBLAS 的首次运行会有较大的开销&#xff08;大约 45 毫秒&#xff09;…...

泛型编程--

auto自动推导数据类型 函数模板 定义和调用 函数模板具体化 函数模板通用版本之外的一个特殊版本 函数模板 具体化函数 &#xff0c;它们的声明和定义都可以分开写。 声明 定义 函数模板写变量 模板参数缺省 类成员函数作为函数模板 类构造函数是函数模板 函数模板重载 函数模…...

Linux USB开发整理和随笔

目录 1 概述 2 硬件原理基础 2.1 USB发展 2.2 USB的拓扑 2.3 硬件接口 2.4 USB总线协议 2.4.1 通信过程 2.4.2 概念关系 2.4.3 管道PIPE 2.4.4 传输 2.4.5 事务 2.4.6 包结构与类型 2.4.6.1 令牌包 2.4.6.2 数据包 2.4.6.3 握手包 2.5 描述符 2.5.1 设备描述符…...

【实验】【H3CNE邓方鸣】交换机端口安全实验+2024.12.11

实验来源&#xff1a;邓方鸣交换机端口安全实验 软件下载&#xff1a; 华三虚拟实验室: 华三虚拟实验室下载 wireshark&#xff1a;wireshark SecureCRT v8.7 版本: CRT下载分享与破解 文章目录 dot1x 开启802.1X身份验证 开启802.1X身份验证&#xff0c;需要在系统视图和接口视…...

使用任务队列TaskQueue和线程池ThreadPool技术实现自定义定时任务框架详解

前言 在桌面软件开发中&#xff0c;定时任务是一个常见的需求&#xff0c;比如定时清理日志、发送提醒邮件或执行数据备份等操作。在C#中有一个非常著名的定时任务处理库Hangfire&#xff0c;不过在我们深入了解Hangfire 之前&#xff0c;我们可以手动开发一个定时任务案例&am…...

在IDE中使用Git

我们在开发的时候肯定是经常使用IDE进行开发的&#xff0c;所以在IDE中使用Git也是非常常用的&#xff0c;接下来以IDEA为例&#xff0c;其他的VS code &#xff0c;Pycharm等IDE都是一样的。 在IDEA中配置Git 1.打开IDEA 2.点击setting 3.直接搜索git 如果已经安装了会自…...

蓝桥杯新年题解 | 第15届蓝桥杯迎新篇

蓝桥杯新年题解 | 第15届蓝桥杯迎新篇 2024年的蓝桥杯即将拉开序幕&#xff01;对于许多编程爱好者来说&#xff0c;这不仅是一次展示自我能力的舞台&#xff0c;更是一次学习和成长的机会。作为一名大一新生的小蓝&#xff0c;对蓝桥杯充满了期待&#xff0c;但面对初次参赛的…...

Docker Swarm实战

文章目录 1、docker swarm介绍2、docker swarm概念与架构2.1 架构2.2 概念 3、docker swarm集群部署3.1 容器镜像仓库 Harbor准备3.2 主机准备3.2.1 主机名3.2.2 IP地址3.2.3 主机名与IP地址解析3.3.4 主机时间同步3.2.5 主机安全设置 3.3 docker安装3.3.1 docker安装3.3.2 配置…...

磁盘空间占用分析工具-wiztree【推荐】

磁盘空间占用分析工具-wiztree【推荐】 如果你遇到过磁盘空间占满、找大文件困难、线上服务器空间飙升等一系列磁盘的问题&#xff0c;并且需要分析文件夹占用空间&#xff0c;传统的方法就是一个一个去看&#xff0c;属实太费劲&#xff0c;效率太低。 而“WizTree”便可解决…...

Vuex在uniapp中的使用

文章目录 一、Vuex概述 1.1 官方解释 1.2 大白话 1.3 组件间共享数据的方式 1.4 再看Vuex是什么 1.5 使用Vuex统一管理好处 二、状态管理 2.1 单页面状态管理 2.2 多页面状态管理 2.3 全局单例模式 2.4 管理哪些状态 三、Vuex的基本使用 3.1 安装 3.2 导入 3.3 创建store对象…...

【含开题报告+文档+PPT+源码】基于微信小程序的点餐系统的设计与实现

开题报告 随着互联网技术的日益成熟和消费者生活水平与需求层次的显著提升&#xff0c;外卖点餐平台在中国市场上迅速兴起并深深植根于民众日常生活的各个角落。这类平台的核心在于构建了一个基于互联网的强大订餐服务系统&#xff0c;它无缝整合了餐饮商户资源与广大消费者的…...

Elasticsearch02-安装7.x

零、文章目录 Elasticsearch02-安装7.x 1、Windows安装Elasticsearch &#xff08;1&#xff09;JDK安装 Elasticsearch是基于java开发的&#xff0c;所以需要安装JDK。我们安装的Elasticsearch版本是7.15&#xff0c;对应JDK至少1.8版本以上。也可以不安装jdk&#xff0c;…...

【数据库】选择题+填空+简答

1.关于冗余数据的叙述中&#xff0c;不正确的是&#xff08;&#xff09; A.冗余的存在容易破坏数据库的完整新 B.冗余的存在给数据库的维护增加困难 C.不应该在数据库中存储任何冗余数据 D.冗余数据是指由基本数据导出的数据 C 2.最终用户使用的数据视图称为&#xff08;&…...

Spark执行计划解析后是如何触发执行的?

在前一篇Spark SQL 执行计划解析源码分析中&#xff0c;笔者分析了Spark SQL 执行计划的解析&#xff0c;很多文章甚至Spark相关的书籍在讲完执行计划解析之后就开始进入讲解Stage切分和调度Task执行&#xff0c;每个概念之间没有强烈的关联&#xff0c;因此这中间总感觉少了点…...

渗透测试-前端验签绕过之SHA256+RSA

本文是高级前端加解密与验签实战的第2篇文章&#xff0c;本系列文章实验靶场为Yakit里自带的Vulinbox靶场&#xff0c;本文讲述的是绕过SHA256RSA签名来爆破登录。 绕过 根据提示可以看出这次签名用了SHA2556和RSA两个技术进行加密。 查看源代码可以看到RSA公钥是通过请求服务…...

Maven完整技术汇总

额外知识点 IDE IDE是集成开发环境的缩写&#xff0c;它是一种软件应用程序&#xff0c;提供了编码、调试和部署软件的一站式解决方案。这些功能集成在一起&#xff0c;使开发人员能够在一个环境中完成整个软件开发过程&#xff0c;从编写代码到调试和测试&#xff0c;直到最终…...

NOI系列赛事LaTeX模板

NOI系列赛事 L a T e X LaTeX LaTeX 模板 照搬照抄&#xff1a; s y k s y k C C C syksykCCC syksykCCC 大佬写的&#xff0c;但是看得人不多。真的很好&#xff0c;比其他的板子优秀多了。现在我当一个校友搬运工&#xff0c;搬过来。 \documentclass[UTF8,a4paper]{ctex…...

JustTrustMe是什么

JustTrustMe是什么 JustTrustMe 是一个用于 Android 的 Xposed 模块&#xff0c;主要用于绕过应用程序的 SSL pinning&#xff08;SSL 证书锁定&#xff09;机制。SSL pinning 是一种安全措施&#xff0c;应用程序通过它来验证服务器返回的 SSL 证书是否与应用程序内置的证书匹…...

题解 - 工作分配

题目描述 在工厂里&#xff0c;如果每道工序让不同的工人来做&#xff0c;所要花费的时间往往不一样。精明的老板为了提高效率&#xff0c;总是把生产某一产品所需要的N道工序进行最佳搭配&#xff0c;使生产某一产品所花费的总时间最少。现在就给出N个工人分别做N道工序所要花…...

GLM-4-Plus初体验

引言&#xff1a;为什么高效的内容创作如此重要&#xff1f; 在当前竞争激烈的市场环境中&#xff0c;内容创作已成为品牌成功的重要支柱。无论是撰写营销文案、博客文章、社交媒体帖子&#xff0c;还是制作广告&#xff0c;优质的内容不仅能够帮助品牌吸引目标受众的注意力&a…...

【Python基础】Python知识库更新中。。。。

1、Python知识库简介 现阶段主要源于个人对 Python 编程世界的强烈兴趣探索&#xff0c;在深入钻研 Python 核心语法、丰富库函数应用以及多样化编程范式的基础上&#xff0c;逐步向外拓展延伸&#xff0c;深度挖掘其在数据分析、人工智能、网络开发等多个前沿领域的应用潜力&…...

【arm】程序跑飞,SWD端口不可用修复(N32G435CBL7)

项目场景&#xff1a; 国民N32G43X系列&#xff0c;烧录了一个测试程序&#xff0c;在DEBUG中不知什么原因挂掉&#xff0c;然后就无法连接SWD或JLINK。 问题描述 在SWD配置中不可见芯片型号&#xff0c;无法connect&#xff0c;无法烧录。但基本判断是芯片没有损坏。怀疑是程…...

C++如何读取包含空格在内的整行字符串s? ← getline(cin,s);

【问题描述】 问&#xff1a;请分析下面代码&#xff0c;在利用 cin 输入带空格的整行字符串时&#xff0c;会输出什么&#xff1f; #include <bits/stdc.h> using namespace std;int main() {string s;cin>>s;for(int i0; i<s.size(); i) {cout<<s[i];}…...

活动预告 | Microsoft 365 在线技术公开课:让组织针对 Microsoft Copilot 做好准备

课程介绍 通过Microsoft Learn免费参加Microsoft 365在线技术公开课&#xff0c;建立您需要的技能&#xff0c;以创造新的机会并加速您对Microsoft云技术的理解。参加我们举办的“让组织针对 Microsoft Copilot for Microsoft 365 做好准备” 在线技术公开课活动&#xff0c;学…...

tomcat被检测到目标URL存在htp host头攻击漏洞

AI越来越火了,我们想要不被淘汰就得主动拥抱。推荐一个人工智能学习网站,通俗易懂,风趣幽默,最重要的屌图甚多,忍不住分享一下给大家。点击跳转到网站 Tomcat被检测到目标URL存在http host头攻击漏洞,这个漏洞复现一下就是黑客访问你的网站,之后中修改请求头中的host属…...

【使用webrtc-streamer解析rtsp视频流】

webrtc-streamer WebRTC (Web Real-Time Communications) 是一项实时通讯技术&#xff0c;它允许网络应用或者站点&#xff0c;在不借助中间媒介的情况下&#xff0c;建立浏览器之间点对点&#xff08;Peer-to-Peer&#xff09;的连接&#xff0c;实现视频流和&#xff08;或&a…...

【数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 测试说明 我的通关代码: 测试结果&#xff1a; 任务描述 本关任务&#xff1a;编写一个程序实现单链表的基本运算。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;初始化线性表、销毁线性表、判定是否为空表、求线性…...

华为FreeBuds Pro 4丢了如何找回?(附查找功能使用方法)

华为FreeBuds Pro 4查找到底怎么用&#xff1f;华为FreeBuds Pro 4有星闪精确查找和离线查找&#xff0c;离线查找功能涵盖播放铃声、导航定位、星闪精确查找、上线通知、丢失模式、遗落提醒等。星闪精确查找是离线查找的子功能&#xff0c;当前仅华为FreeBuds Pro 4充电盒支持…...

直流开关电源技术及应用

文章目录 1. 开关电源概论1.1 开关电源稳压原理1.1.1 开关电源稳压原理 1. 开关电源概论 1.1 开关电源稳压原理 为了提高效率&#xff0c;必须使功率调整器件处于开关工作状态。 作为开关而言&#xff0c;导通时压降很小&#xff0c;几乎不消耗能量&#xff0c;关断时漏电流很…...

langchain 结构化输出

主要流程 1. 使用 Pydantic 定义结构化输出&#xff1a; 定义 AnswerWithJustification 类&#xff0c;用于描述输出的结构&#xff0c;包含以下字段&#xff1a; answer&#xff1a;答案内容&#xff08;字符串类型&#xff09;。justification&#xff1a;答案的理由或解释…...

开源Java快速自测工具,可以调用系统内任意一个方法

java快速测试框架&#xff0c;可以调到系统内任意一个方法&#xff0c;告别写单测和controller的困扰。 开源地址&#xff1a;https://gitee.com/missyouch/Easy-JTest 我们在开发时很多时候想要测试下自己的代码&#xff0c;特别是service层或者是更底层的代码&#xff0c;就…...

挺详细的记录electron【V 33.2.0】打包vue3项目为可执行程序

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、直接看效果 二、具体步骤 1.安装配置electron 1.将 electron 包安装到应用的开发依赖中。 2.安装electron-packager依赖&#xff08;打包可执行文件&#…...

相比普通LED显示屏,强力巨彩软模组有哪些优势?

在科技技术的加持下&#xff0c;LED显示屏市场各类创新产品层出不穷&#xff0c;为市场带来了无限可能。其中&#xff0c;强力巨彩R系列H版&#xff08;软模组&#xff09;凭借其独特的技术优势&#xff0c;在行业内脱颖而出。那么&#xff0c;相比常规LED显示屏&#xff0c;强…...

操作系统(7)处理机调度

前言 操作系统中的处理机调度是一个核心概念&#xff0c;它涉及如何从就绪队列中选择进程并将处理机分配给它以运行&#xff0c;从而实现进程的并发执行。 一、调度的层次 高级调度&#xff08;作业调度&#xff09;&#xff1a; 调度对象&#xff1a;作业&#xff08;包含程序…...

【Spark】Spark的两种核心Shuffle工作原理详解

如果觉得这篇文章对您有帮助&#xff0c;别忘了点赞、分享或关注哦&#xff01;您的一点小小支持&#xff0c;不仅能帮助更多人找到有价值的内容&#xff0c;还能鼓励我持续分享更多精彩的技术文章。感谢您的支持&#xff0c;让我们一起在技术的世界中不断进步&#xff01; Sp…...

10.qml使用 shadereffect 实现高斯模糊

目录 高斯模糊sigma获取加权均值获取 高斯二维公式实现高斯一维公式实现使用总结 高斯模糊 高斯模糊应用领域我就不过多讲解&#xff0c;想了解自己去了解 高斯模糊有 一维公式 二维公式 当然我们图像是二维的 但是实际上二维公式用于计算那是消耗大量的算力的&#xff0c…...

2024年12月GESPC++一级真题解析

一、单选题&#xff08;每题2分&#xff0c;共30分&#xff09; 题目123456789101112131415答案 C C D B B D B C C C D C D B D 1.2024 年 10 月 8 日&#xff0c;诺贝尔物理学奖 “ 意外地 ” 颁给了两位计算机科学家约翰 霍普菲尔德&#xff08; John J. H…...

Nmap脚本参数详解

免责声明&#xff1a;使用本教程或工具&#xff0c;用户必须遵守所有适用的法律和法规&#xff0c;并且用户应自行承担所有风险和责任。 文章目录 一、 按脚本分类1. 检查身份验证机制2. 探测广播行为3. 登录爆破4. 默认脚本运行5. 网络资产发现6. Dos漏洞检测7. 漏洞利用8. 检…...

Rstudio-server的安装、配置、维护

一、安装Rstudio-server (1)安装R语言&#xff1a; sudo apt install r-base # 如果没有管理员权限无法操作 # 这样装上R默认在/usr/bin/R其实基本上的流程都可以参考posit的官网&#xff08;也就是Rstudio的官网&#xff09;&#xff1a; https://posit.co/download/rstudio…...

2024ECCV|DiffBIR: 基于生成扩散先验进行盲图像恢复

文章标题&#xff1a;《DiffBIR: Towards Blind Image Restoration with Generative Diffusion Prior》 DiffBIR收录于2024ECCV&#xff0c;是中科院深圳先进技术研究院&#xff08;董超等人&#xff09;、上海AI Lab和香港中文大学联合发布的一项研究。 原文链接&#xff1a;h…...

前端报错npm ERR cb() never called问题

环境使用node版本v14.21.3&#xff0c;npm版本6.14.18 1.问题描述 1.1使用npm install后报错 npm ERR! cb() never called!npm ERR! This is an error with npm itself. Please report this error at: npm ERR! ? ? <https://npm.community>npm ERR! A complete log…...

SLM510A系列——24V,15到150mA单通道可调电流线性恒流LED驱动芯片

SLM510A 系列产品是单通道、高精度、可调电流线性恒流源的 LED 驱动芯片&#xff0c;在各种 LED 照明产品中非常简单易用。其在宽电压输入范围内&#xff0c;能保证极高的输出电流精度&#xff0c;从而在大面积的光源照明中&#xff0c;都能让 LED 照明亮度保持均匀一致。 由于…...

VBA API 概述 / 声明 / 宏编程

注&#xff1a;本文为 “VBA API 概述 | 宏编程 | 执行速度慢” 相关文章合辑。 未整理去重。 VBA API 详解 Office 二次开发于 2020-12-17 22:27:10 发布 Office 版本变动 在 Office 2010 之前&#xff0c;微软仅提供 32-bit 版本的 Office。而自 Office 2010 起&#xff0…...

Python 开源项目精彩荟萃

一、Web 开发框架 Django 高效路由系统&#xff1a; 支持基于正则表达式的复杂 URL 模式匹配&#xff0c;精准定位视图函数&#xff0c;例如可通过r^articles/(?P<year>\d{4})/$这样的正则表达式来匹配特定年份的文章列表页面 URL&#xff0c;并将年份参数传递给视图函数…...

Debezium系列之:使用Debezium采集oceanbase数据库

Debezium系列之:使用Debezium采集oceanbase数据库 一、oceanbase数据库二、安装OceanBase三、安装oblogproxy四、基于Docker的简单采集案例五、生产实际应用案例Debezium 是一个开源的分布式平台,用于监控数据库变化和捕捉数据变动事件,并以事件流的形式导出到各种消费者。D…...

AI初创企业的未来趋势和潜在挑战

AI初创企业的未来趋势和潜在挑战 AI初创企业的未来趋势和潜在挑战可以从多个方面进行分析&#xff1a; 未来趋势 AI监管: 随着AI技术的快速发展&#xff0c;各国政府开始制定相关法规&#xff0c;以确保AI的安全和伦理使用。这将影响初创企业的运营模式和市场准入。 日常生活…...

Grafana配置告警规则推送企微机器人服务器资源告警

前提 已经部署Grafana&#xff0c;并且dashboard接入数据 大屏编号地址&#xff1a;Node Exporter Full | Grafana Labs 创建企微机器人 备注&#xff1a;群里若有第三方外部人员不能创建 机器人创建完成&#xff0c;记录下来Webhook地址 Grafana配置告警消息模板 {{ define &…...

RFDiffusion xyz_to_c6d函数解读

函数 xyz_to_c6d将给定的蛋白质主链坐标 (N,Cα,C)转换为 6D矩阵表示,即用以下几何特征描述两两残基之间的关系: 距离 dist:残基间 Cβ 原子的欧几里得距离。二面角 omega:两个残基的 Cα−Cβ 向量之间的二面角。二面角 theta:由 N−Cα−Cβ和 Cβ间向量定义的二面角。平…...