为了账号安全,请及时绑定邮箱和手机立即绑定

MQ消息队列资料入门教程

概述

本文介绍了消息队列的基本概念、作用和应用场景,包括异步通信、解耦系统、负载均衡和处理峰值流量等功能。文章详细讲解了消息队列的主要特点,如持久性、消息顺序和重试机制,并提供了Java和Python环境下的使用示例。此外,文章还包括了消息队列的安装与配置、性能调优和高级功能等详细内容。

MQ消息队列简介

什么是MQ消息队列

MQ消息队列是一种软件组件,用于在不同应用程序或系统之间传输消息。它通过在发送方和接收方之间引入一个中间层来提供异步处理和解耦功能。发送方将消息发送到消息队列,接收方从队列中获取并处理消息。这使得应用程序可以更灵活地处理消息,例如异步处理、按照优先级处理以及在系统之间进行解耦等。

MQ消息队列的作用和应用场景

MQ消息队列的主要作用包括异步通信、解耦系统、负载均衡和处理峰值流量。以下是一些具体的应用场景:

  • 系统解耦:通过将发送方和接收方解耦,消息队列可以提高系统的可维护性和可扩展性。
  • 异步处理:发送方可以在发送消息后立即继续执行其他任务,而不需要等待接收方处理完消息。
  • 负载均衡:消息队列可以将消息分发到多个接收方,从而实现负载均衡。
  • 处理峰值流量:通过缓冲请求,消息队列可以在系统遇到峰值流量时提供弹性。

MQ消息队列的主要特点

MQ消息队列的主要特点包括:

  • 持久性:可以将消息持久化存储,确保消息在系统故障之后仍然可以被接收。
  • 消息顺序:可以保证消息按照发送的顺序进行处理。
  • 负载均衡:可以将消息分布到多个接收方,实现负载均衡。
  • 重试机制:在消息处理失败时,可以自动重试消息处理。
  • 消息过滤:可以根据特定条件过滤消息。
  • 事务支持:支持事务处理,确保消息要么全部提交,要么全部回滚。
MQ消息队列的基本概念

生产者与消费者模型

生产者与消费者模型是MQ消息队列中最基本的模型。生产者负责发送消息到消息队列,消费者负责从队列中接收并处理消息。

示例代码

以下是一个简单的Java生产者和消费者示例,使用了Apache Kafka作为消息队列。

// 生产者代码示例
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

public class Producer {
    public static void main(String[] args) throws InterruptedException {
        KafkaProducer<String, String> producer = new KafkaProducer<>(getProducerConfig());
        String topic = "my-topic";
        for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<>(topic, "key-" + i, "value-" + i));
            System.out.println("Sent message: key-" + i + " value-" + i);
            Thread.sleep(1000);
        }
        producer.close();
    }

    private static Properties getProducerConfig() {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        return props;
    }
}

// 消费者代码示例
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

public class Consumer {
    public static void main(String[] args) {
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(getConsumerConfig());
        consumer.subscribe(Arrays.asList("my-topic"));
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records) {
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
            }
        }
    }

    private static Properties getConsumerConfig() {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "my-group");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        return props;
    }
}

消息的发送和接收

消息发送和接收是MQ消息队列的核心功能。生产者通过指定主题将消息发送到消息队列,消费者从队列中接收并处理消息。

发送消息

生产者通过创建一个ProducerRecord对象并将消息发送到指定主题来发送消息。

ProducerRecord<String, String> record = new ProducerRecord<>(topic, "key", "value");
producer.send(record);

接收消息

消费者通过订阅指定主题并调用poll方法从消息队列中获取消息。

ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
    System.out.println("Received: " + record.value());
}

消息的可靠投递与重试机制

消息的可靠投递意味着消息在发送到队列后能够被持久化存储,即使系统发生故障也能保证消息不会丢失。重试机制则是在消息处理失败时自动重试消息处理。

重试机制示例

以下是一个简单的Java代码示例,展示了如何在消息处理失败时进行重试。

import java.util.concurrent.TimeUnit;

