个人面试准备

项目介绍

最近做的项目是兴业银行银银平台汇收付系统,汇收付是一款为基金、保险、信托、消金等金融机构提供线上资金代收代付服务的支付结算系统,目前该汇收付接入了支付宝、京东金融、易方达、微众银行等上百家金融机构,支持银联、人行、大小额、超网、网联和城银请等十几个渠道,日均交易流水在100亿左右。
汇收付系统分为代收付、清结算两大模块,代收付负责对接商户,清结算负责对接渠道。汇收付系统业务包括:普通商户单笔代收、单笔代付,基金商户单笔代收、单笔代付及垫资与平垫资,批量交易、批量一笔账,差错处理、订单快速回盘、大额交易压单、交易失败重发、强制失败等
在交易进来的时候,系统会判断当前请求商户配置的并发上线量和同步转异步的上限量,若当前请求以超过并发量但还未超过转异步的上限,那么则保存为异步订单,后续由定时任务代为发起交易请求。若两则都超过,则拒绝当前交易。交易进来后,若是单笔交易,则代收付会先调用清结算进行智能路由,获取到具体的交易渠道后,再调用清结算,此时清结算会根据渠道ID和业务类型获取对应的渠道处理类,再向对应的渠道发起请求。若是批量交易,代收付会先解析批量文件,获取文件头生成总批数据,再解析文件体获取批量交易明细数据,总批和明细通过总批次号进行关联,然后代收付使用固定长度线程池,将批量数据转单笔放入线程池中调用清结算。针对无法实时获取交易结果的渠道,如超网、网联、大小额渠道,清结算内部制定了快速回盘机制。清结算调用完渠道后,现将订单信息置为处理中,再使用定时任务线程池,在指定N延迟时间后,调用渠道交易查询,若未查出结果,则交由后续定时任务进行回盘处理。
关于代收交易的对账与清算,在对账时,先由清结算获取各渠道对账信息与本地订单进行对账,对账无误后,生成一个清结算汇总对账文件上传至FTP。再由代收付从FTP上获取该对账文件后与本地订单进行对账,无误后方可进行清算。清算时的资金链路是:先由渠道将钱划入清结算的渠道过度户中,清结算再将各个渠道过渡户中的钱划至行内汇总户,最后再由代收付发起清算,将行内汇总户的钱划至各个商户的商户结算户中。
其中所用的技术包含:dubbo、zk、redis、roketMQ、mysql、quartz、shardingJDBC等,我主要负责清结算系统的功能分析与开发,其中包含各个渠道的接入开发、复用渠道的动态扩展、各渠道证书加载等。

我所做的上一个项目是支付宝医保社保行业侧项目,其中医保项目是在2019年6月接入的,我参与了项目从孵化到上线,从0到1的全部过程。该项目主要业务是为支付宝电子医保卡电子社保卡提供服务端接口,主要包含电子凭证绑卡、生码、在线支付等模块。所用技术包括sofa4、sofaMQ、tair、tbase、idb、jdk1.8。这个项目上线后,用户可在支付宝绑定电子医保、社保凭证,进入卡详情页面进行展码,使用医保社保行业码可进行医保在线支付。其中医保在线支付是做过相对最难的,让我至今印象还特别深刻的有一个需求,当时做之前支付宝医保码仅支持做账户验证和扣除用户消费的自费部分,个账和统筹部分还需要商家在上传订单后,先调用支付宝进行账户认证后再自行调用个账、统筹机构查询该笔订单的个账费用及统筹费用进行扣除,在这次改造后,商户仅需在上传订单后,调用一次支付宝,就能完成医保个账、医保统筹、医保自费部分的全部渠道扣款。当时这个需求牵涉到支付宝10多个中心,共计六七十个系统,链路较长,所以要求每个系统接口响应耗时尽量在500ms内。针对这个响应耗时的要求,我当时做了三个减少耗时的设计。1.在收到预结算请求时,系统做出决策并报送金融网络预结算后,不管报送结果如何,均先给上游系统返回成功,同时我们内部使用CompletableFuture开启异步线程,将各个渠道的预结算报送处理均放置在里面,待收到所有渠道返回的预结算结果消息时,计算各个渠道的金额,并放置在缓存中,待收银台发起预结算结果轮询查询时,将缓存中的预结算信息返回;2.对数据量比较大且查询较多的数据库表进行数据库缓存双写处理;3.对较为固定的系统参数配置,使用caffeine Cache做了本地缓存。我在该需求做的工作有:和PD一起做需求分析,将业务需求转化为可能性的技术实现,与关联系统的同事确认接口对接规范,然后写出系统分析文档与相关的TeamLeader评估分析无误后,进行工作分配,该需求核心代码部分全由我独立完成。

