PostgreSQL 18 从新手到大师:实战指南 - 4.6 Citus分布式PostgreSQL
一、分布式数据库概述
随着互联网应用的快速发展,数据量呈爆炸式增长,传统的单节点数据库已经难以满足大规模数据处理的需求。分布式数据库通过将数据分散存储在多个节点上,实现了数据的水平扩展,能够处理PB级别的数据和高并发请求。
1.1 分布式数据库的优势
- 高扩展性:可以通过添加节点来线性扩展存储容量和处理能力
- 高可用性:数据副本分布在多个节点上,单个节点故障不会导致服务中断
- 高性能:并行处理查询请求,提高查询响应速度和吞吐量
- 弹性伸缩:根据业务需求动态调整集群规模
1.2 Citus简介
Citus是一个基于PostgreSQL的分布式数据库扩展,它将PostgreSQL转变为一个分布式数据库系统,同时保持了PostgreSQL的所有特性和生态系统。Citus主要用于处理大规模数据和高并发请求,特别适合实时分析、多租户应用、时序数据等场景。
Citus的主要特点包括:
- 100%兼容PostgreSQL
- 支持水平扩展
- 自动数据分片和查询并行处理
- 支持实时分析和复杂查询
- 易于部署和管理
- 活跃的社区和商业支持
二、Citus架构解析
2.1 Citus核心组件
Citus集群由以下两种类型的节点组成:
-
Coordinator Node(协调器节点):
- 负责接收客户端请求
- 解析和优化查询
- 将查询分解为多个子查询,分发给Worker节点执行
- 收集和合并Worker节点的查询结果
- 维护集群元数据
-
Worker Node(工作节点):
- 负责存储数据分片
- 执行协调器节点分发的子查询
- 将查询结果返回给协调器节点
2.2 Citus数据模型
Citus采用分片(Sharding)技术将数据分散存储在多个Worker节点上。主要有两种数据模型:
-
分布式表(Distributed Tables):
- 数据被分割成多个分片(Shards),分布在不同的Worker节点上
- 每个分片是一个普通的PostgreSQL表
- 分片由分片键(Distribution Column)决定
- 支持水平扩展
-
引用表(Reference Tables):
- 数据被复制到所有Worker节点上
- 适用于小表和经常被连接的表
- 提供快速的本地访问
-
本地表(Local Tables):
- 只存储在协调器节点上
- 适用于管理表和元数据表
2.3 Citus分片策略
Citus支持以下分片策略:
-
哈希分片(Hash Sharding):
- 基于分片键的哈希值将数据分配到不同的分片
- 数据分布均匀,适合随机访问
- 是Citus的默认分片策略
-
范围分片(Range Sharding):
- 基于分片键的范围将数据分配到不同的分片
- 适合范围查询,如时间范围查询
- 可能导致数据分布不均匀
三、Citus安装与部署
3.1 安装Citus扩展
Citus可以通过PostgreSQL扩展的方式安装,支持PostgreSQL 11及以上版本。
3.1.1 在Ubuntu 24.04上安装
# 添加Citus仓库
sudo sh -c 'echo "deb [arch=amd64] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /usr/share/keyrings/postgresql-keyring.gpg
# 更新包列表
sudo apt-get update
# 安装Citus扩展
sudo apt-get install postgresql-18-citus
3.1.3 从源码编译安装
# 克隆Citus源码
git clone https://github.com/citusdata/citus.git
cd citus
# 编译安装
./configure
make
make install
3.2 配置Citus集群
3.2.1 配置协调器节点
-
修改postgresql.conf文件:
# 启用Citus扩展 shared_preload_libraries = 'citus' # 监听所有IP地址 listen_addresses = '*' # 启用远程连接 max_connections = 100 -
修改pg_hba.conf文件:
# 允许Worker节点连接 host all all 0.0.0.0/0 scram-sha-256 -
重启PostgreSQL服务:
sudo systemctl restart postgresql-18
4. **创建Citus扩展**:
```sql
CREATE EXTENSION citus;
3.2.2 配置Worker节点
-
修改postgresql.conf文件:
# 启用Citus扩展 shared_preload_libraries = 'citus' # 监听所有IP地址 listen_addresses = '*' # 启用远程连接 max_connections = 100 # 启用流复制 wal_level = logical max_wal_senders = 10 max_replication_slots = 10 -
修改pg_hba.conf文件:
# 允许协调器节点连接 host all all 0.0.0.0/0 scram-sha-256 -
重启PostgreSQL服务:
sudo systemctl restart postgresql-18
4. **创建Citus扩展**:
```sql
CREATE EXTENSION citus;
3.2.3 添加Worker节点到集群
在协调器节点上执行以下命令,将Worker节点添加到集群中:
-- 添加Worker节点
SELECT * FROM citus_add_node('worker1.example.com', 5432);
SELECT * FROM citus_add_node('worker2.example.com', 5432);
SELECT * FROM citus_add_node('worker3.example.com', 5432);
-- 查看集群状态
SELECT * FROM citus_nodes;
3.3 使用Docker部署Citus集群
Citus提供了官方Docker镜像,可以快速部署Citus集群。
3.3.1 使用Docker Compose部署
创建docker-compose.yml文件:
version: '3.8'
services:
coordinator:
image: citusdata/citus:18-alpine
ports:
- "5432:5432"
environment:
- POSTGRES_PASSWORD=mysecretpassword
volumes:
- coordinator_data:/var/lib/postgresql/data
command: >
bash -c "
echo "host all all 0.0.0.0/0 scram-sha-256" >> /var/lib/postgresql/data/pg_hba.conf &&
pg_ctl reload &&
psql -U postgres -c 'CREATE EXTENSION citus;'
"
worker1:
image: citusdata/citus:18-alpine
environment:
- POSTGRES_PASSWORD=mysecretpassword
volumes:
- worker1_data:/var/lib/postgresql/data
command: >
bash -c "
echo "host all all 0.0.0.0/0 scram-sha-256" >> /var/lib/postgresql/data/pg_hba.conf &&
pg_ctl reload
"
worker2:
image: citusdata/citus:18-alpine
environment:
- POSTGRES_PASSWORD=mysecretpassword
volumes:
- worker2_data:/var/lib/postgresql/data
command: >
bash -c "
echo "host all all 0.0.0.0/0 scram-sha-256" >> /var/lib/postgresql/data/pg_hba.conf &&
pg_ctl reload
"
manager:
image: citusdata/membership-manager:0.3.0
volumes:
- /var/run/docker.sock:/var/run/docker.sock
depends_on:
- coordinator
environment:
- POSTGRES_PASSWORD=mysecretpassword
volumes:
coordinator_data:
worker1_data:
worker2_data:
启动集群:
docker-compose up -d
添加Worker节点到集群:
# 连接到协调器节点
docker-compose exec coordinator psql -U postgres
# 添加Worker节点
SELECT * FROM citus_add_node('worker1', 5432);
SELECT * FROM citus_add_node('worker2', 5432);
# 查看集群状态
SELECT * FROM citus_nodes;
四、Citus分布式表设计
4.1 创建分布式表
创建分布式表是Citus使用的核心,需要指定分片键和分片策略。
4.1.1 创建哈希分布式表
-- 创建分布式表
CREATE TABLE events (
event_id BIGSERIAL PRIMARY KEY,
user_id INT NOT NULL,
event_type VARCHAR(50) NOT NULL,
event_data JSONB,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- 将表分布到Worker节点上,使用user_id作为分片键
SELECT create_distributed_table('events', 'user_id');
4.1.2 创建范围分布式表
-- 创建范围分布式表
CREATE TABLE time_series (
id BIGSERIAL PRIMARY KEY,
timestamp TIMESTAMP NOT NULL,
value DOUBLE PRECISION NOT NULL,
tags JSONB
);
-- 将表分布到Worker节点上,使用timestamp作为分片键,采用范围分片策略
SELECT create_distributed_table('time_series', 'timestamp', distribution_type => 'range');
4.2 创建引用表
引用表适合存储小表和经常被连接的表,数据会被复制到所有Worker节点上。
-- 创建引用表
CREATE TABLE users (
user_id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- 将表转换为引用表
SELECT create_reference_table('users');
4.3 表设计最佳实践
-
选择合适的分片键:
- 选择基数高的列作为分片键,如用户ID、产品ID
- 选择经常用于连接条件的列作为分片键
- 避免选择频繁更新的列作为分片键
-
合理设计表结构:
- 对于大表使用分布式表
- 对于小表和经常被连接的表使用引用表
- 保持表结构简单,避免过度规范化
-
优化连接操作:
- 尽量使用协同定位连接(Co-located Joins),即连接条件包含分片键
- 避免跨分片连接,这会导致网络开销
-
考虑数据分布:
- 确保数据分布均匀,避免数据倾斜
- 定期监控数据分布情况
五、Citus查询优化
5.1 Citus查询处理流程
Citus的查询处理流程如下:
- 查询接收:协调器节点接收客户端查询
- 查询解析:解析查询语句,生成查询树
- 查询优化:优化查询计划,考虑数据分布
- 查询分解:将查询分解为多个子查询
- 子查询执行:将子查询分发到相应的Worker节点执行
- 结果收集:收集Worker节点的查询结果
- 结果合并:合并查询结果,返回给客户端
5.2 查询优化技巧
5.2.1 使用协同定位连接
协同定位连接是指连接条件包含分片键,这样连接操作可以在本地完成,不需要跨节点数据传输。
-- 协同定位连接示例:events表和users表都按user_id分片
SELECT e.event_id, u.name, e.event_type, e.created_at
FROM events e
JOIN users u ON e.user_id = u.user_id
WHERE e.user_id = 123;
5.2.2 避免跨分片查询
尽量避免需要访问所有分片的查询,如不带分片键过滤条件的查询。
-- 不好的查询:需要访问所有分片
SELECT COUNT(*) FROM events;
-- 好的查询:只访问特定分片
SELECT COUNT(*) FROM events WHERE user_id = 123;
5.2.3 使用聚合下推
Citus支持聚合下推,将聚合函数推送到Worker节点执行,减少网络传输数据量。
-- 聚合下推示例:COUNT函数会在Worker节点执行
SELECT user_id, COUNT(*) AS event_count
FROM events
GROUP BY user_id;
5.2.4 使用WHERE条件过滤
在查询中添加WHERE条件,减少需要处理的数据量。
-- 不好的查询:返回大量数据
SELECT * FROM events;
-- 好的查询:添加过滤条件
SELECT * FROM events WHERE created_at > NOW() - INTERVAL '7 days';
5.3 监控查询性能
Citus提供了多种方式监控查询性能:
-
使用EXPLAIN ANALYZE:
EXPLAIN ANALYZE SELECT COUNT(*) FROM events WHERE user_id = 123; -
使用pg_stat_statements:
-- 安装扩展 CREATE EXTENSION pg_stat_statements; -- 查看慢查询 SELECT query, calls, total_time, rows FROM pg_stat_statements ORDER BY total_time DESC LIMIT 10; -
使用Citus统计视图:
-- 查看分片统计信息 SELECT * FROM citus_shards; -- 查看节点统计信息 SELECT * FROM citus_node_statistics;
六、Citus高可用与故障恢复
6.1 Citus高可用架构
Citus通过以下方式实现高可用性:
- 数据副本:每个分片可以有多个副本,分布在不同的Worker节点上
- 节点故障检测:协调器节点定期检测Worker节点的健康状态
- 自动故障转移:当Worker节点故障时,自动将分片转移到健康的Worker节点
- 流复制:使用PostgreSQL的流复制技术保持副本同步
6.2 配置数据副本
可以在创建分布式表时指定副本数量,默认副本数量为2。
-- 创建分布式表时指定副本数量
SELECT create_distributed_table('events', 'user_id', shard_replication_factor => 3);
6.3 处理节点故障
当Worker节点故障时,Citus会自动检测并进行故障转移。
6.3.1 查看节点状态
-- 查看节点状态
SELECT * FROM citus_nodes;
6.3.2 手动移除故障节点
-- 手动移除故障节点
SELECT * FROM citus_remove_node('failed-worker.example.com', 5432);
6.3.3 添加新节点替换故障节点
-- 添加新节点
SELECT * FROM citus_add_node('new-worker.example.com', 5432);
-- 重新平衡分片
SELECT rebalance_table_shards('events');
七、Citus应用场景
7.1 实时分析
Citus适合处理大规模实时分析场景,如:
- 用户行为分析
- 日志分析
- 实时仪表板
- 广告投放分析
7.2 多租户应用
Citus的分片架构非常适合多租户应用,如:
- SaaS应用
- 云服务平台
- 多租户数据库服务
7.3 时序数据
Citus支持范围分片,适合处理时序数据,如:
- IoT传感器数据
- 监控数据
- 金融交易数据
- 网络流量数据
7.4 大规模并行处理
Citus能够并行处理大规模查询,适合:
- 数据仓库
- 批处理作业
- 大规模数据转换
八、Citus最佳实践
-
选择合适的分片键:
- 选择基数高的列
- 选择经常用于连接条件的列
- 避免选择频繁更新的列
-
优化表设计:
- 大表使用分布式表
- 小表和经常被连接的表使用引用表
- 保持表结构简单
-
优化查询:
- 使用协同定位连接
- 避免跨分片查询
- 使用聚合下推
- 添加过滤条件
-
监控和调优:
- 监控节点和分片状态
- 分析查询性能
- 定期重新平衡分片
- 调整PostgreSQL参数
-
高可用配置:
- 配置足够的副本数量
- 定期备份数据
- 测试故障恢复流程
九、实战案例:构建实时分析平台
9.1 需求分析
假设我们需要构建一个实时分析平台,用于分析用户行为数据,要求:
- 支持每秒处理10,000条事件数据
- 支持实时查询和分析
- 支持多维度分析
- 数据保留期为1年
9.2 架构设计
- 数据采集层:使用Kafka收集用户行为数据
- 数据处理层:使用Apache Flink实时处理数据
- 数据存储层:使用Citus存储处理后的数据
- 查询分析层:使用PostgreSQL客户端和BI工具进行查询分析
9.3 表设计
9.3.1 事件表
-- 创建事件表
CREATE TABLE events (
event_id BIGSERIAL PRIMARY KEY,
user_id INT NOT NULL,
session_id VARCHAR(100) NOT NULL,
event_type VARCHAR(50) NOT NULL,
event_data JSONB,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
country VARCHAR(50),
city VARCHAR(100),
device_type VARCHAR(50),
browser VARCHAR(50)
);
-- 分布式表,按user_id分片
SELECT create_distributed_table('events', 'user_id', shard_replication_factor => 2);
9.3.2 用户表
-- 创建用户表
CREATE TABLE users (
user_id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
last_login_at TIMESTAMP
);
-- 引用表,复制到所有节点
SELECT create_reference_table('users');
9.4 数据导入
使用COPY命令导入数据:
-- 从CSV文件导入数据
COPY events (user_id, session_id, event_type, event_data, created_at, country, city, device_type, browser)
FROM '/path/to/events.csv'
DELIMITER ','
CSV HEADER;
9.5 实时查询示例
-- 查询最近24小时的事件数量
SELECT COUNT(*) AS event_count
FROM events
WHERE created_at > NOW() - INTERVAL '24 hours';
-- 按事件类型分组查询
SELECT event_type, COUNT(*) AS event_count
FROM events
WHERE created_at > NOW() - INTERVAL '24 hours'
GROUP BY event_type
ORDER BY event_count DESC;
-- 按国家和设备类型分组查询
SELECT country, device_type, COUNT(*) AS event_count
FROM events
WHERE created_at > NOW() - INTERVAL '24 hours'
GROUP BY country, device_type
ORDER BY event_count DESC;
-- 连接用户表查询
SELECT u.name, COUNT(*) AS event_count
FROM events e
JOIN users u ON e.user_id = u.user_id
WHERE e.created_at > NOW() - INTERVAL '24 hours'
GROUP BY u.name
ORDER BY event_count DESC
LIMIT 10;
十、总结
Citus是一个强大的PostgreSQL分布式扩展,它将PostgreSQL转变为一个分布式数据库系统,同时保持了PostgreSQL的所有特性和生态系统。Citus适合处理大规模数据和高并发请求,特别适合实时分析、多租户应用、时序数据等场景。
通过合理的表设计、查询优化和高可用配置,可以充分发挥Citus的性能优势,构建高性能、可扩展的分布式数据库系统。
Citus的主要优势包括:
- 100%兼容PostgreSQL,易于迁移和学习
- 支持水平扩展,能够处理PB级别的数据
- 自动数据分片和查询并行处理,提高查询性能
- 支持多种数据模型,灵活适应不同应用场景
- 活跃的社区和商业支持
随着数据量的不断增长,分布式数据库已经成为现代应用系统的必备组件。Citus作为PostgreSQL的分布式扩展,为用户提供了一种简单、高效的方式来构建分布式数据库系统,值得在实际项目中推广和应用。
更多推荐


所有评论(0)