生产中的 MySQL 高可用性:InnoDB 集群、组复制和 Kubernetes Operator
有关 InnoDB 集群、组复制、Kubernetes 的 MySQL Operator、Percona XtraDB 集群、多云策略以及使用 Longhorn 存储的裸机 k3s/Rancher 部署的生产工程师指南
在生产中运行 MySQL 对于大多数工程团队来说都是熟悉的领域。以真正的高可用性运行它——节点故障、网络分区或整个可用区变暗不会导致停机或数据丢失——需要精心设计的架构。本指南详细介绍了该架构的每一层:从 MySQL 本身内部的复制原语,到自动化生命周期管理的 Kubernetes 运算符,到每个主要云提供商的托管和自我管理选项,再到 Rancher 管理的裸机 k3s 集群。
在本文结束时,您将拥有在生产中选择和操作 MySQL HA 的完整心智模型,以及可适应您自己的环境的具体配置示例。
MySQL InnoDB 集群架构
InnoDB Cluster 是 Oracle 针对 MySQL 的集成高可用性解决方案。它结合了三个组件:用于数据同步的 MySQL 组复制、用于集群管理的 MySQL Shell 以及用于透明连接路由和自动故障转移的 MySQL 路由器。它们一起形成一个自我修复集群,无需人工干预即可容忍节点故障。
架构简洁而优雅。应用程序连接到 MySQL 路由器,该路由器通过查询 InnoDB 集群元数据架构来保持对集群拓扑的了解。当主服务器发生故障时,组复制会从剩余的辅助服务器中选择一个新的主服务器,MySQL 路由器会自动将写入流量重定向到新的主服务器(通常在几秒钟内)。读取流量可以分布在所有辅助节点上,以实现水平读取扩展。
组复制基础知识
MySQL 组复制是 InnoDB Cluster 的基础。它使用基于 Paxos 的共识协议来确保在主节点上提交的每个事务在被确认之前都被复制到大多数节点。 这提供了虚拟同步复制— 保证提交的数据在提交时至少存在于大多数集群成员上。
组复制以两种模式运行:
- 单主模式— 一个节点接受写入(主节点);所有其他都是只读辅助对象。这是推荐的默认模式。它完全避免了写冲突,因为只有一个节点可以生成事务。
- 多主模式— 所有节点同时接受写入。这为跨不同表或键空间进行干净分区的工作负载提供了更高的写入吞吐量,但是当并发事务修改相同的行时,它会引入认证冲突的可能性。冲突的事务会在一个节点上回滚。仅当您的应用程序设计为处理认证失败和重试逻辑时,才使用多主。
使用 MySQL Shell 设置 InnoDB 集群
MySQL Shell 提供 AdminAPI,这是一组自动化整个集群生命周期的功能。以下是 3 节点集群的完整设置顺序。
# Step 1: Prepare each MySQL instance (run on all 3 nodes)
# Ensure my.cnf has required settings
[mysqld]
server-id=1 # unique per node: 1, 2, 3
gtid_mode=ON
enforce_gtid_consistency=ON
binlog_checksum=NONE
binlog_transaction_dependency_tracking=WRITESET
transaction_write_set_extraction=XXHASH64
loose-group_replication_start_on_boot=OFF
plugin_load_add='group_replication.so'
plugin_load_add='mysql_clone.so'
report_host='mysql-node-1' # unique per node
# Step 2: Use MySQL Shell to configure and create the cluster
mysqlsh -- dba configure-instance root@mysql-node-1:3306 --clusterAdmin=gradmin --clusterAdminPassword='SecurePass123!'
mysqlsh -- dba configure-instance root@mysql-node-2:3306 --clusterAdmin=gradmin --clusterAdminPassword='SecurePass123!'
mysqlsh -- dba configure-instance root@mysql-node-3:3306 --clusterAdmin=gradmin --clusterAdminPassword='SecurePass123!'
# Step 3: Connect to the first node and create the cluster
mysqlsh gradmin@mysql-node-1:3306
# Inside MySQL Shell:
var cluster = dba.createCluster('productionCluster', {
memberWeight: 90,
exitStateAction: 'ABORT_SERVER',
consistency: 'BEFORE_ON_PRIMARY_FAILOVER',
expelTimeout: 10
})
# Step 4: Add remaining nodes
cluster.addInstance('gradmin@mysql-node-2:3306', {recoveryMethod: 'clone'})
cluster.addInstance('gradmin@mysql-node-3:3306', {recoveryMethod: 'clone'})
# Step 5: Verify cluster status
cluster.status()
recoveryMethod: 'clone'选项使用 MySQL 克隆插件执行从主成员到加入成员的完整数据复制,这比从大型数据集的二进制日志进行增量恢复要快得多。
MySQL 路由器配置
MySQL 路由器针对集群进行引导并自动生成其配置文件。
# Bootstrap MySQL Router against the cluster
mysqlrouter --bootstrap gradmin@mysql-node-1:3306 \
--directory /opt/mysqlrouter \
--conf-use-sockets \
--user=mysqlrouter \
--name='production-router'
# Start MySQL Router
/opt/mysqlrouter/start.sh
# Default ports after bootstrap:
# 6446 — R/W (routes to primary)
# 6447 — R/O (round-robin across secondaries)
# 6448 — R/W (X Protocol)
# 6449 — R/O (X Protocol)
应用程序连接到路由器的 R/W 端口进行写入,连接到 R/O 端口进行读取副本。当主节点发生故障时,路由器会检测到拓扑变化并在几秒钟内重新路由。
多区域 MySQL 部署
对于跨地域的灾难恢复和低延迟读取,MySQL 可以跨多个区域部署。 InnoDB ClusterSet 扩展了 InnoDB Cluster,支持主集群与不同区域的一个或多个副本集群之间的异步复制。
InnoDB ClusterSet 与一个处理所有写入的主集群和一个或多个异步接收更改的副本集群一起运行。在灾难场景中,可以通过 MySQL Shell 将副本集群提升为主集群。
# Create a ClusterSet from an existing InnoDB Cluster
mysqlsh gradmin@mysql-node-1:3306
var cluster = dba.getCluster()
var cs = cluster.createClusterSet('globalSet')
# Create a replica cluster in eu-west region
cs.createReplicaCluster('gradmin@mysql-eu-node-1:3306', 'euWestCluster', {
recoveryMethod: 'clone'
})
var euCluster = cs.getCluster('euWestCluster')
euCluster.addInstance('gradmin@mysql-eu-node-2:3306', {recoveryMethod: 'clone'})
euCluster.addInstance('gradmin@mysql-eu-node-3:3306', {recoveryMethod: 'clone'})
# Emergency failover (when primary cluster is unreachable)
cs.forcePrimaryCluster('euWestCluster')
Kubernetes 上的MySQL — 操作模式
在 Kubernetes 上运行 MySQL 需要解决一些不适用于无状态工作负载的挑战:稳定的网络身份、持久存储、有序启动和关闭以及集群感知的运行状况检查。 Kubernetes 操作员将特定于域的操作知识编码到控制器中,该控制器监视自定义资源并将 MySQL 的实际状态与 YAML 中声明的所需状态进行协调。
MySQL 用于 Kubernetes 的操作器 (Oracle)
适用于 Kubernetes 的 Oracle MySQL Operator 在 Kubernetes 上本地部署和管理 InnoDB Cluster 实例。它为 MySQL 服务器 Pod 创建 StatefulSet、为 MySQL 路由器创建部署,并处理自动故障转移、扩展、备份和配置更改。
# Install the MySQL Operator via Helm
helm repo add mysql-operator https://mysql.github.io/mysql-operator/
helm repo update
helm install mysql-operator mysql-operator/mysql-operator \
--namespace mysql-operator \
--create-namespace \
--set image.pullPolicy=IfNotPresent
操作员运行后,通过创建自定义资源来部署 InnoDB 集群。
apiVersion: v1
kind: Secret
metadata:
name: mysql-root-credentials
namespace: production
stringData:
rootUser: root
rootHost: '%'
rootPassword: 'ProductionSecurePass123!'
---
apiVersion: mysql.oracle.com/v2
kind: InnoDBCluster
metadata:
name: production-mysql
namespace: production
spec:
secretName: mysql-root-credentials
instances: 3
tlsUseSelfSigned: true
router:
instances: 2
datadirVolumeClaimTemplate:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
storageClassName: gp3-encrypted
mycnf: |
[mysqld]
innodb_buffer_pool_size=4G
innodb_log_file_size=1G
innodb_flush_log_at_trx_commit=1
sync_binlog=1
max_connections=500
innodb_io_capacity=2000
innodb_io_capacity_max=4000
innodb_read_io_threads=8
innodb_write_io_threads=8
performance_schema=ON
slow_query_log=ON
long_query_time=1
podSpec:
containers:
- name: mysql
resources:
requests:
cpu: "2"
memory: 8Gi
limits:
cpu: "4"
memory: 16Gi
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: component
operator: In
values: [mysqld]
topologyKey: topology.kubernetes.io/zone
Pod 反关联性规则可确保 MySQL Pod 分布在可用区域中,从而提供区域级容错。mycnf模块允许您直接通过 CR 注入经过生产调整的 MySQL 配置。
适用于 MySQL (PXC) 的 Percona 操作器
Percona Operator for MySQL 部署 Percona XtraDB Cluster (PXC),这是一种基于 Galera 的多主同步复制解决方案。 PXC 与 InnoDB Cluster 的不同之处在于,每个节点都可以接受写入(真正的多主),并且使用 wsrep API 在认证级别同步复制。
# Install Percona Operator
helm repo add percona https://percona.github.io/percona-helm-charts/
helm repo update
helm install pxc-operator percona/pxc-operator \
--namespace pxc \
--create-namespace
# Percona XtraDB Cluster Custom Resource
apiVersion: pxc.percona.com/v1
kind: PerconaXtraDBCluster
metadata:
name: production-pxc
namespace: pxc
spec:
crVersion: '1.14.0'
secretsName: pxc-secrets
pxc:
size: 3
image: percona/percona-xtradb-cluster:8.0.35
resources:
requests:
memory: 8Gi
cpu: "2"
limits:
memory: 16Gi
cpu: "4"
volumeSpec:
persistentVolumeClaim:
storageClassName: gp3-encrypted
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 200Gi
affinity:
antiAffinityTopologyKey: topology.kubernetes.io/zone
configuration: |
[mysqld]
innodb_buffer_pool_size=4G
innodb_flush_log_at_trx_commit=1
wsrep_provider_options="gcache.size=2G; gcs.fc_limit=256"
wsrep_slave_threads=8
wsrep_certify_nonPK=1
wsrep_trx_fragment_size=10M
max_connections=500
haproxy:
enabled: true
size: 3
image: percona/haproxy:2.8.5
resources:
requests:
memory: 1Gi
cpu: 500m
proxysql:
enabled: false
backup:
image: percona/percona-xtradb-cluster-operator:1.14.0-pxc8.0-backup-pxb8.0.35
storages:
s3-backup:
type: s3
verifyTLS: true
s3:
bucket: production-mysql-backups
region: us-east-1
credentialsSecret: aws-s3-credentials
schedule:
- name: daily-full
schedule: "0 3 * * *"
keep: 7
storageName: s3-backup
- name: hourly-incremental
schedule: "0 * * * *"
keep: 24
storageName: s3-backup
Percona Operator 处理到 S3 的自动备份、时间点恢复、滚动升级以及用于连接路由的 ProxySQL 或 HAProxy。 PXC 中基于 Galera 的复制提供真正的同步多主写入 - 每个提交的事务都保证存在于所有节点上。
连接路由:MySQL 路由器与 ProxySQL
MySQL路由器和ProxySQL都用作数据库代理,但它们有不同的优势。
MySQL 路由器专为 InnoDB 集群而构建。它读取集群元数据,跟踪拓扑变化,并将连接路由到正确的主节点或辅助节点。其配置极简,并且与 MySQL 生态系统无缝集成。缺点是查询级别路由有限——它在连接级别运行,而不是查询级别。
ProxySQL是一款通用 MySQL 代理,具有高级功能:查询级读/写分离、查询缓存、连接复用、查询重写和复杂的路由规则。它在需要对查询分布方式进行细粒度控制的环境中表现出色。
# ProxySQL configuration for read/write splitting
# proxysql.cnf
mysql_servers:
(
{ hostgroup_id=10, hostname="mysql-primary", port=3306, max_connections=200, weight=1000 },
{ hostgroup_id=20, hostname="mysql-secondary-1", port=3306, max_connections=200, weight=500 },
{ hostgroup_id=20, hostname="mysql-secondary-2", port=3306, max_connections=200, weight=500 }
)
mysql_query_rules:
(
{ rule_id=1, active=1, match_digest="^SELECT .* FOR UPDATE$", destination_hostgroup=10, apply=1 },
{ rule_id=2, active=1, match_digest="^SELECT", destination_hostgroup=20, apply=1 },
{ rule_id=3, active=1, match_digest=".*", destination_hostgroup=10, apply=1 }
)
mysql_replication_hostgroups:
(
{ writer_hostgroup=10, reader_hostgroup=20, comment="InnoDB Cluster" }
)
在 Kubernetes 环境中,ProxySQL 可以作为应用程序 Pod 中的边车容器运行,也可以作为专用部署运行。将其作为 sidecar 运行可以消除网络跃点,但会增加 pod 资源使用率;专用部署更易于大规模管理。
云提供商比较:托管与自管理
AWS:RDS 多可用区与 Aurora 与 EKS
上的自我管理Amazon RDS 多可用区可在不同可用区的主实例和备用实例之间提供自动故障转移。故障转移通常需要 60-120 秒。它使用同步物理复制到备用数据库。可以添加只读副本以进行读取扩展,但它们使用异步复制。 RDS 处理备份、修补和监控,但限制您对 MySQL 配置和版本选择的控制。
Amazon Aurora MySQL是 MySQL 存储引擎的云原生重写。它将计算与存储分开——存储层是一个分布式容错系统,可以跨三个可用区以六种方式复制数据。 Aurora 提供不到 10 秒的故障转移、最多 15 个只读副本(复制延迟最小)以及自动存储扩展至 128 TiB。 Aurora Serverless v2 为不可预测的工作负载添加了自动计算扩展。代价是成本(Aurora 比 RDS 贵 20-40%)以及与某些 MySQL 功能的兼容性降低。
在 EKS上进行自我管理,让您可以完全控制 MySQL 版本、配置和复制拓扑。当您需要托管服务中不可用的特定 MySQL 功能、需要多云可移植性或大规模成本优化证明运营开销合理时,请使用此功能。
# EKS StorageClass for MySQL with gp3 volumes
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-encrypted
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "6000"
throughput: "250"
encrypted: "true"
fsType: ext4
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
allowVolumeExpansion: true
Azure:AKS
上的灵活服务器与自我管理服务器Azure 用于 MySQL 灵活服务器的数据库提供带自动故障转移功能的区域冗余 HA、同区域只读副本以及高达 16 TiB 的存储。它支持可配置的维护窗口、停止/启动实例的能力(有助于节省开发/测试成本)以及与 Azure Private Link 集成以实现网络隔离。关键业务层通过本地 SSD 存储提供最佳性能。
在 AKS 上进行自我管理将 Azure 托管磁盘(建议用于数据库工作负载的高级 SSD v2)与 MySQL Operator 或 Percona Operator 结合使用。 AKS 提供可用性区域支持和用于容器级 VNET 集成的 Azure CNI。
# AKS StorageClass for MySQL
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: premium-ssd-zrs
provisioner: disk.csi.azure.com
parameters:
skuName: Premium_ZRS
cachingMode: None
DiskIOPSReadWrite: "5000"
DiskMBpsReadWrite: "200"
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
allowVolumeExpansion: true
Google Cloud:Cloud SQL 与 GKE
上的自我管理适用于 MySQL的 Google Cloud SQL 提供区域实例,具有跨区域自动故障转移、只读副本(包括跨区域)以及具有时间点恢复功能的自动备份。 Cloud SQL 通过集成到 Google 的 IAM、VPC 和监控生态系统,提供最高级别的管理便利性。 Enterprise Plus 层增加了近乎零停机的维护和数据缓存,以提高读取性能。
在 GKE 上进行自我管理使用持久磁盘 SSD 或 Hyperdisk 进行存储。 GKE Autopilot 简化了节点管理,并且可以以最小的运营开销运行 MySQL Operator。
# GKE StorageClass for MySQL
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ssd-regional
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
replication-type: regional-pd
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
allowVolumeExpansion: true
裸机:k3s + Rancher + Longhorn
并非所有工作负载都属于公共云。为了满足数据主权、合规性、成本优化或延迟要求,运行带有 Rancher 管理和 Longhorn 存储的 k3s 的裸机 Kubernetes 集群为 MySQL HA 提供了生产级平台。
k3s 安装和配置
k3s 是一款轻量级、经过认证的 Kubernetes 发行版,非常适合边缘和裸机。它将所有控制平面组件捆绑到一个二进制文件中,并使用 SQLite 或嵌入式 etcd 进行状态存储。
# Install k3s server (first control-plane node)
curl -sfL https://get.k3s.io | sh -s - server \
--cluster-init \
--tls-san 10.10.0.10 \
--disable traefik \
--disable servicelb \
--write-kubeconfig-mode 644
# Join additional server nodes
curl -sfL https://get.k3s.io | K3S_TOKEN=$(cat /var/lib/rancher/k3s/server/node-token) sh -s - server \
--server https://10.10.0.10:6443 \
--tls-san 10.10.0.10
# Join worker nodes
curl -sfL https://get.k3s.io | K3S_URL=https://10.10.0.10:6443 \
K3S_TOKEN=$(cat /var/lib/rancher/k3s/server/node-token) sh -s -
适用于 MySQL 的 Longhorn 存储
Longhorn是专为Kubernetes构建的云原生分布式块存储系统。 它跨节点复制数据,提供快照和备份,并与 Kubernetes CSI 驱动程序本地集成。对于 MySQL,Longhorn 提供了云提供商通过其托管磁盘产品处理的持久复制存储层。
# Install Longhorn
helm repo add longhorn https://charts.longhorn.io
helm repo update
helm install longhorn longhorn/longhorn \
--namespace longhorn-system \
--create-namespace \
--set defaultSettings.defaultReplicaCount=3 \
--set defaultSettings.storageMinimalAvailablePercentage=15 \
--set defaultSettings.guaranteedInstanceManagerCPU=12
# StorageClass for MySQL on Longhorn
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn-mysql
provisioner: driver.longhorn.io
parameters:
numberOfReplicas: "3"
staleReplicaTimeout: "2880"
dataLocality: best-effort
diskSelector: ssd
fsType: ext4
reclaimPolicy: Retain
volumeBindingMode: Immediate
allowVolumeExpansion: true
MetalLB 用于负载平衡
在裸机上,没有云负载均衡器。 MetalLB 通过使用第 2 层 (ARP) 或 BGP 模式将外部 IP 地址分配给 LoadBalancer 类型的 Kubernetes 服务来填补这一空白。
# MetalLB IP Address Pool and L2 Advertisement
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: mysql-pool
namespace: metallb-system
spec:
addresses:
- 10.10.0.100-10.10.0.110
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: mysql-l2
namespace: metallb-system
spec:
ipAddressPools:
- mysql-pool
备份和恢复策略
高可用性集群可防止节点故障。备份可以防止数据损坏、意外删除、应用程序错误以及整个集群丢失的灾难恢复情况。你两者都需要。
使用 mysqldump 进行逻辑备份
mysqldump生成便携式且可读的 SQL 格式备份。对于 50 GB 以下的数据库,这是最简单的选择。对于较大的数据集,锁定和导出时间使其在工作时间用于生产使用是不切实际的。
# Full logical backup with consistent snapshot
mysqldump --all-databases \
--single-transaction \
--routines \
--triggers \
--events \
--set-gtid-purged=ON \
--result-file=/backups/full-$(date +%Y%m%d-%H%M%S).sql
# Compressed backup piped to S3
mysqldump --all-databases --single-transaction | \
gzip | aws s3 cp - s3://mysql-backups/full-$(date +%Y%m%d).sql.gz
使用 Percona XtraBackup 进行物理备份
Percona XtraBackup 对 InnoDB 数据执行热、非阻塞物理备份。它在跟踪重做日志的同时复制 InnoDB 数据文件,然后在准备阶段应用重做日志以生成一致的备份。对于大型数据集,它比 mysqldump 快得多,并且支持增量备份。
# Full physical backup
xtrabackup --backup \
--target-dir=/backups/full \
--user=backup_user \
--password=SecureBackupPass \
--parallel=4 \
--compress \
--compress-threads=4
# Incremental backup based on previous full
xtrabackup --backup \
--target-dir=/backups/incr-$(date +%H) \
--incremental-basedir=/backups/full \
--user=backup_user \
--password=SecureBackupPass
# Prepare (apply redo log) and restore
xtrabackup --prepare --target-dir=/backups/full
xtrabackup --prepare --target-dir=/backups/full \
--incremental-dir=/backups/incr-01
# Copy back to data directory
xtrabackup --copy-back --target-dir=/backups/full --datadir=/var/lib/mysql
chown -R mysql:mysql /var/lib/mysql
二进制日志 (binlog) 时间点恢复
二进制日志记录每个数据修改事务。与完整备份相结合,它们可以实现时间点恢复 (PITR) — 恢复到任何特定时刻,而不仅仅是上次备份的时间。
# Enable binlog retention (my.cnf)
[mysqld]
log_bin=mysql-bin
binlog_expire_logs_seconds=604800 # 7 days
binlog_format=ROW
gtid_mode=ON
enforce_gtid_consistency=ON
# Restore from backup then replay binlogs to a specific point
mysqlbinlog --start-datetime="2026-04-12 08:00:00" \
--stop-datetime="2026-04-12 09:30:00" \
mysql-bin.000042 mysql-bin.000043 | mysql -u root -p
# Or replay to a specific GTID position
mysqlbinlog --include-gtids="3c2d4f9e-d17e-ec59-9029-6aafa8accef8:1-5000" \
mysql-bin.000042 | mysql -u root -p
Kubernetes 备份 CronJob
在 Kubernetes 中,备份应作为 CronJobs 而不是临时命令运行。这确保了备份是自动化的、受监控的,并且在失败时可以收到警报。
apiVersion: batch/v1
kind: CronJob
metadata:
name: mysql-backup
namespace: production
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 7
failedJobsHistoryLimit: 3
jobTemplate:
spec:
activeDeadlineSeconds: 7200
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: percona/percona-xtrabackup:8.0.35
command:
- /bin/sh
- -c
- |
BACKUP_DIR=/backups/$(date +%Y%m%d-%H%M%S)
mkdir -p $BACKUP_DIR
xtrabackup --backup \
--host=production-mysql.production.svc \
--user=backup_user \
--password=$BACKUP_PASSWORD \
--target-dir=$BACKUP_DIR \
--parallel=4 \
--compress
xtrabackup --prepare --target-dir=$BACKUP_DIR
# Upload to S3
aws s3 sync $BACKUP_DIR s3://mysql-backups/$(date +%Y%m%d)/
# Cleanup local
rm -rf $BACKUP_DIR
env:
- name: BACKUP_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-backup-credentials
key: password
volumeMounts:
- name: backup-scratch
mountPath: /backups
resources:
requests:
cpu: "1"
memory: 2Gi
limits:
cpu: "2"
memory: 4Gi
volumes:
- name: backup-scratch
emptyDir:
sizeLimit: 100Gi
通过 Percona 监控和管理 (PMM) 进行监控
Percona 监控和管理 (PMM) 是一个专为数据库可观察性而构建的开源监控平台。虽然 Prometheus 和 Grafana 提供常规 Kubernetes 监控,但 PMM 增加了 MySQL 特定的见解:识别慢速查询的查询分析 (QAN)、复制滞后仪表板、InnoDB 缓冲池命中率、表锁争用等。
# Deploy PMM Server on Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: pmm-server
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: pmm-server
template:
metadata:
labels:
app: pmm-server
spec:
containers:
- name: pmm-server
image: percona/pmm-server:2
ports:
- containerPort: 443
env:
- name: DISABLE_TELEMETRY
value: "1"
volumeMounts:
- name: pmm-data
mountPath: /srv
resources:
requests:
cpu: "1"
memory: 4Gi
limits:
cpu: "2"
memory: 8Gi
volumes:
- name: pmm-data
persistentVolumeClaim:
claimName: pmm-data-pvc
---
apiVersion: v1
kind: Service
metadata:
name: pmm-server
namespace: monitoring
spec:
type: ClusterIP
selector:
app: pmm-server
ports:
- port: 443
targetPort: 443
# Register MySQL instances with PMM Client (run on each MySQL pod or sidecar)
pmm-admin config --server-url=https://admin:password@pmm-server.monitoring:443 --server-insecure-tls
pmm-admin add mysql \
--username=pmm_monitor \
--password=MonitorPass123 \
--host=127.0.0.1 \
--port=3306 \
--query-source=perfschema \
--service-name=mysql-node-1
PMM 的查询分析在生产中特别有价值。它捕获在服务器上执行的每个查询(通过性能架构或慢速查询日志),通过指纹聚合它们,并显示平均延迟、检查的行与发送的行和锁定时间等指标。这是您识别降低应用程序性能的查询的方法。
生产配置调整
默认 MySQL 配置针对小型通用工作负载进行了调整。具有专用数据库服务器的生产环境需要显着不同的设置。以下是关键参数以及如何调整它们的大小。
InnoDB 缓冲池
InnoDB 缓冲池是 MySQL 在内存中缓存表和索引数据的地方。它是最有影响力的配置参数。对于专用 MySQL 服务器,将其设置为可用 RAM 的 70–80%。
[mysqld]
# Memory: 16Gi container limit -> allocate ~12Gi to buffer pool
innodb_buffer_pool_size = 12G
innodb_buffer_pool_instances = 8 # 1 per GB (up to 64)
innodb_buffer_pool_chunk_size = 1536M # pool_size / instances
重做日志配置
重做日志在将写入刷新到数据文件之前吸收写入。较大的重做日志会降低检查点刷新的频率并提高写入吞吐量。 MySQL 8.0.30+ 使用innodb_redo_log_capacity而不是旧版innodb_log_file_size。
[mysqld]
# MySQL 8.0.30+
innodb_redo_log_capacity = 4G
# MySQL 8.0.29 and earlier
# innodb_log_file_size = 2G
# innodb_log_files_in_group = 2
耐用性与性能的权衡
innodb_flush_log_at_trx_commit和sync_binlog的组合决定了您的耐用性保证。
- 最大耐用性(建议量产):
innodb_flush_log_at_trx_commit=1+sync_binlog=1。每个事务在被确认之前都会刷新到磁盘上的重做日志和二进制日志。崩溃时零数据丢失。 - 平衡:
innodb_flush_log_at_trx_commit=2+sync_binlog=1。重做日志每次提交都会写入操作系统缓存,但每秒仅刷新到磁盘一次。操作系统崩溃时最多可能会丢失 1 秒的事务(MySQL 崩溃仍然是安全的)。 - 最大性能(不建议量产):
innodb_flush_log_at_trx_commit=0+sync_binlog=0。写入会定期进行批量和刷新。任何崩溃都会有丢失最多 1 秒交易的风险。
[mysqld]
# Production durability settings
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1
innodb_doublewrite = ON
innodb_checksum_algorithm = crc32
I/O 配置
[mysqld]
# SSD-optimised I/O settings
innodb_io_capacity = 4000
innodb_io_capacity_max = 8000
innodb_read_io_threads = 8
innodb_write_io_threads = 8
innodb_flush_method = O_DIRECT
innodb_file_per_table = ON
innodb_open_files = 10000
连接和线程管理
[mysqld]
max_connections = 500
max_connect_errors = 1000000
thread_cache_size = 64
table_open_cache = 4096
table_open_cache_instances = 16
back_log = 1500
# Thread pool (MySQL Enterprise or Percona)
thread_pool_size = 16
thread_pool_max_threads = 1000
临时表和排序缓冲区
[mysqld]
tmp_table_size = 64M
max_heap_table_size = 64M
sort_buffer_size = 4M
join_buffer_size = 4M
read_rnd_buffer_size = 2M
完整生产配置模板
[mysqld]
# Identity
server-id = 1
report_host = mysql-node-1
# GTID Replication
gtid_mode = ON
enforce_gtid_consistency = ON
binlog_format = ROW
log_bin = mysql-bin
binlog_expire_logs_seconds = 604800
log_slave_updates = ON
relay_log_recovery = ON
# InnoDB Engine
innodb_buffer_pool_size = 12G
innodb_buffer_pool_instances = 8
innodb_redo_log_capacity = 4G
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1
innodb_doublewrite = ON
innodb_flush_method = O_DIRECT
innodb_io_capacity = 4000
innodb_io_capacity_max = 8000
innodb_read_io_threads = 8
innodb_write_io_threads = 8
innodb_file_per_table = ON
innodb_open_files = 10000
innodb_adaptive_hash_index = ON
innodb_change_buffering = all
# Connections
max_connections = 500
max_connect_errors = 1000000
thread_cache_size = 64
table_open_cache = 4096
table_open_cache_instances = 16
back_log = 1500
# Temp / Sort
tmp_table_size = 64M
max_heap_table_size = 64M
sort_buffer_size = 4M
join_buffer_size = 4M
# Monitoring
performance_schema = ON
slow_query_log = ON
long_query_time = 1
log_queries_not_using_indexes = ON
log_throttle_queries_not_using_indexes = 60
# Security
local_infile = OFF
skip_name_resolve = ON
default_authentication_plugin = caching_sha2_password
# Group Replication (InnoDB Cluster)
plugin_load_add = 'group_replication.so'
plugin_load_add = 'mysql_clone.so'
loose-group_replication_start_on_boot = OFF
loose-group_replication_bootstrap_group = OFF
loose-group_replication_recovery_use_ssl = ON
loose-group_replication_ssl_mode = REQUIRED
故障转移测试和混沌工程
从未在故障情况下进行过测试的高可用性系统并不是真正的高可用性 - 这只是一个假设。故障转移测试必须成为您常规操作节奏的一部分,而不是您在实际事件期间发现有效(或无效)的东西。
受控故障转移测试
# Test 1: Graceful primary failover (InnoDB Cluster)
mysqlsh gradmin@mysql-node-1:3306
var cluster = dba.getCluster()
cluster.setPrimaryInstance('gradmin@mysql-node-2:3306')
# Verify: cluster.status() should show node-2 as PRIMARY
# Test 2: Simulate primary crash
# On the primary node:
kill -9 $(pidof mysqld)
# Monitor: watch the cluster elect a new primary
# Verify: applications reconnect via MySQL Router within seconds
# Test 3: Network partition simulation
# On the primary node, block Group Replication port:
iptables -A INPUT -p tcp --dport 33061 -j DROP
iptables -A OUTPUT -p tcp --dport 33061 -j DROP
# The isolated node should be expelled; cluster continues with remaining members
# Cleanup:
iptables -D INPUT -p tcp --dport 33061 -j DROP
iptables -D OUTPUT -p tcp --dport 33061 -j DROP
# Test 4: Kubernetes pod deletion
kubectl delete pod mysql-0 -n production --grace-period=0 --force
# The StatefulSet controller recreates the pod
# The operator rejoins it to the cluster
采用 Litmus 或混沌网格的混沌工程
结构化混沌工程工具系统地注入故障并测量爆炸半径。 Chaos Mesh 是 CNCF 项目,与 Kubernetes 原生集成。
# Chaos Mesh: Kill a MySQL pod randomly
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
name: mysql-pod-kill
namespace: production
spec:
action: pod-kill
mode: one
selector:
namespaces:
- production
labelSelectors:
app.kubernetes.io/component: mysqld
scheduler:
cron: "0 */4 * * *"
---
# Chaos Mesh: Network delay between MySQL pods
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
name: mysql-network-delay
namespace: production
spec:
action: delay
mode: one
selector:
namespaces:
- production
labelSelectors:
app.kubernetes.io/component: mysqld
delay:
latency: "200ms"
jitter: "50ms"
duration: "5m"
scheduler:
cron: "30 */6 * * *"
---
# Chaos Mesh: Disk I/O stress
apiVersion: chaos-mesh.org/v1alpha1
kind: IOChaos
metadata:
name: mysql-io-latency
namespace: production
spec:
action: latency
mode: one
selector:
namespaces:
- production
labelSelectors:
app.kubernetes.io/component: mysqld
volumePath: /var/lib/mysql
path: '*'
delay: '100ms'
percent: 50
duration: '5m'
在故障转移测试期间测量什么
- 恢复时间目标 (RTO)— 从故障检测到服务恢复需要多长时间? InnoDB Cluster 通常可以达到 5-30 秒。 Percona XtraDB Cluster 可以更快,因为没有选举(所有节点都是可写的)。
- 恢复点目标 (RPO)— 故障转移期间丢失了多少数据?使用同步复制(单主模式下的组复制,PXC),已提交事务的 RPO 为零。对于异步复制(ClusterSet 跨区域),RPO 等于复制滞后。
- 应用程序错误率— 在故障转移窗口期间有多少应用程序请求失败?这不仅测试数据库故障转移,还测试应用程序的连接重试逻辑和 MySQL 路由器的重新路由速度。
- 连接耗尽时间— 与旧主设备的现有连接需要多长时间才能耗尽并重新连接到新主设备?
运行手册:主节点故障检查表
- 验证集群已选择新的主集群:
cluster.status() - 确认 MySQL 路由器正在路由到新的主路由器:检查路由器日志和连接计数
- 监控剩余辅助节点上的复制延迟:
SELECT * FROM performance_schema.replication_group_member_stats - 如果故障节点可以恢复,则重新加入它:
cluster.rejoinInstance('gradmin@failed-node:3306') - 如果故障节点无法恢复,请将其删除并添加新节点:
cluster.removeInstance('gradmin@failed-node:3306', {force: true}) - 验证集群健康状况:所有成员均在线,无复制错误,备份计划完好无损
- 更新您的容量计划:使用 2 个节点运行意味着您的容错能力为零,直到第三个节点恢复为止
安全强化
生产 MySQL 部署必须解决基本身份验证之外的多个安全问题。
传输中和静态加密
[mysqld]
# TLS for client connections
ssl_ca = /etc/mysql/certs/ca.pem
ssl_cert = /etc/mysql/certs/server-cert.pem
ssl_key = /etc/mysql/certs/server-key.pem
require_secure_transport = ON
tls_version = TLSv1.3
# Encryption at rest (InnoDB tablespace encryption)
early-plugin-load = keyring_file.so
keyring_file_data = /var/lib/mysql-keyring/keyring
innodb_undo_log_encrypt = ON
innodb_redo_log_encrypt = ON
default_table_encryption = ON
Kubernetes 的秘密和密封的秘密
# Never store database credentials in plain YAML
# Use Sealed Secrets or an external secret manager
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: mysql-root-credentials
namespace: production
spec:
encryptedData:
rootUser: AgBy3i4OJSWK+PiTy...
rootPassword: AgCtr7pJ2XQWK+Pi...
template:
metadata:
name: mysql-root-credentials
namespace: production
type: Opaque
审计记录
[mysqld]
# MySQL Enterprise Audit (or Percona Audit Log plugin)
plugin-load-add = audit_log.so
audit_log_format = JSON
audit_log_policy = LOGINS
audit_log_rotate_on_size = 100M
audit_log_rotations = 10
决策矩阵:选择 MySQL HA 架构
正确的架构取决于您的具体要求。这是一个决策框架。
- 单云、托管首选项、MySQL 兼容— 使用 Aurora (AWS)、灵活服务器业务关键型 (Azure) 或 Cloud SQL Enterprise Plus (GCP)。这些提供了最低的运营开销。
- 单云、自我管理、需要完整的 MySQL 控制— 在具有云原生存储类的 EKS/AKS/GKE 上使用 MySQL Operator 或 Percona Operator。
- 多云或混合云— 将 Percona Operator 与 PXC 结合使用,或将 MySQL Operator 与 InnoDB Cluster 结合使用。 Kubernetes 抽象层使您的 MySQL 部署可跨云移植。
- 裸机或边缘— 将 k3s 与 Rancher、Longhorn 存储、MetalLB 以及 MySQL Operator 或 Percona Operator 结合使用。
- 最大写入吞吐量,需要多主— 将 Percona XtraDB 集群 (Galera) 与 Percona Operator 结合使用。所有节点都接受具有同步认证的写入。
- 具有灾难恢复功能的全球分布— 使用 InnoDB ClusterSet 进行区域之间的异步复制。接受异步复制的 RPO 与本地写入的延迟优势之间的权衡。
生产 MySQL HA
操作清单在声明您的 MySQL HA 部署已投入生产之前,请验证此清单上的每一项。
- 集群运行状况— 所有成员均报告联机状态。组复制未显示任何错误。
- 自动备份— 按计划运行 CronJob 或操作员管理的备份。通过定期恢复测试验证备份。
- 监控— PMM 或 Prometheus/Grafana 收集 MySQL 指标。针对复制滞后、连接饱和、缓冲池命中率低于 99% 以及磁盘空间配置的警报。
- 故障转移测试— 在过去 30 天内测试了主要故障转移。 RTO 和 RPO 已测量且符合 SLA 目标。
- 连接路由— MySQL 路由器或 ProxySQL 运行状况检查和负载平衡。 应用程序连接字符串指向路由器,而不是单个 MySQL 实例。
- 安全性— 所有连接都需要 TLS。静态加密已启用。凭证存储在秘密管理器中。审核日志记录处于活动状态。每个应用程序的最低权限数据库用户。
- 资源限制— Kubernetes 资源请求和限制设置适当。 PodDisruptionBudgets 到位。 Pod 反关联性将 MySQL Pod 跨区域传播。
- 容量计划— 监控存储利用率,并在 70% 和 85% 时发出警报。已测试体积膨胀。记录垂直和水平缩放程序。
- 运行手册— 主故障转移、节点更换、备份恢复、版本升级和紧急只读模式的记录程序。
- 混沌测试— 计划定期进行混沌实验,以验证弹性假设。
结论
MySQL 在生产中的高可用性不是单一的技术选择 - 它是一个跨复制层、编排平台、存储子系统、监控堆栈及其周围操作流程的连锁决策系统。具有组复制功能的 InnoDB Cluster 提供了基础的 HA 原语。 Oracle 和 Percona 的 Kubernetes 操作员可自动执行生命周期管理,否则会消耗大量工程时间。为了方便起见,云管理服务贸易控制。 k3s、Rancher 和 Longhorn 的裸机部署证明您不需要云提供商来运行生产级 MySQL HA。
最关键的一点是,高可用性是整个系统的属性,而不仅仅是数据库的属性。它包括您的应用程序如何处理连接故障和重试,您的代理层如何检测故障节点并进行路由,您的监控如何在用户注意到之前发出警报,您的备份策略如何实现从仅 HA 无法处理的场景中恢复,以及您的团队如何实践故障转移程序,以便它们在真实事件的压力下干净地执行。
精心构建,定期测试,并将您的操作手册视为随每个事件和每个混乱实验而演变的动态文档。这就是 MySQL 作为生产中可靠、高度可用的数据平台的原因。