继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

MQ源码入门:新手必读指南

缥缈止盈
关注TA
已关注
手记 307
粉丝 34
获赞 152
概述

本文详细介绍了MQ源码入门的相关内容,涵盖了消息队列的基本概念、主流MQ产品的介绍和环境搭建,以及源码下载与编译的步骤。文章还深入解析了MQ的核心模块和开发技巧,并提供了实战案例分析和进阶学习建议。MQ源码入门的内容丰富全面,适合想要深入了解MQ源码的读者。

MQ简介与基础概念
什么是MQ

消息队列(Message Queue,简称MQ)是一种应用开发架构模式,它通过在发送方和接收方之间提供一个中间层来传递消息。消息队列的核心功能是异步处理,能够实现发消息方与接收方解耦,从而提高系统的灵活性和可扩展性。

MQ的主要功能与应用场景

主要功能

  • 异步处理:消息发送后,发送方不需要等待接收方接收确认,可以继续执行其他任务。
  • 解耦:发送方和接收方可以独立开发和部署,不需要相互依赖。
  • 削峰填谷:通过缓冲机制,可以平滑处理突发的大量请求,避免系统过载。
  • 可靠传输:保证消息的可靠传递,确保消息不会丢失。
  • 持久化:消息可以持久化存储,即使系统崩溃后仍能恢复。

应用场景

  • 日志采集:将日志数据发送到消息队列,由不同的消费者处理。
  • 异步调用:前后端分离的系统中,异步处理请求,提高响应速度。
  • 数据同步:不同系统之间的数据同步,如数据库同步、缓存同步。
  • 任务调度:将任务放入队列,由专门的任务调度器执行。
  • 缓存更新:缓存数据更新时,通过消息队列通知其他系统更新缓存。
主流MQ产品介绍

Apache Kafka

  • 简介:Kafka 是由 LinkedIn 开发的一个高性能分布式消息系统,现已成为 Apache 顶级项目。它具有高吞吐量、高可扩展性及持久化数据存储等特性。
  • 应用场景:大数据实时处理、日志收集、事件流处理等。
  • 特点:高吞吐量、支持分布式部署、持久化存储、支持多种消息消费模式。

RabbitMQ

  • 简介:RabbitMQ 是基于 AMQP(高级消息队列协议)标准的消息代理。它是可用性最好的消息代理之一,支持多种编程语言。
  • 应用场景:实时数据处理、日志记录、分布式系统间通信等。
  • 特点:AMQP 标准协议、支持多种消息传递模式、高可用性、支持多种编程语言。

ActiveMQ

  • 简介:ActiveMQ 是 Apache 软件基金会下的一个MQ产品,基于 Java 撰写,支持多种传输协议,包括 AMQP、STOMP、OpenWire 等。
  • 应用场景:企业级消息传递、分布式系统通信、实时数据处理等。
  • 特点:支持多种传输协议、高可用性、支持消息持久化、支持多种编程语言。
MQ源码环境搭建
开发环境准备
  • 操作系统:建议使用 Linux 操作系统,如 Ubuntu 或 CentOS。Windows 和 macOS 也可以,但 Linux 环境更稳定。
  • 编译工具:Java 或 C++ 编译工具,具体取决于你选择的 MQ 产品。例如,Kafka 使用 Java 编写,需要安装 JDK。RabbitMQ 使用 Erlang 编写,需要安装 Erlang 编译器。
  • 版本控制工具:Git,用于下载 MQ 源码。
  • IDE:推荐使用 IntelliJ IDEA 或 Eclipse,用于编写和调试代码。
  • 其他工具:如 Maven、Gradle(对于 Kafka 和 ActiveMQ)、Erlang shell(对于 RabbitMQ)。

示例代码:安装 JDK

# 下载 JDK
wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u201-b09/42ba4fe6fcbb4d38b09868cf92182ea2/jdk-8u201-linux-x64.tar.gz

# 解压 JDK
tar -zxvf jdk-8u201-linux-x64.tar.gz
mv jdk1.8.0_201 /usr/local/jdk1.8.0_201

