PostgreSQL + Redis + Elasticsearch 实时同步方案实践:从触发器到高性能搜索

在现代系统架构中,我们常常既希望:

  • PostgreSQL 担任主数据存储;
  • Redis 提供高速缓存;
  • Elasticsearch 提供模糊搜索和全文索引。

但如何让这三者实时同步数据 ,既可靠又简单?

本文将带你从原理到实现,构建一个轻量级、高性能、可扩展的同步方案。


一、问题背景

在中大型业务系统中,我们常见这样的三层数据结构:

系统 职责 特点
PostgreSQL 结构化主数据存储 强一致、可靠
Redis 高频访问缓存 高速读写
Elasticsearch 搜索/模糊查询 支持全文匹配、分词

理想状态下,当 PostgreSQL 中的数据发生变化时:

  • Redis 缓存应立即更新;
  • Elasticsearch 索引应保持一致。

但如果数据量大、变更频繁,人工同步或定时同步就会滞后。

这时我们需要一种轻量但实时的方案。


二、常见同步方案对比

方案 实时性 实现复杂度 运维成本 适用场景
定时扫描 + 更新时间字段 分钟级 简单系统
Kafka / Debezium CDC 毫秒级 ⭐⭐⭐⭐ ⭐⭐⭐ 大型分布式系统
Trigger + LISTEN/NOTIFY + Worker 秒级 ⭐⭐ ⭐⭐ ✅ 中小系统首选

📌 本文选用第三种方案:

PostgreSQL Trigger + LISTEN/NOTIFY + 异步 Worker 实时同步

它无需额外组件,延迟可低至 1 秒以内,兼顾可靠性与简洁性。


三、系统架构设计

同步流程如下图所示:
AFTER Trigger LISTEN 订阅 查询变更记录 更新/删除 PostgreSQL 表 NOTIFY 通知 JSON Worker 程序 Redis 缓存 Elasticsearch 索引

🔧 核心机制说明:

  1. Trigger:PostgreSQL 在表数据增删改时触发。
  2. NOTIFY:数据库内置的轻量级消息通道。
  3. Worker:独立进程监听事件,异步更新 Redis/ES。
  4. 幂等性设计:重复更新不会出错,保证数据最终一致。

四、PostgreSQL 端实现

1️⃣ 创建触发函数

sql 复制代码
CREATE OR REPLACE FUNCTION notify_data_change()
RETURNS trigger AS $$
DECLARE
  payload JSON;
BEGIN
  IF (TG_OP = 'DELETE') THEN
    payload := json_build_object('table', TG_TABLE_NAME, 'action', TG_OP, 'id', OLD.id);
  ELSE
    payload := json_build_object('table', TG_TABLE_NAME, 'action', TG_OP, 'id', NEW.id);
  END IF;

  PERFORM pg_notify('data_changes', payload::text);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

2️⃣ 为目标表添加触发器

sql 复制代码
CREATE TRIGGER data_change_trigger
AFTER INSERT OR UPDATE OR DELETE ON your_table
FOR EACH ROW EXECUTE FUNCTION notify_data_change();

⚠️ 注意:

  • 触发器只传递轻量级 JSON(表名 + 操作 + 主键 ID);
  • Worker 再根据 ID 查询最新完整数据。

五、Worker 实时监听与同步实现

以下示例使用 Python + psycopg2 + redis + elasticsearch-py

python 复制代码
import psycopg2, select, json, redis
from elasticsearch import Elasticsearch

# 初始化连接
r = redis.Redis(host='localhost', port=6379, db=0)
es = Elasticsearch(['http://localhost:9200'])
conn = psycopg2.connect("dbname=test user=postgres password=123456")
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)

cur = conn.cursor()
cur.execute("LISTEN data_changes;")
print("Listening on channel data_changes...")

def fetch_row(cur, table, id):
    cur.execute(f"SELECT * FROM {table} WHERE id = %s", (id,))
    return cur.fetchone(), [desc[0] for desc in cur.description]

