精华内容
下载资源
问答
  • 三方系统对接日志设计
    千次阅读
    2018-01-04 15:26:53

    1 概述

    由于业务需要,公司需要将原来的积分兑换商品功能由人工下单改为直接对接第三方商城对接,考虑到以后可能会对接多个第三方商城平台,所以采用统一接口门户调用,不同渠道调用不同实现类的设计方式,方便以后渠道的扩展。整体架构图如下:
    这里写图片描述
    第三方商城对接系统本身并不复杂,但是其中的实现细节方面需要注意的方面还是挺多的。比如:商品搜索如何实现,如何保证的库存,如何保证接口的限流,数据如何回滚等等。下面就一些系统实现中遇到的问题一一描述,不妥之处还望大家多多提建议。

    2 商品搜索

    商品搜索功能可以使用ELK来做,实现思路如下:
    1. 晚上定时器从第三方商城拉取商品,更新到ELK。
    2. 商品库存有更新时候,第三方商城回调我们的接口更新库存。
    但是,这样的话面临一个问题:如果商品数量很多,那么晚上拉取商品时候将会是一个很耗时的操作,如果商品过多,就不能用这种方式来实现。所以这种方式只能适用少量商品的情况。

    3 渠道分配器

    整个渠道分配器采用工厂模式,思路如下:根据渠道号,工厂类会去渠道池中获取到对应的渠道实现类。

    3.1 渠道池枚举类

    public enum ShoppingRouteEnum {
        JD(1, ShoppingRouteConstant.JD_BEAN_NAME, "京东", false),
        YX(2, ShoppingRouteConstant.YX_BEAN_NAME, "严选", true);
    
        private int channel;
        private String beanName;
        private String name;
        private boolean available;
    
        ShoppingRouteEnum(int channel, String beanName, String name, boolean available) {
            this.channel = channel;
            this.beanName = beanName;
            this.name = name;
            this.available = available;
        }
    
        public int getChannel() {
            return channel;
        }
    
        public String getBeanName() {
            return beanName;
        }
    
        public String getName() {
            return name;
        }
    
        public boolean isAvailable() {
            return available;
        }
    
        /**
         * 获取所有可用的渠道
         * @return
         */
        public static List<ShoppingRouteEnum> getAllAvailable(){
            List<ShoppingRouteEnum> shoppingRouteEnumList = new ArrayList<>();
            ShoppingRouteEnum[] values = values();
            for(ShoppingRouteEnum shoppingRouteEnum : values){
                if(shoppingRouteEnum.isAvailable()){
                    shoppingRouteEnumList.add(shoppingRouteEnum);
                }
            }
            return shoppingRouteEnumList;
        }
    
        /**
         * 根据渠道号获取所有可用渠道
         * @param channel
         * @return
         */
        public static ShoppingRouteEnum getAvailableByChannel(int channel){
            ShoppingRouteEnum[] values = values();
            for(ShoppingRouteEnum shoppingRouteEnum : values){
                if(shoppingRouteEnum.getChannel() == channel && shoppingRouteEnum.isAvailable()){
                    return shoppingRouteEnum;
                }
            }
            return null;
        }
    }

    3.2 渠道工厂类

    @Component
    public class ShoppingRoute implements ApplicationContextAware{
        private ApplicationContext applicationContext;
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    
        /**
         * 根据渠道号获取渠道实现类
         * @param channel
         * @return
         */
        public ClientShoppingService getClientShoppingServiceByChannel(int channel){
            ShoppingRouteEnum routeEnum = ShoppingRouteEnum.getAvailableByChannel(channel);
            if(ObjectUtil.isNotNull(routeEnum) && routeEnum.isAvailable()){
                return applicationContext.getBean(routeEnum.getBeanName(), ClientShoppingService.class);
            }
            return null;
        }
    
        /**
         * 获取所有可用渠道实现类
         * @return
         */
        public List<ClientShoppingService> getAllAvailableClientShoppingService(){
            List<ClientShoppingService> clientShoppingServiceList = new ArrayList<>();
            List<ShoppingRouteEnum> routeEnumList = ShoppingRouteEnum.getAllAvailable();
            for(ShoppingRouteEnum shoppingRouteEnum : routeEnumList){
                ClientShoppingService clientShoppingService = applicationContext.getBean(shoppingRouteEnum.getBeanName(), ClientShoppingService.class);
                if(ObjectUtil.isNotNull(clientShoppingService)){
                    clientShoppingServiceList.add(clientShoppingService);
                }
            }
            return clientShoppingServiceList;
        }
    }

    4 商品下单

    商品下单考虑到需要批量下单以及同第三方商城系统的交互的性能,所以不能同步提交第三方商城下单,这里我采用的解决方案如下:
    1. 将订单存储到本地数据库,状态为未提交
    2. 定时器1分钟跑批一次提交未提交状态的订单,由于第三方商城接口限流,所以每次只提交按照创建时间排序前若干条的订单。

    采用这种解决方案需要考虑这个问题:某个订单一直提交不成功怎么办?

    这种情况有大多是以下两种情况造成:(1)订单本身有问题,例如库存不足,商品已经下架等(2)数据库中堆积订单过多,超过第三方商城的限流的承受能力,导致一直轮训不到该订单。

    对于这两种情况,暂没有特别好的解决方案,我们可以采取以下措施(1)记录日志(2)人工处理

    5 限流器

    第三方商城的接口通常都有限流,比如 70次/秒 ,如何保证我们的接口的调用次数在超过第三方接口限流的时候快速失败呢?这时候就需要限流器了。限流器器的实现请参考笔者的另外一篇博客:基于Redis的限流器的实现

    6 库存扣减

    商家销售的商品数量是有限的,用户下单后商品会被扣减,我们可以怎么实现呢?
    举个例子:一件商品有1000个库存,现在有1000个用户,每个用户计划同时购买1000个

    • (实现方案1)如果用户加入购物车时进行库存预占,那么将只能有1个用户将1000个商品加入购物车。
    • (实现方案2)如果用户提交订单时进行库存预占,那么将也只能有1个用户将1000个商品提单成功,其它的人均提示“库存不足,提单失败”。
    • (实现方案3)如果用户提交订单&支付成功时进行库存预占,那么这1000个人都能生成订单,但是只有1个人可以支付成功,其它的订单均会被自动取消。

    我们目前采用的方案2,理由:

    • 用户可能只是暂时加入购物车,并不表示用户最终会提单并支付。所以在购物车进行库存校验并预占,会造成其它真正想买的用户不能加入购物车的情况,但是之前加车的用户一直不付款,最终损失的是公司。
    • 方案3会造成生成1000个订单,无论是在支付前校验库存还是在支付成功后再检验库存,都会造成用户准备好支付条件后却会出现99.9%的系统取消订单的概率,也就是说会给99.9%的用户体验到不爽的感觉。
    • 数据表明用户提交订单不支付的占比是非常小的(相对于加入购物车不购买的行为),目前京东到家给用户预留的最长支付时间是30分钟,超过30分钟订单自动取消,预占的库存自动释放

    综上所述,方案2也可能由于用户下单预占库存但最终未支付,造成库存30分钟后才能被其它用户使用的情况,但是相较于方案1,方案3无疑是折中的最好方案。

    7 重复提交订单的问题

    重复提交订单造成的库存重复扣减的后果是比较严重的。比如商家设置有1000件商品,而实际情况可能卖了900件就提示用户无货了,给商家造成无形的损失

    7.1 可能出现重复提交订单的情况

    • (1、用户善意行为)app上用户单击“提交订单”按钮后由于后端接口没有返回,用户以为没有操作成功会再次单击“提交订单”按钮
    • (2、用户恶意行为)黑客直接刷提单接口,绕过App端防重提交功能
    • (3、提单系统重试)比如提单系统为了提高系统的可用性,在第一次调用库存系统扣减接口超时后会重试再次提交扣减请求

    7.2 解决方案

    • (1、用户善意行为)app侧在用户第一次单击“提交订单”按钮后对按钮进行置灰,禁止再次提交订单
    • (2、用户恶意行为)采用令牌机制,用户每次进入结算页,提单系统会颁发一个令牌ID(全局唯一),当用户点击“提交订单”按钮时发起的网络请求中会带上这个令牌ID,这个时候提单系统会优先进行令牌ID验证,令牌ID存在&令牌ID访问次数=1的话才会放行处理后续逻辑,否则直接返回
    • (3、提单系统重试)这种情况则需要后端系统(比如库存系统)来保证接口的幂等性,每次调用库存系统时均带上订单号,库存系统会基于订单号增加一个分布式事务锁。(分布式锁可以参考笔者的另一篇博客:redis分布式锁的实现

    8 库存数据的回滚机制如何做

    需要库存回滚的场景也是比较多的,比如:

    • (1、用户未支付)用户下单后后悔了
    • (2、用户支付后取消)用户下单&支付后后悔了
    • (3、风控取消)风控识别到异常行为,强制取消订单
    • (4、耦合系统故障)比如提交订单时提单系统T1同时会调用积分扣减系统X1、库存扣减系统X2、优惠券系统X3,假如X1,X2成功后,调用X3失败,需要回滚用户积分与商家库存。

    这几种情况我们可以采取以下解决方案:

    • 其中场景1,2,3比较类似,都会造成订单取消,订单中心取消后会发送mq出来,各个系统保证自己能够正确消费订单取消MQ即可。
    • 场景4订单其实尚未生成,相对来说要复杂些,这种情况提单系统T1需要主动发起库存系统X2、优惠券系统X3的回滚请求(入参必须带上订单号),X2、X3回滚接口需要支持幂等性。
    • 场景4还存在一种极端情况,如果提单系统T1准备回滚时自身也宕机了,那么库存系统X2、优惠券系统X3就必须依靠自己为完成回滚操作了,也就是说具备自我数据健康检查的能力,具体来说怎么实现呢?可以利用当前订单号所属的订单尚未生成的特点,可以通过worker机制,每次捞取40分钟(这里的40一定要大于容忍用户的支付时间)前的订单,调用订单中心查询订单的状态,确保不是已取消的,否则进行自我数据的回滚。

    9 多人同时购买1件商品,如何安全地库存扣减

    现实中同一件商品可能会出现多人同时购买的情况,我们可以如何做到并发安全呢?
    基于数据库的伪代码实现:

    int ret=updateSQL("update stock_main set stockNum=stockNum-"+requestBuyNum +" where productId="+productId+" and stockNum>="+requestBuyNum );
    if(ret==1){
       return "扣减成功";
    }else {
       return "扣减失败";
    }

    这段代码只是在where条件里增加了and stockNum>=”+requestBuyNum即可防止超卖的行为

    使用缓存校验减轻数据库压力:

    如果商品是促销品(比如参与了秒杀的商品)并发扣减的机率会更高,那么数据库的压力会更高,这个时候我们可以采用redis缓存进行校验来减少数据库压力。

    更多相关内容
  • 总结和展望 本章节主要介绍了闲鱼同城业务在“闲置时间”和"闲置空间“场景下针对与第三方系统对接的过程中开发资源和稳定性问题展开。通过上述方案也解决了在开篇提到的2个问题: 动态响应机制:商品同步时通过...

    背景

    闲鱼同城作为闲鱼本地生活的主战场,维护闲鱼基本盘闲置物品的同时,还针对闲置时间闲置空间场景进行相关的孵化。

    于买家而言:淘到经济实惠的闲置物品(二手数码),打发闲置时间(兼职,服务)去挣钱。于卖家而言:闲置物品(二手数码)卖钱,闲置空间(二手房租卖)换钱。

    闲置时间(兼职)和闲置空间(租房)区别于同城传统的闲置物品,闲置物品为传统的C2C的商品,也就是买卖双方均为C端的用户。但是对于兼职、租房等业务来说,需要供应商入驻提供供给。因此一旦涉及到第三方提供的供给,就不得不面临以下问题:

    • 随着业务的不断发展,必将有越来越多的供应商入驻。为了能让供应商快速接入,除了必备的接入文档之外在技术侧应该能有一套动态响应机制,防止在供应商接入的过程中被问题频繁的打断(双方数量对不齐、同步失败原因等)。

    • 每个供应商的供给质量和技术水平存在差异,如何控制好供给质量的同时保证服务的稳定性成为另一大关键因素。

    技术方案

    整体架构

    整体架构设计会复用中台的一些基础能力,比如用户、商品、交易等。抽象出三个领域,商家域、审核域和独立业务域(每个业务可以单独划分)。安全生产层面,为了保证系统的稳定性,围绕限流(高并发下限流重试)+ 监控(对异常的状态出对应的报警)+ 数据安全(重试下情况下保证幂等性)展开。

    数据对账

    注:

    • 审核:复用了审核中心的能力(机审+人审)。机审:预置过滤规则,不满足过滤规则的判定为审核失败。

    • 开放接口能力:提供查询商品审核、校验操作日志。

    异常回调

    1. 收到同步消息后会对数据进行校验,包括不限于数据字段合规性校验(长度、枚举值等)、夹带违禁词、状态更新异常(已经下架的宝贝执行下架动作)等。

    2. 针对上述初审通过,会进入审核中心二次审核,审核主要内容为语义违规。

    两种回调场景都复用了异常流转的能力如下图:

    通过catch(LocalBizException e)的方式,将e.getCode()**和e.getMessage()**封装为response进行返回,不需要对不同的异常进行单独catch让异常逻辑在业务回调侧闭环。供应商获取到错误信息后根据错误信息修改信息进行二次同步。

    定时播报

    采集状态变更日志表业务商品表将对应的一个小时内发生状态转化的商品数量(上架、下架、编辑、审核不通过等)最后以钉钉消息播报到钉钉群中(按照钉钉的机器人api)。兼职中主要关心的指标项为上下架成功与否、是否审核失败等指标,兼职播报架构如下图所示:

    开放接口

    与此同时开放的接口能力提供查询商品审核、校验操作日志。接口定义如下,提供时间范围、同步id、类型、分页参数等信息。

    注:涉及图中状态日志表和商品表的插入部分参见下一章节“稳定性”。

    稳定性(安全生产)

    稳定性治理一直是一个系统绕不开的话题,在这个场景中涉及到第三方之间的交互,谁都无法确定对方是如何调用己方的系统。这种情况下,稳定性的重要性更加不言而喻。

    流量控制

    在数据同步时,将请求打入队列对于第三方的同步请求使用异步返回。打入队列的好处就是可以利用队列实现流量控制,削峰填谷。限流这部分依赖于阿里开源的Sentinel框架,网上对于Sentinel的分析很多这里不多加赘述。

    数据一致性保证

    由于存在重试操作,所以必然需要在重试过程中保证数据的正确性。

    • 状态变更日志表:数据库采用的是nosql的数据库,这边会根据参数生成唯一id,进行覆盖插入保证数据的唯一性。

    • 业务商品表:采用先查后插的方式,同时利用分布式锁+itemId唯一键冲突保证数据的唯一性。

    拿兼职插入一致性举个栗子:ic表:底层商品表,包括一些商品的基本信息。岗位表:扩展信息的存储,工作地点、工作时间等。当一条兼职同步消息来了之后这边会涉及到两张表的维护。这里采用的方式是以下架的方式插入ic表,如果业务表成功后去更新上架ic表中状态(如下图)。

    异常监控告警

    攻击流量通常会伪装成正常流量进入。在这种情况下,系统会一直无法消费此异常消息,所以这边设置一个消费重试阈值,如果达到上限后对消息进行丢弃,同时进行系统告警(有的场景是需要强一致性保障,此时报警后需人工接入排查)。

    兼职业务的告警场景包括:

    1. 限流触发报警(持续时间超过10分钟):限流期间被限制的消息业务会主动进行重试,控制重试n次整体持续时间不会超过10分钟,如果限流超过10分钟认定为异常情况会进行告警。(通常来说是供应商大批量上下架岗位导致,未通知前提下认定为他们系统问题)

    2. 状态更新失败(持续时间超过5分钟,每分钟数量大于n):小批量的更新失败可以理解为是垃圾数据,持续时间过于长可以理解为供应商系统异常。

    总结和展望

    本章节主要介绍了闲鱼同城业务在“闲置时间”和"闲置空间“场景下针对与第三方系统对接的过程中开发资源和稳定性问题展开。通过上述方案也解决了在开篇提到的2个问题:

    • 动态响应机制:商品同步时通过实时回调和异步回调的方式将商品的每个异常状态返回给供应商;提供了小时时间维度的统计播报,最后以钉钉消息通知至钉钉群中,如若发现异常也可根据开放接口去查询商品历史变更状态。这样就能很大程度上解放开发,不会因为对账的问题被频繁的打断。

    • 稳定性治理:通过接口限流保证异常流量打满线程池进而影响系统;通过接口幂等保证数据的安全唯一性;通过监控(搭配合适的报警规则)去监控异常场景,如若出现问题人为介入。

    随着业务越来越复杂,对应的独立业务域也将会越来越多,在独立业务域上的开发精力也会越来越多。能否根据大量复杂业务场景的输入找到共同点抽象出较为理想的架构将是努力的目标。

    • 抽象出独立业务域中的共同点,推动业务完善路径:完善租房的订单和履约路径,统一抽取出订单域和履约域。

    • 针对不同业务的不同商家统一商家管理平台,现阶段每个业务都有自己的一套接入方式。

    展开全文
  • 今天我们来聊聊,如何做好第三方系统对接

    千次阅读 多人点赞 2020-12-18 18:40:43
    无论你是做面向ToB、ToC还是ToG的业务,开发业务系统,永远也逃脱不了与第三方系统对接的命运,例如:常见的支付宝、微信支付平台对接、短信平台对接,还有单点登录对接,以及与友商的数据接口对接等等,大..

    💨 作者:laker,因为喜欢LOL滴神faker,又是NBA湖人队🏀(laker)粉丝儿(主要是老詹的粉丝儿),本人又姓,故取笔名:laker
    ❤️喜欢分享自己工作中遇到的问题和解决方案以及一些读书笔记和心得分享
    🌰本人创建了微信公众号【Java大厂面试官】,用于和大家交流分享
    🏰 个人微信【lakernote】,加作者备注下暗号:cv之道


    前言

    无论你是做面向ToBToC还是ToG的业务,开发业务系统,永远也逃脱不了与第三方系统对接的命运,例如:常见的支付宝、微信支付平台对接、短信平台对接,还有单点登录对接,以及与友商的数据接口对接等等,大到相对成熟的平台对接,小到一个接口的调用,基本上所有的开发都避免不了对接,它已经充斥在我们的日常工作中✒️,如何做好与第三方的对接,降低对接风险,按时保质的完成对接任务,已经成为我们非常头疼的问题了 ⚡⚡⚡。

    今天我就在这里总结下,个人的经验以及互联网上发表的一些比较好的观点。有用的话,麻烦点个赞 👍 ,转发一波☀️。

    首先当我们拿到对接需求的时候,直接3W三板斧理论抡起来,来帮助我们梳理一下对接思路。

    What 是什么

    对接的是什么?

    • 一定要从宏观微观的搞清楚我们自己的系统是做什么业务,有哪些数据、哪些字段;需要提供给第三方哪些数据、哪些字段;又需要从第三方获取哪些数据、哪些字段。

    这里的宏观和微观指的是宏观的系统物理架构、逻辑架构、部署架构、业务架构,微观的指系统模块、功能点的实现细节、及其涉及的表和其他三方对接历史。

    要找到掌握这么多的人,可不是一件容易的事儿,这里能掌握这么多的人,一般都是团队的核心骨干首席开发工程师❤️

    • 同样的也要掌握第三方系统大致业务流程,部署架构等等,知己知彼,百战不殆

    为什么要搞清楚部署架构?在这里我分享下之前的一个真实案例,当场裂开的那种 🐜。

    项目对接开始,前期双方都紧锣旗鼓的对接、测试等等,各个环节都很顺利,于是乎在测试环境稳定一周左右后上线。

    上线之后的2-3天,突然的某一天,运营人员的一通电话☎️ 打破了祥和的假象!线上出现了很多重复的业务流水号⚠️。

    例如:

    正常情况数据应该是这样的:

    id流水no创建时间
    10378QJ000730012020-12-18 12:10:11
    10379QJ000730022020-12-18 12:10:11

    但是现场的情况是:

    id流水no创建时间
    10378QJ000730012020-12-18 12:10:11
    10379QJ000730012020-12-18 12:10:11

    存在重复的流水号:QJ00073001⚠️,当场我就裂开了,这TM测试环境跑了一周没事儿,正式环境都搞了2-3天了出现问题,趁事态没有扩大,各种排查,

    是不是线程安全问题啊?是不是第三方问题啊?这2天动了什么现场环境等等。

    很快我们就定位出问题来了,原来是昨天晚上,又对接了一个新的局点导致的,第三方服务部署架构是分级部署的。

    我们以为的对接:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OETL0Aaa-1608287514803)(D:\金金金\如何做好与第三方系统对接.assets\image-20201218152704948.png)]

    实际的对接:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3kBmQL7f-1608287514805)(D:\金金金\如何做好与第三方系统对接.assets\image-20201218153047398.png)]

    第三方的部署架构是每个局点单独部署一套服务,所以他们那边的流水号都是从1开始的,导致我们直接拿流水号来用的时候裂开了。

    处理方式也很简单,流水号加上局点code即可,例如:HW31QJ00073001HW89QJ00073001

    Why 为什么

    为什么要对接第三方系统?

    比如我们经常对接支付宝、微信平台,你不可能自己开发个支付系统的,再说了支付宝和微信支付的用户也多,方便你我他,可以让我们更专注自己的业务开发。

    再比如对接短信平台快递鸟平台等等。

    总结下:基本上都是自己公司想做一件事情,但是自己又没有足够的物力、财力、精力去实现,那么就去找市面上有成熟的解决方案,把专业的活交给专业的人,来以最小化的成本实现我们的需求。

    How 怎么做

    这里我们再以对接前对接中对接后这3个阶段,来分阶段讨论下每个阶段应该怎么做,才能稳步推进我们的对接需求。

    对接前

    • 明确需求,以及对接的价值(是否真的有必要做及其实现的意义,有可能这一步直接让对接流产✋。。。)

    • 搞清楚我们跟第三方的大致交互流程和双方所需数据。(别鸡蛋里面找骨头,他也找不到啊,你要我没有的数据,我可怎么提供)

    • 评估大概所需要花费的钱,毕竟跟别人对接,大部分情况下是花钱的。(花的钱太多有可能得不偿失)

    • 确定双方相关业务负责人、技术负责人,以及要指定一个总负责任人来推动双方稳步前进(别双方都是放羊模式)

    • 制定一个合理的计划,确定联调时间、上线时间、试运行时间(一定要制定合理的计划)

    对接中

    1. 确定细节

    拉上双方业务人员、技术负责人,共同制定接口调用流程,画出接口调用流程图、泳道图、时序图等,毕竟一图胜千言。

    这里一定要拉上真正熟悉业务和技术的人去沟通,别找中间人去传话了,也别找不懂的人去,否则在这一步,坑就已经开始挖了。

    泳道图示例:

    泳道图很清晰的能看到对接双方交互流程,职责划分清晰

    在这里插入图片描述

    时序图示例:

    时序图能清晰看到消息传递的时间顺序。

    在这里插入图片描述

    下面的关键细节我列一下:

    关键细节事项备注
    字段内容双方确定需要提供什么字段、需要获取哪些字段
    接口版本接口版本规范,url中 /v1/xxx,还是header中 verison=v1,以及协商版本具体规范细节
    大量数据是分页返回,还是以文件的形式,例如csv等
    异常处理定义错误码,以及其对应的相应操作,有什么兜底补偿方案没
    调用方式主动拉取还是被动等待推送建议双向即可主动式,又可被动式
    通信协议Restful、Webservice、MQ、Websocket等,复杂的对接,是否可以考虑提供sdk一般建议Restful
    报文协议json、yaml、xml、自定义等一般建议Json
    接口方式同步接口还是异步接口
    接口安全Oauth2.0 sign token等,注意内外网、以及业务的保密级别
    幂等性确保双方接口是否都是幂等的,防止重复提交
    重试机制一定要确认是否需要接口调用失败后的重试机制,保证数据传输的最终一致性
    重试机制包括 实时重试调用:指定次数 + 调用失败持久化,数据库定时任务重试
    接口文档要有详尽的说明和丰富的示例代码
    联调环境可以随便折腾的那种

    2. 一些建议

    • 文档先行,统一规范(双方按照文档来开发,统一规范)

    • 需要自己有对接模拟接口,防止三方公司接口迟迟未完成,影响整个项目进度

    • 第三方对接模块做成单独的服务最好独立出来,尽最大努力保证服务的高可用、稳定性。(不然你滴电话就要被打爆了☎️)

    • 接口升级,尽量做到影响最小,不去修改接口协议,如果必须要动,要做到接口兼容老版本。

    • 尽量保证接口的幂等性,因为会有重试机制和补偿方案;

    • 接口当中生成requestId,用于日志记录,到返回结果的全流程跟踪,接口接收到的数据,以及解析之后的参数值,都要用日志记录下来,方便查看原因。

    • 编码推荐使用注解式声明编程 Feign

    举个例子把第三方对接模块做成单独服务的好处、以及做好对外服务降级操作(业务逻辑降级)

    之前项目中有个需求,需要对接一个厂商的服务,大致流程是调用一个实时接口,输入身份证号,返回这个身份证号关联人的最新的所有的档案信息。

    第一阶段

    一开始我们是直接把对接代码写到我们业务系统的,这也很正常。

    第二阶段

    使用一段时间后,公司的另一个团队也需要对接这个厂商,那么现在是否也复制一份儿代码去对接呢?显然我们不会这么去做,第三方接口升级啥的,我们2个团队都要改,耦合性太强了,还有万一后面我们还有其他团队对接呢?不可能都用这种方式。

    所以我们的做法是把这个对接模块独立出来,做成一个单独的服务,供所有的团队使用,作为微服务的一部分

    架构如下图:
    在这里插入图片描述

    第三阶段

    平稳运行一段时候后,突然的某一天第三方服务厂商服务down掉了,导致我们这边所有相关的业务出现了类似“档案查询接口不可用”的提示。

    已经做了断路器、服务通用降级,返回提示信息“档案查询接口不可用

    虽然是第三方的问题,也有提示的降级操作,但是呢,用户体验还是不够友好,于是乎我们开会讨论更完美的方案,我们的需求是返回实时的个人档案信息

    但是如果当第三方服务down了,返回之前查询过的非实时数据,并给个不是最新的提示,也总比提示“档案查询接口不可用”要优雅的多。

    于是乎我们会在每个用户查询到档案后,自己本地数据库存一份,当第三方服务不可用的时候去降级到本地数据库查询使用

    架构如下图:

    在这里插入图片描述

    对接后

    持续跟踪一段时间系统的使用情况,然后进行总结,看看是否可以把这块对接流程规范化,做标准化接口,将自己的能力或者数据标准化出去,类似于开放平台,让别人按照你的标准来做。(那就乌鸦变凤凰了)

    最后我也总结下,之前遇到的风险点:

    对接风险

    • 对方不配合 (奶茶☕、香烟给安排上,还不行只能让上级去推动了)

    • 对接要从源头留痕,防止后面对方扯皮,要留证据。(拿证据说话)

    • 对方服务升级,接口变了,未通知升级。(接口版本号制定好,接口做到向下兼容)

    • 对方服务不稳定影响自有系统。(服务熔断、降级处理)

    • 调用并发频率过高等。(控制频率)

    总结与思考

    没有最完美的方案,只有尽可能完善的方案,就像系统可用性达不到100%一样,我们做的每一步都要想好失败的场景,以及相应的对应策略,做好降级处理,最终的兜里方案就是人肉补偿了✈️✈️✈️。


    QQ群【837324215
    关注我的公众号【Java大厂面试官】,回复:架构资源等关键词(更多关键词,关注后注意提示信息)获取更多免费资料。

    公众号也会持续输出高质量文章,和大家共同进步。

    展开全文
  • 三方支付系统全新年华云支付易支付网站源码 带彩虹模板 全新年华云支付易支付,附彩虹模板网站源码 想开通第三方支付,第四方支付的看过来了 源码支持单独设置支付宝,QQ,微信的费率 源码可对接官方支付,易支付,...
  • 对接第三方系统实操经验分享

    千次阅读 2020-06-13 12:58:47
    对接第三方系统实操经验分享,erp系统、oa系统、邮件系统,任何系统都避免不了与第三方系统打交道

    对接第三方系统实操经验分享

    前言

    为使得指示性更强,有以下名词说明

    • A系统:是指要发起对接的我方系统,可以理解成 Client
    • B系统:是要对接的第三方系统,可以理解成 Server

    对接第三方的特殊性

    • 请求方式不同。比较老的系统有可能使用 webservice 接受参数,比较人性化的系统会使用 http + json 方式接受参数
    • 登录方式不同。有些系统压根就不需要登录,有些系统通过 token登录,有些系统通过 cookie 登录
    • 字段名不同。除非双方共同开发,否则字段名不一致的概率几乎为 100%
    • 基础资料不同。就算是同一个供应商,在A系统的唯一id 为 333,在B系统的唯一id 为 supplier301,这才是棘手之处
    • 网络请求不可靠,数据一致性(事务)无法保证。对接过程中,你想实现分布式事务的可能性基本为0,因为你基本没有可能能使得对面的数据库按照你的意愿 回滚。示例如下图:

      服务器成功了,但客户端失败

    对接框架必备功能

    • 字段名转换工具类
    • 字段值转换机制
    • 重试机制
    • 系统数据一致性(幂等)机制
    • 完整的日志机制

    必备功能实现参考方案

    字段名转换工具类

    方案一:使用注解建立字段关联关系

    • 例子:
    	@ErpDataField(name = "FSaler", dataType = ErpBaseInfoTypeEnum.Employee,keyName = "FSTAFFNUMBER")
    	private Integer fsaleAid;
    
        @ErpDataField(name = "F_PAEZ_Text4")
        private String fsoNumber;
    
    • 解释
    1. 类似fastjson@JSONField 注解一样,对字段进行重命名,根据目标系统的字段名进行关联
    2. 在重命名的同时,亦可以对字段值的类型进行标记,为字段值转换机制打上标记
    3. 可以按照对接框架的需要附上所需要的属性,比如说 字段是否需要强转成数字形式,单位是多少,保留多少位小数等属性,也是为了字段值转换机制打基础
    • 优点:
    1. 注解写在字段上,清晰易懂,维护直接在代码中
    2. 反射也更好获取——工具类更好写
    • 缺点:
    1. 由于字段之间的映射关系已经以 注解的形式 硬编码写在了代码里,想要 修改就必须重构代码,而重构往往对于 生产环境是不可容忍的
    2. 有可能污染了代码,尤其是 pojo(可以通过 新建一个vo类专门用于对接)

    方案二:使用数据库建立字段关联关系

    其实也就是将注解的内容 放进数据库里进行维护,通过如前端填写注解表格进行 对接交互,可实时增删改,更为灵活

    • 优点:
    1. 灵活,不使用硬编码
    2. 如果不涉及值转换机制的新增,基本可以实现 不用写任何代码,不用重构代码,即可新对接一张表单。
    • 缺点
    1. 反射工具类书写难度加大:需要考虑到 字符串的拼写、合法性以及嵌套字段名解析等问题

    字段值转换方案

    我们知道 A系统中供应商id 为 333 的供应商 = B系统中 供应商唯一主键 为 S303 的供应商,我们需要在值转换阶段,将其转换成 B系统能够接受的值

    建议实现步骤

    1. 在字段名转换阶段,给字段打上 值转换类型属性(如上述注解中的dataType属性)
    2. 维护值转换 策略模式集合,各种转换策略实现同一个接口
    public interface ErpBaseInfoHandler {
        String handleField(ErpJsonField jsonField,String extInfo);
    }
    
    public enum ErpBaseInfoTypeEnumAdmin{
        None(0,"不是基本信息", NotBaseInfoHandler.class),
        Channel(1,"渠道客户", ChannelBaseInfoHandler.class),
        XyCompany(2,"公司主体Id",  XyCompanyBaseInfoHandler.class),
        Employee(3,"员工faid", EmployeeBaseInfoHandler.class),
        Currency(4,"币种", CurrencyBaseInfoHandler.class),
        //....
     }
    
    1. 在值转换阶段,根据值转换类型进入对应的转换策略中

    转换策略推荐实现方案——以员工信息为例

    • 输入的参数含有 需要转换的A系统的员工id值 : A23,要转换成 B系统的值 为 B100567
    • 以下 值映射关系 是指 A系统的值 => B系统的值,如A系统员工id A23 => B系统 的 B100567

    值映射关系不稳定

    也就是说 A23 => B100567 在某时刻是成立的,但是之后又不成立了

    此种情况只能通过 B系统开发转换接口,然后A系统每次转换值时 调用接口 实时查询对应的值

    值映射关系稳定

    1. 根据员工id查找数据库,如果可以找到映射关系则立即返回
    2. 根据B系统提供的查询接口(如果有),则根据一定的筛选条件获取对应的B系统的值(如根据员工工号 查询B系统的员工id),如果查询成功,则保存映射关系保存在数据库中(以便再次查询),然后立即返回
    3. 如果还不行,则根据B系统提供的新增表单接口(如果有),则以A系统的员工 在B系统创建 该员工的信息,一般创建成功 B系统 会返回能唯一确定这个员工的 B系统id,此时也将映射关系保存到数据库中,然后返回
    4. 否则,则报错,手动处理
    public class EmployeeBaseInfoHandler implements ErpBaseInfoHandler{
        @Override
        public String handleField(ErpJsonField jsonField,String extInfo){
            //在数据库中查询
            String dbFnumber = this.searchInDb(jsonField);
            if (!StringUtils.isEmpty(dbFnumber)) {
                return dbFnumber;
            }
            String fname = this.getFname(jsonField);
            //在第三方系统,如 erp 中查询
            String fnumberByName = this.searchInErpByFname(jsonField, fname);
            if (!StringUtils.isEmpty(fnumberByName)){
                return fnumberByName;
            }
            //使用现有的员工信息,保存至第三方系统
            String fnumber = saveToGetFnumber(jsonField, fname);
            return fnumber;
        }
    }
    

    重试机制

    前文我们提到过 任何一个网络请求 都可能出现 失败情况,因此我们必须要考虑加入重试功能

    建议实现方案

    1. 推送服务接收到 数据后,立即将能唯一确定数据的 信息保存在数据库,然后将 原始数据 临时缓存在 Redis等缓存容器

      保存在数据库的信息,必须能保证足够可以用从 缓存容器 中重新取出 原始数据

    2. 最后再进行 值转换、发送网络请求等操作

    3. 所有操作成功后,将缓存删除;否则,延长 缓存时间

      记得缓存时间 要大于 预期下一次定时任务处理时间,否则还没等到 定时任务来处理自己,缓存的数据就没了

    触发机制

    定时任务触发
    设置原因
    1. 解决由于非数据错误导致的失败,如网络波动
    2. 减少人工处理的压力
    注意事项
    1. 设置最大失败次数、缓存超时机制,以便淘汰由于数据错误导致的对接失败
    2. 设置每次最大处理量,保证不会使得 A系统的服务压力猛增 ,同时 B系统也不一定能够同时处理那么多内容
    人工立即触发
    设置原因
    1. 肯定有等不到定时任务,紧急需要重试 的需求
    2. 方便测试
    注意事项
    1. 不需要跟定时任务一样处理多条任务,往往用户只需要 紧急处理 少量的单据
    源头手动触发
    设置原因
    1. 方便测试
    2. 缓存已经超时,需要重新提交数据
    3. 数据源有可能已经修改了——尤其在 由于数据错误导致失败时,更需要使用
    注意事项
    1. 源头重新发起后,如果旧数据仍留在 缓存中,应该用新数据覆盖
    2. 源头重新发起后,旧数据对接产生的日志记录不应该被抹去,应该被保留下来,以便追踪问题

    系统数据一致性(幂等)机制实现

    但是如果重试,有可能就会出现以下情况
    创建了两张单

    问题分析

    为什么会出现上图的结果?关键在于 A系统在请求失败后无法判断请求是否成功了

    想一下,如果你是B系统的设计者,你要怎样才能让 A系统 知道是否成功了呢?

    幂等机制支持关键点

    举例

    幂等机制需要 B系统来支持,什么样的系统才能支持幂等机制?我以 金蝶K3Cloud ERP 中销售订单表单(下称 SO单)举例,以下是简介:

    1. so单中的订单号(字段名:FbillNo) 是唯一的,支持创建的时候自定义订单号(关键1),如不自定义则自动生成
    2. 除此之外,每一张创建的单据有另外一个唯一的自增id,称为 FerpId
    3. erp提供了查询接口,可以根据 订单号 查询 表单信息,包括 FerpId(关键2)
    关键点分析: 存在一个字段,可以在创建阶段指定值并且可以据此值查询到表单是否存在

    如果提供以上的条件,就可以实现幂等机制:因为你可以根据上次请求预期结果,来查询上次预期结果是否成功

    可以在创建阶段指定值 的字段

    问:这种字段非常多,能传值的字段都是可以指定值的字段,是否需要挑选值做了唯一索引的呢?
    答:有更好,没有也可以。为什么呢?因为此值是 A系统自己控制的,A系统在传值时完全可以构造一个全局唯一的 值,比如加入时间戳、全局id生成器之类的。
    再问:如果我选的 K 字段没有唯一索引,A系统传的值为 :k123-20200613,此时保存成功了但A系统没有收到结果,超时失败了;但是B系统的前端用户想搞事情,也创建了一张 K 字段为 k123-20200613,保存成功了。后来A系统重试时,查询时发现了两张单,不知道哪张单是它推送过去的了,这怎么办?
    再答:这就是为啥说 有唯一索引更好 的原因。个人觉得程序无法解决,只能人工来处理,揪出这个”捣蛋鬼”了。

    可以据指定值查询到单据是否存在 的字段

    这个是幂等机制的基础!因为没有 这个字段或者查询接口不支持 ,则无法在重试前查询 上一次推送的结果是否成功

    实现示例
    1. A系统在每次重试so单推送erp前,需要根据 so单号 去查询此 so单是否存在于erp
    2. 如果存在,则将 FerpId存入数据库,并标记为已成功
    3. 如果没有查询成功(网络又失败了),则放弃重试
    4. 如果查询结果说明不存在,则重新推送
    注意事项
    1. 不是所有第三方系统都符合实现幂等机制的条件,也不是所有的 A系统都有实现幂等机制的需求,如发送邮件、短信——多一条感觉也没什么关系(反正都是垃圾邮件/邮箱)

    完整的日志机制

    推送日志

    推送日志是 事后定位bug 重要内容,最好不要只停留在日志文件中,数据库中应该也保留最近一次的错误原因等重要信息

    日志基本元素
    1. 推送时间
    2. 错误原因
    3. 推送人(如果有)
    4. 推送数据(如果是json就得打印,webservice 就算了吧)
    日志打印时间点
    1. 构造数据前:开始构造数据了
    2. 网络请求前:请求参数
    3. 网络请求后:请求结果,catch 超时异常
    日志打印原则
    1. 重要操作
    2. 易错操作
    3. 反映程序执行链路,也就是可追踪

    回调日志

    如果B系统会在某个时间回调 A系统的某些接口,一定要打印接收到的原始数据,因为这是 追责的重要依据。

    展开全文
  • ClickHouse 是一款常用于大数据分析的数据库,因为其压缩存储,高性能,丰富的函数等特性,近期有很多尝试 ClickHouse 做日志系统的案例。本文将分享如何用 ClickHouse 做出好用的通用日志系统
  • (2)mybatis源码日志模块用到对象适配器模式。 适配器模式将一个接口转为另外一个接口。它有三种实现方式: (1)当希望将一个类转换为满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的...
  • 内置10套首页模板随意切换支持对接codepay码支付实现免签支付支持多接口,支付轮询功能, 支持自定义支付插件。 支付通道改成插件形式,方便自行对接其他上游通道,增加了京东支付、银联支付、小微支付、码支付等。 ...
  • JavaWEB对接支付宝三方支付

    千次阅读 2019-03-27 21:19:43
    JavaWeb对接支付宝三方支付欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、...
  • 点评内容平台日均处理百万条内容,涉及百万次业务场景的执行、高达亿级的逻辑节点的执行,而业务日志分散在不同的应用中,并且不同内容,不同场景,不同节点以及多次执行的日志混杂在一起,无论是日志的搜集还是现场...
  • 与第三方Api接口对接需要注意的点

    千次阅读 2020-09-25 10:29:25
    与第三方Api接口对接需要注意的点 目录一、非技术对接二、技术对接三、必要措施四、常见错误解决 一、非技术对接 【重要】画出流程时序图 查看对方文档,积极沟通 本公司接口文档和图发对方确认,一定要对方明确...
  • Java对接地磅串口工作日志

    千次阅读 2019-08-29 10:44:01
    目录参考Demo三方包环境配置地磅相关串口冲突读取数据时有时无,更新太慢假的Windows64系统 参考Demo SerialPortDemo 三方包环境配置 RXTXcomm 官网链接 下载好以后 , 按照Install.txt 中的说明配置本地环境即可, ...
  • 原标题:基于golang+lua的Web日志安全分析系统FBI-AnalyzerFBI-Analyzer是一个灵活的日志分析系统,基于golang和lua,插件风格类似ngx-lua。使用者只需要编写简单的lua逻辑就可以实现golang能实现的所有需求,跳转...
  • 这些功能都有一些共通点: 保存渠道信息 调用渠道 保存渠道日志 失败报警 基于 Spring 事务模板(TransactionTemplate) 设计思想,我抽取出了 ChannelLogTemplate 这个模板类: ChannelLogTemplate.java @Component(...
  • 目的: 在支付之后,获取支付状态,回调自己系统的接口 问题: 回调不成功,支付宝(沙箱版)显示支付完成,但是并...这里的应用网关和授权回调地址是系统上线和需要返回三方授权信息的时候才需要填,(具体是什么.
  • 二、系统概述与功能设计 2.1系统概述 小区弱电工程共计公寓楼10栋。根据小区的户型结构和甲方的具体要求,我们设计采用总线型数字化联网(即基于TCP/IP通信协议的数字化楼宇对讲系统)彩色可视对讲门禁系统。可以...
  • 本文主角是一个SaaS模式的“后台发货系统”,对接美团等O2O平台的订单。目的是将各销售渠道的订单统一管理,完成发货。既然统一发货,就少不了做统一的库存同步到销售渠道的机制。这样才能确保各平台数据互通,良性...
  • 如何高效对接第三方支付

    千次阅读 2020-05-24 17:41:24
    目前我们已经服务30个国家和地区,不同国家往往需要对接不同的第三方支付公司,所以最近两年,研发组对接了大量的第三方支付公司,积累了一定的经验。 本文主要分享如何对接第三方支付,以及在生产上实际遇到的一些...
  • KubeSphere 对接 KeycloakKubeSphere 对接 Keycloak 实践OIDC 身份提供者准备工作步骤console 界面确认配置内容Keycloak Configuring realms排错步骤查看日志如果更新没有效果,就查看错误提示错误提示正确写法效果...
  • 第三方接口对接注意事项

    千次阅读 2021-05-11 20:17:27
    确认接口对接的网络协议和请求地址:https/http 端口号 请求地址 接口请求方面 尽量全部约定 数据传参+响应格式为:application/json POST请求 确认请求参数是否必传以及数据类型,非必传字段需要确认是传null还是空...
  • 简单付(simple-pay)的目的是让开发者对接第三方支付更加简单,便捷。聚合第三方支付通道并且实现零代码就能聚合收款的目的。技术框架核心框架:Spring-Boot 2.0视图框架:FreeMarker 2.3.28持久层框架:MyBatis ...
  • 什么是集中交易系统

    千次阅读 2020-12-03 11:29:12
    一、集中交易系统 从名字上看,一定是从分散的交易系统整合而来的,事实上也确实如此。 在上世纪90年代证券市场发展初期,证券经纪业务是以营业部为单位开展的,每家营业部都有自己的证券交易系统,单独保存自己的...
  • 最近公司销售部门用到了腾讯EC,实现公司内部OA系统与腾讯ec的数据同步,要求如下: 1、OA内部系统账号与腾讯ec登陆账号同步 2、首先做义工客户端工具用来把现有客户导入到EC,销售人员的客户信息与EC同步,同时EC...
  • 4.2、通用的数据源模块来对接多种数据源 一般公司肯定是存在多种不同类型的数据源的,比如 Mysql,Oracle,Hive 等,可以制作一个通用的模块,提供统一的接口,来对接这些不同的数据源。 数据源模块则提供三方接口...
  • 今天给大家分享一篇关于对账系统设计的文章,出自在支付行业摸爬滚打好几年的小黑哥之手。如果你之前做过支付相关的业务一定多多少少都接触过“支付数据对账”的问题。这个问题其实有非常多的解法,而不...
  • DDD的创新思想:开发即设计思想

    千次阅读 2020-08-18 12:01:15
    领域驱动设计(DDD)和微服务架构(MSA)近年来非常火热,尤其是在互联网公司的生产实践过程中。微服务架构为互联网公司自身业务的发展壮大提供了技术支撑,日渐显现出解决复杂业务需求的威力。...
  • 介绍在saas场景下如何技术选型,saas架构设计中关键的技术点等内容。
  • 常用的日志组件提问:如果自己开发一个开源框架(如Spring),你将采用上述哪个日志组件?发现哪个都不能采用,只能基于应用程序实际使用的日志组件来,不然就会日志打印多份。那具体怎么找到应用程序实际使用的日志...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,248
精华内容 499
热门标签
关键字:

三方系统对接日志设计