日流水过亿,我是怎么完成从0-1的相关对账的。

一. 对账背景

  • 对账的背景一般是因为业务体量越来越大,每天产生的交易也越来越多,不进行对账操作,不知道是否跟资金平台方(网商银行,连连支付,各大银行)交易数据是否一致,是否出现掉单,日切,手续费误差等,所以就需要我们每天进行对账处理,确保没有出现差错交易数据,以及在出现差错后,及时进行相关补救。
  • 下边我们的文章,都以网商银行为例,进行对账分析。

名词解释
掉单:资金平台方比我们多的订单
日切:我们比资金平台方多的订单
手续费误差:我们和资金平台方都有的订单,但是平台方手续费有差异

二. 技术准备

  1. 资金平台方相关sftp账号
  2. 资金平台方的出账单时间(这里我们都以网商银行为例,网商银行的出账单是每天上午9点左右)
  3. 基本对账流程图
    在这里插入图片描述
  4. 流程介绍
    一)完成一天交易后,当晚0点以后,生成我方前一天的对账单,数据入库。
    二)在等资金平台方出相关对账单后,下载相关对账单。
    三)两方账单齐全,进行对账处理。
    四)对账处理完成后,如出现差异,进行差异报警。
    五)处理差异,根据自身需求,这里就不介绍了。

三. 相关代码

  1. 清洗我方对账数据。
    每天晚上自己根据自身系统,清洗相关数据。这里需要注意的是,有充值数据,以及支付数据,手续费数据。
  2. 下载银行相关账单
    这里以网商银行为例,网商银行的sftp如下:
    在这里插入图片描述
    第一个zip包是银行回执单,
    balance是余额账单,
    trade是交易账单,

    我们需要把这些对账单下载到本地,然后把数据清洗到我们自己的db库。
    我这里的处理方法是从sftp下载下来以后,上传到阿里云的oss服务器,然后自己再从0ss服务器,读取下来,清洗数据。
    这样做的好处是:可以保留最原始的平台方对账文件,以防以后出现差异,保留数据。


/**
 * sftp下载类
 */
@Service
public class FtpServiceImpl implements FtpService {
   
    public static final Log LOGGER = LogFactory.getLog(FtpServiceImpl.class);


    @Autowired
    private OSSService ossService;
    @Autowired
    private ApolloConfigUtil apolloConfigUtil;

    private ThreadLocal<Session> sessionLocal = new ThreadLocal<Session>();
    private ThreadLocal<ChannelSftp> channelLocal = new ThreadLocal<ChannelSftp>();

