布景

现在线上事务量与日俱增,每日的订单量超越千万,资金活动大,资金安全成为了要点关注的问题。为了保证每一笔买卖的正确性,提高资金的正确性和保证事务的利益,除了RD代码逻辑严厉以外,还需求对每日甚至每小时订单的流水进行核对,对反常状况能及时处理。面对千万级的订单量,人工对账肯定是不可行的,所以,完结一套对账体系成为了必定的事,不只为资金安全供给依据,也节省公司运维人力,数据愈加可视化。现在这套体系已覆盖聚合途径网关与外部途径100%的对账事务,完结春晚期间付出宝亿级订单量对账,完结日常AC项目千万级订单量对账,对账准确率完结6个9,为公司节省2~3个人力。

介绍

对账模块是付出体系的核心功用之一,不同事务规划的对账模型不同,可是都会遇到以下几个问题:

  • 海量的数据,就现在聚合付出的订单量来看,规划的对账体系需求应对千万级的数据量;
  • 面对日切、多账、少账等反常差异订单应该怎么处理;
  • 账单格局、下载账单时刻、下载办法等不一致问题。

针对以上问题,并结合财经聚合付出体系的特色,本文将规划一套能够应对千万级数据量、分布式和高可用的对账体系,运用音讯队列Kafka的解耦性处理对账体系各模块之间的强依赖性。文章从三个方面介绍对账体系,第一方面,全体介绍对账体系的规划,顺次介绍各个模块的完结及其进程中运用到的规划形式;第二方面,介绍对账体系版别迭代的进程,为什么需求进行版别迭代,以及版别迭代进程中踩过的“坑”;第三方面,总结现有版别的特色并提出下一步的优化思路。

体系规划

体系结构图

图1为对账体系总结构图,分为六个模块,别离是文件下载模块、文件解析并推送模块、途径数据获取并推送模块、履行对账模块、对账成果计算模块和中心态模块,每个模块担任自己的职能。

千万级高可用分布式对账系统设计实践

图1 对账体系总结构图

图2为对账体系运用Kafka完结的状况转化图。每个模块独立存在,彼此之间经过音讯中心件Kafka完结体系状况转化,经过中心态UpdateReconStatus类完结状况更新和message发送。这种规划不只完结流水线对账,也运用音讯中心件的特色,完结重试和模块之间的解耦。

千万级高可用分布式对账系统设计实践

图2 对账体系状况转化图

为了更好的了解每个模块的完结进程,下面将顺次对各个模块进行阐明。

文件下载模块

规划

文件下载模块首要完结各个外部途径账单的下载功用。众所周知,聚合付出是聚众家三方机构才能为一体的付出办法,其间三方机构包含付出宝、微信等付出界的领头羊,多样性的付出途径导致账单下载存在多样性,怎么完结多形式、可拔插的文件下载才能成为该模块规划的要点。剖析Java规划形式的特色,本模块选用接口形式,符合面向对象的规划理念,可完结快速接入。详细完结类图如图3所示(只展现部分类图)。

千万级高可用分布式对账系统设计实践

图3 文件下载完结类图

下面就以付出宝对账文件下载办法为例,详细阐述一下完结进程。

完结

剖析付出宝接口文档,现在选用的下载办法为HTTPS,文件格局为.csv的压缩包。依据这些条件,本体系的完结办法如下(只摘取了部分代码)。由于音讯中心件Kafka和中心态模块的机制,已经从体系层面考虑了重试的才能,因而不需求考虑重试机制,后续模块也如此。