~~ 其中医保在线支付是做过相对最难的,让我至今印象还特别深刻的有一个需求,当时做之前支付宝医保码仅支持扣除用户消费的自费部分,个账和统筹部分还需要商家在上传订单后,先调用支付宝进行账户认证后,再自行调用个账、统筹机构查询该笔订单的个账费用及统筹费用进行扣除。再这次改造后,商户仅需在上传订单信息后,调用支付宝完成医保个账、医保统筹、医保自费金额扣款。我在该需求做的工作有:和PD一起做需求分析,将业务需求转化为可能性的技术实现,与关联系统的同事确认接口对接规范,然后写出系统分析文档与相关的TeamLeader评估分析无误后,进行工作分配,该需求核心代码部分全由我独立完成。
核心的业务逻辑是:医保在线支付全链路预结算相关功能的实现,其中包含资产决策、预结算报送、预结算结果咨询、预结算结果异步消息监听等。通过以上鸡蛋,决策出本次交易涉及的扣款渠道,并向不同的渠道分别并同时报送预结算请求,在通过异步消息监听接受并计算出各个渠道的预扣款结果,最后收银台轮询预结算结果时,将放置在缓存中的预结算结果返回。整个项目后续的结算、退款等业务链路使用的金额,均为预结算时所计算出的金额。
同时,由于该需求关联了支付宝内部8个中心,五六十个系统,所以对每个系统接口的耗时有严格要求,我们负责的三个系统链路耗时需在1000ms内,所以此次还对数据量比较大且查询次数较多的表做了缓存处理。如绑卡信息表,我们做了数据库与缓存双写,同时对于系统内较为固定的配置使用java8的caffeine Cache做了本地缓存。同时上述提到的多个渠道同时分别进行预结算报送,使用了CompletableFuture,开启异步线程对多个渠道进行并发报送。后期我还负责整个需求全链路的联调以及测试支持,并按照计划顺序推进~~

DUBBO

dubbo组件

  1. 容器:服务运行容器
  2. 提供者:暴露服务的服务提供方
  3. 消费者:调用远程服务的服务消费方
  4. 注册中心:服务注册与发现的注册中心,服务提供者先启动,然后注册服务订阅者在注册中心获取自己想要的服务,若未获得到自己想要的服务,要么它会不断地尝试订阅。同时,新的服务注册到注册中心后,注册中心会通知消费者

dubbo的执行流程

  1. 服务容器负责启动加载运行服务提供者,根据提供者配置的文件根据发布协议完成服务的初始化
  2. 提供者在启动时,根据配置中的注册地址链接注册中心,将自己的服务信息发送到注册中心去进行注册
  3. 消费者在启动时,根据消费者xml配置文件中服务引用信息,链接注册中心订阅服务
  4. 注册中心根据服务订阅关系,返回提供者地址列表给到消费者,如果提供者服务信息有变更,那么注册中心会将新的服务信息发送给订阅的消费者
  5. 消费者调用服务时,会根据dubbo的负载均衡算法,选择本次要调用的服务器提供者
  6. 服务提供者和消费者,会在内存中记录调用的次数和时间,每一分钟统计数据到监控着

zk的服务注册订阅流程

  1. 服务启动,想zk注册/dubbo节点,并在该节点下注册一个以该服务为名称的节点
  2. 消费者,通过watch监听机制,实时监听该服务节点
  3. 消费端监听到该节点下子节点发生变化,便会通知消费端去得到该服务,完成相关操作
  4. 消费端启动后,会向zk注册一个/consumers节点,用于监听服务之间的调用关系