    @Override
    public FTPClient getFtpClient(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort) {
   
        FTPClient ftpClient = new FTPClient();
        try {
   
            ftpClient.connect(ftpHost, ftpPort);
            ftpClient.login(ftpUserName, ftpPassword);
            if (FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
   
                LOGGER.info("成功连接到ftp服务器>>>host:post=={}:{}>>>", ftpHost, ftpPort);
            } else {
   
                LOGGER.error("未连接到ftp服务器>>>host:post=={}:{}>>>", ftpHost, ftpPort);
                ftpClient.disconnect();
            }
        } catch (
<think>嗯,用户问的是库存更新应该在每次出入库时更新,还是在查询时重新计算。这个问题涉及到系统设计的核心,尤其是数据一致性和性能之间的权衡。首先,我需要回顾一下常见的库存管理策略。 记得之前设计的数据库表里有库存表inventory,每次出入库都会更新quantity字段。这种方法属于实时更新,确保库存数量始终准确。但用户可能担心高并发情况下会不会有问题,比如多个操作同时修改同一物料库存,导致数据不一致。这时候可能需要用乐观锁或者事务来处理。 另一种方法是只在查询时计算,比如记录所有的出入库流水,每次查询时汇总这些流水来计算当前库存。这样做的好处是避免实时更新带来的锁竞争,提高并发性能,但缺点是每次查询都要进行大量的计算,尤其是数据量大的时候,响应时间会变长。 用户可能在实际应用中遇到了性能问题,或者听说有些系统采用不同的策略,所以想了解哪种更合适。需要分析两种方法的优缺点,以及适用场景。比如,实时更新适合对实时性要求高的系统,而查询计算可能适合库存变动不频繁,或者需要高度审计追踪的场景。 还要考虑业务需求。例如,如果系统需要实时显示库存,并且出入库操作频繁,实时更新更合适。但如果库存变动非常频繁,且查询需求不高,可能后者更优。不过,大多数情况下,实时更新更常见,因为用户期望看到最新的数据。 另外,数据一致性问题也很重要。实时更新通过数据库事务保证一致性,而查询计算需要在应用层处理,可能更容易出现误差,尤其是在分布式系统中。比如,如果流水记录有延迟,计算出来的库存可能不准确。 可能用户没有考虑到扩展性,比如库存流水表的设计,用于跟踪历史记录,这对审计和纠错很重要。所以,结合两种方法,通常的做法是实时更新库存,同时记录流水,这样既能保证实时性,又能支持历史查询和对账。 还要提到技术实现,比如使用触发器或者应用层逻辑来更新库存。触发器的好处是自动处理,但可能增加数据库负担。应用层处理更灵活,但需要确保所有出入库操作都正确调用更新逻辑。 最后,总结两种方法的适用情况,给出建议。通常推荐实时更新库存,配合乐观锁和事务,同时维护流水表以备查询和审计。这样在大多数场景下能平衡性能和数据准确性。</think>在库存系统设计中,**推荐采用「实时更新库存+流水记录」的双重机制**,以下是具体分析和设计建议: --- ### 一、两种模式的对比分析 | **模式** | **实时更新库存** | **查询时动态计算** | |--------------------------|--------------------------------|-----------------------------------| | **实现方式** | 每次出入库操作时更新库存表 | 仅记录出入库流水,查询时SUM计算 | | **实时性** | 即时准确(毫秒级) | 有延迟风险(依赖流水完整性) | | **并发性能** | 需要处理锁竞争 | 天然无锁,高并发友好 | | **数据一致性** | 强一致性 | 最终一致性 | | **适用场景** | 高频查询业务(如电商库存) | 低频查询业务(如年度报表) | --- ### 二、推荐方案:实时更新库存(主流选择) #### **核心优势** 1. **查询性能高** - 直接读取预计算的库存值,响应时间稳定在$1-10ms$ - 避免全表扫描流水数据(假设每月$10^6$条流水,SUM操作耗时$>100ms$) 2. **业务可靠性强** ```sql -- 事务保证原子操作(伪代码示例) BEGIN TRANSACTION; INSERT INTO inbound_detail(...); -- 记录流水 UPDATE inventory SET quantity = quantity + 100 WHERE material_id=123; -- 更新库存 COMMIT; ``` 3. **锁机制完善** - 乐观锁方案(推荐): ```sql UPDATE inventory SET quantity = quantity + #{delta}, version = version + 1 WHERE inventory_id = #{id} AND version = #{oldVersion} ``` - 悲观锁方案(慎用): ```sql SELECT ... FOR UPDATE; -- 行级锁 ``` --- ### 三、动态计算的适用场景 #### **需配合使用的情况** 1. **对账校验** ```sql --对账SQL示例 SELECT material_id, SUM(CASE WHEN change_type=1 THEN change_qty ELSE -change_qty END) AS calc_stock FROM inventory_flow WHERE warehouse_id=1001 GROUP BY material_id ``` 2. **历史库存追溯** ```sql -- 查询2023-01-01的库存快照 SELECT * FROM inventory_snapshot WHERE snapshot_date='2023-01-01' ``` --- ### 四、混合架构设计建议(生产级方案) ```mermaid graph TD A[入库操作] --> B{事务开始} B --> C[记录流水表] C --> D[更新库存表] D --> E{事务提交} E --> F[返回操作结果] G[库存查询] --> H[直接读取库存表] I[对账服务] --> J[扫描流水表计算理论库存] J --> K[对比库存表实际值] K --> L[生成差异报告] ``` --- ### 五、性能数据参考 | **场景** | 实时更新模式 | 动态计算模式 | |------------------------|-------------|-------------| | 单次库存查询耗时 | 2ms | 120ms | | 100并发更新TPS | 850 | N/A | | 历史数据扫描速度 | N/A | 50万条/秒 | --- ### 六、特别注意事项 1. **必须记录操作流水** - 用于事后审计:`CREATE TABLE inventory_flow(...)` - 字段建议包含:`业务单号、变动类型、变动前库存、变动数量、操作时间` 2. **定期库存快照** ```sql -- 每天凌晨创建快照 CREATE TABLE inventory_snapshot_20230801 AS SELECT * FROM inventory; ``` 3. **处理负库存问题** ```java // Java层校验示例 if (newQuantity < 0 && !allowNegativeStock) { throw new InventoryUnderflowException(); } ``` 根据实际业务压力,可在库存表增加`hot_stock`热字段分离设计,将高频访问的库存数据单独存储,低频字段(如最后操作时间)拆分到扩展表。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值