# 设置环境变量
echo 'export JAVA_HOME=/usr/local/jdk1.8.0_201' >> ~/.bashrc
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc
source ~/.bashrc

Windows 环境安装 JDK

# 下载 JDK
https://www.oracle.com/java/technologies/javase-jdk8-downloads.html

# 安装 JDK
执行下载的 JDK 安装包,按照安装向导进行安装。

# 设置环境变量
在系统环境变量中设置 JAVA_HOME 和 PATH,指向 JDK 安装路径。

macOS 环境安装 JDK

# 下载 JDK
https://www.oracle.com/java/technologies/javase-jdk8-downloads.html

# 使用 Homebrew 安装 JDK
brew install adoptopenjdk

# 设置环境变量
echo 'export JAVA_HOME=/usr/local/opt/openjdk' >> ~/.bash_profile
source ~/.bash_profile
MQ源码下载与编译

源码下载

  • Kafka:使用 Git 克隆 Kafka 源码仓库。

    git clone https://github.com/apache/kafka.git
    cd kafka
  • RabbitMQ:下载 RabbitMQ 源码包。

    wget https://github.com/rabbitmq/rabbitmq-server/releases/download/rabbitmq_v3_8_15/rabbitmq-server-3.8.15.tar.gz
    tar -zxvf rabbitmq-server-3.8.15.tar.gz
    cd rabbitmq_server-3.8.15
  • ActiveMQ:使用 Git 克隆 ActiveMQ 源码仓库。
    git clone https://github.com/apache/activemq.git
    cd activemq

编译

  • Kafka:使用 Maven 编译。

    cd kafka
    mvn clean install
  • RabbitMQ:编译 RabbitMQ 需要 Erlang 编译环境。

    cd rabbitmq_server-3.8.15
    make
  • ActiveMQ:使用 Maven 编译。
    cd activemq
    mvn clean install
常用开发工具介绍
  • IntelliJ IDEA:强大的 Java 和 Kotlin IDE,支持多种语言和框架。
  • Eclipse:开源的 Java IDE,支持多种语言和插件。
  • Visual Studio Code:轻量级代码编辑器,支持多种语言和插件。
  • Postman:API 测试和文档工具,可以用来测试 MQ 服务。
  • Docker:容器化工具,可以用来快速搭建和测试 MQ 环境。

示例代码:使用 IntelliJ IDEA 打开 Kafka 项目

# 打开 IntelliJ IDEA
idea.sh /path/to/kafka
MQ核心模块解析
消息发送与接收流程

Kafka

  • 生产者发送消息

    1. 生产者将消息发送到 Kafka 的 Broker。
    2. Broker 将消息写入磁盘。
    3. 生产者返回消息发送成功或失败。
  • 消费者接收消息
    1. 消费者从 Broker 拉取消息。
    2. 消费者处理消息。
    3. 消费者返回消息处理结果。

RabbitMQ

  • 生产者发送消息

    1. 生产者将消息通过 AMQP 协议发送到 RabbitMQ 服务器。
    2. RabbitMQ 服务器将消息发送到正确的队列。
    3. 生产者返回消息发送结果。
  • 消费者接收消息
    1. 消费者从队列中拉取消息。
    2. 消费者处理消息。
    3. 消费者返回消息处理结果。

ActiveMQ

  • 生产者发送消息

    1. 生产者将消息发送到 ActiveMQ 服务器。
    2. ActiveMQ 服务器将消息发送到正确的队列。
    3. 生产者返回消息发送结果。
  • 消费者接收消息
    1. 消费者从队列中拉取消息。
    2. 消费者处理消息。
    3. 消费者返回消息处理结果。

示例代码:Java 生产者发送消息到 Kafka

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

import java.util.Properties;

