扩展 MySQL 8详解

在上一章中,我们学习了如何优化 MySQL 8。我们还了解了实现优化需要进行哪些配置,以及如何利用缓存和缓冲进行优化。为了在以下组件中实现优化,我们一步一步地进行了用例研究:

在本章中,我们将学习如何扩展 MySQL 8。我们将检查允许扩展哪些 MySQL 8 组件,并了解如何根据特定的业务需求定制 MySQL 8。您将了解扩展 MySQL 8 之前的基本组件,以及用于扩展 MySQL 8 的 MySQL 插件 API 的特性。以下是本章涵盖的主题列表:

  • 扩展 MySQL 8 概述
  • 扩展插件并使用服务调用它们
  • 添加新功能
  • 调试和移植

在本节中,您将了解如何根据需要扩展 MySQL 8 的最激动人心的主题之一。在尝试扩展 MySQL 8 之前,您应该了解 MySQL 8 的几个组件。下面列出了对扩展 MySQL 8 非常重要的组件:

  • MySQL 8 内部
  • MySQL 8 插件 API
  • MySQL 8 组件和插件服务
  • 向 MySQL 8 添加新函数
  • MySQL 8 的调试与移植

在开始使用 MySQL 代码之前,您应该知道一些事情。为了帮助或跟踪 MySQL 开发,您应该按照系统或操作系统平台的说明安装源代码分发版。源代码包括内部文档,这对于从开发人员的角度理解 MySQL 内部如何工作非常重要。您也可以从订阅内部邮件列表 https://lists.mysql.com/internals ,其中包括从事 MySQL 代码工作的人员,您还可以讨论与 MySQL 开发相关的主题或发布补丁:

  • MySQL 8 线程:MySQL 服务器使用InnoDB存储引擎创建连接管理器线程、信号线程、读写线程、调度线程处理连接、复制和事件处理等线程。
  • MySQL 8 测试套件:MySQL 8 提供 Unix 源码发行版附带的测试系统,帮助用户和开发人员使用 MySQL 代码进行回归测试。您还可以使用测试框架编写自己的测试用例。

MySQL 8 支持插件 API,通过插件 API 可以创建服务器组件本身。插件可以在服务器启动时加载,也可以在运行时加载和卸载;无需重新启动服务器。API 非常通用,因为它没有指定插件在限制方面可以做什么,而是允许它们做的不仅仅是内置组件。API 支持存储引擎插件、全文解析器插件、服务器扩展等组件的接口。

插件接口利用 MySQL 8 数据库中的plugin表,通过INSTALL PLUGIN语句永久存储已安装插件的信息。在 MySQL 8 安装过程中会创建plugin表。对于单服务器调用,也可以使用--plugin--load选项安装插件,但使用此选项不会将安装的插件记录到plugin表中。

MySQL 8 还为用于特定目的的客户端插件提供支持 API,例如通过不同的身份验证方法启用客户端的服务器连接。

MySQL 8 服务器插件可以访问和启动服务器插件服务;类似地,服务器组件也可以访问和请求组件服务。MySQL 8 插件服务接口通过公开服务器功能来补充 API 插件,这些功能可以由插件调用。以下是插件服务的特点:

  • 这些服务使插件能够使用普通函数调用访问服务器代码,还可以调用用户定义的函数
  • 这些服务是可移植的,可以在多个平台上工作
  • 这些服务提供版本控制支持,防止插件和服务之间的不兼容
  • 这些服务还支持测试插件服务

MySQL 为插件和组件提供两种服务类型,如下所示:

  1. 锁定服务:锁定服务接口分 C 级和 SQL 级两级提供。该接口处理锁名称空间、锁名称和锁模式属性。
  2. keyring 服务:keyring 服务提供了一个安全存储敏感信息的接口,供内部服务器组件和插件稍后检索。

您可以将自己的函数添加到 MySQL 8 中,这可以通过支持的三种函数类型中的任意一种来完成。调用新函数的方式与调用内置函数(如ABS())的方式相同,无论您新添加了哪种函数类型,都是如此。以下是 MySQL 8 中支持的三种新函数:

  1. 通过自定义函数自定义函数界面增加一个函数。
  2. 添加一个函数作为本机(内置)MySQL 函数。
  3. 通过创建存储函数来添加函数。