public class RetryExample {
    public static void main(String[] args) {
        while (true) {
            try {
                // 处理消息
                processMessage();
                break;
            } catch (Exception e) {
                // 重试机制
                System.out.println("处理失败,准备重试...");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
            }
        }
    }

    private static void processMessage() throws InterruptedException {
        // 模拟消息处理失败
        if (Math.random() < 0.5) {
            throw new InterruptedException("处理失败");
        }
        System.out.println("消息处理成功");
    }
}

以下是一个简单的Python代码示例,展示了如何在消息处理失败时进行重试。

import time

def process_message(message):
    if random.random() < 0.5:
        raise Exception("处理失败")
    print("消息处理成功:", message)

def retry_example():
    while True:
        try:
            process_message("测试消息")
            break
        except Exception as e:
            print("处理失败,准备重试...")
            time.sleep(1)

if __name__ == "__main__":
    retry_example()
MQ消息队列的安装与配置

选择合适的MQ消息队列产品

选择合适的MQ消息队列产品需要根据具体的应用场景和需求进行选择。常见的MQ消息队列产品包括:

  • Apache Kafka:一个高吞吐量的消息队列系统,常用于日志聚合和流处理。
  • RabbitMQ:一个高度可靠的消息队列系统,支持多种消息协议。
  • ActiveMQ:一个支持多种传输协议的消息队列系统,提供了丰富的消息类型支持。
  • RocketMQ:一个分布式消息队列系统,支持海量消息堆积和低延迟消息传递。

安装步骤详解

以下是以Apache Kafka为例的安装步骤:

  1. 下载Kafka安装包

    • 访问Kafka官网下载最新的稳定版本。
    • 解压下载的安装包到指定目录。
  2. 配置环境变量

    • 设置PATH环境变量,指向Kafka的bin目录。
  3. 启动Kafka服务器
    • 在命令行窗口中运行bin/kafka-server-start.sh config/server.properties启动Kafka服务器。

配置参数说明

Kafka的主要配置参数包括:

  • broker.id:Kafka服务器的唯一标识。
  • listeners:监听的IP地址和端口。
  • log.dirs:日志存储的目录。
  • num.partitions:分区数量。
  • zookeeper.connect:连接Zookeeper的地址和端口。

以下是一个server.properties配置文件的示例:

broker.id=1
listeners=PLAINTEXT://localhost:9092
log.dirs=/tmp/kafka-logs
num.partitions=3
zookeeper.connect=localhost:2181
MQ消息队列的使用示例

Java环境下的MQ消息队列使用示例

以下是一个Java环境下的Apache Kafka消息队列使用示例。

安装依赖

pom.xml中添加Kafka客户端依赖:

<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>2.8.0</version>
</dependency>

发送消息

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

public class Producer {
    public static void main(String[] args) throws InterruptedException {
        KafkaProducer<String, String> producer = new KafkaProducer<>(getProducerConfig());
        String topic = "my-topic";
        for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<>(topic, "key-" + i, "value-" + i));
            System.out.println("Sent message: key-" + i + " value-" + i);
            Thread.sleep(1000);
        }
        producer.close();
    }

    private static Properties getProducerConfig() {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        return props;
    }
}

接收消息

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

public class Consumer {
    public static void main(String[] args) {
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(getConsumerConfig());
        consumer.subscribe(Arrays.asList("my-topic"));
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records) {
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
            }
        }
    }

    private static Properties getConsumerConfig() {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "my-group");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        return props;
    }
}

Python环境下的MQ消息队列使用示例

以下是一个Python环境下的RabbitMQ消息队列使用示例。

安装依赖

requirements.txt中添加RabbitMQ客户端依赖:

pika==1.2.0

发送消息

import pika

def send_message():
    connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = connection.channel()
    channel.queue_declare(queue='my_queue')
    message = 'Hello World!'
    channel.basic_publish(exchange='', routing_key='my_queue', body=message)
    print(f"Sent: {message}")
    connection.close()

if __name__ == '__main__':
    send_message()

接收消息

import pika

def callback(ch, method, properties, body):
    print(f"Received: {body}")

def receive_message():
    connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = connection.channel()
    channel.queue_declare(queue='my_queue')
    channel.basic_consume(queue='my_queue', on_message_callback=callback, auto_ack=True)
    print('Waiting for messages. To exit press CTRL+C')
    channel.start_consuming()