public class KafkaProducerExample {
    public static void main(String[] args) {
        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");

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

示例代码:Java 消费者从 Kafka 消息队列中读取消息

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

import java.util.Arrays;
import java.util.Properties;

public class KafkaConsumerExample {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "test");
        props.put("enable.auto.commit", "true");
        props.put("auto.commit.interval.ms", "1000");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList("my-topic"));
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(100);
            for (ConsumerRecord<String, String> record : records) {
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
            }
        }
    }
}
消息存储与传输机制

Kafka

  • 存储机制:Kafka 使用分区来存储消息,每个分区都是一个顺序日志文件,消息按顺序写入并持久化存储。
  • 传输机制:Kafka 使用消息队列来传输消息,每个分区都会分配给一个消费者组中的消费者,消费者从分区中读取消息并处理。

示例代码:Java 生产者发送消息到 Kafka

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

import java.util.Properties;

public class KafkaProducerExample {
    public static void main(String[] args) {
        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");

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

RabbitMQ

  • 存储机制:RabbitMQ 使用队列来存储消息,消息在队列中等待消费者处理。
  • 传输机制:RabbitMQ 使用 AMQP 协议传输消息,消息在生产者和消费者之间通过交换器(Exchange)和队列(Queue)进行传输。

示例代码:Java 生产者将消息发送到 RabbitMQ 指定队列

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class RabbitMQProducer {
    private static final String QUEUE_NAME = "my-queue";

    public static void sendMessage(String message) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
        System.out.println(" [x] Sent '" + message + "'");
    }

    public static void main(String[] argv) throws IOException, TimeoutException {
        RabbitMQProducer.sendMessage("Hello, world!");
    }
}

ActiveMQ

  • 存储机制:ActiveMQ 使用持久化机制存储消息,消息可以存储在磁盘或内存中。
  • 传输机制:ActiveMQ 使用消息队列传输消息,消息在生产者和消费者之间通过队列进行传输。

示例代码:Java 生产者发送消息到 ActiveMQ

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

public class ActiveMQProducer {
    public static void main(String[] args) throws Exception {
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
        Connection connection = connectionFactory.createConnection();
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        Queue queue = session.createQueue("my-queue");

        MessageProducer producer = session.createProducer(queue);
        producer.setDeliveryMode(DeliveryMode.PERSISTENT);

        TextMessage message = session.createTextMessage("Hello, world!");
        producer.send(message);

        session.close();
        connection.close();
    }
}
消息队列与路由机制

Kafka

  • 分区与副本机制:Kafka 使用分区来存储消息,每个分区都是一个顺序日志文件,消息按顺序写入并持久化存储。每个分区都有多个副本,以提供高可用性。
  • 路由机制:Kafka 的路由机制基于主题(Topic),生产者将消息发送到指定的主题,消费者从指定的主题中拉取消息。

RabbitMQ

  • 交换器与队列机制:RabbitMQ 使用交换器(Exchange)和队列(Queue)来路由消息。生产者将消息发送到交换器,交换器根据路由键(Routing Key)将消息路由到相应的队列。
  • 路由机制:RabbitMQ 支持多种路由模式,如直接模式(Direct)、主题模式(Topic)、扇入模式(Fanout)等。

示例代码:Java 生产者将消息发送到 RabbitMQ 指定队列

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class RabbitMQProducer {
    private static final String QUEUE_NAME = "my-queue";

    public static void sendMessage(String message) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
        System.out.println(" [x] Sent '" + message + "'");
    }

    public static void main(String[] argv) throws IOException, TimeoutException {
        RabbitMQProducer.sendMessage("Hello, world!");
    }
}

ActiveMQ

  • 消息路由机制:ActiveMQ 使用消息路由机制将消息发送到正确的队列。消息路由可以基于主题(Topic)、队列(Queue)或其他自定义路由规则。
  • 路由机制:ActiveMQ 支持多种消息路由模式,如简单路由、持久化路由、临时路由等。

示例代码:Java 生产者发送消息到 ActiveMQ

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

