跳到主要内容

读写分离解决方案

Halo 读写分离原理是在主服务器上写,在从服务器上读。主服务器负责一切写操作的负载,而读的操作分摊在所有主从服务器上,通过读写分离来提升数据库的并发和负载能力。

一般设置一台数据库服务器作为主服务器(RW,读写节点),主要承担数据的增、删和改的任务;配置 N 台数据库服务器为从服务器(RO,只读节点),主要承担数据的查询任务。从服务器实时复制主服务器的数据,以此保证数据的一致性。

读写分离的实现方式​

Halo 的读写分离(DLB,Database Load Balancer)由数据库内核原生实现,无需部署独立的中间件或代理进程。其核心机制为:

  • 主库作为读写节点(RW),承担全部写操作;
  • 从库通过参数 instance_type = 'DLB' 标识为只读节点(RO),并以 nac_servername 指向主库,自动注册为读节点;
  • 主从之间通过流复制保持数据同步;
  • 应用连接 DLB 入口后,读请求由内核在多个 RO 节点间分摊,实现读负载均衡,写请求则统一发往主库。

说明:DLB 在主从之间建立通道时使用 dlb_fdw 外部数据包装器扩展。该扩展仅用于节点间连接管理,读写分离与负载均衡的调度逻辑由内核完成,无需额外配置负载均衡规则。

读写分离架构

读写分离的配置步骤​

下面以一台主库(${主库IP})和两台从库(${备库1IP}、${备库2IP})为例,说明 DLB 读写分离的配置过程。最小可部署拓扑为一主一从;增加从库即扩大读负载均衡池。

执行环境说明:以下命令与输出基于 Halo 1.0.16 实测。示例中 ${主库IP} 等为占位符,部署时替换为实际 IP;密码请使用实际强密码,不要照抄示例。

主库访问配置​

修改主库数据目录下的 pg_hba.conf,放通从库的复制连接与普通访问连接:

vi $PGDATA/pg_hba.conf

添加以下两行(0.0.0.0/0 表示允许任意 IP,生产环境应按实际网段收窄):

# TYPE DATABASE USER ADDRESS METHOD
host all all 0.0.0.0/0 md5
host replication replica 0.0.0.0/0 md5

重新加载配置使其生效:

pg_ctl reload
server signaled

主库参数配置​

确认主库 postgresql.conf 中以下参数(默认安装通常已满足,仅需核对):

listen_addresses = '*'
port = 1921
wal_level = replica
max_wal_senders = 10 # 需大于等于从库数量
hot_standby = on

若修改了参数,重启主库使其生效:

pg_ctl restart

主库数据库配置​

连接主库默认数据库,创建流复制用户、DLB 管理用户与元数据库:

hsql -p 1921 -d halo0root
halo0root=# CREATE USER replica PASSWORD '${实际密码}' REPLICATION;
CREATE ROLE

halo0root=# CREATE USER nac SUPERUSER PASSWORD '${实际密码}';
CREATE ROLE

halo0root=# CREATE DATABASE metadata;
CREATE DATABASE

切换到 metadata 库(以 nac 用户连接),创建 dlb_fdw 扩展:

hsql -U nac -d metadata
metadata=# CREATE EXTENSION dlb_fdw CASCADE;
CREATE EXTENSION

说明:若主库启用了 Oracle 兼容模式,还需先执行 CREATE EXTENSION aux_oracle CASCADE;。默认(非 Oracle)模式下无需创建。

在主库为从库创建外部服务器(server)与用户映射(user mapping),用于主从之间的连接通道:

metadata=# CREATE SERVER nac_server_01
FOREIGN DATA WRAPPER dlb_fdw
OPTIONS (host '${主库IP}', port '1921', dbname 'halo0root');
CREATE SERVER

metadata=# CREATE USER MAPPING FOR nac
SERVER nac_server_01
OPTIONS (user 'nac', password '${实际密码}');
CREATE USER MAPPING

从库配置​

1)初始化从库

在每台从库主机上,使用 pg_basebackup 从主库克隆数据,并同时创建物理复制槽(每个从库使用不同的复制槽名称):

pg_basebackup -F p -X stream -v -P -h ${主库IP} -p 1921 -U replica \
-D $PGDATA -R -C --slot ${复制槽名称}

以第一台从库为例,执行结果如下:

pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/6000028 on timeline 1
pg_basebackup: starting background WAL receiver
pg_basebackup: created replication slot "standby1_slot"
166290/166290 kB (100%), 1/1 tablespace
pg_basebackup: base backup completed

注意:pg_basebackup 克隆生成的数据目录权限可能为 1777,Halo 要求其权限为 0700 或 0750,否则启动会报 data directory has invalid permissions。克隆完成后需手动修正:

chmod 0700 $PGDATA

2)从库参数配置

修改从库 postgresql.conf,将本节点标识为 DLB 只读节点:

vi $PGDATA/postgresql.conf
nac_servername = 'nac_server_01' # 与主库创建的 server 名一致
instance_type = 'DLB' # 标识此节点为 DLB 只读节点

启动从库使参数生效:

pg_ctl start
waiting for server to start.... done
server started

说明:每增加一台从库,重复本节"初始化从库"与"从库参数配置"两步即可,新节点会自动加入读负载均衡池。无需在主库额外登记。

至此,DLB 读写分离配置完成。

读写分离的验证​

查看复制状态​

在主库查看从库的复制连接状态,确认所有 RO 节点已在线:

halo0root=# SELECT application_name, client_addr, state, sync_state
FROM pg_stat_replication;

一主两从的预期输出:

application_name | client_addr | state | sync_state
------------------+---------------+-----------+------------
walreceiver | ${备库1IP} | streaming | async
walreceiver | ${备库2IP} | streaming | async
(2 rows)

查看复制槽的激活状态:

halo0root=# SELECT slot_name, active FROM pg_replication_slots;
slot_name | active
----------------+--------
standby1_slot | t
standby2_slot | t
(2 rows)

验证读写分离效果​

在主库执行写操作:

halo0root=# CREATE TABLE dlb_test(id int, msg text);
CREATE TABLE

halo0root=# INSERT INTO dlb_test VALUES (1, 'from_master');
INSERT 0 1

在从库查询,确认数据已同步:

-- 在从库执行
SELECT * FROM dlb_test;
id | msg
----+-------------
1 | from_master
(1 row)

在从库尝试写操作,确认被拒绝(只读节点不允许写):

-- 在从库执行
INSERT INTO dlb_test VALUES (2, 'try_from_standby');
ERROR: cannot execute INSERT in a read-only transaction

确认从库处于只读恢复模式:

-- 在从库执行
SELECT pg_is_in_recovery();
pg_is_in_recovery
-------------------
t
(1 row)

说明:上述验证表明——写操作仅能在主库执行,从库为只读节点且数据与主库实时同步。当存在多个从库时,读请求可由内核分发到任一 RO 节点,实现读负载均衡;复制延迟通常在毫秒级。

说明:若需实现"应用连接只读节点、写请求透明转发至主库"的能力,请参见透明写重定向(TWR)。