if __name__ == '__main__':
    receive_message()

Java环境下的RabbitMQ示例

以下是一个Java环境下的RabbitMQ示例,包括发送和接收消息的代码。

发送消息

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class RabbitMQProducer {
    private static final String QUEUE_NAME = "my_queue";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            String message = "Hello World!";
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println("Sent '" + message + "'");
        }
    }
}

接收消息

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.DeliverCallback;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class RabbitMQConsumer {
    private static final String QUEUE_NAME = "my_queue";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                String message = new String(delivery.getBody(), "UTF-8");
                System.out.println("Received '" + message + "'");
            };
            channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
        }
    }
}

常见问题及解决方法

问题1:消息丢失

问题描述:消息发送到队列后,消费者没有接收到消息。

解决方法:检查消息队列的配置,确保消息被持久化存储,并且消费者能够正常连接到队列。

问题2:消息重复

问题描述:消费者接收到重复的消息。

解决方法:确保消费者在处理消息时使用了正确的消息确认机制,以防止重复消息。

问题3:性能瓶颈

问题描述:消息队列在高负载下性能下降。

解决方法:增加消息队列的分区数量,实现负载均衡。优化消息处理代码,减少处理时间。

MQ消息队列的性能调优

优化性能的方法与技巧

  1. 增加分区数量:通过增加分区数量,可以实现负载均衡,提高消息处理速度。
  2. 消息压缩:对消息进行压缩可以减少网络传输时间和存储空间。
  3. 批量处理:批量处理消息可以减少网络请求次数,提高性能。
  4. 使用异步模式:使用异步模式发送和接收消息可以提高系统吞吐量。

示例代码

以下是一个Java示例,展示了如何使用异步模式发送消息。

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.RecordMetadata;

public class AsyncProducer {
    public static void main(String[] args) {
        KafkaProducer<String, String> producer = new KafkaProducer<>(getProducerConfig());
        String topic = "my-topic";
        for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<>(topic, "key-" + i, "value-" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    if (exception == null) {
                        System.out.println("Message sent successfully");
                    } else {
                        System.out.println("Error occurred while sending message: " + exception);
                    }
                }
            });
        }
        producer.close();
    }

    private static Properties getProducerConfig() {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("acks", "all");
        return props;
    }
}

监控指标与异常排查

监控MQ消息队列的性能指标可以帮助及时发现和解决问题。常见的监控指标包括:

  • 消息发送速度:每秒发送的消息数量。
  • 消息接收速度:每秒接收的消息数量。
  • 消息堆积量:队列中的消息数量。
  • 延迟时间:消息从发送到接收的时间延迟。
  • 错误率:消息处理失败的比率。

监控工具

常见的监控工具包括:

  • Kafka自带的监控工具:如kafka-topics.shkafka-consumer-groups.sh
  • 第三方监控工具:如Prometheus和Grafana。

性能测试与压力测试

性能测试和压力测试可以帮助评估消息队列系统的性能极限和稳定性。

示例代码

以下是一个Java示例,展示了如何使用JMeter进行性能测试。

import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
import org.apache.jmeter.samplers.SampleResult;

public class KafkaPerfTest extends AbstractJavaSamplerClient {
    private KafkaProducer<String, String> producer;

    @Override
    public void setupTest(JavaSamplerContext context) {
        producer = new KafkaProducer<>(getProducerConfig());
    }

    @Override
    public SampleResult runTest(JavaSamplerContext context) {
        SampleResult result = new SampleResult();
        result.setSampleLabel("Kafka Message Send");
        result.setSuccessful(true);

        String topic = "my-topic";
        String message = "test message";
        ProducerRecord<String, String> record = new ProducerRecord<>(topic, message);
        producer.send(record, (metadata, exception) -> {
            if (exception != null) {
                result.setSuccessful(false);
                result.setResponseMessage(exception.getMessage());
            }
        });

        result.setResponseData("Message sent successfully", "UTF-8");
        return result;
    }

    @Override
    public void teardownTest(JavaSamplerContext context) {
        producer.close();
    }

