微服务架构 --- 使用RabbitMQ进行异步处理

目录

一.什么是RabbitMQ?

二.异步调用处理逻辑:

三.RabbitMQ的基本使用:

1.安装:

2.架构图:

 3.RabbitMQ控制台的使用:

(1)Exchanges 交换机:

(2)Queues 队列:

 (3)Admin :

①Users(用户管理):

②Virtual Hosts(虚拟主机):

 四.SpringAMOP的使用:

1.导入依赖:

2.添加配置:

3.在publisher服务中利用RabbitTemplate实现消息发送:

4.定义消费者实现异步调用:

①@RequiredArgsConstructor:

②@RabbitListener:

③QueueBinding(队列绑定):

5.总流程处理过程:

五.使用配置类管理定义交换机,队列及两者关系:

1.创建队列:Queue:

2.创建交换机:Exchange:

 3.创建绑定关系:Binding:

 4.多个队列绑定到同一个交换机:

5.配置不同类型的交换机:

(1)Direct Exchange:

(2)Fanout Exchange:

(3)Headers Exchange:

总结:


一.什么是RabbitMQ?

RabbitMQ 是一种流行的消息队列(Message Queue)实现,基于 AMQP 协议(Advanced Message Queuing Protocol)。它支持异步通信,使多个系统之间以非阻塞的方式交换数据。

在我们使用微服务的时候,微服务一旦拆分,必然涉及到服务之间的相互调用,目前我们服务之间调用采用的都是基于 OpenFeign 的调用。这种调用中,调用者发起请求后需要等待服务提供者执行业务返回结果后,才能继续执行后面的业务。也就是说调用者在调用过程中处于阻塞状态,因此我们称这种调用方式为同步调用,也可以叫同步通

如果我们的业务需要实时得到服务提供方的响应,则应该选择同步通讯(同步调用)。而如果我们追求更高的效率,并且不需要实时响应,则应该选择异步通讯(异步调用)。

二.异步调用处理逻辑:

异步调用方式其实就是基于消息通知的方式,一般包含三个角色:

  • 消息发送者:投递消息的人,就是原来的调用方

  • 消息Broker:管理、暂存、转发消息,你可以把它理解成微信服务器

  • 消息接收者:接收和处理消息的人,就是原来的服务提供方

 在异步调用中,发送者不再直接同步调用接收者的业务接口,而是发送一条消息投递给消息Broker。然后接收者根据自己的需求从消息Broker那里订阅消息。每当发送方发送消息后,接受者都能获取消息并处理。这样,发送消息的人和接收消息的人就完全解耦了。

异步调用的优势包括:

  • 耦合度更低

  • 性能更好

  • 业务拓展性强

  • 故障隔离,避免级联失败

当然,异步通信也并非完美无缺,它存在下列缺点:

  • 完全依赖于Broker的可靠性、安全性和性能

  • 架构复杂,后期维护和调试麻烦

三.RabbitMQ的基本使用:

下面是RabbitMQ的官网:https://www.rabbitmq.com/

1.安装:

首先将RabbitMQ的镜像拉取下来,然后运行下面命令:

docker run \-e RABBITMQ_DEFAULT_USER=itheima \-e RABBITMQ_DEFAULT_PASS=123321 \-v mq-plugins:/plugins \--name mq \--hostname mq \-p 15672:15672 \-p 5672:5672 \--network hm-net\-d \rabbitmq:3.8-management

随后我们访问http://虚拟机IP地址:15672来打开RabbitMQ的控制台。

在控制台上主要可以关注三个信息:Exchanges(交换机),Queues(队列),Admin(用户管理)。

2.架构图:

 

其中包含几个概念:

  • publisher:生产者,也就是发送消息的一方

  • consumer:消费者,也就是消费消息的一方

  • queue:队列,存储消息。生产者投递的消息会暂存在消息队列中,等待消费者处理

  • exchange:交换机,负责消息路由。生产者发送的消息由交换机决定投递到哪个队列。

  • virtual host:虚拟主机,起到数据隔离的作用。每个虚拟主机相互独立,有各自的exchange、queue

 3.RabbitMQ控制台的使用:

RabbitMQ 中,交换机(Exchange)队列(Queue) 是核心概念。它们之间的关系决定了消息的路由和存储方式。

(1)Exchanges 交换机:

  • 交换机是 消息的路由器,负责决定消息应该被发送到哪个队列。
  • 生产者将消息发送给交换机,而不是直接发送到队列。
  • 交换机根据路由规则 决定消息的走向(即发往哪些队列)。

Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

RabbitMQ 提供了四种常用的交换机类型,每种类型的路由规则不同:

  • Fanout:广播,将消息交给所有绑定到交换机的队列。我们最早在控制台使用的正是Fanout交换机

  • Direct:订阅,基于RoutingKey(路由key)发送给订阅了消息的队列

  • Topic:通配符订阅,与Direct类似,只不过RoutingKey可以使用通配符

  • Headers:头匹配,基于MQ的消息头匹配,用的较少。

我们可以再这里创建交换机,Name表示创建的交换机的名字,Type表示可以选择交换机的四种类型。创建成功后就可以在上面看到创建的交换机名字:

比如我们点击amq.fanout查看交换机数据并且可以发送消息给消费者。

注意!!!如果我们不将交换机指定队列的话,由于没有消费者存在,最终消息丢失了,这样说明交换机没有存储消息的能力。

 所以下面我们要先创建队列,然后让生产者推送的消息经过交换机的传递后,到达消息队列,然后再给消费者。所以生产者无需知道队列的存在以此来达到解耦的效果

(2)Queues 队列:

  • 队列用于 存储消息,直到消费者消费它们。
  • 队列与消费者一一对应,即一个消费者从一个队列读取消息。
  • 队列按 FIFO(First In, First Out)的顺序存储消息。

在这里我们填写队列名字即可,其他暂时可以不用填写。 

随后我们向交换机进行绑定(bind)队列,随后通过队列传输给消费者。

这里的Routing key的出现是为了让 Direct (交换机的类型)能够选择队列而存在的。

我们在绑定队列完成后会出现下面这样,这样证明我们成功为交换机绑定好两个队列:

随后我们在下面窗口推送消息:

 (3)Admin :

①Users(用户管理):

管理 RabbitMQ 中的用户账号,在这里 添加、删除用户,并设置每个用户的权限。

每个用户可分配不同的 角色

  • administrator:管理员,具有所有权限。
  • monitoring:可以监控和查看信息,但不能管理。
  • policymaker:可以设置策略和参数。
  • management:可以访问管理界面但没有策略权限。

  • Nameitheima,也就是用户名

  • Tagsadministrator,说明itheima用户是超级管理员,拥有所有权限

  • Can access virtual host/,可以访问的virtual host,这里的/是默认的virtual host

②Virtual Hosts(虚拟主机):

将 RabbitMQ 服务器划分为多个 虚拟主机(vhost),类似于一个独立的命名空间。

  • 不同的应用可以使用不同的虚拟主机,彼此隔离。
  • 每个虚拟主机都有自己的 交换机、队列和用户权限

 四.SpringAMOP的使用:

Spring AMQPSpring for Advanced Message Queuing Protocol)是 Spring 提供的一个消息队列集成模块,主要用于简化与 RabbitMQ 的集成。它通过 AMQP 协议来实现消息的生产和消费。

  • Publisher:生产者,不再发送消息到队列中,而是发给交换机

  • Exchange:交换机,一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。

  • Queue:消息队列也与以前一样,接收消息、缓存消息。不过队列一定要与交换机绑定。

  • Consumer:消费者,与以前一样,订阅队列,没有变化

1.导入依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2.添加配置:

publisher以及consumer服务的application.yml中添加配置:

spring:rabbitmq:host: 192.168.150.101 # 你的虚拟机IPport: 5672 # 端口virtual-host: /hmall # 虚拟主机username: hmall # 用户名password: 123 # 密码listener:simple:prefetch: 1 # (能者多劳)每次只能获取一条消息,处理完成才能获取下一个消息