将 MySQL 8 移植到其他操作系统目前受到许多操作系统的支持;支持的操作系统列表见http://www.mysql.com/support/supportedplatforms/database.html 。如果您添加了一个新端口,并且在使用新端口时遇到问题,您可以使用 MySQL 8 的调试。

根据您在 MySQL 服务器或 MySQL 客户端中遇到的问题,有不同的启动调试的方法。根据问题的位置,您可以分别在 MySQL 服务器或客户端启动调试,也可以从DBUG包获得帮助来跟踪程序的活动。

在本节中,您将了解插件 API、其接口和 MySQL 服务如何相互作用并在 MySQL 8 中提供扩展。这些插件也被认为是 MySQL 8 体系结构中的组件,因此您可以使用它们来提供可插入的特性。插件 API 和插件服务接口有以下区别:

  • 插件 API 启用服务器将使用的插件。插件的调用和调用是由服务器发起的,因此插件可以扩展服务器的功能,或者注册自己以接收服务器处理通知。
  • 插件服务接口允许插件调用服务器代码。服务功能的调用和调用由插件启动,因此许多插件可以利用相同的服务器功能,而无需单独实现该功能。

要创建插件库,必须提供所需的描述符信息,因为它指定库文件包含哪些插件。为每个指定的插件编写接口函数也是必要的。

每个服务器插件都必须有一个向插件 API 提供信息的通用描述符,以及一个为指定插件类型提供接口信息的类型特定描述符。用于指定通用描述符的结构对于所有插件类型都是相同的,并且特定于类型的描述符可以根据插件行为或功能的要求而变化。服务器插件接口允许插件公开系统变量和状态。

客户端插件的体系结构与服务器端插件略有不同。例如,每个插件都必须有描述符信息,但在通用描述符和特定类型描述符之间没有单独的划分

插件可以用 C 或 C++或任何其他可以使用 C 调用约定的语言编写。插件是动态加载和卸载的,因此操作系统必须动态支持动态编译调用应用程序的位置。具体来说,对于服务器插件,这意味着mysqld必须动态链接。

As we cannot be sure of what application will use the plugin, the dependencies on the symbols of the calling application should be avoided by the client plugin writers.

以下是可实现多种功能的受支持插件创建类型:

  • 认证
  • 密码验证和强度检查
  • 协议跟踪
  • 查询重写
  • 安全密钥环存储和检索
  • 存储引擎
  • 全文分析器
  • 后台程序
  • INFORMATION_SCHEMA表格
  • 半同步复制
  • 审计

您可以通过查看 MySQL 8 源发行版的include/mysql/components和相应的services目录来识别 MySQL 提供的组件服务和功能。

同样,您可以通过查看 MySQL 8 源代码发行版的include/mysql目录和相关文件来识别 MySQL 提供的插件服务和功能,如下所示:

  • plugin.h文件包括services.h文件,该services.h文件包含其中所有可用的特定于服务的头文件
  • 特定于服务的头文件的名称为service_xxx.h

以下是 MySQL 8 中可用组件服务的列表:

  • component_sys_variable_registercomponent_sys_variable_unregister:用于注册和注销系统变量
  • log_builtinslog_builtins_string:用于日志组件服务
  • mysql_service_udf_registrationmysql_service_udf_registration_aggregate:用于对组件和插件中的标量和聚合自定义函数进行注册和注销
  • mysql_string:用于字符串服务 API
  • pfs_plugin_table:用于动态性能模式表操作

以下是 MySQL 8 中可用的插件服务列表:

  • get_sysvar_source:用于检索系统变量设置
  • locking_service:用于 C 语言和 SQL 级接口的锁实现,具有名称空间、名称和模式属性
  • my_plugin_log_service:用于将错误消息写入日志
  • my_snprintf:用于字符串格式设置,以保持跨平台输出的一致性
  • status_variable_registration:用于注册状态变量
  • my_thd_scheduler:用于线程调度程序选择
  • mysql_keyring:用于钥匙圈存储服务
  • mysql_password_policy:用于密码强度和验证检查
  • plugin_registry_service:用于访问组件注册表及相关服务
  • security_context:用于管理线程安全上下文
  • thd_alloc:用于内存分配
  • thd_wait:用于报告睡眠或暂停

现在,您已经清楚地了解了插件服务和组件服务。MySQL 8 提供以下类型的服务来支持插件和组件服务:

  1. 锁定服务
  2. 钥匙扣服务

以下各节提供了这两种服务的详细信息。

锁定服务接口分为两个级别:C 级别和 SQL 级别。该接口处理锁名称空间、锁名称和锁模式属性。C 语言接口可以作为插件服务从用户定义的函数或服务器插件中调用,SQL 级接口用作用户定义的函数集,映射为调用服务例程。

以下是锁定接口的特征:

  • 锁名称空间、锁名称和锁模式是锁的三个属性。
  • 通过形成锁名称空间和锁名称组合来标识锁。
  • 锁定模式可以是读或写。读锁是共享的,而写锁是独占的。
  • 锁名称和名称空间最多可以包含 64 个字符,并且必须是非 NULL 和非空字符串。
  • 锁名称和命名空间被视为二进制字符串,因此比较区分大小写
  • 函数用于获取和释放锁,不需要任何特权来调用函数。
  • 在不同会话中的锁获取调用期间检测死锁;调用方被选择并终止其锁获取请求,持有读锁的调用方会话优先于持有写锁的会话。
  • 典型的会话可以通过一个锁获取调用请求多个锁获取。它为请求提供原子行为,并在获取所有锁时成功,或在任何锁获取失败时失败。
  • 会话可以获取同一锁标识符的多个锁,其中锁实例可以是写锁、读锁或读锁和写锁的混合。
  • 获取的锁通过显式调用 release lock 函数从会话中释放,或者在会话终止时隐式释放。
  • 释放时,给定命名空间中的所有锁将在会话中一起释放。

keyring 服务提供了一个接口,用于安全地存储敏感信息,供内部服务器组件和插件稍后检索。在 keyring 服务中,来自密钥库本身的记录由密钥数据和可访问密钥的唯一标识符组成。标识符由以下两部分组成:

  1. key_id:名称。key_id或以mysql_开头的密钥 ID 值由 MySQL 服务器保留。
  2. user_iduser_id表示每次会话有效user_id。如果没有用户上下文,则可以是NULL,并且该值不一定需要是实际的user,而是取决于应用程序。

以下是钥匙环服务功能的常见特征:

  • 每个函数都返回 1 表示失败,0 表示成功
  • user_idkey_id参数构成了一个唯一的组合,指示要在钥匙圈中使用的钥匙
  • 有关密钥的附加信息随key_type参数值一起提供,作为其预期用途、其加密方法或其他此类信息
  • 用户名、密钥 ID、类型和值在 keyring 服务函数中被视为二进制字符串,因此比较区分大小写

以下是可用的钥匙扣服务功能列表:

  • my_key_generate():顾名思义,它生成一个给定类型和长度的新随机密钥,并存储在密钥环中。函数由参数key_iduser_idkey_typekey_len组成,函数语法如下:
 bool my_key_generate(const char *key_id, const char*key_type, 
          const char *user_id, size_t key_len)
  • my_key_fetch():deobfoused 参数值并从 keyring 及其类型中检索密钥。该函数由参数key_iduser_idkey_typekeykey_len组成,以及以下函数语法:
 bool my_key_fetch(const char *key_id, const char **key_type, 
          const char* user_id, void **key, size_t *key_len)
  • my_key_remove():从钥匙圈中移除相关钥匙。该函数由参数key_iduser_id以及以下函数语法组成:
 bool my_key_remove(const char *key_id, const char* user_id)
  • my_key_store():混淆参数值,并将一个键存储在 keyring 中。该函数由参数key_iduser_idkey_typekeykey_len组成,以及以下函数语法:
 bool my_key_store(const char *key_id, const char *key_type, 
          const char* user_id, void *key, size_t key_len)

MySQL 8 中支持的三种类型中的任何一种都可以添加新函数。每种类型都有各自的优缺点。在何处以及应添加或实现哪种类型的功能取决于功能的要求。

以下是 MySQL 8 中支持的三种新函数的列表,我们将在下一节中介绍:

  1. 通过用户定义的函数界面添加函数。
  2. 添加一个函数作为本机(内置)MySQL 函数。
  3. 通过创建存储函数来添加函数。

用户定义的功能接口为用户用途的功能提供独立的功能。

MySQL 接口为用户定义的函数提供了以下特性和功能:

  • 函数可以接受整型、字符串或实值的参数,并且可以返回相同类型的值
  • 可以将简单函数定义为一次操作一行,也可以将其定义为聚合函数以操作一组行
  • 函数提供了启用它们的信息,以便它们可以检查传递的参数的类型、名称和数量
  • 在将参数传递给给定函数之前,还可以要求 MySQL 强制参数
  • 如果该功能导致任何错误或返回NULL则可以进行指示

UDF 函数必须用 C 或 C++编写,底层操作系统必须支持动态加载行为。有一个文件sql/udf_example.cc,它定义了五个 UDF 函数,它包含在 MySQL 源代码发行版中。分析该文件将让您了解 UDF 的调用约定是如何工作的。include/mysql_com.h文件中定义了用户定义的功能相关符号和数据结构,该文件包含在mysql.h头文件中。

UDF 中包含的典型代码在运行的服务器中执行,因此在编写 UDF 代码服务器代码时,所有约束都适用。当服务器升级时,当前适用的约束可能会被修改,这可能导致需要重写 UDF 代码,因此在为 UDF 编写代码时必须小心。

为了使用 UDF,必须动态链接mysqld。对于 SQL 语句中使用的任何函数,必须有底层 C 或 C++函数。遵循分离 SQL 和 C/C++代码的约定,其中大写的xxx()表示 SQL 函数调用,而小写的xxx()表示 C/C++函数调用

Encapsulate your C function as shown in following sentence when you are using C++: extern "C" { ... } This way it is ensured that your C++ function names are readable in the completed user-defined function. 

要编写和实现接口功能名称XXX(),必须使用主功能xxx(),另外还需要从以下位置实现一个或多个功能:

  • xxx():生成函数结果的主函数
  • xxx_init():主功能xxx()的初始化功能,可用于以下任一目的:
    • 正在检查要传递给XXX()的参数数
    • 调用 main 函数时使用声明验证参数类型
    • 在需要时为主功能分配内存
    • 结果的最大长度验证
    • 为结果中的最大值设置十进制数限制
    • 指定结果是否可以为NULL
  • xxx_deinit():表示主函数的去初始化,如果初始化函数为主函数分配了内存,则释放内存

在 MySQL 8 中,聚合 UDF 的处理顺序如下:

  1. 调用xxx_init()以便分配存储结果信息所需的内存。
  2. 按照GROUP BY功能的规定对表格/结果进行排序。
  3. 调用xxx_clear()以便重置每个新组中第一行的当前聚合值。
  4. 调用xxx_add(),将参数添加到当前聚合值。
  5. 调用xxx()通过更改或在处理最后一行后获取分组数据聚合结果。
  6. 重复步骤 3-5,直到处理完所有指定/结果行。
  7. 调用xxx_deinit()为 UDF 释放任何分配的内存。

所有函数都必须是线程安全的,包括主函数以及所需的其他附加函数,以及初始化和反初始化函数

与上述顺序类似,在添加新的用户定义函数时,需要注意以下重要方面:

  • 自变量处理
  • UDF 返回值和错误处理
  • UDF 编译和安装
  • UDF 安全预防措施

要添加新的本机函数,需要源代码分发,以便使用包含新本机函数的修改源代码进行编译。当您迁移到另一个 MySQL 版本时,还需要重复此操作。

如果要在语句中引用新的本机函数并将其复制到从属服务器,请确保每个从属服务器都有新的本机函数可用,否则,在尝试调用新的本机函数时,从属服务器上的复制将失败。

以下是在sql目录的源分发文件中添加新本机函数的步骤:

  1. 需要在item_create.cc中添加该函数的子类:
    • 在参数数量固定的情况下,根据本机函数中所需的参数数量,将从Create_func_arg0Create_func_arg1Create_func_arg2Create_func_arg3创建子类。您可以参考Create_func_absCreate_func_uuidCreate_func_pow类。
    • 如果参数数量可变,则从Create_native_func创建子类。您可以参考Creat_func_concat类。
  2. SQL 语句中要引用的函数名需要在item_create.cc中注册,方法是在数组中添加以下行:static Native_func_registry func_array[]
    • 如果需要,可以为同一函数注册多个名称。您可以参考LOWERLCASE的行,它们是代表Create_func_lcase的别名。
  3. 声明从Item_str_funcItem_num_func继承的类是必要的,这取决于您的函数返回类型是item_func.h文件中的字符串还是数字。
  4. 根据您的函数在item_func.cc文件中定义为字符串还是数值函数,需要添加以下声明之一:
 double Item_func_newname::val()
 longlong Item_func_newname::val_int() 
        String *Item_func_newname::Str(String *str)
  1. 如果函数是不确定的,也就是说,如果针对固定给定参数的不同调用返回的结果不同,则需要在项构造函数中包含以下语句,指示不应缓存函数结果:current_thd->lex->safe_to_cache_query=0;
  2. 您可能还需要为本机函数定义以下对象函数:
    • void Item_func_newname::fix_length_and_dec()
    • 函数应至少包括对给定参数的max_length计算
    • 如果主函数不能返回任何NULL值,也应设置maybenull = 0
    • 您可以参考Item_func_mod::fix_length_and_dec来了解相同的内容

线程安全是所有功能的必备条件。在没有互斥锁保护的情况下,不应在函数中使用任何静态或全局变量。

目前许多操作系统都支持将 MySQL 8 移植到其他操作系统。最新支持的操作系统列表见http://www.mysql.com/support/supportedplatforms/database.html 。如果您已经添加或试图添加新端口(受支持的平台)并且遇到问题,您可以使用 MySQL 8 的调试来查找和修复问题。

First, you should get the test program mysys/thr_lock to work before debugging mysqld. This makes sure that your thread installation can have a remote chance to work!

根据您遇到问题的位置,启动调试有不同的可能性——可能在 MySQL 服务器中,也可能在 MySQL 客户端中。根据问题的位置,您可以分别在 MySQL 服务器或 MySQL 客户端中开始调试,并且为了跟踪程序的活动,您将从DEBUG包获得帮助。

The MySQL source code includes internal documentation written using Doxygen, which is very helpful in understanding the developer perspective on how MySQL works.

在本节中,您将看到有关以下主题的详细信息:

  • 调试 MySQL 服务器
  • 调试 MySQL 客户端
  • DBUG

如果您正在使用 MySQL 中的一些非常新的功能,并且面临一些问题,比如说服务器正在崩溃,那么您可以尝试使用--skip-new选项运行mysqld。此选项告诉 MySQL 服务器禁用所有新的和可能不安全的功能

如果mysqld未启动,请验证my.cnf文件,因为它们可能会干扰设置!您可以使用mysqld --print-defaults选项检查my.cnf中的参数,然后使用--no-defaults选项启动mysqld以避免使用它们。

如果mysqld开始消耗内存或 CPU 或挂起,您可以检查mysqladmin processlist status并确定某人执行的查询是否花费了很长时间。当您面临性能问题或新客户端无法连接时,您可以使用mysqladmin -i10进程列表状态。

您还可以使用 debug 命令mysqladmin,该命令将查询使用情况、内存使用情况和正在使用的锁的信息转储到 MySQL 日志文件中,可以为您解决一些问题。如果您还没有编译 MySQL 进行调试,这个命令也可以使用,提供了一些有用的信息

如果您遇到表速度变慢的问题,您应该尝试使用myisamchkOPTIMIZE_TABLE优化表。您可能应该检查慢速查询(如果有),使用EXPLAIN查找并修复查询问题。

以下是在 MySQL 8 中调试时要考虑的重要领域:

  • 编译 MySQL 进行调试:如果遇到非常具体的问题,您可以尝试调试 MySQL。为此,您必须使用-DWITH_DEBUG=1选项配置 MySQL。调试配置自动启用许多额外的安全检查功能,以监控mysqld的运行状况。
  • 创建跟踪文件:您可以尝试通过创建跟踪文件来发现问题。为此,必须使用调试支持编译mysqld。然后您可以使用--debug选项,该选项将在 Unix 上的/tmp/mysqld.trace和 Windows 上的\mysqld.trace中添加跟踪日志。
  • 使用 WER 和 PDB 创建 Windows crashdump:程序数据库文件作为 MySQL 的单独发行版包含在 ZIP 归档调试二进制文件和测试套件中。这些文件提供有关调试 MySQL 安装问题的信息。它们可以与 WinDbg 或 Visual Studio 一起用于调试mysqld
  • 在 gdb下调试 mysqld:当您面临线程问题或mysqld服务器在ready for connections之前挂起时,可以使用此选项。
  • 使用堆栈跟踪:您也可以在mysqld意外死亡时使用此选项,并找出问题所在。
  • 使用服务器日志查找mysqld中的错误原因:您可以通过启用常规查询日志来使用此选项-在此之前,您应该使用myisamchk工具检查所有表,并验证日志中是否存在任何问题。
  • 如果您遇到表损坏,则生成测试用例:此选项用于您面临表损坏问题时,并且仅适用于MyISAM表。

在 MySQL 客户机中遇到问题的情况下,您也可以在 MySQL 客户机中进行调试,但要做到这一点,您必须拥有集成的调试包。您需要使用-DWITH_DEBUG=1配置 MySQL,以便在 MySQL 客户端中进行调试。

在运行 MySQL 客户端之前,需要对环境变量MYSQL_DEBUG进行如下设置:

shell> MYSQL_DEBUG=d:t:O,/tmp/client.trace 
shell> export MYSQL_DEBUG

这使得 MySQL 客户端在 Unix 的/tmp/client.trace或 Windows 的\client.trace中生成跟踪文件。

如果您自己的客户机代码有问题,您可以尝试通过使用已知可用的客户机运行查询来连接到服务器。为此,您应在调试模式下运行mysqld

shell> mysql --debug=d:t:O,/tmp/client.trace

如果您想邮寄问题的 bug 报告,此跟踪将提供有用的信息。

如果您的客户机在某些看起来像legal的代码中崩溃,您可以检查您的mysql.h头文件是否包含与 MySQL 库文件匹配的文件。这是一个非常常见的错误,使用旧 MySQL 安装中的旧mysql.h文件和新的 MySQL 库,导致了这个问题。

Fred Fish 最初使用 MySQL 服务器和大多数 MySQL 客户端创建了DBUG包。如果将 MySQL 配置为调试,那么这个包可以生成一个跟踪文件,其中包含有关程序正在执行的操作的信息。

可以指定调试选项,以便使用DBUG包获取跟踪文件的特定信息。它可以在程序调用中使用-# [debug_options]选项或--debug[=debug_options]选项。

如果指定了--debug-#选项而未指定debug_options值,则大多数 MySQL 程序将使用默认值。Windows 上的服务器默认值为d:t:i:O,\mysqld.trace,Unix 上的服务器默认值为d:t:i:o,/tmp/mysqld.trace。此默认值的影响如下所示:

  • d:为所有调试宏启用输出
  • t:跟踪函数调用和退出
  • i:在跟踪文件的输出行中增加PID
  • o,/tmp/mysqld.trace O,\mysqld.trace:分别在 Unix 和 Windows 中设置调试输出文件

在大多数情况下,无论平台工作情况如何,对大多数客户端程序都使用默认的debug_optionsd:t:o,/tmp/myprogram_name.trace。对于 Windows,请使用\myprogram_name.trace

以下是要在 shell 命令行上指定的调试控制字符串的一些示例:

--debug=d:t 
--debug=d:f,main,subr1:F:L:t,20 
--debug=d,input,output,files:n 
--debug=d:t:i:O,\\mysqld.trace

在本章中,您学习了如何通过自定义函数和 API 扩展 MySQL 8。您还了解了编写函数以及插件服务和 API 的相关特性。现在,您可以创建自己的函数或插件,以满足特定的业务需求,还可以在函数不能按预期工作时进行调试,并测试它是否能正常工作。

在下一章中,您将了解 MySQL 8 的最佳实践和 MySQL 8 中的基准测试。您将了解基准测试和用于基准测试的工具。您还将学习 MySQL 8 的一些非常重要的功能的最佳实践,如 memcached、复制、数据分区和索引。

教程来源于Github,感谢apachecn大佬的无私奉献,致敬!

技术教程推荐

从0开始学游戏开发 -〔蔡能〕

程序员的数学基础课 -〔黄申〕

OpenResty从入门到实战 -〔温铭〕

小马哥讲Spring核心编程思想 -〔小马哥〕

说透敏捷 -〔宋宁〕

Selenium自动化测试实战 -〔郭宏志〕

大厂晋升指南 -〔李运华〕

郭东白的架构课 -〔郭东白〕

云原生架构与GitOps实战 -〔王炜〕