KES 高可用架构实战:主备复制、读写分离与容灾切换深度解析

KES 高可用架构实战:主备复制、读写分离与容灾切换深度解析

开篇的话

做过几年数据库运维的人都有体会,单点故障是悬在头顶的一把剑。白天系统跑得顺风顺水,半夜突然告警电话响起------数据库挂了,业务全停。这种场景经历过一次就终身难忘。高可用架构不是锦上添花的东西,而是生产环境的底线要求。

这两年接触 KingbaseES 的项目越来越多,从单机部署到主备集群再到分布式架构,踩了不少坑也积累了一些经验。这篇文章主要聊聊 KES 数据库在高可用方面的几种典型架构方案,包括流复制主备、读写分离、自动故障切换以及异地容灾。内容偏实战,重点讲配置细节和那些文档里不太容易找到的注意事项。如果你正在规划一个基于 KingbaseES 的高可用方案,或者接手了一个现成的集群需要优化,这篇文章应该能提供一些参考。

一、流复制主备架构基础

流复制(Streaming Replication)是 KES 实现高可用的核心机制。主库通过 WAL(Write-Ahead Log)日志把数据变更实时传给备库,备库重放这些日志保持数据同步。这套机制成熟稳定,也是大多数项目的首选方案。

环境准备

先说硬件和网络。主备两台服务器最好在同一机房,网络延迟控制在 1ms 以内。如果跨机房部署,延迟超过 5ms 就要慎重考虑是否用同步复制模式了,后面会详细说这个事。

bash 复制代码
# 主备节点都要安装 KES,版本保持一致
# 假设主库 IP: 192.168.10.100,端口 54321
# 备库 IP: 192.168.10.101,端口 54321

# 检查两台机器的时间同步
date
# 如果时间差超过 1 秒,用 ntpdate 同步
ntpdate cn.pool.ntp.org

时间不同步会导致备库重放 WAL 时出现时序问题,虽然不常见但确实遇到过。有一次客户的主备时钟差了半小时,备库一直报 "recovery target time is in the future",排查了半天才发现是 NTP 没配好。

创建复制用户

在主库上创建一个专门用于流复制的用户,权限不用给太大,REPLICATION 就够了:

sql 复制代码
-- 在主库执行
CREATE ROLE repl_user WITH REPLICATION LOGIN PASSWORD 'Repl@2026Secure';

-- 修改 sys_hba.conf,允许备库通过该用户连接
# 在 sys_hba.conf 末尾添加
host    replication    repl_user    192.168.10.101/32    scram-sha-256

# 重新加载配置
sys_ctl -D /data/kingbase/data reload

这里有个细节要注意:sys_hba.conf 里的 replication 关键字表示这是一条针对流复制连接的规则,跟普通的数据库访问控制是分开的。有些新手会直接在 host all 那行加备库 IP,那样也能通,但不符合最小权限原则,审计的时候容易被挑刺。

搭建备库

备库的搭建有两种方式:一种是用 sys_basebackup 做基础备份,另一种是手动拷贝数据目录。推荐用第一种,干净利落:

bash 复制代码
# 在备库机器上执行
# 先清空备库的数据目录(如果有的话)
rm -rf /data/kingbase/data/*

# 从主库拉取基础备份
sys_basebackup -h 192.168.10.100 -p 54321 \
  -U repl_user \
  -D /data/kingbase/data \
  -Fp -Xs -P -R

# 参数说明:
# -Fp: 输出格式为 plain(普通文件)
# -Xs: 以流式方式传输 WAL 文件
# -P: 显示进度条
# -R: 自动生成 standby.signal 文件和 primary_conninfo 配置

-R 参数是关键,它会自动在备库的数据目录下生成两个东西:一个是 standby.signal 空文件,标记这是一个备库;另一个是在 kingbase.auto.conf 里写入 primary_conninfo 连接串。省得手动配置,不容易出错。

如果不加 -R,就得自己写这两个东西:

bash 复制代码
# 手动创建 standby.signal
touch /data/kingbase/data/standby.signal

# 编辑 kingbase.auto.conf
cat >> /data/kingbase/data/kingbase.auto.conf << EOF
primary_conninfo = 'host=192.168.10.100 port=54321 user=repl_user password=Repl@2026Secure application_name=standby1'
EOF

application_name 这个参数建议加上,后面监控的时候能通过这个名字区分不同的备库。如果一个主库带多个备库,每个备库的 application_name 要不一样。

启动备库并验证

bash 复制代码
# 启动备库
sys_ctl -D /data/kingbase/data start

# 等个几秒,在主库上查看复制状态
ksql -U system -d test -p 54321 -c "
SELECT pid, usename, application_name, client_addr,
       state, sync_state, sent_lsn, write_lsn, flush_lsn, replay_lsn
FROM sys_stat_replication;"

正常的话能看到一条记录,state 是 streaming,sync_state 根据配置可能是 async、potential 或者 sync。几个 LSN 值的含义:

  • sent_lsn: 主库已经发送给备库的位置
  • write_lsn: 备库已经写入磁盘的位置
  • flush_lsn: 备库已经刷盘的位置
  • replay_lsn: 备库已经重放完成的位置

这几个值越接近说明同步延迟越小。如果 replay_lsn 落后 sent_lsn 很多,说明备库重放跟不上,可能有性能瓶颈。

二、同步复制与异步复制的选择

这是高可用架构设计里最关键的决策之一。同步复制保证 RPO(恢复点目标)为零,任何事务至少在备库落盘后才算提交成功;异步复制性能好但有丢数据的风险。怎么选要看业务对数据一致性和可用性的权衡。

异步复制配置

异步复制是默认模式,不需要额外配置。只要备库正常运行,主库就会持续发送 WAL。优点是主库写入不受备库影响,即使备库挂了或者网络断了,主库照样工作。缺点是在极端情况下(比如主库刚写完 WAL 还没来得及发给备库就宕机),可能会丢失最后几秒的数据。

sql 复制代码
-- 查看当前复制模式
SHOW synchronous_standby_names;
-- 返回 empty string 表示异步模式

对于非核心业务,比如日志采集、统计分析这类场景,异步复制完全够用。我见过不少项目把所有库都配成同步复制,结果因为备库偶尔的网络抖动导致主库写入超时,得不偿失。

同步复制配置

关键业务(金融交易、订单系统等)一般要求零数据丢失,这时候得上同步复制:

sql 复制代码
-- 在主库的 kingbase.conf 中设置
synchronous_standby_names = 'standby1'

# 改完重载配置
sys_ctl -D /data/kingbase/data reload

synchronous_standby_names 的值要和备库的 application_name 对应。如果有多个备库,可以写成 'standby1,standby2',表示任意一个备库确认即可(FIRST 模式),也可以写成 'ANY 2 (standby1,standby2)',表示至少两个备库确认。

同步复制开启后,可以在主库上看到 sync_state 变成 sync:

sql 复制代码
SELECT application_name, sync_state FROM sys_stat_replication;
-- 输出示例:
--  application_name | sync_state
-- ------------------+------------
--  standby1         | sync

性能影响实测

同步复制对性能的影响跟网络延迟直接相关。我在测试环境做过一组对比:

网络延迟 异步 TPS 同步 TPS 性能下降
0.5ms 12000 11500 4%
2ms 12000 9800 18%
5ms 12000 6500 46%
10ms 12000 3200 73%

延迟超过 5ms 之后性能下降非常明显。所以跨机房部署的时候,一定要先测一下网络延迟再决定用哪种模式。如果延迟实在降不下来,可以考虑用"本地同步 + 远程异步"的混合方案:同城两个机房做同步复制,异地机房做异步复制。

潜在同步模式

还有一个折中的选择叫"潜在同步"(Potential Synchronous)。配置方式是:

sql 复制代码
synchronous_standby_names = '*'

这个模式下,如果备库正常且能及时响应,就走同步复制;如果备库挂了或者响应慢,自动降级成异步,不会阻塞主库。听起来很美好,但实际用的时候要小心:降级成异步之后可能会有数据丢失风险,而且降级过程不会有明显告警,等你发现的时候可能已经丢了一批数据。

我的建议是:要么明确用同步,要么明确用异步,别用这种自动降级的模式。真要兼顾可用性和一致性,应该在应用层做补偿逻辑,而不是依赖数据库的自动切换。

三、读写分离架构实践

主备搭好了,下一步就是让备库承担读流量,减轻主库压力。KES 的备库默认是 hot standby 模式,可以接受只读查询。但要实现真正的读写分离,还需要在应用层或者中间件层做路由。

备库只读验证

sql 复制代码
-- 连接到备库
ksql -U system -d test -h 192.168.10.101 -p 54321

-- 尝试写入,应该会报错
INSERT INTO test_table VALUES (1, 'test');
-- ERROR: cannot execute INSERT in a read-only transaction

-- 查询正常
SELECT count(*) FROM test_table;

hot_standby 参数默认是 on,如果备库不能查,检查一下 kingbase.conf 里的配置:

sql 复制代码
hot_standby = on

应用层读写分离

最简单的做法是在应用代码里配置两个数据源,写操作走主库,读操作走备库。以 Java Spring Boot 为例:

yaml 复制代码
spring:
  datasource:
    master:
      jdbc-url: jdbc:kingbase8://192.168.10.100:54321/test
      username: app_user
      password: App@2026
    slave:
      jdbc-url: jdbc:kingbase8://192.168.10.101:54321/test
      username: readonly_user
      password: Read@2026

然后在 Service 层用注解或者 AOP 来路由:

java 复制代码
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {
}

@Aspect
@Component
public class DataSourceAspect {
    
    @Before("@annotation(readOnly)")
    public void setDataSource(ReadOnly readOnly) {
        DataSourceContextHolder.setSlave();
    }
    
    @After("@annotation(readOnly)")
    public void clearDataSource() {
        DataSourceContextHolder.clear();
    }
}

// 使用示例
@Service
public class OrderService {
    
    @ReadOnly
    public List<Order> listOrders() {
        // 这个方法会自动走备库
        return orderMapper.selectList();
    }
    
    public void createOrder(Order order) {
        // 这个方法走主库
        orderMapper.insert(order);
    }
}

这种方式灵活可控,但缺点是代码侵入性强,每个读方法都要加注解。如果漏加了,那个查询就会走到主库,长期下来主库压力还是大。

中间件方案

更优雅的做法是用数据库中间件做透明的读写分离。常用的有 Kingbase Proxy、Pgpool-II 等。这里以 Pgpool-II 为例简单说一下配置思路:

ini 复制代码
# pgpool.conf 关键配置
backend_hostname0 = '192.168.10.100'
backend_port0 = 54321
backend_weight0 = 1
backend_data_directory0 = '/data/kingbase/data'
backend_flag0 = 'ALLOW_TO_FAILOVER'

backend_hostname1 = '192.168.10.101'
backend_port1 = 54321
backend_weight1 = 1
backend_data_directory1 = '/data/kingbase/data'
backend_flag1 = 'DISALLOW_TO_FAILOVER'

# 启用负载均衡
load_balance_mode = on

# SELECT 语句自动分发到备库
master_slave_mode = on
master_slave_sub_mode = 'stream'

配置完之后,应用只需要连 Pgpool 的地址(比如 192.168.10.200:9999),不用关心背后有几个节点。Pgpool 会根据 SQL 类型自动路由:INSERT/UPDATE/DELETE 走主库,SELECT 走备库。

不过中间件方案也有代价:多了一层网络跳转,延迟会增加 0.5-1ms;中间件本身也要做高可用,不然它挂了整个数据库都不可用;还有一些复杂的 SQL(比如事务中的查询、存储过程调用)可能无法正确路由,需要特殊处理。

复制延迟的处理

读写分离最大的坑是复制延迟。主库刚写完一条数据,备库还没同步过去,这时候如果立即去备库查,可能查不到最新数据。这个问题在强一致性要求的场景下特别头疼。

常见的解决方案有这么几种:

方案一:关键查询强制走主库

对于那些必须读到最新数据的查询,在代码里显式指定走主库:

java 复制代码
// 不使用 @ReadOnly 注解,默认走主库
public Order getOrderDetail(Long orderId) {
    return orderMapper.selectById(orderId);
}

方案二:基于时间戳的判断

在应用层记录最后一次写操作的时间,如果查询发生在写操作之后的短时间内(比如 1 秒内),强制走主库:

java 复制代码
public class DataSourceRouter {
    private static final ThreadLocal<Long> lastWriteTime = new ThreadLocal<>();
    
    public static void markWrite() {
        lastWriteTime.set(System.currentTimeMillis());
    }
    
    public static boolean shouldRouteToMaster() {
        Long writeTime = lastWriteTime.get();
        if (writeTime == null) {
            return false;
        }
        // 如果距离上次写操作不到 1 秒,走主库
        return System.currentTimeMillis() - writeTime < 1000;
    }
}

方案三:等待备库同步

在 KES 里可以用 sys_wal_lsn 函数等待备库追上指定的 LSN 位置:

sql 复制代码
-- 在主库获取当前 LSN
SELECT sys_current_wal_lsn();

-- 在应用层把这个 LSN 传给备库,等待备库重放完成
SELECT sys_wal_lsn_wait('0/3000000', 5000); -- 最多等 5 秒

这个方案能保证强一致性,但会牺牲一些性能,适合对数据一致性要求极高的场景。

四、自动故障切换与高可用组件

主备架构再好,如果主库挂了还得人工介入切换,那也算不上真正的高可用。KES 提供了多种自动故障检测和切换的方案,从简单的脚本到完整的集群管理工具都有。

基于 watchdog 的自动切换

KES 自带的 watchdog 组件可以实现主备自动切换。watchdog 运行在每个节点上,互相心跳检测,当主库挂掉时,备库提升为主库继续提供服务。

bash 复制代码
# 在主备节点上都安装 watchdog
cd /opt/Kingbase/ES/V9/bin
./watchdog -D /data/kingbase/watchdog -f /etc/kingbase/watchdog.conf

watchdog.conf 的关键配置:

ini 复制代码
# 集群名称
cluster_name = 'kes_cluster'

# 本节点信息
hostname = 'node1'
port = 9000

# 其他节点
other_hosts = '192.168.10.101:9000'

# 虚拟 IP(VIP),客户端通过这个 IP 访问数据库
delegate_IP = '192.168.10.200'
if_cmd_path = '/sbin'
if_up_cmd = 'ip addr add $_IP_$/24 dev eth0 label eth0:0'
if_down_cmd = 'ip addr del $_IP_$/24 dev eth0'

# 健康检查
health_check_period = 10
health_check_timeout = 20
health_check_user = 'system'
health_check_password = 'System@2026'

# 故障切换策略
failover_command = '/usr/local/bin/failover.sh %d %H %P'
follow_master_command = '/usr/local/bin/follow_master.sh %d %H %m %P'

VIP(虚拟 IP)是这个方案的核心。正常情况下 VIP 绑定在主库所在的服务器上,客户端通过 VIP 连接数据库。当主库故障时,watchdog 会把 VIP 漂移到备库,同时把备库提升为主库。整个过程对应用透明,只需要重连一下数据库就行。

故障切换脚本示例

bash 复制代码
#!/bin/bash
# failover.sh - 故障切换时执行的脚本
# 参数:$1=失败节点ID, $2=新主库主机名, $3=旧主库主机名, $4=备用参数

OLD_MASTER=$3
NEW_MASTER=$2

echo "$(date): Failover triggered. Old master: $OLD_MASTER, New master: $NEW_MASTER" >> /var/log/kes_failover.log

# 在新主库上执行提升操作
ssh $NEW_MASTER "sys_ctl promote -D /data/kingbase/data"

# 可选:给旧主库发告警
curl -X POST http://alert-server/api/alert \
  -d "message=Database failover: $OLD_MASTER -> $NEW_MASTER"

第三方高可用方案

除了自带的 watchdog,还有一些成熟的第三方方案可以用,比如 Pacemaker + Corosync、Keepalived 等。这些方案的原理类似,都是通过虚拟 IP + 健康检查来实现自动切换。

以 Keepalived 为例,配置相对简单:

bash 复制代码
# 安装 Keepalived
yum install keepalived -y

# 配置 /etc/keepalived/keepalived.conf
vrrp_script chk_kes {
    script "/usr/local/bin/check_kes.sh"
    interval 2
    weight -20
}

vrrp_instance VI_KES {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    
    virtual_ipaddress {
        192.168.10.200
    }
    
    track_script {
        chk_kes
    }
}

健康检查脚本 check_kes.sh:

bash 复制代码
#!/bin/bash
# 检查 KES 是否正常
ksql -U system -d test -h localhost -p 54321 -c "SELECT 1" > /dev/null 2>&1
exit $?

Keepalived 会在主备节点之间选举出一个 MASTER,MASTER 持有 VIP。如果 MASTER 的健康检查失败,BACKUP 节点会接管 VIP。配合之前说的流复制,就能实现自动故障切换。

切换后的注意事项

自动切换虽然方便,但切换完成后还有一些收尾工作要做:

1. 重建复制关系

原来的主库修好之后,要把它变成新主库的备库。这个过程叫"重做备库":

bash 复制代码
# 在旧主库(现在是备库)上执行
# 先停止服务
sys_ctl -D /data/kingbase/data stop

# 清空数据目录
rm -rf /data/kingbase/data/*

# 从新主库重新拉取基础备份
sys_basebackup -h 192.168.10.101 -p 54321 \
  -U repl_user \
  -D /data/kingbase/data \
  -Fp -Xs -P -R

# 启动备库
sys_ctl -D /data/kingbase/data start

2. 更新应用配置

如果应用是直接连物理 IP 而不是 VIP,需要更新配置文件指向新的主库。这也是为什么推荐用 VIP 或者域名,切换的时候不用改应用配置。

3. 验证数据一致性

切换完成后要抽查一些关键数据,确保没有丢失或损坏:

sql 复制代码
-- 在新主库上检查最近的事务
SELECT max(created_at) FROM orders;
SELECT count(*) FROM transactions WHERE status = 'pending';

-- 对比切换前后的统计信息
SELECT schemaname, relname, n_live_tup, n_dead_tup
FROM sys_stat_user_tables
ORDER BY n_dead_tup DESC LIMIT 10;

五、异地容灾与多活架构

对于金融、政务这类对可用性要求极高的行业,光有同城主备还不够,还得有异地容灾。异地容灾的核心思路是:在另一个城市部署一套备库,平时做异步复制,灾难发生时切换到异地继续服务。

级联复制架构

异地容灾常用的一种拓扑是级联复制:主库 → 同城备库 → 异地备库。这样异地备库不从主库直接拉 WAL,而是从同城备库拉,减少主库的压力。

css 复制代码
[主库 北京] --同步复制--> [同城备库 北京] --异步复制--> [异地备库 上海]

配置级联复制的步骤:

bash 复制代码
# 先在同城备库上配置,允许它作为上游节点
# 在同城备库的 kingbase.conf 中
max_wal_senders = 10
hot_standby = on
hot_standby_feedback = on

# 在异地备库上执行基础备份,从同城备库拉取
sys_basebackup -h 192.168.10.101 -p 54321 \
  -U repl_user \
  -D /data/kingbase/data \
  -Fp -Xs -P -R

# 修改 kingbase.auto.conf,指向同城备库
primary_conninfo = 'host=192.168.10.101 port=54321 user=repl_user password=Repl@2026Secure application_name=standby_shanghai'

级联复制的好处是主库只需要维护一个同步连接,异地备库的延迟不会影响主库性能。缺点是如果同城备库挂了,异地备库也会断连,需要重新配置。

两地三中心架构

更完善的方案是"两地三中心":两个城市,三个数据中心。典型部署方式是:

  • 中心 A(北京机房1):主库
  • 中心 B(北京机房2):同城同步备库
  • 中心 C(上海机房):异地异步备库

这种架构能容忍单机房故障甚至单城市灾难。北京两个机房之间做同步复制保证零数据丢失,上海机房做异步复制提供灾难恢复能力。

网络规划要点

两地三中心对网络的要求比较高:

  • 同城两个机房之间的延迟要控制在 2ms 以内,带宽至少 1Gbps
  • 异地机房的延迟一般在 10-30ms,带宽根据数据量定,建议 100Mbps 起步
  • 三个机房之间要有独立的专线或者 VPN,不能用公网

切换策略

异地容灾的切换比同城切换复杂得多,因为涉及到跨城市的网络切换和业务迁移。一般会制定详细的应急预案,包括:

  1. 决策条件:什么情况下触发异地切换?通常是同城两个机房都不可用,或者预计恢复时间超过 RTO(恢复时间目标)要求。

  2. 切换流程

    • 确认主库和同城备库都不可用
    • 在异地备库上执行提升操作:sys_ctl promote
    • 更新 DNS 或者负载均衡配置,把流量切到异地
    • 通知相关业务方,启动应急流程
  3. 回切流程:灾难恢复后,如何把业务切回原主数据中心?这个过程更复杂,因为要把异地产生的新数据同步回去,通常需要一个数据比对和合并的过程。

容灾演练的重要性

很多单位建了异地容灾,但从来没演练过。真到灾难发生的时候才发现预案有问题、网络不通、权限不够,各种状况层出不穷。

建议至少每半年做一次容灾演练,模拟真实故障场景:

bash 复制代码
# 演练步骤示例
# 1. 在异地备库上执行提升
sys_ctl -D /data/kingbase/data promote

# 2. 验证备库已经变成主库
ksql -c "SELECT sys_is_in_recovery();"
# 应该返回 f(false),表示已经是主库

# 3. 写入测试数据
CREATE TABLE dr_test (id int, created_at timestamp);
INSERT INTO dr_test VALUES (1, now());

# 4. 验证数据可读写
SELECT * FROM dr_test;

# 5. 演练结束后,重新搭建复制关系
# (按照前面说的重做备库步骤)

演练不仅要验证技术层面能不能切换,还要检验业务流程、人员协作、沟通机制是否顺畅。很多时候技术问题好解决,人和流程的问题反而更棘手。

六、监控告警与日常巡检

高可用架构建好了,不代表就可以高枕无忧。持续的监控和定期的巡检才能保证系统长期稳定运行。

关键监控指标

以下是我总结的一些必须监控的核心指标:

复制延迟

sql 复制代码
-- 在主库执行,查看备库的延迟
SELECT application_name,
       sys_wal_lsn_diff(sys_current_wal_lsn(), replay_lsn) AS replay_lag_bytes,
       extract(epoch from now() - replay_timestamp) AS replay_lag_seconds
FROM sys_stat_replication;

复制延迟超过一定阈值(比如 100MB 或者 10 秒)就要告警。延迟太大会导致备库数据过时,切换的时候可能丢数据。

WAL 生成速率

sql 复制代码
-- 查看 WAL 文件的生成情况
SELECT pg_size_pretty(sum(size)) AS total_wal_size,
       count(*) AS wal_file_count
FROM pg_ls_waldir();

WAL 生成太快说明写入压力大,要关注磁盘空间。如果归档没跟上,WAL 文件堆积会把磁盘撑满。

长事务和锁等待

sql 复制代码
-- 超过 5 分钟的事务
SELECT pid, usename, now() - xact_start AS duration, query
FROM sys_stat_activity
WHERE state = 'active'
  AND now() - xact_start > INTERVAL '5 minutes'
ORDER BY duration DESC;

-- 锁等待
SELECT blocked.pid AS blocked_pid,
       blocking.pid AS blocking_pid,
       blocked.query AS blocked_query
FROM sys_stat_activity blocked
JOIN sys_locks l ON blocked.pid = l.pid AND NOT l.granted
JOIN sys_locks granted ON l.relation = granted.relation AND granted.granted
JOIN sys_stat_activity blocking ON granted.pid = blocking.pid;

长事务会阻止 VACUUM 回收死元组,导致表膨胀。锁等待会影响并发性能,严重时会引发雪崩。

磁盘空间和 inode

bash 复制代码
# 磁盘使用率
df -h /data/kingbase

# inode 使用率(有时候磁盘空间够但 inode 用完了也会出问题)
df -i /data/kingbase

磁盘使用率超过 80% 就该清理或者扩容了。WAL 文件、日志文件、临时文件都是吃磁盘的大户。

自动化巡检脚本

把这些检查项整合成一个脚本,每天定时跑:

bash 复制代码
#!/bin/bash
# daily_check.sh - KES 每日巡检脚本

DATA_DIR="/data/kingbase/data"
KSQL="ksql -U system -d test -t -A -c"
REPORT_FILE="/tmp/kes_daily_report_$(date +%Y%m%d).txt"

echo "===== KES 每日巡检报告 $(date) =====" > $REPORT_FILE

# 1. 服务状态
echo "" >> $REPORT_FILE
echo "【服务状态】" >> $REPORT_FILE
sys_ctl -D $DATA_DIR status >> $REPORT_FILE 2>&1

# 2. 复制状态
echo "" >> $REPORT_FILE
echo "【复制状态】" >> $REPORT_FILE
$KSQL "SELECT application_name, state, sync_state,
       sys_wal_lsn_diff(sys_current_wal_lsn(), replay_lsn) AS lag_bytes
       FROM sys_stat_replication;" >> $REPORT_FILE

# 3. 数据库大小
echo "" >> $REPORT_FILE
echo "【数据库大小】" >> $REPORT_FILE
$KSQL "SELECT datname, sys_size_pretty(sys_database_size(datname))
       FROM sys_database ORDER BY sys_database_size(datname) DESC;" >> $REPORT_FILE

# 4. TOP 10 大表
echo "" >> $REPORT_FILE
echo "【TOP 10 大表】" >> $REPORT_FILE
$KSQL "SELECT schemaname||'.'||relname AS table_name,
       sys_size_pretty(sys_total_relation_size(relid)) AS size
       FROM sys_stat_user_tables
       ORDER BY sys_total_relation_size(relid) DESC LIMIT 10;" >> $REPORT_FILE

# 5. 长事务
echo "" >> $REPORT_FILE
echo "【长事务(>5分钟)】" >> $REPORT_FILE
$KSQL "SELECT pid, usename, now() - xact_start AS duration
       FROM sys_stat_activity
       WHERE state = 'active'
         AND now() - xact_start > INTERVAL '5 minutes'
       ORDER BY duration DESC LIMIT 5;" >> $REPORT_FILE

# 6. 磁盘使用
echo "" >> $REPORT_FILE
echo "【磁盘使用】" >> $REPORT_FILE
df -h $DATA_DIR >> $REPORT_FILE

# 发送邮件
mail -s "KES Daily Report $(date +%Y-%m-%d)" dba-team@company.com < $REPORT_FILE

配合 crontab 每天早上 8 点执行:

bash 复制代码
# crontab -e
0 8 * * * /usr/local/bin/daily_check.sh

告警规则建议

基于上面的监控指标,设置以下告警规则:

指标 警告阈值 严重阈值 处理方式
复制延迟 > 100MB 或 > 10s > 500MB 或 > 60s 检查网络和备库负载
磁盘使用率 > 80% > 90% 清理日志或扩容
长事务 > 5 分钟 > 30 分钟 联系开发人员确认
锁等待 > 10 个 > 50 个 排查死锁或热点数据
连接数使用率 > 70% > 90% 检查连接池配置
WAL 文件数量 > 1000 > 2000 检查归档是否正常

告警可以通过邮件、短信、企业微信等多种渠道发送。重要的是告警要分级,不要什么事情都发严重告警,否则时间长了大家会麻木。

七、常见问题与排查案例

最后分享几个实际遇到过的典型问题和排查思路。

案例一:备库突然停止同步

现象:某天早上发现备库的 replay_lsn 不再更新,sys_stat_replication 里看不到备库的记录。

排查过程

  1. 先看备库的日志:
bash 复制代码
tail -f /data/kingbase/data/sys_log/kingbase-2026-06-05.log

发现大量 "could not receive data from WAL stream: FATAL: terminating connection due to administrator command"。

  1. 检查主库的 sys_hba.conf,发现有人改了配置但没有 reload,导致备库的认证失败。

  2. 重新加载配置后,备库还是连不上。再看主库日志,发现 "requested WAL segment has already been removed"。

原因:备库断开期间,主库的 WAL 文件被清理掉了,备库需要的 WAL 已经不存在。

解决:只能重做备库,用 sys_basebackup 重新拉一份基础备份。

教训

  • 改 sys_hba.conf 一定要记得 reload
  • 主库的 wal_keep_size 参数要设大一点,保留足够的 WAL 文件
  • 重要配置变更要在低峰期做,并且做好回滚预案

案例二:同步复制导致主库写入超时

现象:开启同步复制后,应用时不时报 "connection timeout",特别是晚高峰时段。

排查过程

  1. 检查主库的等待事件:
sql 复制代码
SELECT wait_event_type, wait_event, count(*)
FROM sys_stat_activity
WHERE wait_event IS NOT NULL
GROUP BY 1, 2
ORDER BY 3 DESC;

发现大量 "SyncRep" 等待,说明主库在等备库确认。

  1. 检查网络延迟:
bash 复制代码
ping 192.168.10.101
# 平均延迟 8ms,偶尔抖到 20ms+
  1. 检查备库负载,发现备库在做大批量的报表查询,CPU 打到 90%,重放 WAL 的速度跟不上。

解决

  • 把报表查询从备库迁到专门的只读实例
  • 优化备库的资源配置,增加 CPU 和内存
  • 调整同步复制策略,改成 "ANY 1 (standby1,standby2)",有两个备库时只要一个确认即可

教训:同步复制对备库的性能也很敏感,备库不只是被动接收数据,还要能快速重放。备库的资源配置不能太低,也不能在上面跑太重的查询。

案例三:VIP 漂移失败

现象:主库故障后,watchdog 检测到故障但 VIP 没有漂移到备库,导致业务中断。

排查过程

  1. 查看 watchdog 日志:
bash 复制代码
tail -f /var/log/kes_watchdog.log

发现 "failed to execute if_up_cmd: Permission denied"。

  1. 检查权限,发现 watchdog 进程是用 kingbase 用户跑的,但 ip addr add 需要 root 权限。

解决

  • 给 ip 命令加 setuid 位(不推荐,有安全风险)
  • 或者用 sudo 配置 kingbase 用户可以免密执行 ip 命令:
bash 复制代码
# /etc/sudoers
kingbase ALL=(ALL) NOPASSWD: /sbin/ip
  • 然后修改 watchdog.conf 里的 if_up_cmd:
ini 复制代码
if_up_cmd = 'sudo ip addr add $_IP_$/24 dev eth0 label eth0:0'

教训:VIP 漂移涉及系统级操作,权限配置一定要提前测试好。最好在搭建阶段就做几次模拟切换,验证整个流程是否能跑通。

写在最后

高可用架构不是一蹴而就的,需要根据业务发展逐步演进。刚开始可能就是个单机数据库,后来加了备库做备份,再后来做了读写分离,最后上了自动切换和异地容灾。每一步都要权衡成本、复杂度和收益。

KingbaseES 在高可用方面的功能还是比较完善的,流复制、同步/异步模式、watchdog、级联复制这些特性都能覆盖大部分场景。但工具再好,也得靠人来设计和运维。架构设计阶段要多考虑各种故障场景,运维阶段要做好监控和演练,这样才能真正做到"高可用"。

这篇文章提到的方案和配置都是经过实际项目验证的,但每个项目的具体情况不一样,不能完全照搬。建议在实施之前充分测试,特别是在生产环境上线之前,一定要在测试环境模拟各种故障场景,确保切换流程没有问题。

数据库高可用这条路没有终点,只有不断迭代和优化。希望这篇文章能给你一些启发,少走一些弯路。如果遇到问题,多看看官方文档,多在社区里交流,很多问题前人已经踩过坑了。共勉。

相关推荐
神奇小汤圆1 小时前
沉迷 Vibe coding 后我幡然醒悟:为什么可持续开发要回归半古法编程
后端
lichenyang4531 小时前
鸿蒙电商 Demo v2:真实商品接口 + 支付/订单闭环 + 收藏功能,外加一个 ArkUI V2 @Builder 响应式断链的硬核坑
前端·后端
前端的阶梯1 小时前
如何节省你的token,请看CodeGraph
前端·人工智能·后端
用户8356290780511 小时前
Python 在 PowerPoint 中创建箱形图
后端·python
万少2 小时前
产品原型不用从零画 -GPT 出图,Gemini 生成 HTML
前端·javascript·后端
小宇子2B2 小时前
一个 Vec 的数据到底在内存哪:栈、堆,和它们相向而行的真相
后端·编程语言
程序员黑豆3 小时前
全新系列开启:AI 全栈开发
前端·后端·全栈
自进化Agent智能体3 小时前
Skill Marketplace架构:AI能力的民主化与生态建设
后端
千云3 小时前
ClaudeCode Skill生成教学培训文档,助力新人快速学习项目
人工智能·后端·ai编程