    private Properties getProducerConfig() {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        return props;
    }
}
MQ消息队列的高级功能

消息过滤与路由

消息过滤与路由允许根据特定条件过滤和路由消息。例如,可以通过主题、标签或其他元数据来过滤和路由消息。

示例代码

以下是一个Java示例,展示了如何使用Apache Camel进行消息路由。

import org.apache.camel.builder.RouteBuilder;

public class CamelRoute extends RouteBuilder {
    @Override
    public void configure() throws Exception {
        from("direct:start")
            .filter(simple("${in.header.name} == 'John'"))
            .to("mock:result");
    }
}

以下是一个Java示例,展示了如何使用RabbitMQ进行消息路由。

import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.QueueingConsumer;

public class RabbitMQRoutingExample {
    private static final String ROUTING_KEY = "test";
    private static final String EXCHANGE_NAME = "my_exchange";
    private static final String QUEUE_NAME = "routing_queue";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            channel.exchangeDeclare(EXCHANGE_NAME, "direct");
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);

            String message = "Hello World!";
            channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, message.getBytes("UTF-8"));
            System.out.println("Sent '" + message + "' to routing queue");

            QueueingConsumer consumer = new QueueingConsumer(channel);
            channel.basicConsume(QUEUE_NAME, true, consumer);
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String receivedMessage = new String(delivery.getBody(), "UTF-8");
            System.out.println("Received '" + receivedMessage + "'");
        }
    }
}

消息分组与事务处理

消息分组允许将消息分组进行处理,事务处理确保消息要么全部提交,要么全部回滚。

示例代码

以下是一个Java示例,展示了如何使用RabbitMQ进行事务处理。

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class MessageTransaction {
    private final String queueName = "my_queue";

    public void sendMessageWithTransaction(String message) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            channel.txSelect(); // 开始事务
            channel.basicPublish("", queueName, null, message.getBytes());
            channel.txCommit(); // 提交事务
        }
    }
}

以下是一个Java示例,展示了如何使用Kafka进行事务处理。

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class KafkaTransaction {
    public static void main(String[] args) {
        Properties producerProps = new Properties();
        producerProps.put("bootstrap.servers", "localhost:9092");
        producerProps.put("acks", "all");
        producerProps.put("enable.idempotence", "true");
        producerProps.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        producerProps.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        KafkaProducer<String, String> producer = new KafkaProducer<>(producerProps);
        producer.initTransactions();
        try {
            producer.beginTransaction();
            producer.send(new ProducerRecord<>("my-topic", "key", "value"));
            producer.commitTransaction();
        } catch (Exception e) {
            producer.abortTransaction();
        } finally {
            producer.close();
        }
    }
}

消息队列的集群与高可用配置

消息队列的集群与高可用配置可以提高系统的可靠性和可用性。

示例代码

以下是一个Java示例,展示了如何使用Apache Kafka配置集群。

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class KafkaClusterExample {
    public static void main(String[] args) {
        Properties producerProps = new Properties();
        producerProps.put("bootstrap.servers", "localhost:9092,localhost:9093,localhost:9094");
        producerProps.put("acks", "all");
        producerProps.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        producerProps.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        KafkaProducer<String, String> producer = new KafkaProducer<>(producerProps);
        producer.send(new ProducerRecord<>("my-topic", "key", "value"));
        producer.close();

        Properties consumerProps = new Properties();
        consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
        consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group");
        consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps);
        consumer.subscribe(Arrays.asList("my-topic"));
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records) {
                System.out.println("Received: " + record.value());
            }
        }
    }
}

以下是一个Java示例,展示了如何使用RabbitMQ配置集群。

import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;

public class RabbitMQClusterExample {
    private static final String QUEUE_NAME = "my_queue";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            channel.queueDeclare(QUEUE_NAME, true, false, false, null);
            String message = "Hello World!";
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println("Sent '" + message + "'");
        }
    }
}

通过以上内容,我们可以全面了解MQ消息队列的基本概念、使用方法、性能调优和高级功能。希望这些信息能帮助你更好地理解和使用MQ消息队列。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消