分布式消息队列RocketMQ基础原理+Docker部署RocketMQ+SpringBoot整合RocketMQ(全网zui全的RocketMQ教程)

👩🏽‍💻个人主页:阿木木AEcru

🔥 系列专栏:Docker容器化部署系列

💹每一次技术突破,都是对自我能力的挑战和超越。

一、RocketMQ介绍

1.1 发展史

RocketMQ是一个由阿里巴巴开源的分布式消息中间件,具有高性能、高可靠、高实时和分布式等特点。它支持事务消息、顺序消息、批量消息、定时消息和消息回溯等功能。阿里在2016年底捐赠给Apache开源基金会成为孵化项目,2017年正式成为了Apache顶级项目。 Apache RocketMQ 自诞生以来,因其架构简单、业务功能丰富、具备极强可扩展性等特点被众多企业开发者以及云厂商广泛采用。历经十余年的大规模场景打磨,RocketMQ 已经成为业内共识的金融级可靠业务消息首选方案,被广泛应用于互联网、大数据、移动互联网、物联网等领域的业务场景。

1.2 基本概念

这是最基础的消息模型,一个生产者,一个消息主题,一个消费者。 扩展后的消息模型如下:

它拥有多个生产者,多个消息主题(每个主题中存在多个分区),两组消费者(每组中存在多个消费者)。

  • 为了消息写入能力的水平扩展,在这里的消息主题中被进行了分区,也就是俗称的"队列"。
  • 为了消费能力的水平扩展,ConsumerGroup "消费者组" 的概念诞生了,可以存在多个消费者组,而每个消费者组中又存在着多个消费者。如何提高消费能力呢, 即广播模式,和集群模式。而图中使用的是集群模式。

这两种模式是有却别的

  1. 广播模式: 使用这种模式时, 所有的Consumer都会消费到每个 队列 中消息。例如 "MessageQueue 0" 队列中存在一条消息为 "你好" , 则所有 Consumer 都会消费到 "你好" 这条消息,相当于 村里的广播,一条消息,所有人都能听到。
  2. 集群模式: 使用这种模式时,每个 ConsumerGroup 中的 Consumer 是负载均衡消费的,例如图中 ConsumerGroup A 中的 两个 Consumer 是监听消费了 不同的 MessageQueue 。

1.3 部署模型

  • 每个 Broker 与 NameServer 集群中的所有节点建立长连接,定时注册 Topic 信息到所有 NameServer。
  • Producer 与 NameServer 集群中的其中一个节点建立长连接,定期从 NameServer 获取Topic路由信息,并向提供 Topic 服务的 Master 建立长连接,且定时向 Master 发送心跳。Producer 完全无状态。
  • Consumer 与 NameServer 集群中的其中一个节点建立长连接,定期从 NameServer 获取 Topic路由信息,并向提供 Topic 服务的 Master、Slave 建立长连接,且定时向 Master、Slave发送心跳。Consumer 既可以从 Master 订阅消息,也可以从Slave订阅消息。

1.4 集群工作流程

  1. 启动NameServer。NameServer启动后监听端口,等待Broker、Producer、Consumer连接,相当于一个路由控制中心。
  2. 启动 Broker。与所有 NameServer 保持长连接,定时发送心跳包。心跳包中包含当前 Broker 信息以及存储所有 Topic 信息。注册成功后,NameServer 集群中就有 Topic跟Broker 的映射关系。
  3. 创建 Topic 时需要指定该 Topic 要存储在哪些 Broker 上,也可以在发送消息时自动创建Topic。
  4. 生产者发送消息。启动时先跟 NameServer 集群中的其中一台建立长连接,并从 NameServer 中获取当前发送的 Topic存在于哪些 Broker 上,轮询从队列列表中选择一个队列,然后与队列所在的 Broker建立长连接从而向 Broker发消息。
  5. 消费者接受消息。跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,然后开始消费消息。

1.5 优势及使用场景

1.5.1 优势:

  • 高性能:RocketMQ在消息的存储、传输和消费等各个环节都进行了优化,能够提供高性能的消息传递能力。
  • 可靠性:RocketMQ采用了分布式架构,支持消息的持久化存储,具备高可靠性和数据安全性。
  • 高吞吐量:RocketMQ支持集群部署,能够通过增加消费者和消息队列的方式来提高系统的吞吐量。
  • 消息顺序性:RocketMQ支持消息的顺序消费,能够保证按照消息发送的顺序进行消费,适用于对消息顺序有严格要求的场景。
  • 分布式事务:RocketMQ支持分布式事务消息的处理,能够保证在分布式环境下的事务的一致性和可靠性。

