读写分离解决方案
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)。