dubbo断熔-服务降级(consumer层面)

  1. 作用:为了防止提供者无报错的情况下长时间响应,影响其他服务进度,从而导致降低了consumer的消费性能;
  2. 使用-mock机制,利用消费者的mock属性,手动对消费端的调用进行降级
    2.1 使用@DubboReference注解或dubbo:reference标签的mock属性
    2.2 自定义
  3. mock调用策略
    3.1 返回mock数据
    3.2 抛出自定义异常
    3.3 执行默认的mock实现类
    3.4 执行指定的mock实现类

dubbo限流

  1. execute-仅提供者端:可以设置为接口级别,也可以设置为方法级别.限制的是并发执行数量
  2. accepts-仅提供者端:尽可以在提供端dubbo:provider/与dubbo:protocl/。用户对指定协议的连接数量进行限制

zk选举机制

  1. 第一次启动时(假设5台服务器ABCDE)
    1.1 A启动投自己一票,当前选票不够半数,状态设置为looking
    1.2 B启动时,AB分别投各自一票,测试A发现B的myid比自己大,于是更改选票投B,此时A1票,B2票,B票数不过半,AB状态设置为looking
    1.3 ABC启动时,AB发现myid小于C,ABC均投C,此时C三票过半数,状态更改为leading,AB更改为following,C为zk集群LEADER
    1.4 DE启动时,发现集群已有leader了,于是跟投C,状态设置为following
  2. LEADER挂掉后选举
    2.1 所有follower节点状态更新为looking
    2.2 每个service节点发起投票,投标内容为(myid ,zxid),myid-配置在myid文件中的整型数字,要求集群唯一;zxid-事务id,用来标记服务器状态的变更,某一时刻不同机器zxid不同
    2.3 集群中的每台机器都会受到其他服务器的投票信息,首先会判断投票的失效性,是否是本次投票,是否来自looking状态的服务器投票
    2.4 统计投票信息,判断是否有过半的机器收到了相同的投票信息
    2.5 一旦确定了leader,leader服务器会将自身状态改为leading,而其他服务器状态更改为following

线程池

线程池种类

  1. 定长线程池FixedThreadPool
  2. 定时线程池ScheduledThreadPool
  3. 缓存线程池CacheThreadPool
  4. 单线程池SingleThreadPool

以上线程池的本质都是ThreadPoolExecutor

  1. 定长线程池,只有核心线程池,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列
  2. 定时线程池,核心线程数量固定,非核心线程数量无限,执行完闲置10ms后回收,任务队列为延迟阻塞队列
  3. 缓存线程池,无核心线程,非核心线程数量无限,执行完闲置60s后回收,任务队列为不存储元素的阻塞队列
  4. 单线程池,只有一个核心线程,无非核心线程,执行完立刻回收,任务队列为链表结构的有界队列

线程池主要参数

  1. corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  2. maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
  3. keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  4. unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
  5. workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
  6. threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
  7. handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

线程池的工作流程

  1. 一个新任务来的时候,优先使用核心线程池,若核心线程池未满,则使用核心线程执行
  2. 核心线程池已满,判断当前等待队列是否放满,若等待队列未满,则放入等待队列
  3. 若核心线程池与等待队列均满,判断是否超过最大线程数,若未超过,则新增一个非核心线程执行,执行完毕后非核心线程在keepAliveTime时间内未执行其他线程,则会被回收。若配置了allowCoreThreadTimeOut配置为true,核心线程也会根据keepAliveTime回收

线程池的拒绝策略

  1. AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
  2. CallerRunsPolicy:由调用线程处理该任务。
  3. DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
  4. DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。

线程池工作队列

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
  2. LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。

如何合理设置线程池的核心线程数

看线程任务是IO密集型,还是计算密集型

  1. IO密集型:需要考虑系统内存上限,需要服务器测试到底有多少个线程比较合适。最佳线程数目=(线程等待时间+线程执行时间)/线程执行时间+CPU个数。
  2. 计算密集型:线程数=N*CPU个数+1

Java基础

ArrayList和LinkedList区别

ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为数组长度固
定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList(需要创建大量的node对象)

LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐
一遍历。遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需
要对list重新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexlOf对list进行了遍历,当结
果为空时会遍历整个列表。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值