3.在publisher服务中利用RabbitTemplate实现消息发送:

@SpringBootTest
public class SpringAmqpTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSimpleQueue() {// 队列名称String queueName = "simple.queue";// 消息String message = "hello, spring amqp!";// 发送消息rabbitTemplate.convertAndSend(queueName, message);}
}

4.定义消费者实现异步调用:

@Component
@RequiredArgsConstructor
public class PayStatusListener {private final IOrderService orderService;@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "trade.pay.success.queue", durable = "true"),exchange = @Exchange(name = "pay.topic"),key = "pay.success"))public void listenPaySuccess(Long orderId){//调用方法orderService.markOrderPaySuccess(orderId);}
}

①@RequiredArgsConstructor:

这是 Lombok 提供的注解,自动为类中所有 final 修饰的字段生成一个包含这些字段的构造函数。使用这个注解可以免去手动编写构造函数的麻烦,尤其是在使用依赖注入时(例如注入 IOrderService)。

②@RabbitListener:

@RabbitListener 注解用于监听来自 RabbitMQ 队列的消息。它会自动监听指定的队列,当有消息到达时,会触发 listenPaySuccess 方法进行处理。

③QueueBinding(队列绑定):

通过 @QueueBinding 注解,绑定了 队列交换机,并指定了 路由键

  • @Queue
    • name = "trade.pay.success.queue":指定队列的名称为 trade.pay.success.queue
    • durable = "true":表示队列是 持久化 的,即 RabbitMQ 重启后队列依然存在。
    • 队列的作用:队列是消息的临时存储地,消费者会从队列中拉取消息并处理。
  • @Exchange
    • name = "pay.topic":指定交换机的名称为 pay.topic,这是一个 Topic Exchange(主题交换机)。
    • 交换机的作用:交换机决定消息如何路由到队列。Topic Exchange 可以根据路由键的匹配规则将消息路由到合适的队列。
  • key = "pay.success"
    • 路由键:指定了路由键为 pay.success。这意味着当生产者发送的消息路由键是 pay.success 时,消息将被路由到 trade.pay.success.queue 队列。

5.总流程处理过程:

  • 生产者:在支付成功后,生产者会发送一条消息到 pay.topic 交换机,消息的路由键为 pay.success
  • 交换机pay.topic 交换机会根据路由键 pay.success 将消息路由到 trade.pay.success.queue 队列。
  • 消费者PayStatusListener 作为消费者监听 trade.pay.success.queue,当有消息到达队列时,它会接收到订单 ID 并调用订单服务更新订单状态。

五.使用配置类管理定义交换机,队列及两者关系:

在 Spring AMQP 中,交换机(Exchange)、队列(Queue)、以及绑定(Binding)可以通过配置类来定义和管理。配置类可以帮助你灵活地创建和绑定交换机与队列,并且可以根据业务需求自定义各种参数。

创建配置类效果展示:

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitMQConfig {// 创建队列@Beanpublic Queue queue() {return new Queue("trade.pay.success.queue", true); // durable=true 表示队列持久化}// 创建交换机@Beanpublic TopicExchange exchange() {return new TopicExchange("pay.topic"); // 创建主题交换机}// 创建绑定关系(队列与交换机通过 routing key 绑定)@Beanpublic Binding binding(Queue queue, TopicExchange exchange) {return BindingBuilder.bind(queue).to(exchange).with("pay.success"); // 路由键是 pay.success}
}

1.创建队列:Queue:

@Bean
public Queue queue() {return new Queue("trade.pay.success.queue", true);
}
  • 作用:通过 @Bean 注解定义了一个队列 Bean。Spring 容器会自动管理这个队列,并在 RabbitMQ 上创建该队列。
  • 参数
    • "trade.pay.success.queue":这是队列的名称。每个队列在 RabbitMQ 中必须有唯一的名称。
    • true:表示这个队列是 持久化 的。持久化的队列在 RabbitMQ 服务重启后依然存在。

2.创建交换机:Exchange:

@Bean
public TopicExchange exchange() {return new TopicExchange("pay.topic");
}
  • 作用:通过 @Bean 注解定义了一个 Topic Exchange 类型的交换机。
  • 参数
    • "pay.topic":这是交换机的名称。同样,交换机在 RabbitMQ 中也必须有唯一的名称。

Topic Exchange 是一种交换机类型,它允许使用通配符来进行路由。例如,路由键可以是 "pay.*",可以匹配 "pay.success""pay.failure"。在这里可以使用四种交换机类型来定义交换机,具体场景具体分析使用。

 3.创建绑定关系:Binding:

@Bean
public Binding binding(Queue queue, TopicExchange exchange) {return BindingBuilder.bind(queue).to(exchange).with("pay.success");
}
  • 作用:通过 @Bean 注解定义了队列和交换机的绑定关系。这个绑定决定了消息在何种条件下会从交换机路由到队列。
  • 参数
    • queue:我们定义的 trade.pay.success.queue 队列。
    • exchange:我们定义的 pay.topic 交换机。
    • "pay.success":这是路由键(Routing Key)。它用于告诉交换机:只有当消息的路由键是 pay.success 时,消息才会被路由到 trade.pay.success.queue 队列。

 4.多个队列绑定到同一个交换机:

我们可以将多个队列绑定到同一个交换机,并使用不同的路由键。这样可以实现根据不同的路由键来发送不同类型的消息到各自的队列。

@Bean
public Queue paySuccessQueue() {return new Queue("pay.success.queue", true);
}@Bean
public Queue payFailureQueue() {return new Queue("pay.failure.queue", true);
}@Bean
public Binding paySuccessBinding(Queue paySuccessQueue, TopicExchange exchange) {return BindingBuilder.bind(paySuccessQueue).to(exchange).with("pay.success");
}@Bean
public Binding payFailureBinding(Queue payFailureQueue, TopicExchange exchange) {return BindingBuilder.bind(payFailureQueue).to(exchange).with("pay.failure");
}
  • 在这个例子中,pay.success.queuepay.failure.queue 都绑定到同一个交换机 pay.topic,但使用不同的路由键。
  • 消息路由逻辑
    • 当生产者发送路由键为 pay.success 的消息时,消息会路由到 pay.success.queue 队列。
    • 当生产者发送路由键为 pay.failure 的消息时,消息会路由到 pay.failure.queue 队列。

5.配置不同类型的交换机:

除了 Topic Exchange,RabbitMQ 还支持其他几种常见的交换机类型。这里分别演示如何创建 Direct ExchangeFanout ExchangeHeaders Exchange

(1)Direct Exchange:

@Bean
public DirectExchange directExchange() {return new DirectExchange("direct.exchange");
}@Bean
public Binding directBinding(Queue queue, DirectExchange directExchange) {return BindingBuilder.bind(queue).to(directExchange).with("direct.routing.key");
}

 Direct Exchange:直接交换机会根据 完全匹配的路由键 将消息发送到队列。只有当消息的路由键和绑定的路由键 完全一致 时,消息才会被路由到指定队列。

(2)Fanout Exchange:

@Bean
public FanoutExchange fanoutExchange() {return new FanoutExchange("fanout.exchange");
}@Bean
public Binding fanoutBinding(Queue queue, FanoutExchange fanoutExchange) {return BindingBuilder.bind(queue).to(fanoutExchange);  // 不需要路由键
}

 Fanout Exchange:扇出交换机会将消息发送到所有绑定的队列,不需要考虑路由键。这个交换机通常用于广播消息。

(3)Headers Exchange:

@Bean
public HeadersExchange headersExchange() {return new HeadersExchange("headers.exchange");
}@Bean
public Binding headersBinding(Queue queue, HeadersExchange headersExchange) {return BindingBuilder.bind(queue).to(headersExchange).where("header-key").matches("header-value");
}

 Headers Exchange:头交换机根据消息头的内容进行路由,而不是依赖路由键。适用于按消息的元数据进行路由的场景。

总结:

通过 Spring AMQP 的配置类,你可以非常灵活地定义 RabbitMQ 的 交换机队列绑定关系,并通过不同的路由键和交换机类型实现复杂的消息路由逻辑。以下是一些关键要点:

  1. 队列(Queue):消息的临时存储地,可以是持久化的。
  2. 交换机(Exchange):控制消息如何分发到不同的队列。
    • Direct Exchange:严格匹配路由键。
    • Topic Exchange:支持通配符匹配路由键。
    • Fanout Exchange:广播消息到所有绑定的队列。
    • Headers Exchange:根据消息头的内容进行路由。
  3. 绑定(Binding):将队列与交换机连接起来,使用路由键来决定消息的流向。

通过配置类来定义这些组件,能够简化 RabbitMQ 与 Spring 应用的集成,并且通过灵活的路由规则支持复杂的消息传递需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/56868.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是不同类型的微服务测试?

大家好&#xff0c;我是锋哥。今天分享关于【什么是不同类型的微服务测试&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; 什么是不同类型的微服务测试&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 微服务架构中的测试可以分为多种类…

WPF基础权限系统

一.开发环境 VisualStudio 2022NET SDK 8.0Prism 版本 8.1.97Sqlite 二. 功能介绍 WPF 基础权限系统&#xff0c;是一个支持前后端分离设计的 客户端(C/S)项目&#xff0c;该示例项目前端xaml使用UI库 &#xff0c;Material Design Themes UI 来构建用户界面&#xff0c;确保…

【into outfile写文件】

简介 select * from user into outfile C:/Users/ichunqiu/Desktop/PhpStudy2018/PHPTutorial/WWW/1.txt;用法的意思就是把user表中查询到的所有字段都导出到1.txt文件中 我们之前还有学到dumpfile&#xff0c;单是它只能导出一条数据 写入shell 测试注入点 usernameadmin&…

【工具】使用perf抓取火焰图

背景 当程序存在cpu性能问题时&#xff0c;我们需要找到是哪个函数占用较多的CPU&#xff0c;也就是找出热点函数&#xff1b;perf的火焰图就是这个用途 安装 在Linux系统中&#xff0c;perf 是 Linux 内核提供的性能分析工具&#xff0c;它通常包含在内核源代码包中。大多数…

编码方式知识整理【ASCII、Unicode和UTF-8】

编码方式 一、ASCII编码二、Unicode 编码三、UTF-8编码四、GB2312编码五、GBK编码 计算机中对数据的存储为二进制形式&#xff0c;但采用什么样的编码方式存储&#xff0c;效率更高。主要编码方式有 ASCII、Unicode、UTF-8等。 英文一般为1个字节&#xff0c;汉字一般为3个字节…

Linux 线程互斥

1.相关背景概念 临界资源&#xff1a;多线程执行流共享的资源就叫做临界资源 临界区&#xff1a;每个线程内部&#xff0c;访问临界资源的代码&#xff0c;就叫做临界区 #include <iostream> #include <pthread.h> #include <string> #include <vector…

mac安装jdk8

这里写自定义目录标题 一、下载JDK8二、安装JDK8三、配置环境变量3.1 找到JDK安装目录3.2 打开终端&#xff1a;3.3 输入如下配置&#xff1a;3.3 查看配置是否成功&#xff1a; 一、下载JDK8 oracle官网下载或从下面链接获取 https://download.csdn.net/download/qq_44107684…

【小沐学Golang】基于Go语言搭建静态文件服务器

文章目录 1、简介2、安装2.1 安装版2.2 压缩版 3、基本操作3.1 go run3.2 go build3.3 go install3.4 go env3.5 go module 4、文件服务器4.1 filebrowser4.2 gohttpserver4.3 goFile 5、FAQ5.1 go.mod 为空5.2 超时 结语 1、简介 https://golang.google.cn/ Go语言诞生于2007…

day02 -- docker

1.docker的介绍 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go语言 并遵从 Apache2.0 协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。容器是完全使…

●day 35 动态规划part01