public class ActiveMQProducer {
    public static void main(String[] args) throws Exception {
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
        Connection connection = connectionFactory.createConnection();
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        Queue queue = session.createQueue("my-queue");

        MessageProducer producer = session.createProducer(queue);
        producer.setDeliveryMode(DeliveryMode.PERSISTENT);

        TextMessage message = session.createTextMessage("Hello, world!");
        producer.send(message);

        session.close();
        connection.close();
    }
}
MQ源码阅读技巧
如何快速定位关键代码
  • 使用 IDE 的搜索功能:许多 IDE 支持搜索功能,可以快速定位关键代码。
  • 查看文档和注释:文档和注释通常会指出关键代码的位置。
  • 依赖关系图:使用依赖关系图工具,可以快速了解各模块之间的依赖关系。
  • 日志输出:通过日志输出,可以快速定位问题代码。
  • 代码审查:通过代码审查,可以快速找到关键代码。

示例代码:使用 IntelliJ IDEA 查找 Consumer 关键代码

# 打开 IntelliJ IDEA
idea.sh /path/to/kafka

# 使用 Ctrl+Shift+F 查找 Consumer 关键字
常用调试工具与方法
  • 断点调试:通过设置断点,可以逐行调试代码,查看变量值。
  • 日志输出:通过输出日志,可以查看变量值和程序执行流程。
  • 单元测试:编写单元测试,可以快速验证代码逻辑。
  • 性能分析:使用性能分析工具,可以查看代码执行效率。
  • 依赖关系图:使用依赖关系图工具,可以快速了解各模块之间的依赖关系。

示例代码:使用 IntelliJ IDEA 断点调试 Kafka 代码

# 打开 IntelliJ IDEA
idea.sh /path/to/kafka

# 在 Kafka 源码中找到一个合适的断点位置,如 KafkaConsumer 类的 poll 方法
# 设置断点后,运行 KafkaConsumer
# 可以逐行调试,查看变量值
代码结构与注释阅读技巧
  • 理解项目结构:了解项目的文件结构和模块划分,可以快速找到需要的代码。
  • 阅读注释:注释通常会解释代码的逻辑和意图,可以快速理解代码。
  • 阅读文档:阅读项目的文档,可以快速了解代码的功能和使用方法。
  • 了解设计模式:了解常用的设计模式,可以快速理解代码的结构和实现方式。
  • 使用代码导航:许多 IDE 支持代码导航功能,可以快速跳转到相关代码。

示例代码:阅读 Kafka 源码中的注释

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
实战案例分析
典型问题与解决方案

Kafka 数据丢失问题

  • 问题描述:生产者发送的消息没有被消费者接收到。
  • 解决方案:检查生产者和消费者的配置是否正确,确保生产者和消费者之间的网络连接正常。
  • 示例代码:检查生产者的配置,确保 bootstrap.serverskey.serializervalue.serializer 配置正确。
    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");

RabbitMQ 消息积压问题

  • 问题描述:生产者发送的消息在队列中积压,导致消费者处理速度跟不上。
  • 解决方案:增加消费者数量,或者优化消费者处理逻辑,提高处理速度。
  • 示例代码:增加消费者的数量,确保消息可以被快速处理。
    channel.basicQos(0);
    channel.basicConsume(QUEUE_NAME, false, "consumer1", "consumer2");

ActiveMQ 消息重复问题

  • 问题描述:消费者接收到的消息存在重复。
  • 解决方案:检查生产者和消费者的配置是否正确,确保生产者和消费者之间的网络连接正常。
  • 示例代码:检查生产者的配置,确保 brokerURLdestination 配置正确。
    Properties props = new Properties();
    props.setProperty("brokerURL", "tcp://localhost:61616");
    props.setProperty("destination", "queue://my-queue");
源码调试与优化实践

Kafka 源码调试

  • 调试步骤
    1. 在 IntelliJ IDEA 中打开 Kafka 源码。
    2. 设置断点,调试关键代码。
    3. 查看变量值,分析代码逻辑。
    4. 优化代码,提高性能。

RabbitMQ 源码优化

  • 优化步骤
    1. 查看 RabbitMQ 源码,了解其内部实现。
    2. 通过日志输出,分析代码逻辑。
    3. 优化消息路由逻辑,提高消息处理速度。
    4. 优化消息存储机制,提高消息存储效率。