1.5.2使用场景:

  • 大规模分布式系统:RocketMQ适用于大规模分布式系统,能够进行高并发的消息传递,保证系统的稳定性和可靠性。

  • 实时数据流处理:RocketMQ能够支持实时的数据流处理,适合用于实时监控、实时日志处理等场景。

  • 异步任务处理:RocketMQ支持异步消息发送和消费,适用于异步任务处理、解耦系统组件等场景。

  • 顺序消息处理:RocketMQ能够保证消息的顺序性,适用于需要严格保证消息顺序的场景,如订单处理、流程控制等。

二、RocketMQ 部署

2.1 部署Name Server

2.1.1 拉取镜像

powershell 复制代码
docker pull foxiswho/rocketmq:server-4.7.0

2.1.2 创建持久化挂载文件

bash 复制代码
mkdir -p /usr/local/rockermq/rocketmq-server

2.1.3 运行name server镜像

powershell 复制代码
docker run -d \
--restart=always \
--name rmq-namesrver \
-p 9876:9876 \
-v /usr/local/rockermq/rocketmq-server/logs:/root/logs \
-v /usr/local/rockermq/rocketmq-server/store:/root/store \
-e "MAX_POSSIBLE_HEAP=100000000" \
foxiswho/rocketmq:server-4.7.0  \
sh mqnamesrv

指令解析:

  • --restart=always: 容器停止后自动重启
  • --name rmqnamesrv: 容器的名称为"rmqnamesrv"
  • -p 9876:9876: 将主机的9876端口映射到容器的9876端口
  • -v /usr/local/rockermq/rocketmq-server/logs:/root/logs: 将主机的/usr/local/rockermq/rocketmq-server/logs目录挂载到容器的/root/logs目录
  • -v /usr/local/rockermq/rocketmq-server/store:/root/store: 将主机的/usr/local/rockermq/rocketmq-server/store目录挂载到容器的/root/store目录
  • -e "MAX_POSSIBLE_HEAP=100000000": 设置环境变量MAX_POSSIBLE_HEAP为100000000 sh mq-namesrver: 在容器内执行shmq-namesrver命令

2.1.4 开放防火墙端口

powershell 复制代码
firewall-cmd --zone=public --add-port=9867/tcp --permanent
systemctl restart firewalld.service

注: 如果使用的云服务器,还需要再安全组加上白名单。

2.2 部署Broker

2.2.1 拉取镜像

bash 复制代码
docker pull foxiswho/rocketmq:broker-4.7.0

2.2.2 创建挂载文件以及配置文件

ini 复制代码
mkdir -p /usr/local/rockermq/rocketmq-broker/conf
touch /usr/local/rockermq/rocketmq-broker/conf/broker.conf
vim /usr/local/rockermq/rocketmq-broker/conf/broker.conf
```broker.conf配置文件内容如下
```powershell
# 所属集群名字
brokerClusterName=DefaultCluster

# broker 名字,注意此处不同的配置文件填写的不一样,如果在 broker-a.properties 使用: broker-a,
# 在 broker-b.properties 使用: broker-b
brokerName=broker-a

# 0 表示 Master,> 0 表示 Slave
brokerId=0

# nameServer地址,多个的话用分号分割
# namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
namesrvAddr=[服务器ip]:9876

# 启动IP,如果 docker 报 com.alibaba.rocketmq.remoting.exception.RemotingConnectException: connect to <192.168.0.120:10909> failed
# 解决方式1 加上一句 producer.setVipChannelEnabled(false);
# 解决方式2 brokerIP1 设置宿主机IP,不要使用docker 内部IP (建议直接设置)
brokerIP1=[服务器ip]

# 在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
#defaultTopicQueueNums=4

# 是否允许 Broker 自动创建 Topic,建议线下开启,线上关闭 !!!这里仔细看是 false,false,false
#autoCreateTopicEnable=false

# 是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
#autoCreateSubscriptionGroup=false

# Broker 对外服务的监听端口
listenPort=10911

# 删除文件时间点,默认凌晨4点
deleteWhen=04

# 文件保留时间,默认48小时
fileReservedTime= 120

# commitLog 每个文件的大小默认1G
#mapedFileSizeCommitLog=1073741824

# ConsumeQueue 每个文件默认存 30W 条,根据业务情况调整
#mapedFileSizeConsumeQueue=300000

# destroyMapedFileIntervalForcibly=120000
# redeleteHangedFileInterval=120000
# 检测物理文件磁盘空间
#diskMaxUsedSpaceRatio=88
# 存储路径
# storePathRootDir=/home/ztztdata/rocketmq-all-4.1.0-incubating/store
# commitLog 存储路径
# storePathCommitLog=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/commitlog
# 消费队列存储
# storePathConsumeQueue=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/consumequeue
# 消息索引存储路径
# storePathIndex=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/index
# checkpoint 文件存储路径
# storeCheckpoint=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/checkpoint
# abort 文件存储路径
# abortFile=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/abort
# 限制的消息大小
maxMessageSize=65536

# flushCommitLogLeastPages=4
# flushConsumeQueueLeastPages=2
# flushCommitLogThoroughInterval=10000
# flushConsumeQueueThoroughInterval=60000

# Broker 的角色
# - ASYNC_MASTER 异步复制Master
# - SYNC_MASTER 同步双写Master
# - SLAVE
brokerRole=ASYNC_MASTER

# 刷盘方式
# - ASYNC_FLUSH 异步刷盘
# - SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH

# 发消息线程池数量
# sendMessageThreadPoolNums=128
# 拉消息线程池数量
# pullMessageThreadPoolNums=128

2.2.3 运行broker镜像

bash 复制代码
docker run -d  \
--restart=always \
--name rmq-broker \
--link rmq-namesrv:namesrv \
-p 10911:10911 \
-p 10909:10909 \
-v  /usr/local/rockermq/rocketmq-broker/logs:/root/logs \
-v  /usr/local/rockermq/rocketmq-broker/store:/root/store \
-v  /usr/local/rockermq/rocketmq-broker/conf:/opt/rocketmq-4.7.0/conf \
-e "NAMESRV_ADDR=namesrv:9876" \
-e "MAX_POSSIBLE_HEAP=200000000" \
-e "autoCreateTopicEnable=true" \
foxiswho/rocketmq:broker-4.7.0  \
sh mqbroker -c /opt/rocketmq-4.7.0/conf/broker.conf

指令解析:

  • --restart=always: 如果容器发生错误或被停止,自动重新启动。
  • --name rmq-broker: 容器的名称为rmq-broker。
  • --link rmq-namesrv:namesrv: 将rmq-namesrv容器链接到rmq-broker容器,以便它们可以相互通信。
  • -p 10911:10911: 将主机的10911端口映射到容器的10911端口,用于消息存储。
  • -p 10909:10909: 将主机的10909端口映射到容器的10909端口,用于管理控制台。
  • -v /usr/local/rockermq/rocketmq-broker/logs:/root/logs: 将主机的日志目录挂载到容器内的日志目录。
  • -v /usr/local/rockermq/rocketmq-broker/store:/root/store: 将主机的存储目录挂载到容器内的存储目录。
  • -v /usr/local/rockermq/rocketmq-broker/conf:/opt/rocketmq-4.7.0/conf: 将主机的配置文件目录挂载到容器内的配置文件目录。
  • -e "NAMESRV_ADDR=namesrv:9876": 设置环境变量NAMESRV_ADDR为namesrv:9876,指定NameServer的地址和端口。
  • -e "MAX_POSSIBLE_HEAP=200000000": 设置环境变量MAX_POSSIBLE_HEAP为200000000,指定Broker的堆内存大小。
  • -e "autoCreateTopicEnable=true": 设置环境变量autoCreateTopicEnable为true,启用自动创建Topic功能。
  • foxiswho/rocketmq:broker-4.7.0: 使用名为foxiswho/rocketmq的镜像的broker-4.7.0标签。
  • sh mqbroker -c /opt/rocketmq-4.7.0/conf/broker.conf: 在容器内执行启动Broker的命令,并指定配置文件的路径。

2.2.4 开放防火墙端口

powershell 复制代码
firewall-cmd --zone=public --add-port=10911/tcp --permanent
systemctl restart firewalld.service

注: 如果使用的云服务器,还需要再安全组加上白名单。

实际上部署到这已经是可以使用了,为了更方便的查看消息的发送和消费,最好再多部署一个RocketMQ的客户端界面。

2.3 部署client客户端界面

2.3.1 拉取并运行镜像

diff 复制代码
docker run -itd -e "JAVA_OPTS=-Drocketmq.namesrv.addr=[服务器ip]:9876 \
-Dcom.rocketmq.sendMessageWithVIPChannel=false" \
-p 8082:8080 \
--name rmq-client \
-t styletang/rocketmq-console-ng:latest

如果镜像不存在 则会自动拉取并启动 指令解析:

  • -itd:创建一个交互式容器,并在后台运行。
  • -e "JAVA_OPTS=-Drocketmq.namesrv.addr=[服务器ip]:9876 \ -Dcom.rocketmq.sendMessageWithVIPChannel=false":设置容器内的环境变量,指定RocketMQ的地址和端口。
  • -p 8082:8080:将容器内的8080端口映射到主机的8082端口。
  • --name rmq-client:给容器指定一个名称为rmq-client。
  • -t styletang/rocketmq-console-ng:latest:指定容器使用的镜像为styletang/rocketmq-console-ng:latest。

ps: 这里需要注意的是,-Drocketmq.namesrv.addr=[服务器ip]:9876 这段启动参数的中的服务器ip最好与 broker配置文件中的 ip 一致,否则会导致连接不上broker服务

2.3.2 客户端界面展示

三、SpringBoot整合RocketMQ

3.1 引入依赖

xml 复制代码
<!--SpringBoot基础依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- rocketMq -->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <!-- hutool工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-bom</artifactId>
            <version>5.8.10</version>
            <type>pom</type>
        </dependency>

		<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

3.2 yml配置文件

yml 复制代码
server:
  port: 8888
rocketmq:
  name-server: [部署的ip]:9876

3.3 demo目录

3.4 config目录下的类

3.4.1 RocketMqConfig------mq配置文件

java 复制代码
package com.itbanana.rocketmqdemo.config;

import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

/**
 * mq配置文件
 */
@Configuration
public class RocketMqConfig {

	@Autowired
	private RocketMqProductAdapter rocketMqProductAdapter;

	@Autowired
	private RocketMqConsumeAdapter rocketMqConsumeAdapter;


	//初始化生产者
	@Lazy
	@Bean(destroyMethod = "destroy")
	public RocketMQTemplate productionPackagingConfirmMqTemplate() {
		return rocketMqProductAdapter.getTemplateByTopicName(RocketMqConstant.MQ_TEST_TOPIC);
	}
}

3.4.2 RocketMqConstant------公用属性配置类

java 复制代码
package com.itbanana.rocketmqdemo.config;

public class RocketMqConstant {
	// 延迟消息 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h (1-18)

	/**
	 * 延迟发送时间 2 = 5s
	 */
	public static final int DELAY_LEVEL = 2;

	/**
	 * 默认发送消息超时时间
	 */
	public static final long TIMEOUT = 3000;

	/**
	 * 测试队列
	 */
	public static final String MQ_TEST_TOPIC = "mq-test-topic";
}

3.4.3 RocketMqConsumeAdapter------消费者配置类

java 复制代码
package com.itbanana.rocketmqdemo.config;


import lombok.RequiredArgsConstructor;
import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQMessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * 获取消费者
 */
@Configuration
@RequiredArgsConstructor
public class RocketMqConsumeAdapter {

	private final RocketMQMessageConverter rocketMqMessageConverter;
	
	@Value("${rocketmq.name-server:}")
	private String nameServer;
	
	//消费者
	public RocketMQTemplate getTemplateByConsumeTopicName(String topic) {
		RocketMQTemplate mqTemplate = new RocketMQTemplate();
		DefaultLitePullConsumer sender = new DefaultLitePullConsumer(topic);
		sender.setMessageModel(MessageModel.CLUSTERING);
		sender.setNamesrvAddr(nameServer);
		mqTemplate.setConsumer(sender);
		mqTemplate.setMessageConverter(rocketMqMessageConverter.getMessageConverter());
		return mqTemplate;
	}

}

3.4.4 RocketMqProductAdapter------生产者配置类

java 复制代码
package com.itbanana.rocketmqdemo.config;


import cn.hutool.core.util.IdUtil;
import lombok.RequiredArgsConstructor;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.UtilAll;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQMessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

/**
 * 获取生产者
 */
@Configuration
@RequiredArgsConstructor
public class RocketMqProductAdapter {

	private final RocketMQMessageConverter rocketMqMessageConverter;

	@Value("${rocketmq.name-server:}")
	private String nameServer;

	//初始化连接rockerMq的客户端名称
	@PostConstruct
	public void init() {
		System.setProperty("rocketmq.client.name", "AEcru"+ IdUtil.getSnowflakeNextIdStr() + "@" + System.currentTimeMillis());
	}

