电话

18600577194

当前位置: 首页 > 资讯观点 > 软件开发

软件开发教程:项目开发中的数据同步

标签: 数据同步 软件开发 2025-09-21 

不久前,北京心玥软件公司在一个软件项目中遇到了一个具体任务:需要在两个外部应用之间共享部分数据,尤其是要让两个应用以相同结构同步数据,确保双方都能访问到一致的信息。这意味着,任何一方新增数据集或更新数据时,另一方也必须同步变化。这听起来像是开发者时不时会遇到的常规需求,对吧?  

但在我们的案例中,还存在一些额外限制,导致解决方案并不直观。接下来,我将深入探讨这个问题,分析如何实现这种功能,最后分享我针对该任务的解决方案。  

目录

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。  

本次探索的核心启示是:最直观的方案未必最适合具体场景。遇到问题时,不妨拆解问题、转换视角,预判不同方案的可行性,再选择最优路径。正如我的物理老师常说的:“动手前先思考十遍。”