ActiveMQ 源码调试

  • 调试步骤
    1. 在 IntelliJ IDEA 中打开 ActiveMQ 源码。
    2. 设置断点,调试关键代码。
    3. 查看变量值,分析代码逻辑。
    4. 优化代码,提高性能。
开发中常见问题解答

如何解决 Kafka 生产者发送消息失败的问题?

  • 解决方法:检查生产者和消费者的配置是否正确,确保生产者和消费者之间的网络连接正常。
  • 示例代码:检查生产者的配置,确保 bootstrap.serverskey.serializervalue.serializer 配置正确。
    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");

如何解决 RabbitMQ 消息积压的问题?

  • 解决方法:增加消费者数量,或者优化消费者处理逻辑,提高处理速度。
  • 示例代码:增加消费者的数量,确保消息可以被快速处理。
    channel.basicQos(0);
    channel.basicConsume(QUEUE_NAME, false, "consumer1", "consumer2");

如何解决 ActiveMQ 消息重复的问题?

  • 解决方法:检查生产者和消费者的配置是否正确,确保生产者和消费者之间的网络连接正常。
  • 示例代码:检查生产者的配置,确保 brokerURLdestination 配置正确。
    Properties props = new Properties();
    props.setProperty("brokerURL", "tcp://localhost:61616");
    props.setProperty("destination", "queue://my-queue");
深入理解与进阶学习建议
MQ 源码进阶学习资源推荐
  • 官方文档:Kafka、RabbitMQ 和 ActiveMQ 的官方文档提供了详细的源码和设计文档。
  • 开源社区:如 Kafka 社区、RabbitMQ 社区、ActiveMQ 社区等,可以获取最新技术资料和案例分享。
  • 在线课程:推荐在慕课网(https://www.imooc.com/)学习相关课程,如《Kafka 消息中间件实战》、《RabbitMQ 消息队列实战》、《ActiveMQ 消息队列实战》等。
  • 技术博客:如 CNCF、Apache 官方博客等,可以获取最新的技术文章和案例分享。
  • 书籍和论文:虽然不推荐书籍,但可以参考官方技术文档和论文,如 Kafka 官方技术文档。

示例代码:使用 Maven 编译 Kafka

cd kafka
mvn clean install
持续学习与提升的建议
  • 跟踪技术社区:加入技术社区,如 Kafka 社区、RabbitMQ 社区、ActiveMQ 社区等,跟踪最新技术动态。
  • 参与开源项目:参与开源项目,如 Kafka、RabbitMQ、ActiveMQ 等,提高实际开发经验。
  • 编写技术博客:编写技术博客,分享自己的学习心得和实战经验。
  • 参加技术培训:参加技术培训,如慕课网(https://www.imooc.com/)提供的技术培训,提升技术水平
  • 持续实践和应用:通过实际项目应用,不断提升自己的技术能力和经验。
社区与论坛交流渠道
  • Kafka 社区:Kafka 官方邮件列表、GitHub 仓库、Stack Overflow。
  • RabbitMQ 社区:RabbitMQ 官方邮件列表、GitHub 仓库、Stack Overflow。
  • ActiveMQ 社区:ActiveMQ 官方邮件列表、GitHub 仓库、Stack Overflow。

示例代码:加入 Kafka 社区邮件列表

# 加入 Kafka 社区邮件列表
git clone https://github.com/apache/kafka.git
cd kafka
./dev/create-mirror-mailing-list.sh

示例代码:提交 Kafka 源码 Issue

# 提交 Kafka 源码 Issue
cd kafka
git clone https://github.com/apache/kafka.git
cd kafka
git checkout master
git status
git add .
git commit -m "Add new feature"
git push origin master

通过以上内容,你可以全面了解 MQ 的概念、源码环境搭建、核心模块解析、源码阅读技巧、实战案例分析,以及如何继续深入学习和提升自己的能力。希望对你有所帮助。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP