MySQL InnoDB Cluster

一、InnoDB Cluster 基本概述

MySQL InnoDB Cluster 为 MySQL 提供了一个完整的高可用解决方案。通过使用 MySQL Shell 提供的 AdminAPI,你可以轻松地配置和管理一组至少由3个MySQL服务器实例组成的 InnoDB 集群。

InnoDB 集群中的每个 MySQL 服务器实例都运行 MySQL Group Replication(组复制),该复制提供了在 InnoDB 集群中复制数据的机制,具有内置的故障切换功能。AdminAPI 使我们避免了直接在 InnoDB 集群中使用组复制。从 MySQL 8.0.27开始,你还可以通过链接一个主 InnoDB 集群及其一个或多个异地(例如不同数据中心)副本来组成 InnoDB ClusterSet,以此来提供异地容灾功能。

MySQL Router 可以根据你部署的集群自动配置自己,并将客户端应用程序透明地连接到 MySQL 服务器实例。如果 MySQL 服务器实例发生意外故障,集群将自动重新配置。在默认的单主模式下,InnoDB 集群只有一个读写服务器实例–主服务器,多个辅助服务器实例是主服务器的副本。如果主服务器故障,辅助服务器将自动升级为主服务器角色。MySQL Router 检测到这一点后,会将客户端应用程序的请求转发到新的 MySQL 主服务器上。高级用户还可以将集群配置为具有多个主节点,但大多情况都不必要这样做。

InnoDB 集群至少由三个 MySQL 服务器实例组成,并提供高可用性和可扩展性功能。将使用到以下 MySQL 技术:

  • MySQL Shell,它是MySQL官方提供的高级客户端和代码编辑器。
  • MySQL Server 和 Group Replication(组复制),它们配合工作可以使一组MySQL实例对外提供高可能性。InnoDB Cluster提供了另一种易于使用的编程方式来使用Group Replication(组复制)功能。
  • MySQL Router,一个能在应用程序和InnoDB集群之间提供透明路由的轻量级中间件,是官方提供的MySQL实例负载均衡器(不再需要借助类似HAProxy的第三方负载均衡器了)。

基于MySQL的Group Replication(组复制)构建的集群,可提供自动成员管理、容错、自动故障切换等功能。InnoDB集群通常以单主模式(single-primary)运行,具有一个主实例(primary读写)和多个辅助实例(secondary只读)。高级用户还可以利用多主模式(multi-primary),其中所有实例都是主实例。

你可以使用 MySQL Shell 提供的 AdminAPI 来操作 InnoDB集群。AdminAPI 有 JavaScript 和 Python 两种版本,非常适合通过编写脚本来自动化部署MySQL,以实现高可用性和可扩展性。通过使用 MySQL Shell 的 AdminAPI,你可以避免手动配置许多实例。AdminAPI 为 MySQL 实例集(组)提供了一个高效的现代化接口,可以方便地在一个统一的工具中进行配置、管理和监控部署工作。

InnoDB Cluster支持MySQL Clone(克隆),这使你可以方便地向集群配置新的实例。在过去,一个新的实例加入到MySQL集群实例集之前,你可能需要通过某种方式手动将事务传输到待加入的新实例中。这可能涉及制作文件副本、手动复制它们等等。使用InnoDB Cluster,你只需向集群添加一个实例,它就会被自动配置。类似地,InnoDB Cluster与 MySQL Router 也进行了紧密集成,你可以使用AdminAPI来使它们一起工作。MySQL Router可以基于InnoDB Cluster,通过一个被称为bootstrapping(引导)的过程自动化的配置它自己,你无需手动配置路由。然后 MySQL Router 便可透明地将客户端应用程序连接到InnoDB集群,为客户端连接提供路由和负载平衡。这种集成还允许你使用 AdminAPI 管理基于 InnoDB Cluster 引导的 MySQL Router 的某些方面。InnoDB Cluster 的集群状态信息包含了基于集群引导的 MySQL Router 的详细信息。Operations 使你可以在集群级别创建 MySQL Router 用户,以使用基于集群引导的 MySQL Router 等等。

二、重点知识

2.1 InnoDB集群要求

在进行 InnoDB Cluster 的生产部署安装之前,请确保要使用的 MySQL 服务器实例满足以下要求:

  • InnoDB Cluster使用组复制,因此你的服务器实例必须满足相同的要求(参考组复制要求)。AdminAPI 提供 dba.checkInstanceConfiguration() 方法来验证某个实例是否满足组复制要求,以及 dba.configureInstance() 方法来配置某个实例以满足要求。(注意:使用沙箱部署时,实例会自动配置为满足这些要求。)
  • 用于组复制的数据以及用于 InnoDB 集群的数据,必须存储在 InnoDB 事务存储引擎中。使用其他存储引擎(包括临时内存存储引擎)可能会导致组复制错误。在使用组复制和 InnoDB 集群之前,需要将使用其他存储引擎的所有表转换为使用 InnoDB 引擎。你可以通过在MySQL服务器实例上设置 disabled_storage_engines 系统变量来防止使用其他存储引擎,例如:disabled_storage_engines=“MyISAM,BLACKHOLE,FEDERATED,ARCHIVE,MEMORY”
  • 在安装配置集群时,任何MySQL服务器实例都不能有入站复制通道(到达型)。在一个复制组上,由Group Replication(组复制)自动创建的通道才允许被采用(group_replication_applier 和 group_replication_recovery)。InnoDB 集群不支持手工配置的异步复制通道,使用 AdminAPI 管理的除外。如果要将现有复制拓扑迁移到一个InnoDB集群部署,并且需要在安装过程中暂时跳过此验证,则可以在创建集群时使用force选项绕过它。
  • 必须在要与 InnoDB Cluster 一起使用的所有实例上启用Performance Schema。
  • MySQL Shell 用于配置 InnoDB 集群中的服务器时使用到的配置脚本需要访问Python。Windows版本的 MySQL Shell 中已经包含了 Python,不需要用户再做配置。在类 Unix 系统上,Python 必须配置为 shell 环境的一部份。要检查你的 Unix 类系统是否正确配置了Python,请执行以下操作:$ /usr/bin/env python ,如果Python解释器启动,则无需进一步操作。如果前面的命令失败,请在你的 Unix类系统上安装正确版本的Python,并在 /usr/bin/python 和你安装的 Python 二进制可执行文件之间创建一个软链接。
  • 从8.0.17版开始,MySQL实例必须在 InnoDB 集群中使用唯一的 server_id 。当你使用 Cluster.addInstance(instance) 操作时,如果 instance 的 server_id 已经被集群中的其他实例使用,那么该操作将失败并报错。
  • 从8.0.23版开始,应将MySQL实例配置为使用并行复制申请者(applier)。
  • 在配置 InnoDB 集群的某个MySQL实例过程中,实例要使用的绝大部分系统变量都已经被配置好了。但是 AdminAPI 不会进行 transaction_isolation 系统变量的配置,这意味着它将保持默认值 REPEATABLE READ 。这不会影响单主模式(single-primary)集群,但是如果你使用的是多主模式(multi-primary)集群,那么除非你的应用程序确实依赖默认值 REPEATABLE READ ,否则我们强列建议将值设置为 READ COMMITTED 隔离级别。
  • 实例的相关配置项,尤其是组复制配置项,必须位于单个 option 文件中。InnoDB Cluster只支持服务器实例的单个 option 文件,不支持使用–defaults-extra-file 选项指定附加的 option 文件。对于任何使用实例 option 文件的 AdminAPI 操作,必须指定主文件。 如果你想为与 InnoDB Cluster 无关的配置项使用多 option 文件,你必须手动配置这些文件,并考虑多 option 文件的使用优先规则,确保它们被正确更新,并确保与 InnoDB Cluster 相关的设置没有错误地被一个未被认可的额外 option 文件中的选项覆盖。

2.2 InnoDB集群限制

由于InnoDB Cluster使用了Group Replication(组复制),所以除了下面列的限制外,还要考虑组复制的限制:

  • InnoDB 集群不管理手动配置的异步复制通道。Group Replication(组复制)和 AdminAPI 不确保异步复制仅在主服务器上处于活动状态,并且不跨实例复制状态。这可能会导致复制不再工作的各种情况,并且可能导致集群出现脑裂。只有从 MySQL 8.0.27 版本开始可用的 InnoDB ClusterSet 才支持在一个 InnoDB 集群与另一个集群之间的复制,它管理着从一个活动的主读写 InnoDB 集群到多个只读副本集群的复制工作。
  • InnoDB Cluster 被设计为在局域网中部署。在广域网上部署单个 InnoDB 集群对写性能有显著影响。稳定且低延迟的网络对于InnoDB 集群成员服务器使用底层 Group Replication(组复制)技术相互通信以达成事务共识非常重要。然而,InnoDB ClusterSet 是被设计为跨多个数据中心部署的,每个 InnoDB 集群部署在单个数据中心中,通过异步复制通道将它们连接起来。
  • 对于 AdminAPI 操作,你只能使用 TCP/IP 连接和经典的 MySQL 协议连接到InnoDB集群中的服务器实例。AdminAPI操作不支持使用 Unix sockets 和 named pipes(命名管道),AdminAPI 操作也不支持使用 X 协议。同样的限制也适用于MySQL 服务器实例本身之间的连接。(注意:客户端应用程序可以使用 X 协议、Unix sockets 和 named pipes(命名管道)连接到InnoDB集群中的实例,这些限制仅适用于使用 AdminAPI 命令的管理操作,以及集群内实例之间的连接)
  • AdminAPI 和 InnoDB Cluster支持 MySQL Server 5.7 版本的实例。但是对这些实例会有一些限制,并且有一些功能在这版本上不支持。
  • 使用多主模式时,不支持针对同一对象但在不同服务器上发出的并发DDL语句和DML语句。在某个对象上发出数据定义语句(DDL)期间,在同一对象上但从不同服务器实例发出并发数据操作语句(DML),可能会导致在不同实例上执行的DDL冲突风险而未被检测到。(参考组复制的限制)

2.3 InnoDB集群的用户账户

InnoDB集群中的成员服务器使用三种类型的用户帐户。一个 InnoDB 集群服务器配置帐户用于为集群配置服务器实例。可以为管理员创建一个或多个 InnoDB 集群管理员帐户,以便在集群安装后管理服务器实例。可以为 MySQL Router 实例创建一个或多个 MySQL Router 帐户,用于连接到集群。这些用户帐户必须存在 InnoDB 集群中的所有成员服务器上都存在,并且使用相同的用户名和密码。

2.3.1 InnoDB 集群服务器配置帐户

此帐户用于创建和配置 InnoDB 集群的成员服务器。每个成员服务器只有一个服务器配置帐户。集群中的每个成员服务器必须使用相同的用户帐户名和密码。为此,你可以在服务器上使用 root 帐户,但如果这样做,集群中每个成员服务器上的 root 帐户必须具有相同的密码。出于安全原因,不建议这样做。

首选方法是使用 clusterAdmin 选项中的 dba.configureInstance()命令创建一个 InnoDB集群服务器配置账号。为提高安全性,请在交互提示下指定密码,也可以使用 clusterAdminPassword 选项指定密码。在将成为 InnoDB 集群一部分的每个服务器实例上以相同的方式创建相同的帐户,使用相同的用户名和密码——包括你连接以创建集群的实例,以及之后将加入集群的实例。

dba.configureInstance()命令会自动授予帐户所需的权限(你也可以手动设置该帐户并授权,但不建议这么做)。除了完整的 MySQL 管理员权限外,该帐户还需要对 InnoDB 集群元数据表拥有完整的读写权限。

使用 dba.configureInstance()命令创建一个 InnoDB集群服务器配置账号不会复制到集群内的其他服务器上。MySQL Shell 禁用了 dba.configureInstance()命令的二进制日志记录。这意味着你必须在每个 MySQL 服务器实例上单独创建该帐户。

2.3.2 InnoDB 集群管理员帐户

这些帐户在你完成集群配置之后,可用于管理 InnoDB 集群。你可以设置多个,每个帐户必须存在于 InnoDB 集群中的每个成员服务器上,并具有相同的用户名和密码。

为了给 InnoDB ClusterSet 创建 InnoDB 集群管理员账号,你需要在将所有实例添加到集群后执行 cluster.setupAdminAccount()命令。该命令将使用你给定的用户名和密码创建账号,并赋予必要的权限。使用cluster.setupAdminAccount()创建一个账号的事务将被写入到二进制日志,并发送到集群内所有其他服务器实例上,用于在这些服务器上创建相同的账号。(备注:如果主InnoDB 集群是在MySQL Shell 8.0.20之前版本部署的,cluster.setupAdminAccount()命令可能与 update 选项一起使用,以更新 InnoDB集群服务器配置帐户的权限,这是未写入二进制日志的命令的特殊用法)

2.3.3 MySQL Router 帐户

这些账号被 MySQL Router 用于连接到 InnoDB 集群中的MySQL服务器实例。你可以设置多个此类账号,每个帐户必须存在于InnoDB 集群中的每个成员服务器上,并具有相同的用户名和密码。创建 MySQL Router 帐户的过程与创建 InnoDB 群集管理员帐户相同,但使用的是 cluster.setupRouterAccount() 命令。(也会写binary log 进行同步创建)

2.3.4 InnoDB集群创建的内部用户帐户

作为使用组复制的一部分,InnoDB 集群会创建内部恢复用户,以实现群集中服务器之间的连接。这些用户是集群的内部用户,生成的用户名遵循 mysql_innodb_cluster_server_id@% 的命名方案,其中 server_id 对每个实例是唯一的。在 8.0.17 之前的版本中,生成用户的用户名遵循 mysql_innodb_cluster_r[10_numbers] 的命名方案。

这些内部用户使用的主机名设置为“%”。在 v8.0.17 之前,ipAllowlist 会通过在 ipAllowlist 中每个主机一个账号来影响主机名行为。

每个内部用户都有一个随机生成的密码。从 8.0.18 版开始,AdminAPI 允许你更改内部用户生成的密码。请参阅【重置恢复帐户密码】。随机生成的用户将获得以下授权:GRANT REPLICATION SLAVE ON . to internal_user;

内部用户帐户在 seed 实例上创建,然后复制到集群中的其他实例。内部用户包括:

  • 通过执行 dba.createCluster()命令创建一个新集群时生成的
  • 通过执行 Cluster.addInstance()命令向集群添加一个新实例时生成的
  • 使用主要成员正在使用的身份验证插件时生成的

在 v8.0.17 之前,ipAllowlist 会导致 Cluster.rejoinInstance()命令移除老的内部用户并生成新的用户,而不是重用它们。

2.3.5 重置恢复帐户密码

从8.0.18版开始,你可以使用 Cluster.resetRecoveryAccountsPassword()命令重置InnoDB群集创建的内部恢复帐户的密码,例如为了遵循自定义密码生存期策略。使用 Cluster.resetRecoveryAccountsPassword()命令重置群集使用的所有内部恢复帐户的密码。该命令为每个联机实例的内部恢复帐户设置一个新的随机密码,如果无法访问实例,则操作失败。你可以使用 force 选项忽略此类脱机的实例,但不建议这样做,在使用此命令之前将实例重新联机更安全。此命令仅适用于InnoDB Cluster创建的密码,不能用于更新手动创建的密码。

备注:执行此操作的用户必须具有所有必需的管理权限,尤其是创建用户权限CREATE USER,以确保无论所需的密码验证策略如何,都可以更改恢复帐户的密码。换句话说,这与系统变量 password_require_current 是否启用无关。

三、生产环境部署

在生产环境中,组成 InnoDB 集群的MySQL服务器实例运行在以网络链接的多个主机上,而不是一台机器上。在组建 InnoDB 集群之前,我们必须将所需的软件安装到要作为集群服务器实例的每台机器上。

下图说明了将组建的 InnoDB 集群的逻辑架构(IP只是做示例作用):

注意:对于生产部署,必须持久化实例上的任何配置变更,如何做到这一点取决于实例上运行的MySQL版本。

为了将服务器的连接信息传递给 AdminAPI,可以使用类似 URI 的连接字符串或数据字典,本例中我们将使用类似 URI 的连接字符串。

3.1 环境规划

IP地址 主机名 角色 操作系统 MySQL版本
192.168.10.51 node1 MGR/mysqlshell CentOS 7.9 mysql 8.0.31
192.168.10.52 node2 MGR/mysqlshell CentOS 7.9 mysql 8.0.31
192.168.10.53 node3 MGR/mysqlshell CentOS 7.9 mysql 8.0.31
192.168.10.54 node0 mysqlrouter/mysqlshell CentOS 7.9 mysql 8.0.31

3.1.1基础环境准备

  • 关闭防火墙
#在每个节点执行
systemctl disable firewalld
systemctl stop firewalld
  • 永久关闭selinux
#在每个节点执行
sed -i 's/^ *SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
  • 设置主机名
#在node1执行
hostnamectl set-hostname node1
#在node2执行
hostnamectl set-hostname node2
#在node3执行
hostnamectl set-hostname node3
#在node0执行
hostnamectl set-hostname node0#没每个节点执行
cat >> /etc/hosts << EOF
192.168.10.51    node1
192.168.10.52    node2
192.168.10.53    node3
192.168.10.54    node0
EOF
  • 配置SSH互信
#在每个节点分别生成私钥对
ssh-keygen -t rsa
#在node1,根据提示完成操作
ssh-copy-id -i /root/.ssh/id_rsa.pub 192.168.10.52
ssh-copy-id -i /root/.ssh/id_rsa.pub 192.168.10.53
ssh-copy-id -i /root/.ssh/id_rsa.pub 192.168.10.54#在node2,根据提示完成操作
ssh-copy-id -i /root/.ssh/id_rsa.pub 192.168.10.51
ssh-copy-id -i /root/.ssh/id_rsa.pub 192.168.10.53
ssh-copy-id -i /root/.ssh/id_rsa.pub 192.168.10.54#在node3,根据提示完成操作
ssh-copy-id -i /root/.ssh/id_rsa.pub 192.168.10.51
ssh-copy-id -i /root/.ssh/id_rsa.pub 192.168.10.52
ssh-copy-id -i /root/.ssh/id_rsa.pub 192.168.10.54#在node0,根据提示完成操作
ssh-copy-id -i /root/.ssh/id_rsa.pub 192.168.10.51
ssh-copy-id -i /root/.ssh/id_rsa.pub 192.168.10.52
ssh-copy-id -i /root/.ssh/id_rsa.pub 192.168.10.53
  • yum配置

离线环境下,需要为每个节点配置本地yum源。

#将镜像文件mount至/media
[root@node1 ~]# mount -o loop -t iso9660 CentOS-7-x86_64-DVD-2009.iso /media
[root@node1 ~]# cat /etc/yum.repos.d/local.repo
[local]
name=local
baseurl=file:///media
gpgcheck=0
enabled=1
gpgkey=file:///media/RPM-GPG-KEY-CentOS-7
#校验yum
[root@node1 ~]# yum clean all

3.2 安装mysql

前往mysql官方下载地址:https://dev.mysql.com/downloads/。下载操作系统适配的MySQL Shell安装包,MySQL Router安装包以及MySQL Server。以下以8.0.31为例:

将以上文件上传至各个服务器临时安装目录,准备进行下一步安装。

3.2.1 安装3个数据库实例

安装并初始化3个数据库实例,这里步骤省略,跟安装部署MySQL步骤一样,以node1为例进行说明。

#清理操作系统自带的 MySQL或者 Mariadb
rpm -qa | grep mariadb | xargs rpm -e --nodeps
rpm -qa | grep mysql | xargs rpm -e --nodeps#解压mysql安装压缩包
tar -xvf mysql-8.0.31-1.el7.x86_64.rpm-bundle.tar
#yum安装mysql server相关的安装包,可以自动处理依赖关系
yum install -y mysql-community-*                            #改命令安装mysql全部包
yum install -y mysql-community-{server,client,common,libs}-*#亦有此命令安装mysql基础包
#以上两种安装方式都可以,一个是安装全部安装包,一个是安装基本安装包。建议全部安装。

3.2.2 初始化3个数据库实例

在第一次启动的/var/log/mysqld.log中找到初始密码,并使用该密码进行登录。而后分别在每个数据库节点执行以下操作。

#启动mysql
[root@node1 ~]# systemctl start mysqld
[root@node1 ~]# cat /var/log/mysqld.log | grep 'temporary password'
2022-11-13T09:54:43.615664Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: oVdjq0vrAd/C
#登录并修改root用户初始密码
[root@node1 ~]# mysql -u root -p
mysql> alter user 'root'@'localhost' identified with mysql_native_password by 'Abcd@1234';
#修改root访问权限
mysql> use mysql;
mysql> update user set host='%' where user='root';
mysql> grant all on *.* to 'root'@'%' with grant option;

3.2.3 在所有节点安装MySQL Shell

yum install -y mysql-shell-8.0.31-1.el7.x86_64.rpm

3.3 InnoDB Cluster 配置及初始化

因root用户权限较大,不建议将其作为集群配置用户。可以参考以下权限,新建用户。

CREATE USER 'ic_admin'@'%' IDENTIFIED BY 'Abcd@1234';
GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,ALTER,DROP ON *.* TO 'ic_admin'@'%' WITH GRANT OPTION;
GRANT CREATE USER, FILE, PROCESS, RELOAD, REPLICATION CLIENT, REPLICATION SLAVE, SHUTDOWN, SUPER ON *.* TO 'ic_admin'@'%' WITH GRANT OPTION;
GRANT ALTER ROUTINE, CREATE ROUTINE, CREATE TEMPORARY TABLES, CREATE VIEW, EVENT, EXECUTE, INDEX, LOCK TABLES, REFERENCES, SHOW VIEW, TRIGGER ON mysql_innodb_cluster_metadata.* TO 'ic_admin'@'%' WITH GRANT OPTION;
GRANT ALTER ROUTINE, CREATE ROUTINE, CREATE TEMPORARY TABLES, CREATE VIEW, EVENT, EXECUTE, INDEX, LOCK TABLES, REFERENCES, SHOW VIEW, TRIGGER ON mysql_innodb_cluster_metadata_bkp.* TO 'ic_admin'@'%' WITH GRANT OPTION;
GRANT ALTER ROUTINE, CREATE ROUTINE, CREATE TEMPORARY TABLES, CREATE VIEW, EVENT, EXECUTE, INDEX, LOCK TABLES, REFERENCES, SHOW VIEW, TRIGGER ON mysql_innodb_cluster_metadata_previous.* TO 'ic_admin'@'%' WITH GRANT OPTION;
GRANT CLONE_ADMIN, CONNECTION_ADMIN, EXECUTE, GROUP_REPLICATION_ADMIN, PERSIST_RO_VARIABLES_ADMIN, REPLICATION_APPLIER, REPLICATION_SLAVE_ADMIN, ROLE_ADMIN, SYSTEM_VARIABLES_ADMIN ON *.* TO 'ic_admin'@'%' WITH GRANT OPTION;
flush privileges;

在三个MySQL Server所在虚拟机上用mysqlsh命令打开MySQL Shell,并分执行以下命令将其上的MySQL实例配置为用于InnoDB集群的实例(实际上这些命令会查找MySQL的配置文件my.cnf,将集群需要的配置写入其中,当然还有其他些方面的配置变更),同时指定 InnoDB 集群管理员账号——icadmin,必须是相同的账号名称和密码(注意:MySQL 5.7用的是configureLocalInstance(),不是configureInstance()):

3.3.1 节点配置检查

  • 检查

执行每个节点的参数及权限配置预需求检测。

mysqlsh ic_admin@node1
MySQL  node1:33060+ ssl  JS > dba.checkInstanceConfiguration('ic_admin@node1')
MySQL  node1:33060+ ssl  JS > dba.checkInstanceConfiguration('ic_admin@node2')
MySQL  node1:33060+ ssl  JS > dba.checkInstanceConfiguration('ic_admin@node3')

检测通过会显示如下:
检测失败会显示如下:

  • 配置

对每个节点,通过dba.configureInstance()进行集群相关信息配置。

MySQL  node1:33060+ ssl  JS > dba.configureInstance('ic_admin@node1')
MySQL  node1:33060+ ssl  JS > dba.configureInstance('ic_admin@node2')
MySQL  node1:33060+ ssl  JS > dba.configureInstance('ic_admin@node3')

以下为对node1节点的配置,在交互式窗口中输入’y’进行确认。执行完成后,再次通过上面的检测命令进行每个节点检测,直到所有节点状态都是ok为止。

 MySQL  node1:33060+ ssl  JS > dba.configureInstance('ic_admin@node1')
Configuring local MySQL instance listening at port 3306 for use in an InnoDB cluster...This instance reports its own address as node1:3306
Clients and other cluster members will communicate with it through this address by default. If this is not correct, the report_host MySQL system variable should be changed.applierWorkerThreads will be set to the default value of 4.NOTE: Some configuration options need to be fixed:
+----------------------------------------+---------------+----------------+--------------------------------------------------+
| Variable                               | Current Value | Required Value | Note                                             |
+----------------------------------------+---------------+----------------+--------------------------------------------------+
| binlog_transaction_dependency_tracking | COMMIT_ORDER  | WRITESET       | Update the server variable                       |
| enforce_gtid_consistency               | OFF           | ON             | Update read-only variable and restart the server |
| gtid_mode                              | OFF           | ON             | Update read-only variable and restart the server |
| server_id                              | 1             | <unique ID>    | Update read-only variable and restart the server |
+----------------------------------------+---------------+----------------+--------------------------------------------------+Some variables need to be changed, but cannot be done dynamically on the server.
Do you want to perform the required configuration changes? [y/n]: y
Do you want to restart the instance after configuring it? [y/n]: y
Configuring instance...
The instance 'node1:3306' was configured to be used in an InnoDB cluster.
Restarting MySQL...
NOTE: MySQL server at node1:3306 was restarted.

3.3.2 创建集群

  • 创建集群

将node1规划为创建集群的种子节点,在node1上使用集群管理员账号重新进入MySQL Shell中,执行以下命令创建一个名为 "myCluster"的集群:

[root@node1 software]# mysqlsh --uri ic_admin@node1
MySQL Shell 8.0.31Copyright (c) 2016, 2022, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its affiliates.
Other names may be trademarks of their respective owners.Type '\help' or '\?' for help; '\quit' to exit.
Creating a session to 'ic_admin@node1'
Fetching schema names for auto-completion... Press ^C to stop.
Your MySQL connection id is 22 (X protocol)
Server version: 8.0.31 MySQL Community Server - GPL
No default schema selected; type \use <schema> to set one.
#在创建集群添加Seed Instance的过程中可能会报错,这个时候要 cat /var/log/mysqld.log 打开日志看看具体报错,比如我的就是由于防火器阻止了端口33061上的连接所致:MySQL  node1:33060+ ssl  JS > var cluster=dba.createCluster('myCluster')
A new InnoDB Cluster will be created on instance 'node1:3306'.Validating instance configuration at node1:3306...This instance reports its own address as node1:3306Instance configuration is suitable.
NOTE: Group Replication will communicate with other members using 'node1:3306'. Use the localAddress option to override.Creating InnoDB Cluster 'myCluster' on 'node1:3306'...Adding Seed Instance...
Cluster successfully created. Use Cluster.addInstance() to add MySQL instances.
At least 3 instances are needed for the cluster to be able to withstand up to
one server failure.
  • 添加节点

如果你前面使用 dba.createCluster(‘myCluster’);创建集群时,没用 var cluster来承接,或者你创建完集群后重新进入MySQL Shell的,那么你可以通过下面命令来获取的目标集群对象(旧时代叫【句柄】):

 MySQL  node1:33060+ ssl  JS > var cluster=dba.getCluster('myCluster')
#添加节点MySQL  node1:33060+ ssl  JS > cluster.addInstance('ic_admin@node2')MySQL  node1:33060+ ssl  JS > cluster.addInstance('ic_admin@node3')

添加节点显示如下:

3.3.3 查看集群

#查看集群状态MySQL  node1:33060+ ssl  JS > cluster.status()

至此,集群配置完成。查看集群状态,正常显示如下,所有节点ONLINE。

3.4 安装mysql router

3.4.1 mysql router简介

从前面出现过的这两张集群逻辑架构图中我们可以清楚的看到:在一个MySQL InnoDB集群中,MySQL Router 是独立出来放在集群外面的,这也是由它在整个集群中所充当的角色所决定的。

MySQL Router作为整个InnoDB集群的流量入口(门神),肩负着流量承接、负载均衡、成员管理以及故障转移的重任(就像前置Nginx服务器一样),非常有必要使用一台高性能的独立服务器进行部署,还要配以足够的带宽(事实上,如果仅有一台集中式MySQL Router服务器的话,它将成为整个集群性能和可用性的瓶颈,而解决这个问题方法是接下来要讲的内容)。另外, MySQL Shell 作为InnoDB集群的管理入口,其实也可以放在整个集群之上安装,但在用MySQL Shell的指令dba.configureInstance(“configer@linux01:3306”);将MySQL Server实例配置为用于InnoDB集群的实例时,会在命令执行的本机查找MySQL Server的配置文件my.cnf,找不到将会报错,所以上面三台MySQL Server实例所在主机上也安装上MySQL Shell。

实际生产中,为了获得最佳性能,MySQL Router通常会与它服务的客户端应用程序安装在同一台主机上,主要原因有:

  • 这将允许客户端应用程序使用本地UNIX域socket来与MySQL Router建立链接,而不需要走TCP/IP;(注意:本地UNIX域socket只能在应用程序和MySQL Router之间发挥作用,在MySQL Router和MySQL Server之间不行)
  • 这将减少网络延迟;
  • 这将允许MySQL Router直接使用分配给客户端应用程序的MySQL账号(例如myapp@192.168.0.254)来连接MySQL,而不需要单独为MySQL Router的宿主机再分配一个额外的MySQL账号(例如放大可登录范围的账号myapp@%);
  • 这将允许MySQL Router跟随应用程序服务器一起横向扩展,形成一对一的服务,消除集中式MySQL Router性能和可用性问题;

当然,你也可以在网络上运行多个MySQL Router实例,并且不需要将MySQL Router 独立部署到一台机器上。这是因为MySQL Router不会依赖于何特定的服务器或主机!

下面是官方给出的MySQL Router的系统要求:

  • 硬件:至少需要1核CPU和256MB的内存,建议使用4核CPU和4GB以上的内存;
  • 磁盘空间:最少需要100MB的空间;
  • 外部库依赖:大部分外部依赖都已经打包在了MySQL Router包中(例如protobuf和rapidjson),有一个例外,那就是OpenSSL,它仅在Windows版本的MySQL Router包中有捆绑。类Linux系统的包管理器会自动解决OpenSSL依赖,并根据需要安装正确的OpenSSL版本。

3.4.2 安装mysql router

在node4安装mysql router

yum install -y mysql-router-community-8.0.31-1.el7.x86_64.rpm

3.4.3 配置mysql router

  • 配置MySQL Router,实现InnoDB集群读写分离和故障自动转移:

MySQL Router通过使用–bootstrap和其他命令行选项为现有的InnoDB集群自动配置MySQL Router。在引导过程中,MySQL Route连接到目标集群,获取其元数据来实现自我配置(如果对应的InnoDB集群重新创建了,使用它的MySQL Router需要删除旧引导生成的配置文件(/etc/mysqlrouter/mysqlrouter.conf),并重新初始化引导)。

mysqlrouter --bootstrap ic_admin@node1:3306 --user root
# ic_admin 是InnoDB集群管理员账号,node1:3306是集群主节点或成员节点,如果给的是成员节点引导模块会自动转发到主节点(PRIMARY);
# --user root指定的是操作系统账号,不MySQL Router用于连接到 InnoDB 集群的账号,该账号你可以重新建一个专用的,也可以直接用了root账号;

可以看到配置完成后显示:MySQL经典协议读写操作走的是6446端口,而只读协议走的是6647端口,有兴趣的话你可以进一步查看一下生成的MySQL Router配置文件 /etc/mysqlrouter/mysqlrouter.conf。后面启动MySQL Router时,如不做特殊指定的话将使用这个生成的默认配置文件!

如果开启了防火墙,要第一时间记得对它们进行防火墙例外设置,并重新载入防火墙。

  • 为集群配置 MySQL Router 帐户:

在安装有MySQL Shell的主机上连接上集群,使用MySQL Shell AdminAPI为集群添加MySQL Router专用账号(可以多个,但实际上可以不添加MySQL Router账号,直接使用分配给应用程序的MySQL账号即可,否则你还得为这个新添加的专用账号到MySQL服务器上做授权操作,默认权限很少)。

[root@node4 software]# mysqlsh --uri ic_admin@node1MySQL  node1:33060+ ssl  JS > var cluster =dba.getCluster('myCluster')MySQL  node1:33060+ ssl  JS > cluster.setupRouterAccount('myrouter')
#以上命令为集群创建了一个可以在任意位置向集群发起连接的MySQL Router账号——myrouter@%。如果要限制在某个IP或域名下才能发起连接,可以这样做:MySQL  node1:33060+ ssl  JS > cluster.setupRouterAccount('myrouter@192.168.10.%')

输入密码和再次确认密码后,账户创建成功,显示如下:

注意:该命令创建的myrouter用户只有基础权限,在实际应用中,应当秉承最小权限原则,在主节点对myrouter用户进行正确的授权。

3.4.4 启动mysql router

mysqlrouter -c /etc/mysqlrouter/mysqlrouter.conf &

3.4.5 验证

在只装有mysql client的服务器或者其他客户端上面进行登录、数据操作等验证。以windows端navicat客户端为例进行验证。

以上配置中,主机地址为mysql routher服务器的ip地址,端口为读写端口,用户名和密码为3.4.3中配置的MySQL Router 帐户。同样的方式进行只读端口验证。

应用程序通过MySQL Router连接上MySQL InnoDB集群里MySQL Server实例的过程:

  • MySQL客户端或Connector连接器连接到MySQL Router的指定端口,例如这里的6446或6447;
  • MySQL Router检查可用的MySQL Server实例;
  • MySQL Router向合适的MySQL Server实例建立连接并打开(根据端口类型:读写(RW)端口发给主节点,只读(RO)端口会进行负载均衡选取从节点);
  • MySQL Router在应用程序和MySQL服务器之间来回转发数据包。
  • 如果连接的MySQL服务器出现故障,MySQL Router将断开应用程序的连接。然后,应用程序可以重试连接到MySQL Router,MySQL Router会选择另一个可用的MySQL服务器建立连接。

3.5 验证

3.5.1 读写分离

通过Mysql命令show variables like ‘server_id’; 先查看一下InnoDB集群中三台MySQL Server实例的server_id是多少,并将它们记下来:

node1:2861026507

node2:770008088

node3:1770854672

可以看到,读写请求被分发到node1,只读请求分别被分发到node2和node3:

3.5.2 故障转移

在上一步的基础上,保持上一步的各个会话连接不要断开,直接停掉当前的主节点(node1上的MySQL Server实例),我们看看会发生什么:

登录node2节点,查看集群状态:

我们看到故障转移在上一步实验的客户端会话连接未关闭的情况下就自动发生了,但却有一个只读(RO)端口的连接连在了当前的新主节点上!这是为什么呢?不是说只读(RO)端口建立连接时只会选择从节点吗?其实原因是:我们是在没有关闭上一步的会话连接的情况下做的本次实验,这次关掉旧主节点时,其它节点上原先保持住的会话连接并不会重新建立,只有原来建立在旧的主节点上的连接会重新进行连接!当然后续新发起的连接还是会根据端口类型:读写(RW)端口发给主节点,只读(RO)端口会进行负载均衡选取从节点!

四、集群相关操作

4.1 查看集群状态

cluster.status()

 MySQL  node1:33060+ ssl  JS > var cluster=dba.getCluster('myCluster')MySQL  node1:33060+ ssl  JS > cluster.status()
{"clusterName": "myCluster", "defaultReplicaSet": {"name": "default", "primary": "node1:3306", "ssl": "REQUIRED", "status": "OK", "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.", "topology": {"node1:3306": {"address": "node1:3306", "memberRole": "PRIMARY", "mode": "R/W", "readReplicas": {}, "replicationLag": "applier_queue_applied", "role": "HA", "status": "ONLINE", "version": "8.0.31"}, "node2:3306": {"address": "node2:3306", "memberRole": "SECONDARY", "mode": "R/O", "readReplicas": {}, "replicationLag": "applier_queue_applied", "role": "HA", "status": "ONLINE", "version": "8.0.31"}, "node3:3306": {"address": "node3:3306", "memberRole": "SECONDARY", "mode": "R/O", "readReplicas": {}, "replicationLag": "applier_queue_applied", "role": "HA", "status": "ONLINE", "version": "8.0.31"}}, "topologyMode": "Single-Primary"}, "groupInformationSourceMember": "node1:3306"
}MySQL  node1:33060+ ssl  JS >

4.2 配置节点权重

memberWeight选项的值域为0到100之间的整数,缺省值为50。该值是故障转移时自动选举主节点的百分比权重,具有较高memberWeight值的实例更有可能在单主群集中被选为主节点

// 在集群创建完成后修改权重
var cluster = dba.getCluster()
cluster.setInstanceOption('node1:3306','memberWeight',100)
cluster.setInstanceOption('node2:3306','memberWeight',50)
cluster.setInstanceOption('node3:3306','memberWeight',25)// 查看集群的参数配置(包括memberWeight优先级配置)
cluster.options()// 在集群创建时配置
dba.createCluster('myCluster', {memberWeight:75}) // 第一个节点配置方式
var cluster = dba.getCluster()
cluster.addInstance('node2:3306',{memberWeight:50})
cluster.addInstance('node3:3306',{memberWeight:25})

4.3 将节点重新加入集群

状态为mssing的节点,通常是组复制关闭或中断状态,可以用cluster.rejoinInstance()重新加入集群,会重新对该节点设置MGR相关参数(持久化到mysqld-auto.conf中)

4.3.1 rejoinInstance

{"node1:3306": {"address": "node1:3306", "memberRole": "PRIMARY", "mode": "R/W", "readReplicas": {}, "replicationLag": "applier_queue_applied", "role": "HA", "status": "ONLINE", "version": "8.0.31"}, "node2:3306": {"address": "node2:3306", "memberRole": "SECONDARY", "mode": "R/O", "readReplicas": {}, "replicationLag": "applier_queue_applied", "role": "HA", "status": "(MISSING)", "version": "8.0.31"}, "node3:3306": {"address": "node3:3306", "memberRole": "SECONDARY", "mode": "R/O", "readReplicas": {}, "replicationLag": "applier_queue_applied", "role": "HA", "status": "ONLINE", "version": "8.0.31"}
}// 重新加入集群
cluster.rejoinInstance("ic_admin@node2:3306")

4.3.2 removeInstance && addInstance

如果一些参数做了修改,如server_uuid变更,导致rejoin失败,则需要将节点从集群中删除后重新加入

cluster.removeInstance("root@node2:3306",{force:true})
cluster.rescan()
cluster.addInstance("root@node2:3306")

4.4 集群多数节点异常恢复

当集群多个节点异常,则失去了仲裁机制,剩下的一个节点

// 将集群剥离为单节点运行
JS > cluster.forceQuorumUsingPartitionOf("ic_admin@node3:3306")// 重新加另外2个节点加入
JS > cluster.rejoinInstance("ic_admin@node1:3306")
JS > cluster.rejoinInstance("ic_admin@node2:3306")

4.5 完整关闭的集群如何恢复

每个节点都是正常的停止且正常的触发stop group_replication停止的,当重新启动时如果手工我们需要做以下操作才可拉起集群

  • 判断哪个节点的GTID最新

  • 将最新的GTID节点group_replication_bootstrap_group设置为on表示以这个节点为基础启动组复制

  • 再将其他节点的组复制启动,集群恢复

我们也可以用mysqlsh的功能来让其自行判断最新数据的节点,再采用dba.rebootClusterFromCompleteOutage()方式启动集群

// 通过node1节点触发命令
MySQL  node1:3306 ssl  JS > dba.rebootClusterFromCompleteOutage()
Restoring the default cluster from complete outage...// 检测到node2服务器也包含在集群中,确认添加
The instance 'node2:3306' was part of the cluster configuration.
Would you like to rejoin it to the cluster? [y/N]: y// 检测到node3服务器也包含在集群中,确认添加
The instance 'node3:3306' was part of the cluster configuration.
Would you like to rejoin it to the cluster? [y/N]: y// 检测到node1不是最新的节点,node2才是
Dba.rebootClusterFromCompleteOutage: The active session instance (node1:3306) isn't the most updated in comparison with the ONLINE instances of the Cluster's metadata. Please use the most up to date instance: 'node2:3306'. (RuntimeError)// 重新连接到node2上执行相同操作
MySQL  node1:3306 ssl  JS > \connect "ic_admin@node2:3306"
MySQL  node1:3306 ssl  JS > dba.rebootClusterFromCompleteOutage()// 完成后查看集群状态是否正常
var cluster = dba.getCluster()
cluster.status()

4.6 集群节点角色切换

在MGR的管理下提供了一下3中方式进行角色切换,mysqlsh对其进行了封装调用

  • group_replication_set_as_primary(member_uuid);

    • cluster.setPrimaryInstance("IP:PORT")
  • group_replication_switch_to_single_primary_mode()
    • cluster.switchToSinglePrimaryMode("IP:PORT")
  • group_replication_switch_to_multi_primary_mode()
    • cluster.switchToMultiPrimaryMode()

4.6.1 单主模式-指定主节点切换

var cluster = dba.getCluster()
cluster.setPrimaryInstance('node2:3306')
cluster.status()

切换结果如下:

4.6.2 单主模式和多主模式相互切换

// 切换为多主模式
var cluster = dba.getCluster()
cluster.switchToMultiPrimaryMode()// 指定明确的主节点将多主模式切换为单主模式
cluster.switchToSinglePrimaryMode("node1:3306")

4.7 参数配置

可以用cluster.options()查看当前集群的配置属性,集群参数配置分为两种方式:

  • cluster.setOption() 用来设置所有节点的参数
  • cluster.setInstanceOption() 用来对指定节点配置属性
// 将所有节点的权重都改为50/重新加入集群重试次数改为5次
var cluster = dba.getCluster()
cluster.setOption("memberWeight",50)
cluster.setOption("autoRejoinTries",5)// 将其中一个节点的权重改为75/重新加入集群重试次数改为10次
cluster.setInstanceOption("node1:3306","memberWeight",75)
cluster.setInstanceOption("node1:3306","autoRejoinTries",10)

4.8 销毁集群

删除与群集关联的所有元数据和配置,并禁用实例上的组复制,但不会删除在实例之间复制的任何数据。要再次创建集群,使用,dba.createCluster()

var cluster = dba.getCluster()
cluster.dissolve()

4.9 常见错误

4.9.1 getCluster() 报错

// 获取集群报错,集群中的该节点MGR功能未启动MySQL  10.186.63.65:3310 ssl  JS > var cluster = dba.getCluster()
Dba.getCluster: This function is not available through a session to a standalone instance (metadata exists, instance belongs to that metadata, but GR is not active) (RuntimeError)// 可以切换到sql模式查看集群当前状态
// 可以看到当前集群只有一个节点且处于OFFLINE状态MySQL  10.186.63.65:3310 ssl  SQL > \sql select * from performance_schema.replication_group_members\G
*************************** 1. row ***************************CHANNEL_NAME: group_replication_applierMEMBER_ID: 67c08e33-92c4-11eb-803d-02000aba3f41MEMBER_HOST: 10.186.63.65MEMBER_PORT: 3310MEMBER_STATE: OFFLINEMEMBER_ROLE:
MEMBER_VERSION:
1 row in set (0.0038 sec)-- 可以将该节点作为引导节点启动集群
set global group_replication_bootstrap_group=on;
start group_replication;-- 启动完毕后需关闭该参数
set global group_replication_bootstrap_group=off;-- 再次查看可看到第一个节点恢复正常MySQL  10.186.63.65:3310 ssl  SQL > select * from performance_schema.replication_group_members\G
*************************** 1. row ***************************CHANNEL_NAME: group_replication_applierMEMBER_ID: 67c08e33-92c4-11eb-803d-02000aba3f41MEMBER_HOST: 10.186.63.65MEMBER_PORT: 3310MEMBER_STATE: ONLINEMEMBER_ROLE: PRIMARY
MEMBER_VERSION: 8.0.22
1 row in set (0.0016 sec)

4.9.2 addInstance 报错 Unknown MySQL server host

报这个错误需要配置/etc/hosts将主机名和IP对应关系配置上

Cluster.addInstance: Unknown MySQL server host '10-186-63-65' (2) (MySQL Error 2005)// cluster.status() 命令也会有如下显示
"shellConnectError": "MySQL Error 2005 (HY000): Unknown MySQL server host '10-186-63-65' (2)",

4.9.3 清理残留的mgr信息

dba.dropMetadataSchema()

Dba.checkInstanceConfiguration: Dba.checkInstanceConfiguration: This function is not available through a session to a standalone instance (metadata exists, instance belongs to that metadata, but GR is not active) (RuntimeError)

4.9.4 克隆插件不能设置为force_plus_permanent

在多次对同一个实例做集群初始化时,不管是全量还是增量都会对克隆插件做一次重新加载,会如果对克隆插件开启了force_plus_permanent属性,则无法卸载。

Cluster.addInstance: error uninstalling plugin 'clone': 10.186.63.67:3310: Plugin 'clone' is force_plus_permanent and can not be unloaded (RuntimeError)

MySQL InnoDB Cluster部署方案与实践相关推荐

  1. MySQL NDB Cluster部署方案与实践

    MySQL Cluster集群 一.概述 ​ MySQL Cluster是MySQL适用于分布式计算环境的高实用.高冗余版本.他采用了NDB Cluster存储引擎,允许在一个Cluster中运行多个 ...

  2. MySQL InnoDB Cluster部署

    文章目录 安装 下载Yum存储库 安装发布包 导入密钥 安装MySQL 启动MySQL 查看临时口令 登录并修改口令 安装MySQL Shell InnoDB Cluster 账户配置 服务器配置账户 ...

  3. Mysql InnoDB Cluster部署装配

    Mysql集群安装 参考官网:https://dev.mysql.com/doc/refman/5.7/en/mysql-innodb-cluster-userguide.html 环境简介: 四台虚 ...

  4. mysql高可靠部署_可能是我见过最好的 MySQL 高可用解决方案 MySQL InnoDB Cluster 中文教程!...

    公众号关注 「运维之美」设为「星标」,每天带你玩转 Linux ! 这篇文章将详细地介绍 MySQL 的高可用解决方案-- MySQL InnoDB Cluster. 说到高可用性,首先要了解一下什么 ...

  5. MySQL InnoDB Cluster

    Mysql高可用环境的搭建比较麻烦,而且之前的那种mysql cluster使用的是内存式存储引擎,一旦断电就会灰飞烟灭,数据丢失. 自从mysql被oracle收购后,新版本发布频繁,推出了很多好用 ...

  6. JavaWeb+MySql+Tomcat一键部署方案

    前言 前段时间,公司接单子做一套JavaWeb系统.作为主要负责人,我们小组的工作一切顺利.但在交付前,客户要求该Web系统做到一键安装部署.这是因为该系统使用环境为机房局域网,每个机房需要部署一套, ...

  7. MySQL InnoDB Cluster安装

    环境说明 centos 7.x 64位 mysql 5.7.20 编译源码安装 mysql shell linux generic版本 mysql router linux generic版本 路由节 ...

  8. 技术分享 | InnoDB Cluster+GreatSQL快速部署MGR集群,并实现读写分离和故障自动转移(超详细)...

    本文来源:原创投稿 * GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 0. 内容提纲 1. 部署环境及初始化 2. 利用MySQL Shell构建MGR集群 3. 对在 ...

  9. MySQL高可用架构InnoDB Cluster (和NDB Cluster是两码事)

    MySQL的高可用架构无论是社区还是官方,一直在技术上进行探索,这么多年提出了多种解决方案,比如MMM, MHA, NDB Cluster, Galera Cluster, InnoDB Cluste ...

  10. innodb 集群_部署MySQL InnoDB集群以实现高可用性

    innodb 集群 In this article, I am going to explain how we can deploy the MySQL InnoDB cluster. InnoDB ...

最新文章

  1. 在三角形中rt是什么意思_数学中RT三角形是什么意思
  2. MEET大会报名开启 | 李开复张亚勤等产学研大咖邀你共同见证智能未来
  3. java进阶08 GUI图形界面
  4. Android借助Application重写App的Crash(简易版)
  5. SPARK安装一:Windows下VirtualBox安装CentOS
  6. Pytorch:Dataset总结
  7. python下载bt文件_Python实现解析Bit Torrent种子文件内容的方法
  8. linux的dlan脚本,Linux下的DLNA播放器源码
  9. 网易云音乐批量下载工具编写过程
  10. HCNE---succeed,HCSE---Learning!!!
  11. 破解jQuery插件收费、下载币(单页扒站小工具)
  12. Java实现搭积木_Java搭积木游戏
  13. iOS视频添加水印两种方式(不用到第三方框架)
  14. ID卡IC卡的复制卡种类介绍
  15. ffmpeg 生成单色测试视频
  16. 【华为机试真题 Python】一个正整数到 Excel 编号之间的转换
  17. 微信号名称乱码什么情况_“微信号”与“微信账号”有什么区别?
  18. 4G模块中RSRP RSRQ RSSI SINR等信号值的含义和区别
  19. java 复制excel_PoiUtil.java 用于excel间sheet复制
  20. SybilFuse:Combining Local Attributes with Global Structure to Perform Robust Sybil Detect(论文笔记)

热门文章

  1. 恢复linux硬盘数据,Linux下的硬盘数据如何恢复?
  2. Openbravo3.0 客户端代码开发与API
  3. 蓝桥杯java技巧总结
  4. Amazon软件开发工程师面试题
  5. 苹果mac休眠快捷键_有人说 Mac 系统不适合搞科研,我笑了:还不是因为你不会用!...
  6. 使用python PIL 模块合成图片
  7. Albumentation使用指南
  8. springboot接口慢_Springboot tomcat 启动慢 响应时间超长 问题解决
  9. iOS音乐播放器(歌词自动滚动)
  10. Fern wifi cracker 无线破解工具——图解