第九章 动态规划part01 动态规划的类别 理论基础 动态规划下五步曲&#xff1a; 1、确定dp数组&#xff08;dp table&#xff09;以及下标的含义 2、确定递推公式 3、dp数组如何初始化 4、确定遍历顺序 5、打印dp数组 代码随想录 斐波那契数 代码随想录 动态规划5部曲 cla…

高级语言源程序转换为可执行目标文件

将高级语言源程序转换为可执行目标文件的过程通常包括以下几个主要步骤&#xff1a; ​ 1. 预处理&#xff08;Preprocessing&#xff09;&#xff1a; 由谁完成预处理器&#xff08;cpp&#xff09;操作处理源代码中的预处理指令&#xff08;如宏定义、文件包含、条件编译等&…

Linux——动态卷的管理

确保已经设置了对应的动态卷的驱动&#xff08;provisioner 制备器&#xff09;基于动态驱动创建对应的存储类创建PVC &#xff08;PVC 将会自动根据大小、访问模式等创建PV&#xff09;Pod的spec 中通过volumes 和 volumemounts 来完成pvc 的绑定和pvc对应pv的挂载删除pod 不…

Linux网络编程(七)-TCP协议客户端及代码实现

1.TCP的客户端代码流程简述 这一章将为大家讲解Socket通信中客户端的实现过程&#xff0c;还是先上图&#xff0c;请大家了解客户端的步骤 可以看到&#xff0c;相比服务端&#xff0c;客户端的步骤简单的很多。事实上这种情况比较多&#xff0c;比如一个服务端会有多个客户端…

JMeter模拟并发请求

PostMan不是严格意义上的并发请求工具&#xff0c;实际是串行的&#xff0c;如果需要测试后台接口并发时程序的准确性&#xff0c;建议采用JMeter工具。 案例&#xff1a;JMeter设置20个并发卖票请求&#xff0c;查看后台是否存在超卖的情况 方式一&#xff1a;一共10张票&…

TrickMo 安卓银行木马新变种利用虚假锁屏窃取密码

近期&#xff0c;研究人员在野外发现了 TrickMo Android 银行木马的 40 个新变种&#xff0c;它们与 16 个下载器和 22 个不同的命令和控制&#xff08;C2&#xff09;基础设施相关联&#xff0c;具有旨在窃取 Android 密码的新功能。 Zimperium 和 Cleafy 均报道了此消息。 …

编写一个通用的i2c控制器驱动框架

往期内容 I2C子系统专栏&#xff1a; I2C&#xff08;IIC&#xff09;协议讲解-CSDN博客SMBus 协议详解-CSDN博客I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客内核提供的通用I2C设备驱动I2c-dev.c分析&#xff1a;注册篇内核提供的通用I2C设备驱动I2C-dev.…

时空数据时序预测模型: HA、VAR、GBRT、GCN、DCRNN、FCCF、ST-MGCN

HA (Historical Average) HA (Historical Average&#xff0c;历史平均模型) 是一种基础的时间序列预测方法&#xff0c;通常用于预测具有周期性或季节性规律的数据。它通过计算历史上同一时间段的平均值来预测未来值&#xff0c;假设数据会遵循某种周期性的变化模式。以下是对…

智能家居的“眼睛”:计算机视觉如何让家更智能

引言 在不远的未来&#xff0c;当我们走进家门&#xff0c;灯光自动亮起&#xff0c;空调已经调至最舒适的温度&#xff0c;甚至音乐也播放着我们最喜欢的歌曲。 这一切&#xff0c;都得益于智能家居系统的发展。而在这个系统中&#xff0c;计算机视觉技术扮演着至关重要的角色…

SpringBoot车辆管理系统:构建与优化

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

群晖通过 Docker 安装 MySQL

1. 打开 Docker 应用&#xff0c;并在注册表搜索 MySQL 2. 下载 MySQL 镜像&#xff0c;并选择版本 3. 在 Docker 文件夹中创建 MySQL&#xff0c;并创建子文件夹 4. 设置权限 5. 选择 MySQL 映像运行&#xff0c;创建容器 6. 配置 MySQL 容器 6.1 使用高权限执行容器 6.2 启…