	//创建生产者
	public RocketMQTemplate getTemplateByTopicName(String topic) {
		RocketMQTemplate mqTemplate = new RocketMQTemplate();
		DefaultMQProducer producer = new DefaultMQProducer(topic);
		producer.setNamesrvAddr(nameServer);
		producer.setRetryTimesWhenSendFailed(2);
		//默认发送消息超时时间
		producer.setSendMsgTimeout((int) RocketMqConstant.TIMEOUT);
		//最大消息大小(如需兼容大消息内容,还需修改borker服务配置文件)
		producer.setMaxMessageSize(1024 * 1024 * 1024);
		mqTemplate.setProducer(producer);
		mqTemplate.setMessageConverter(rocketMqMessageConverter.getMessageConverter());
		return mqTemplate;
	}
}

3.5 controller------Product生产者

为了方便,我这边就想生产者写到了Controller这边,方便后续的测试调用,实际开发中需要按照实际业务来进行编写。

java 复制代码
package com.itbanana.rocketmqdemo.controll;

import com.itbanana.rocketmqdemo.config.RocketMqConstant;
import lombok.AllArgsConstructor;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Objects;

/**
 * @author lengleng
 * @date 2018/12/16
 */
@RestController
@AllArgsConstructor
@RequestMapping("/mq-test")
public class RocketMqController {

	private final RocketMQTemplate pigProductTestTemplate ;

	/**
	 * 发送消息到mq
	 * @param msg 发送消息内容
	 */
	@PostMapping("/send")
	public void send(@RequestBody String msg){
		sendMq(msg,pigProductTestTemplate, RocketMqConstant.MQ_TEST_TOPIC,"消息备注信息");
	}

	/**
	 * 发送数据
	 *
	 * @param rvo
	 * @param topic
	 * @return
	 */
	private  void sendMq(Object rvo, RocketMQTemplate rocketMqTemplate, String topic, String msg) {
		SendStatus sendStatus = rocketMqTemplate.syncSend(topic, new GenericMessage<>(rvo), RocketMqConstant.TIMEOUT).getSendStatus();
		if (!Objects.equals(sendStatus, SendStatus.SEND_OK)) {
			throw new RuntimeException(msg);
		}
	}
}

3.6 listener------Consumer消费者

java 复制代码
package com.itbanana.rocketmqdemo.listener;


import com.itbanana.rocketmqdemo.config.RocketMqConstant;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * 消费者
 */
@Slf4j
@Component
@RequiredArgsConstructor
@RocketMQMessageListener(topic = RocketMqConstant.MQ_TEST_TOPIC, consumerGroup = RocketMqConstant.MQ_TEST_TOPIC, messageModel = MessageModel.CLUSTERING)
public class MqConsumerImpl implements RocketMQListener<String>{

	@Override
	public void onMessage(String message) {
		log.info("listener 消费数据:{}", message);
	}

}

3.7 启动项目后展示

3.7.1 开发工具控制台

3.7.2 RocketMQ界面

3.8 测试使用

3.8.1 接口请求

3.8.2 RocketMQ界面客户端

3.8.3 控制台打印结果

四、结尾

  • 感谢您的观看! 如果本文对您有帮助,麻烦用您发财的小手点个三连吧!您的支持就是作者前进的最大动力!再次感谢!
  • 如果觉得本专栏还不错的话可以点个订阅!
  • 下期将推出Docker部署Mysql主从+SpringBoot整合ShardingJdbc实现分库分表相关内容
相关推荐
gs801407 分钟前
替换 Docker.io 的 Harbor 安全部署指南:域名与 IP 双支持的镜像管理解决方案
docker·harbor
coco_1998_29 分钟前
nvidia docker, nvidia docker2, nvidia container toolkits区别
docker·容器
团儿.24 分钟前
Docker服务发现新纪元:探索Consul的无限魅力
运维·docker·云计算·服务发现·consul
vvw&1 小时前
Docker Build 命令详解:在 Ubuntu 上构建 Docker 镜像教程
linux·运维·服务器·ubuntu·docker·容器·开源
dessler2 小时前
Docker-Dockerfile讲解(二)
linux·运维·docker
小奏技术2 小时前
RocketMQ结合源码告诉你消息量大为啥不需要手动压缩消息
后端·消息队列
-指短琴长-5 小时前
Docker之技术架构【八大架构演进之路】
docker·容器·架构
大熊程序猿5 小时前
docker 搭建集群
spring cloud·docker·微服务
lcx_defender6 小时前
【Docker】部署MySQL容器
mysql·docker·容器
登高·6 小时前
Docker pull images Error
运维·docker·容器