【深入理解SpringCloud微服务】深入理解微服务中的远程调用,并手写一个微服务RPC框架
【深入理解SpringCloud微服务】深入理解微服务中的远程调用,并手写一个微服务RPC框架
- 远程过程调用
- 微服务中的RPC框架
- 如何实现一个微服务中的RPC框架
- 接口扫描
- 生成代理对象
- 代理对象处理逻辑
- 手写一个微服务RPC框架
- @RPCClient
- @EnableRPCClient
- MicroServiceRPCClientRegistrar
- RPCClientBeanDefinitionScanner
- RPCClientFactoryBean
- RPCInvocationHandler
远程过程调用
远程过程调用简称RPC,也就是本地调用远程服务器上的方法。比如本地调用UserService的getUser(String userId)方法,经过网络通信,调用到远程服务器上的UserServiceImpl的getUser(String userId)方法。
一般的RPC框架,会屏蔽掉网络通信的细节,通过动态代理返回一个代理对象,我们调用这个代理对象,RPC框架就会帮我们向服务器发送RPC请求。
然后服务器那一端接收到RPC请求后,还要解析出接口类型、方法、参数等信息,然后调用对象的实现类进行处理。
如果一个接口有多个提供者提供服务,那么客户端的RPC框架还要处理负载均衡的逻辑。
上面说到的只是RPC中的服务调用流程,在此之前,还有服务暴露(或者叫服务注册)和服务引入(或者叫服务发现)两个过程。比如服务暴露就是把服务提供方的ip地址端口号,以及相关的接口信息发布到注册中心。而服务引入则是从注册中心把服务提供者发布上去的信息拉取到本地,根据该信息生成对应接口的代理对象。
以上这些,都是作为一个RPC框架需要实现的,可以看出来实现一个完整RPC框架还是比较复杂的。而我们作为使用者只要做相应的配置,既可以像调用本地方法一样调用远程服务。
微服务中的RPC框架
比起实现一个完整的RPC框架(比如Duboo),实现一个微服务中的RPC框架则要简单的多。由于微服务本身自带了注册中心和注册中心客户端等组件,服务暴露和服务引入就不需要RPC框架的实现者去考虑了。并且微服务还自带了负载均衡(比如Ribbon),RPC框架的实现者也可以复用,无需重复造轮子。
因此作为微服务中的RPC框架,只需要扫描接口生成代理对象,然后代理对象中复用微服务的负载均衡器即可。
如何实现一个微服务中的RPC框架
接口扫描
接口扫描这一步要做的事情就是扫描出所有需要生成代理对象的接口,这些接口在我们本地没有实现类,都是通过代理对象调用底层的远程调用逻辑请求到远程服务器上的接口实现类。
我们可以定义一个注解(比如OpenFeign的@FeignClient注解),注解需要指定服务名(表示这个接口是请求哪个微服务的),然后通过Spring的ClassPathBeanDefinitionScanner扫描我们自定义的注解修饰的接口,然后生成BeanDefinition,注册到Spring容器中。
生成代理对象
由于要根据扫描到的接口生成代理对象,因此扫描接口时生成的BeanDefinition的beanClass属性需要设置为Spring提供的FactoryBean类型。但是FactoryBean是一个接口,因此我们要实现自己的FactoryBean实现类,重写FactoryBean的getObject()方法,getObject()方法生成接口的代理对象,我们可以使用JDK动态代理。
代理对象处理逻辑
如果我们使用的是JDK动态代理,那么就会进入我们实现的InvocationHandler的handle()方法。
首先要解析修饰接口的自定义注解,获取注解上的服务名。
除服务名外,我们还要有请求的url,因此一般接口方法上也要有注解,我们们可以复用SpringMVC的注解(@RequestMapping、@GetMapping等),也可以自己重写定义一套。
获取到服务名后,然后要读取接口方法上的注解,解析注解上的url,从注解解析出来的url前面拼接上修饰接口的自定义注解的服务名,就形成了完成的请求地址。
得到请求地址后,我们复用负载均衡微服组件进行客户端负载均衡,然后通过RestTemplate或者OkHttp等其他的http工具发出http请求即可。
手写一个微服务RPC框架
@RPCClient
@RPCClient是自定义的用于修饰接口的注解,功能相当于OpenFegin的@FeignClient,用于被扫描并生成代理对象。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface RPCClient {String serviceName() default "";}
@EnableRPCClient
@EnableRPCClient注解也是自定义注解,功能与OpenFeign的@EnableFeignClients注解类型。用于修饰启动类,表示启动微服务RPC功能。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Import({MicroServiceRPCClientRegistrar.class})
public @interface EnableRPCClient {String[] scanBasePackages() default {};}
scanBasePackages属性指定了包扫描路径,@Import引入了一个MicroServiceRPCClientRegistrar,由MicroServiceRPCClientRegistrar进行包扫描并生成BeanDefinition。
MicroServiceRPCClientRegistrar
MicroServiceRPCClientRegistrar 实现了Spring的ImportBeanDefinitionRegistrar接口,并重写了registerBeanDefinitions方法。在registerBeanDefinitions方法中进行包扫描。
public class MicroServiceRPCClientRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware,ResourceLoaderAware {private Environment environment;private ResourceLoader resourceLoader;public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 创建包扫描使用的scannerClassPathBeanDefinitionScanner scanner = new RPCClientBeanDefinitionScanner(registry, false, environment, resourceLoader);// 设置扫描@RPCClient注解scanner.addIncludeFilter(new AnnotationTypeFilter(RPCClient.class));// 解析@EnableRPCClient注解,获取包扫描路径Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);for (String packageToScan : packagesToScan) {// 调用scan方法进行包扫描scanner.scan(packageToScan);}}public void setEnvironment(Environment environment) {this.environment = environment;}public void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}private Set<String> getPackagesToScan(AnnotationMetadata metadata) {// 获取@EnableRPCClient注解中的属性AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(EnableRPCClient.class.getName()));// 获取@EnableRPCClient注解属性中的包扫描路径scanBasePackagesString[] basePackages = attributes.getStringArray("scanBasePackages");Set<String> packagesToScan = new LinkedHashSet<String>();packagesToScan.addAll(Arrays.asList(basePackages));return packagesToScan;}
}
Spring会在处理@Import注解时会调用ImportBeanDefinitionRegistrar的registerBeanDefinitions()方法。
MicroServiceRPCClientRegistrar的registerBeanDefinitions()方法:
- 创建一个RPCClientBeanDefinitionScanner,它继承了Spring的包扫描器ClassPathBeanDefinitionScanner
- 解析@EnableRPCClient注解中的包扫描路径scanBasePackages
- 调用RPCClientBeanDefinitionScanner的scan方法进行包扫描
RPCClientBeanDefinitionScanner
RPCClientBeanDefinitionScanner是自定义的包扫描器,继承了Spring的包扫描器ClassPathBeanDefinitionScanner,我们重写doScan()方法,当调用scan()方法时,就会调用到我们重写的doScan()方法。
public class RPCClientBeanDefinitionScanner extends ClassPathBeanDefinitionScanner implements BeanClassLoaderAware {private ClassLoader classLoader;public RPCClientBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, Environment environment, ResourceLoader resourceLoader) {super(registry, useDefaultFilters, environment, resourceLoader);}@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {// 只有接口才会被加载return beanDefinition.getMetadata().isInterface();}@Overrideprotected Set<BeanDefinitionHolder> doScan(String... basePackages) {// 调用父类ClassPathBeanDefinitionScanner的doScan()方法进行扫描Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);// 处理扫描结果for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {ScannedGenericBeanDefinition beanDefinition = (ScannedGenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();String beanClassName = beanDefinition.getBeanClassName();Class<?> beanClass = ClassUtils.resolveClassName(beanClassName, classLoader);// 修改beanDefinition 的beanClass属性为RPCClientFactoryBeanbeanDefinition.setBeanClass(RPCClientFactoryBean.class);// 设置RPCClientFactoryBean中的type字段值为接口的类型beanDefinition.getPropertyValues().addPropertyValue("type", beanClass);// 设置自动装配类型为byTypebeanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);}return beanDefinitionHolders;}@Overridepublic void setBeanClassLoader(ClassLoader classLoader) {this.classLoader = classLoader;}
}
首先调用父类ClassPathBeanDefinitionScanner的doScan()方法进行扫描,获取扫描到的BeanDefinition集合。
然后处理扫描到的BeanDefinition集合:
- 修改BeanDefinition的beanClass属性为RPCClientFactoryBean,RPCClientFactoryBean实现了FactoryBean接口,后续Spring实例化Bean时会调用RPCClientFactoryBean的getObject()方法。我们会在getObject()方法方法中进行动态代理
- 设置RPCClientFactoryBean中的type字段值为接口的类型,type是动态代理时指定的接口类型
- 设置自动装配类型为byType
RPCClientFactoryBean
RPCClientFactoryBean实现了Spring的FactoryBean接口,并重写了getObject()方法。Spring在实例化Bean时,如果BeanDefinition的beanClass属性是FactoryBean类型的实现类,会调用getObject()方法进行实例化。我们在RPCClientFactoryBean的getObject()方法中使用JDK动态代理生成代理对象。
public class RPCClientFactoryBean<T> implements FactoryBean<T> {@Autowiredprivate LoadBalanceClient loadBalanceClient;private Class<T> type;public T getObject() throws Exception {// JDK动态代理生成代理对象// InvocationHandler是RPCInvocationHandler// RPCInvocationHandler中持有LoadBalanceClient,用于做负载均衡return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{type}, new RPCInvocationHandler(type, loadBalanceClient));}...}
RPCInvocationHandler
当我们调用代理对象的接口方法时,就会进入RPCInvocationHandler的invoke()方法。invoke()方法会进行url拼接、负载均衡重写url、收集方法参数、OkHttp发送请求、解析返回结果等处理。
由于我们这里直接服用了SpringMVC的注解,因此会涉及到很多SpringMVC的注解比如@RequestMapping、@GetMapping等的解析操作。
public class RPCInvocationHandler<T> implements InvocationHandler {private static final Logger LOGGER = LoggerFactory.getLogger(RPCInvocationHandler.class);private Class<T> type;private LoadBalanceClient loadBalanceClient;public RPCInvocationHandler(Class<T> type, LoadBalanceClient loadBalanceClient) {this.type = type;this.loadBalanceClient = loadBalanceClient;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 处理接口上的@RPCClient注解,得到服务名serviceNameRPCClient rpcClient = type.getAnnotation(RPCClient.class);String serviceName = rpcClient.serviceName();// 拼接请求url用的StringBuilderStringBuilder urlBuilder = new StringBuilder();// http://拼接上服务名urlBuilder.append("http://").append(serviceName);// 处理接口上的@RequestMapping注解,得到@RequestMapping注解中的url,然后拼接到urlBuilder上RequestMapping requestMapping = type.getAnnotation(RequestMapping.class);appendUrlByRequestMapping(urlBuilder, requestMapping);// 处理方法上的@RequestMapping、@GetMapping、@PostMapping、@PutMapping、@DeleteMapping注解// 也是获取注解上的url进行拼接// 此外还会解析出RequestMethodRequestMethod requestMethod = null;if (method.getAnnotation(RequestMapping.class) != null) {requestMapping = method.getAnnotation(RequestMapping.class);appendUrlByRequestMapping(urlBuilder, requestMapping);RequestMethod[] methods = requestMapping.method();if (methods != null && methods.length > 0) {requestMethod = methods[0];}} else if (method.getAnnotation(GetMapping.class) != null) {...} else if (method.getAnnotation(PostMapping.class) != null) {...} else if (method.getAnnotation(PutMapping.class) != null) {...} else if (method.getAnnotation(DeleteMapping.class) != null) {...}// 完整的请求url(负载均衡重写前)String requestUrl = urlBuilder.toString().trim();// 调用LoadBalanceClient进行客户端负载均衡,重写url,// LoadBalanceClient是我们的微服务负载均衡组件,这里直接服用requestUrl = loadBalanceClient.reconstructUrl(serviceName, requestUrl);// 处理参数,注解包括:@PathVariable、@RequestParam、@RequestBodyMap<String, Object> requestParamMap = new HashMap<>();...// 适用OkHttp发送请求OkHttpClient okHttpClient=new OkHttpClient();...// 处理返回结果okhttp3.ResponseBody responseBody = null;...}private void appendUrl(StringBuilder urlBuilder, String[] paths) {if (paths.length > 0) {String path = paths[0];if (!path.startsWith("/")) {urlBuilder.append("/");}urlBuilder.append(path);}}private void appendUrlByRequestMapping(StringBuilder urlBuilder, RequestMapping requestMapping) {if (requestMapping != null) {String[] paths = requestMapping.value();appendUrl(urlBuilder, paths);}}
}
我们使用一个StringBuilder进行url拼接,首先是获取到@RPCClient上的服务名,http://加上服务名作为url的开头拼接到StringBuilder上;然后解析接口上的@RequestMapping注解得到注解上的url,拼接到StringBuilder上;然后是解析方法上的注解@RequestMapping、@GetMapping、@PostMapping、@PutMapping、@DeleteMapping等,解析得到注解上的url,同样的拼接到StringBuilder上。
然后调用LoadBalanceClient进行负载均衡操作,重写url,LoadBalanceClient是直接复用了我们的微服务负载均衡组件。
然后就是收集方法参数,使用OkHttp发送请求,处理返回结果。都是一些简单但是繁琐的代码,因此直接省略掉了。
代码已上传至https://gitee.com/huang_junyi/simple-microservice,如果想看详细代码的话可以到上面去下载。
相关文章:

【深入理解SpringCloud微服务】深入理解微服务中的远程调用,并手写一个微服务RPC框架
【深入理解SpringCloud微服务】深入理解微服务中的远程调用,并手写一个微服务RPC框架 远程过程调用微服务中的RPC框架如何实现一个微服务中的RPC框架接口扫描生成代理对象代理对象处理逻辑 手写一个微服务RPC框架RPCClientEnableRPCClientMicroServiceRPCClientRegi…...

数据结构----二叉树
小编会一直更新数据结构相关方面的知识,使用的语言是Java,但是其中的逻辑和思路并不影响,如果感兴趣可以关注合集。 希望大家看完之后可以自己去手敲实现一遍,同时在最后我也列出一些基本和经典的题目,可以尝试做一下。…...

通过python管理mysql
打开防火墙端口: 使用 firewall-cmd 命令在防火墙的 public 区域中永久添加 TCP 端口 7500(FRP 控制台面板端口)、7000(FRP 服务端端口)以及端口范围 6000-6100(一组客户端端口)。这些端口是 FR…...
Run the OnlyOffice Java Spring demo project in local
Content 1.Download the sample project in java2.Run the project3.Test the example document 1.Download the sample project in java Link: download the sample code in official website document properties setting spring 项目所在的服务器 server. Address192.168…...

11. Rancher2.X部署多案例镜像
**部署springboot项目 : ** **部署中间件Mysql8.0 : ** 名称:service-mysql 端口 :3306:3306 镜像:mysql:8.0 环境变量: MYSQL_ROOT_PASSWORDxdclass.net168路径映射 /home/data/mysql/data /var/lib/mysql:rw /etc/localtime…...

探索Linux世界之Linux环境开发工具的使用
目录 一、yum -- Linux软件包管理器 1、什么是yum 2、yum的使用 2.1 yum一些经常见的操作 1.查看软件包 2. 安装软件包 3. 删除软件包 3、yum的周边知识 3.1 yum的软件包都是从哪里来的?是从哪里能下载到这些软件包? 3.2 yum的拓展软件源 二、…...

探索Spring Boot微服务架构的最佳实践
目录 引言 一、Spring Boot简介 二、微服务架构的关键要素 三、Spring Boot在微服务中的最佳实践 3.1 清晰的服务边界 3.2 自动化配置与依赖管理 3.3 服务注册与发现 3.4 配置管理 3.5 安全与认证 3.6 监控与日志 3.7 分布式事务 四、总结 引言 在当今快速迭代的软…...

[论文泛读]zkLLM: Zero Knowledge Proofs for Large Language models
文章目录 介绍实验数据实验数据1实验数据2实验数据3 介绍 这篇文章发在CCS2024,CCS是密码学领域的顶会。作者是来自加拿大的University of Waterloo。文章对大语言模型像GPT和LLM等大语言模型实现了零知识可验证执行,但不涉及零知识可验证训练。个人觉得…...

vscode插件中的图标怎么设置
首先在ts文件目录下和package.json同级的目录下加入一张图片,后缀是jpg、png、jpeg都可以。 然后package.json中加入该行 重新 vsce package即可 如果出现报错 The specified icon xxx/xxx/icon.jpg wasnt found in the extension. 那就是没有放正确文件夹的位…...
Study--Oracle-08-oracle数据库的闪回技术
一、闪回恢复区(Flash Recovery Area) 1、什么是闪回恢复区 闪回恢复区是Oracle数据库中的一个特殊存储区域,用于集中存放备份和恢复数据库所需的所有文件,包括归档日志和闪回日志。这个区域可以帮助数据库在遇到介质故障时进行完全恢复。通过将备份数…...

获取客户端真实IP
出于安全考虑,近期在处理一个记录用户真实IP的需求。本来以为很简单,后来发现没有本来以为的简单。这里主要备忘下,如果服务器处于端口回流(hairpin NAT),keepalived,nginx之后,如何取得客户端的…...

韩式告白土味情话-柯桥生活韩语学习零基础入门教学
你们韩国人别太会告白了! 1、너 얼굴에 뭐가 조금 묻었어! 你的脸上有点5376东西! 뭐가 조금 묻었1585757는데? 有点什么? 이쁨이 조금 묻었네. 有点漂亮。 2、돌잡이 때 뭐 잡았어요? 你抓周的时候抓了什么? 쌀 잡았…...
Linux安全与高级应用(一)深入探讨Linux安全与高级应用
文章目录 深入探讨Linux安全与高级应用引言目录一、Linux安全与应用概述1.1 Linux的应用现状1.2 Linux的安全需求 二、构建LAMP企业网站平台2.1 LAMP平台简介2.2 安装和配置Apache服务器2.2.1 安装Apache2.2.2 配置Apache 2.3 安装和管理MySQL数据库2.3.1 安装MySQL2.3.2 配置M…...
【nginx 第二篇章】各个环境安装 nginx
一、Windows环境安装 1、下载 Nginx 访问Nginx官网(http://nginx.org/en/download.html)下载稳定版本的 Nginx 压缩包,如 nginx-1.xx.x.zip。下载后解压到指定的目录,例如 D:\nginx。 2、启动 Nginx 直接双击解压目录下的 ngi…...
在Spring Boot和MyBatis-Plus项目中,常见的错误及其解决方法2.0
1. org.springframework.beans.factory.BeanCreationException: Error creating bean with name requestMappingHandlerMapping 现象 在创建bean时发生错误,通常是因为存在重复的URL映射。 解决方法 检查所有控制器方法上的URL映射注解,确保没有重复…...

招聘信息数据清洗
文章目录 前言代码示例如下 前言 相关知识 为了完成本关任务,你需要掌握: 1.Spark 清洗数据的相关方法, 2.空值列怎么删除; 3.怎么数据切分才能达到想要的数据。 Spark清洗数据相关方法 一、将含有空值的数据删除 1.将含有空值的数据删除&a…...

机器学习——支持向量机(SVM)(1)
目录 一、认识SVM 1. 基本介绍 2. 支持向量机分类器目标 二、线性SVM分类原理(求解损失) 三、重要参数 1. kernel(核函数) 2 .C(硬间隔与软间隔) 四、sklearn中的支持向量机(自查&#…...

Elastic Observability 8.15:AI 助手、OTel 和日志质量增强功能
作者:来自 Elastic Alex Fedotyev, Tom Grabowski, Vinay Chandrasekhar, Miguel Luna Elastic Observability 8.15 宣布了几个关键功能: 新的和增强的原生 OpenTelemetry 功能: OpenTelemetry Collector 的 Elastic 分发:此版本…...
Unity3D ECS架构的优缺点详解
前言 Unity3D作为一款强大的游戏开发引擎,近年来在性能优化和架构设计上不断进化,其中ECS(Entity-Component-System)架构的引入是其重要的里程碑之一。ECS架构通过重新定义游戏对象的组织和处理方式,为开发者带来了诸…...
理解Go语言中多种并发模式
Go 的同步原语使实现高效的并发程序成为可能,并且选择合适的同步原语和并发模式可以更加容易地实现并发的可能,减少错误的发生。这里谈论的并发模式是只在 Go 语言中常见的并发的“套路” ,一种可解决某一类通用场景和问题的惯用方法。 1. 并发模式概述 我们先来回顾下同步…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...

windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...