while True:
    if select.select([conn], [], [], 5) == ([], [], []):
        continue
    conn.poll()
    while conn.notifies:
        notify = conn.notifies.pop(0)
        payload = json.loads(notify.payload)
        action, table, id_ = payload['action'], payload['table'], payload['id']

        if action in ['INSERT', 'UPDATE']:
            cur2 = conn.cursor()
            row, columns = fetch_row(cur2, table, id_)
            if not row:
                continue
            doc = dict(zip(columns, row))
            # Redis 同步
            r.hset(f"{table}:{id_}", mapping=doc)
            # Elasticsearch 同步
            es.index(index=table, id=id_, document=doc)
            cur2.close()

        elif action == 'DELETE':
            r.delete(f"{table}:{id_}")
            es.delete(index=table, id=id_, ignore=[404])

六、可靠性与性能优化

问题 解决方案
Worker 停机期间可能漏消息 启动时根据 updated_at 字段扫描补偿
通知频繁引发阻塞 Worker 内部用队列异步处理(如 asyncio 或 Redis Stream)
Redis/ES 更新失败 增加重试机制或死信队列
数据量极大 可考虑引入 Kafka / Debezium 替代触发器同步

七、方案优劣对比总结

特性 Trigger + LISTEN Debezium (CDC) 定时扫描
实时性 ✅ 秒级 ✅ 毫秒级 ❌ 分钟级
复杂度 ⭐⭐ ⭐⭐⭐⭐
成本 ⭐⭐ ⭐⭐⭐⭐
可维护性 ✅ 高 ⚠️ 中 ✅ 高
适用规模 中小型 大型 小型
实现语言 任意 (Python/Node/Go) Java/Kafka 任意

📌 结论:

对于中小型项目、单库多缓存/索引场景,

最推荐方案是 ------
PostgreSQL Trigger + LISTEN/NOTIFY + 异步 Worker 实时同步


八、最终方案架构图

事件通知 Worker 拉取数据 同步索引 监听 LISTEN data_changes 更新 Redis 更新 Elasticsearch PostgreSQL Trigger AFTER INSERT/UPDATE/DELETE 表 your_table NOTIFY data_changes


九、结语

本方案实现了:

  • 🔁 PostgreSQL、Redis、Elasticsearch 的秒级数据一致;
  • ⚡ 支持模糊搜索(由 ES 负责);
  • 🧩 低耦合、可扩展、可监控;
  • 🧰 部署简单,无需引入重型中间件。

对于希望"简单、实时、可靠"的中小团队来说,

这就是一条足够优雅的生产级道路


实用小工具

App Store 截图生成器应用图标生成器在线图片压缩Chrome插件-强制开启复制-护眼模式-网页乱码设置编码
乖猫记账,AI智能分类的最佳聊天记账App。
Elasticsearch可视化客户端工具

相关推荐
Elastic 中国社区官方博客8 小时前
如何减少 Elasticsearch 集群中的分片数量
大数据·数据库·elasticsearch·搜索引擎·全文检索
顧棟8 小时前
【ES实战】ES6.8到9.1.4的常用客户端变化
elasticsearch
yinke小琪9 小时前
从秒杀系统崩溃到支撑千万流量:我的Redis分布式锁踩坑实录
java·redis·后端
知识浅谈9 小时前
Elasticsearch 核心知识点全景解读
大数据·elasticsearch·搜索引擎
会跑的葫芦怪9 小时前
Go语言操作Redis
开发语言·redis·golang
Komorebi_999910 小时前
Git 常用命令完整指南
大数据·git·elasticsearch
还是鼠鼠11 小时前
《黑马商城》Elasticsearch基础-详细介绍【简单易懂注释版】
java·spring boot·spring·elasticsearch·搜索引擎·spring cloud·全文检索
阿湯哥12 小时前
Redis数据库隔离业务缓存对查询性能的影响分析
数据库·redis·缓存
麦兜*12 小时前
Redis 7.2 新特性实战:Client-Side Caching(客户端缓存)如何大幅降低延迟?
数据库·spring boot·redis·spring·spring cloud·缓存·tomcat