不久前,北京心玥软件公司在一个软件项目中遇到了一个具体任务:需要在两个外部应用之间共享部分数据,尤其是要让两个应用以相同结构同步数据,确保双方都能访问到一致的信息。这意味着,任何一方新增数据集或更新数据时,另一方也必须同步变化。这听起来像是开发者时不时会遇到的常规需求,对吧?
但在我们的案例中,还存在一些额外限制,导致解决方案并不直观。接下来,我将深入探讨这个问题,分析如何实现这种功能,最后分享我针对该任务的解决方案。
目录
1. 什么是数据同步?
2. 需求与限制
3. 应用程序编程接口(API)
4. 跳出思维定式
5. 核心在于扩展
6. 教程:如何实现数据同步?
7. 一切尽在……表中
8. 双向同步:数据的“镜像”
9. 触发同步机制
10. 保持实时同步
11. 确保顺序一致性
12. 数据同步的挑战
13. 确保数据完整性
14. 数据同步总结
什么是数据同步?
数据同步是指确保多台设备或系统持有相同数据的过程。此外,当数据发生变更时,所有关联设备或系统需实时感知这些变化。在数字化时代,数据需要持续生成、更新和共享,这使得数据同步至关重要——它为利益相关方提供了最新、准确的信息洞察,进而保障决策的可靠性。无论是移动设备、Web应用还是数据库,核心目标都是保持所有平台间数据的一致性。
需求与限制
为了简化问题、聚焦核心矛盾,我们对任务背景做了适当调整:
如前所述,我们需要在两个独立应用中同步相同数据,确保跨应用的数据一致性和可靠性是关键目标。
现在补充具体限制条件:
• 两个应用运行在独立的服务器上;
• 均采用Rails框架开发;
• 均使用Postgres存储数据;
• 功能交付时间紧迫;
• 必须保证数据修改在双应用间实时同步,以维护数据完整性。
应用程序编程接口(API)
直观的想法是通过REST API实现同步——这在多数场景下是可行方案。但我们的案例有个特殊限制:其中一个应用是遗留系统(长期未维护,无人清楚其内部逻辑)。此时,开发双端API、设置数据变更触发机制的方案可能面临额外风险(例如修改遗留系统时意外破坏原有功能)。如果你曾接触过“前任开发者避之不及”的遗留代码,应该能体会这种担忧。
跳出思维定式
既然API方案存在隐患,我们需要从更底层的角度思考:问题的核心是数据库中的数据本身,而非应用层逻辑。因此,我们可以将工作完全放在数据库层面完成——应用无需感知同步过程(甚至可能被“蒙在鼓里”),这既简化了流程,也规避了遗留系统的干扰。
核心在于扩展
关键在于掌握Postgres的扩展(Extension)能力。这里要介绍的是postgres_fdw扩展——它是一个“外部数据包装器”,支持访问其他PostgreSQL服务器上的数据。接下来,我们将聚焦代码实现。
教程:如何实现数据同步?
步骤1:安装扩展(双应用均需执行)
在Postgres控制台(需具备足够权限的root用户)执行以下命令:
CREATE EXTENSION postgres_fdw;
步骤2:创建远程服务器配置(以新应用连接遗留应用为例)
根据postgres_fdw官方文档,需先定义远程服务器信息(如主机、端口、数据库名)。假设遗留应用的地址为legacy_host,数据库名为legacy_database,端口5432,则执行:
CREATE SERVER legacy_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'legacy_host', dbname 'legacy_database', port '5432');
步骤3:验证远程服务器配置
输入\des+查看已配置的外部服务器,应看到类似以下结果:
List of foreign servers
Name | Owner | Foreign-data wrapper | FDW options
---------------+----------+----------------------+---------------------------------------------------------
legacy_server | postgres | postgres_fdw | (host 'legacy_host', dbname 'legacy_app', port '5432')
(1 row)
步骤4:配置用户映射
为新应用的用户分配访问遗留数据库的权限。假设遗留应用的用户是abcdef,密码是iwillnotrevealmypassword,则执行:
CREATE USER MAPPING FOR current_user SERVER legacy_server OPTIONS (user 'abcdef', password 'iwillnotrevealmypassword');
步骤5:创建外部表映射(关键操作)
为避免表名冲突,我们为遗留应用的products表添加legacy_前缀。通过以下命令创建外部表(结构需与原表一致):
CREATE FOREIGN TABLE legacy_products ( id bigint, name varchar(255), description varchar(255), price integer, created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL ) server legacy_server OPTIONS (table_name 'products');
此时,新应用可直接操作legacy_products表(查询、插入、删除、更新),其行为与操作本地表完全一致。在Rails应用层,我们甚至可以为legacy_products表创建对应的ActiveRecord模型,团队成员可无缝使用,无需额外说明。
双向同步:数据的“镜像”
若需双向同步,只需在遗留应用侧重复上述步骤(创建指向新应用的外部表、配置用户映射等),即可实现双向数据同步。
保持实时同步
新增数据同步(触发器方案)
根据需求,任一应用新增数据时,另一应用需同步新增。我们可以通过触发器(Trigger)实现这一逻辑:
在products表上创建插入触发器,触发时调用自定义函数insert_legacy_products():
CREATE TRIGGER AfterProductsInsert AFTER insert ON products FOR EACH ROW EXECUTE PROCEDURE insert_legacy_products();
定义触发函数(插入新数据到legacy_products表):
CREATE OR replace FUNCTION insert_legacy_products() RETURNS trigger AS $$ BEGIN insert INTO legacy_products( id, name, description, price, created_at, updated_at ) values( NEW.id, NEW.name, NEW.description, NEW.price, clock_timestamp(), clock_timestamp() ); RETURN NEW; EXCEPTION WHEN undefined_table THEN RETURN NEW; END; $$ LANGUAGE 'plpgsql';
更新数据同步(触发器方案)
同理,任一应用更新数据时,另一应用需同步更新。我们可以通过类似的触发器和函数实现:
定义更新函数:
CREATE OR replace FUNCTION update_legacy_products() RETURNS trigger AS $$ BEGIN UPDATE legacy_products SET name = NEW.name, description = NEW.description, price = NEW.price, updated_at = NEW.updated_at WHERE id = NEW.id; RETURN NEW; EXCEPTION WHEN undefined_table THEN RETURN NEW; END; $$ LANGUAGE 'plpgsql'; 创建更新触发器: CREATE TRIGGER AfterProductsUpdate AFTER UPDATE ON products FOR EACH ROW EXECUTE PROCEDURE update_legacy_products();
确保顺序一致性(序列同步)
实际开发中,我们遇到了一个意外问题:当遗留应用新增数据时,新应用的id字段出现重复错误。这是因为新应用的products_id_seq序列(用于生成自增id)仅在本地递增,而遗留应用的序列未同步。
解决方案是利用视图(View)和外部表:
1. 在遗留应用侧创建视图,每次查询时自动递增序列:
CREATE VIEW products_id_seq_view AS SELECT nextval('products_id_seq') as next_id;
2. 在新应用侧创建外部表,映射该视图:
CREATE FOREIGN TABLE foreign_products_id_seq (next_id bigint) server legacy OPTIONS (table_name 'products_id_seq_view');
3. 在新应用插入数据后,查询该外部表以同步序列:
PERFORM next_id FROM foreign_products_id_seq;
数据同步的挑战
数据同步(尤其是分布式系统中的同步)可能面临以下挑战:
• 数据完整性:确保所有设备/系统的数据准确、完整、一致;
• 数据冲突:多用户/设备同时修改同一数据时的冲突解决;
• 更新效率:大规模系统中,如何高效同步所有更新;
• 数据安全:传输和存储过程中的加密与防泄露。
确保数据完整性
保障数据完整性的策略包括:
• 数据验证规则:同步前校验数据准确性;
• 校验和/数字签名:防止传输或存储中的数据篡改;
• 备份与恢复:定期备份,应对数据丢失或损坏;
• 数据审计:定期检查数据一致性,及时修复错误。
数据同步总结
本文通过具体案例演示了如何利用Postgres扩展(如postgres_fdw)和触发器实现跨应用数据同步。考虑到开发者更习惯通过代码理解逻辑,我们在北京心玥软件公司中提供了完整的Docker化示例(包含两个应用和独立数据库服务器),详细步骤可参考README。
本次探索的核心启示是:最直观的方案未必最适合具体场景。遇到问题时,不妨拆解问题、转换视角,预判不同方案的可行性,再选择最优路径。正如我的物理老师常说的:“动手前先思考十遍。”