public interface BillFetcher {
    // ReconTaskMessage 为kafka音讯,
    // FetcherConsumer为自界说账单下载后的处理办法
    String[] fetch(ReconTaskMessage message,FetcherConsumer consumer) throws IOException;
}
@Component
public class AlipayFetcher implements BillFetcher {
public AlipayFetcher(@Autowired BillDownloadService billDownloadService) {
        Security.addProvider(new BouncyCastleProvider());
billDownloadService.register(BillFetchWay.ALIPAY, this);
    }
    ...
    @Override
    public String[] fetch(ReconTaskMessage message, FetcherConsumer consumer) throws IOException {
	String appId = map.getString("appId");
	String privateKey = getConvertedPrivateKey(map.getString("privateKey"));
	String alipayPublicKey = getPublicKey(map.getString("publicKey"), appId);
	String signType = map.getString("signType");
	String url = "https://openapi.alipay.com/gateway.do";
	String format = "json";
	String charset = "utf-8";
	String billDate = DateFormatUtils.format(message.getBillDate(), DateTimeConstants.BILL_DATE_PATTERN);
	String notExists = "isp.bill_not_exist";
	String fileContentType = "application/oct-stream";
	String contentTypeAttr = "Content-Type";
	//实例化客户端
	AlipayClient alipayClient = new DefaultAlipayClient(url, appId, privateKey, format, charset, alipayPublicKey, signType);
	//实例化详细API对应的request类,类称号和接口称号对应,当时调用接口称号
	AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();   
	//  trade指商户基于付出宝买卖收单的事务账单
	//  signcustomer是指基于商户付出宝余额收入及支出等资金改动的帐务账单
	request.setBizContent("{" +
		""bill_type":"trade"," +
		""bill_date":"" + billDate + """ +
                "  }");
        AlipayDataDataserviceBillDownloadurlQueryResponse response = alipayClient.execute(request);
        if(response.isSuccess()){
            //do 依据下载地址获取对账文件,经过流式办法将文件放到指定的目录下
            ...
            System.out.println("调用成功");
        } else {
            System.out.println("调用失利");
        }
    }
}

详细进程:

  1. 重写结构办法,将完结类注入到一个map中,依据对应的下载办法获取完结类;
  2. 完结fetch接口,包含结构恳求参数、恳求付出宝、解析响应成果、选用流式将文件放入对应的目录下,以及这个进程中的反常处理。

文件解析并推送模块

规划

前面说到,聚合付出是面对不同的外部途径,对账文件的多样性不言而喻。比方微信是选用txt格局,付出宝选用csv格局等等,并且各个途径的账单内容也是不一致的。怎么处理途径之间账单的差异性成为该模板需求要点考虑的问题。经过调研和现有对账体系的剖析,本体系选用接口形式+RDF(结构化文本文件)的完结办法,其间接口形式处理账单多形式的问题,一起也完结可拔插的机制,RDF工具组件完结账单的快速标准化,操作简略易会。详细完结类图如图4所示(只展现部分类图)。

千万级高可用分布式对账系统设计实践

图4 文件标准化完结类图

下面就以付出宝对账文件解析为例,详细阐述一下完结进程。
完结

依据付出宝的账单格局,提早界说RDF标准模板,后续账单解析将依据模板将每一行对账文件解析为对应的一个实体类,其间需求留意标准模板的字段有必要要和账单数据一一对应,实体类的字段能够多于账单字段,但有必要包含所有的账单字段。接口界说如下:

public interface BillConverter<T> {
    //账单是否能够运用匹配器
    boolean match(String channelType, String name);
    //转化原始对账文件到Hive
    void convertBill(InputStream sourceFile, ConverterConsumer<T> consumer) throws IOException;
    //转化原始对账文件到Hive
    void convertBill(String localPath, ConverterConsumer<T> consumer) throws IOException;
}

详细完结进程如图5所示:

千万级高可用分布式对账系统设计实践

图5 文件解析流程图

  1. 界说RDF标准模板,如下为付出宝事务流水明细模板,其间body结构内字段名有必要和实体类名保持一致。
{
  "head": [
    "title|付出宝事务明细查询|Required",
    "merchantId|账号|Required",
    "billDate|起始日期|Required",
    "line|事务明细列表|Required",
    "header|header|Required"
  ],
  "body": [
    "channelNo|付出宝买卖号",
    "merchantNo|商户订单号",
    "businessType|事务类型",
    "production|商品称号",
    "createTime|创立时刻|Date:yyyy-MM-dd HH:mm:ss",
    "finishTime|完结时刻|Date:yyyy-MM-dd HH:mm:ss",
    "storeNo|门店编号",
    "storeName|门店称号",
    "operator|操作员",
    "terminalNo|终端号",
    "account|对方账户",
    "orderAmount|订单金额|BigDecimal",
    "actualReceipt|商家实收|BigDecimal",
    "alipayRedPacket|付出宝红包|BigDecimal",
    "jiFenBao|集分宝|BigDecimal",
    "alipayPreferential|付出宝优惠|BigDecimal",
    "merchantPreferential|商家优惠|BigDecimal",
    "cancelAfterVerificationAmount|券核销金额|BigDecimal",
    "ticketName|券称号",
    "merchantRedPacket|商家红包消费金额|BigDecimal",
    "cardAmount|卡消费金额|BigDecimal",
    "refundOrRequestNo|退款批次号/恳求号",
    "fee|服务费|BigDecimal",
    "feeSplitting|分润|BigDecimal",
    "remark|补白",
    "merchantIdNo|商户识别号"
  ],
  "tail": [
    "line|事务明细列表结束|Required",
    "tradeSummary|买卖算计|Required",
    "refundSummary|退款算计|Required",
    "exportTime|导出时刻|Required"
  ],
  "protocol": "alib",
  "columnSplit":","
}
  1. 完结接口的getChannelType、match办法,这两个办法用于匹配详细运用哪一个Convert类。如匹配付出宝账单,完结办法为:
@Override
public String getChannelType() {
    return ChannelType.ALI.name();
}
@Override
public boolean match(String channelType, String name) {
    return name.endsWith(".csv.zip");
}
  1. 完结接口的convertBill办法,完结账单标准化;
@Override
public void convertBill(String path, ConverterConsumer<ChannelBillPojo> consumer) throws IOException {
    FileConfig config = new FileConfig(path, "rdf/alipay-business.json", new StorageConfig("nas"));
    config.setFileEncoding("UTF-8");
    FileReader fileReader = FileFactory.createReader(config);
    AlipayBusinessConverter.AlipayBusinessPojo row;
    try {
        while (null != (row = fileReader.readRow(AlipayBusinessConverter.AlipayBusinessPojo.class))) {
            convert(row, consumer);
        }
        ...
}
  1. 将标准化账单推送至Hive

途径数据获取并推送模块

途径数据获取一般都是从数据库中获取,数据量小的时分,查询时数据库的压力不会很大,可是数据量很大时,如电商买卖,每天成交量在100万以上,经过数据库查询是不可取的,不只功率低,并且简单导致数据库崩溃,影响线上买卖,这点会在后续的版别迭代中表现。因而,途径数据的抽取是从Hive上获取,只需求提早将买卖数据同步到Hive表中即可,这样做不只功率高,并且愈加安全。考虑到抽取的Hive表不同、数据的表结构,数据收集器Collector类也选用了接口形式。Collector接口界说如下:

public interface DataCollector {
    void collect(OutputStream os) throws IOException;
}

依据现在途径数据收集器完结状况,能够得到类图如图6所示。

千万级高可用分布式对账系统设计实践

图6 途径数据收集器完结类图

履行对账模块

该模块首要完结Hive命令的履行,在途径账单和途径账单已悉数推送至Hive的前提下,运用Hive处理大数据功率高的特色,履行全衔接sql,并将成果存入指定的Hive表中,用于对账成果计算。履行对账sql能够依据事务需求而定,如需求了解本体系的全衔接sql,欢迎与我交流。

对账成果计算模块

对账使命履行成功之后,需求计算全衔接后的数据,要点计算金额不一致、状况不一致、日切、少账(途径无账,途径有账)和多账(途径有账,途径无账)等差异。针对不同的状况,本体系别离选用如下的处理方案:

  1. 金额不一致:前端页面展现差异原因,人工进行核对;
  2. 状况不一致:针对退款订单,查询途径退款表,存在且金额一致认为已对平,不展现差异,其他状况,需求在前端页面展现差异原因,人工进行核对;
  3. 日切:当途径订单为成功,途径无单时,依据途径订单创立时刻判别是否或许存在日切,假如判别是日切订单,会将这笔订单存入buffer文件中,待计算结束后,将buffer文件上传至Hive日切表中,等第二天从头加载这部分数据完结跨日对账。关于途径无订单,途径有单的状况,经过查询途径数据库判别是否存在差异,假如存在差异,需求在前端页面展现差异,人工进行核对。
  4. 少账:现在首要经过查询途径数据库判别是否存在差异,承认确实存在差异时,需求在前端页面展现差异,人工进行核对。
  5. 多账:现在这种有或许是日切,会先考虑日切,假如不在日切范围内,需求在前端页面展现差异,人工进行核对。

中心态模块

中心态模块是用于各模块之间状况转化的模块,运用Kafka和状况是否更新的机制,完结音讯的重发和对账状况的更新。从一个状况到下一个状况,有必要满意当时状况为成功,对账流程才会往下一步履行。中心态的规划不只处理了重试问题,并且将数据库的操作进行了收敛,更符合模块化的规划,各个模块各司其职。重试次数也不是无限的,现在设置的重试次数为3次,假如3次重试后仍然没有成功,会发lark通知,人工介入处理。

总归,对账工作,既杂乱也不杂乱,需求我们细心,对事务要有深化的了解,并挑选适宜的处理办法,针对不同的事务,不断迭代优化体系。

版别迭代

体系的规划很大程度受事务规划的影响,关于财经聚合付出而言,订单量发生了几个数量级的改动,这个进程中不断暴露出对账体系存在的问题,优化改善对账体系是必定的事。从体系规划到现在大致能够分为三个阶段:初始阶段、过渡阶段和当时阶段。

初始版(v1.0)

初始版上线后完结了聚合途径对账的自动化,尤其在2018年的春节活动中,资金安全供给了重要的保证,完结了聚合和老合众、付出宝、微信等途径的对账。跟着财经事务的开展,抖音电商的快速崛起,对账体系逐渐暴露出缺乏,比方对账使命失利增多,尤其是数据量大的对账、非正常差异成果展现、对账功率低一级问题。经过不断剖析,发现存在以下几个问题:

  1. 体系的文件都是放在临时目录tmp下的,TCE途径会对这个目录下的文件定时整理,导致推送文件到Hive时会报找不到文件的状况,尤其是大数据量的对账使命;
  2. Kafka音讯积累多,导致对账流程中断,首要是新增途径,对账使命添加,一起Hive履行队列是共享队列,大部分的对账流程因为没有资源而卡住;
  3. 非正常差异成果展现,首要是查单没有添加重试机制,当查询进程中出现超时等反常,会出现非正常差异成果,还有部分原因是日切跨度小而导致的非正常差异成果。
过渡版(v2.0)

考虑到初始版对账体系存在的缺乏和对账功用的急切性,对初始版进行过渡性的优化,初步完结大数据量的对账功用,一起也提高了差异成果的准确率。比较初始版,该版别首要进行了以下几点优化:

  1. 文件存放目录由临时现在改为服务下的某一个目录,防止大文件被回收,文件上传到Hive后删除文件;
  2. 从头请求独占的履行队列,处理资源缺乏导致对账流程卡住的问题;
  3. 查单新增重试机制,日切跨度增大,处理非正常差异成果展现,供给差异成果的准确率。

过渡版会集处理初始版明显存在的问题,关于一些潜在的问题并没有彻底处理,如代码容错率低、对账使命反常后人工响应慢、对账功率低、数据库安全性低一级问题。

当时版(v3.0)

当时版优化的主旨是完结对账体系的”三高”,别离为高功率、高准确率(6个9)和高稳定性。

关于高功率,首要表现在途径数据获取慢,并且存在数据库安全问题,针对这块逻辑进行了优化,改动数据获取途径,由本来的数据库获取改为从高功率的Hive中获取,只需求提早将数据同步到Hive表中即可。

关于高准确率,首要优化对账差异处理逻辑,进一步细化差异处理办法,新增差异成果报警,细化前端页面差异原因。

关于高稳定性,首要优化RDF处理对账文件发生反常时新增兜底逻辑,提高体系的容错性;对账使命失利或超越指定重试阈值时添加报警,加快人工响应速率;对查单等操作数据库逻辑添加限流,防止数据库崩溃。

版别迭代进程能够总结如下,期望读者别重复入坑,尤其是大文件处理方面。

事务状况 长处 存在的问题 方针
初始版(v1.0) 财经部门初期,订单量少,事务结构简略 完结少数买卖量对账;支撑分布式 功率低;对账使命简单卡住;非反常case普遍;大数据基本不能完结对账 保证资金安全问题,完结聚合途径网关与外部途径的对账功用
过渡版(v2.0) 电商事务崛起,订单量添加,事务种类增多 完结海量数据对账;查单新增重试机制;下降非反常case数量 影响数据库安全性;代码容错率低;对账功率低;对账使命反常时人工响应慢 支撑千万级订单量对账
当时版(v3.0) 优化过渡版遗失问题,改动数据获取路径 功率大大提高;完结千万级数据量对账;完结高稳定性,高准确率,高功率 全衔接功率低;不支撑订单状况推进 完结对账体系的高功率,准确率完结6个9;功用全面

总结

对账体系模型与事务休戚相关,事务不同,对账体系模型也会不同,可是大部分对账体系的全体架构改动不大,首要区别是各个模块的完结办法不同。期望本文介绍的对账体系能为各位读者供给规划思路,防止重复入坑。对对账体系感兴趣的同学能够找财经付出团队同学详聊,一起深化探讨,提出优化建议,比方优化全衔接策略,也欢迎各种简历引荐。

参阅文章

信息流对账与途径化完结-曾佳
混合编程在财经对账中的使用-王亚宁

内推链接

千万级高可用分布式对账系统设计实践