关于java:面试官问我什么是高并发下的请求合并

40次阅读

共计 6800 个字符,预计需要花费 17 分钟才能阅读完成。

这是 why 哥的第 76 篇原创文章

从一道面试题说起

前段时间一个在深圳的,两年教训的小伙伴进来面试了一圈,收割了几个大厂 offer 的同时,还总结了一下面试的过程中遇到的面试题,面试题有很多,文末的时候我会分享给大家。

这次的文章次要分享他面试过程中遇到的一个场景题:

他说对于这个场景题,面试的时候没有什么思路。

说真的,申请合并我晓得,高并发无非就是疾速的申请合并。

然而在我无限的认知外面,如果相似于秒杀的高并发扣库存这个场景,用申请合并的形式来做,我个人感觉是有点怪怪的不够传统。

在传统的,或者说是业界罕用的秒杀解决方案中,从前端到后盾,你也找不到申请合并的字样。

我了解申请合并更加实用的场景是查问类的,或者说是数值减少类的需要,对于库存扣减这种,你稍不留神,就会呈现超卖的状况。

当然也有可能是我了解错题意了,看到高并发扣库存就想到秒杀场景了。

然而不重要,咱们也不能间接和面试官硬刚。

我会从新给个我感觉正当的场景,通知大家我了解的申请合并和高并发下的申请合并是什么玩意。

申请合并

当初咱们抛开秒杀这个场景。

换一个更加适合,大家可能更容易了解的场景来聊聊什么是申请合并。

就是热点账户。

什么是热点账户呢?

在第三方领取零碎或者银行这类交易机构中,每产生一笔转入或者转出的交易,就须要对交易波及的账户进行记账操作。

记账一般来说波及到两个局部。

  • 交易系统记录这一笔交易的信息。
  • 账户零碎须要减少或缩小对应的账户余额。

如果对于某个账户操作十分的频繁,那么当咱们对账户余额进行操作的时候,就会波及到并发解决的问题。

并发了怎么办?

是的,咱们能够对账户进行加锁解决。这样一来,这个账户就波及到频繁的加锁解锁操作。

这样咱们能够保证数据不出问题,然而随之带来的问题是随着并发的进步,账户零碎性能降落。

这个账户,就是热点账户,就是性能瓶颈点。

热点账户是业界的一个十分常见的问题。

我所理解到的惯例解决方案大略能够分为三种:

  • 异步缓冲记账。
  • 设立影子账户。
  • 多笔合一记账。

本大节次要是介绍“多笔合一记账”解决方案,从而引出申请合并的概率。

对于另外两个解决方案,就先简略的说一下。

首先异步缓冲记账。

我先不解释,你就看着这个名字,想着这个场景,你感觉你会想到什么?

异步,是不是想到了 MQ?

那么请问你零碎外面为什么要引入 MQ 呢?

来,面试八股文背起来:异步解决、零碎解耦、削峰填谷。

你说咱们以后的这个场景下属于哪一种状况?

必定是为了做削峰填谷呀。

假如账务零碎的 TPS 是 200 笔每秒,当申请低于 200 笔每秒的时候,账务服务基本上可能及时处理马上返回。

从用户的角度来说就是:啪的一下,很快啊。我就收到了记账胜利的告诉了,也看到账户余额产生了变动。

然而在业务高峰期的时候,流量间接翻倍,每秒过去了 400 笔申请,这个时候对于账务零碎来说就是流量洪峰,须要进行削峰了,队列外面开始沉积着申请,开始排队解决了。

在流量低谷的时候,就能够把这部分数据生产实现。

相当于数据扔到队列外面之后,就能够通知用户记账胜利了,钱马上就到。

然而这个计划带来的问题也是很显著的, 如果流量真的爆了,一天都没有谷让你填,队列外面沉积着大量的申请还没来得及解决,你怎么办?

这对于用户而言就是:你明明通知我记账胜利了,为什么我的账户余额迟迟没有变动呢?是不是想阴我钱,我反手就是一波投诉。

另外一个危险点就是对于收入类的申请,如果被削峰,很显著,咱们提前就通知了用户操作胜利,然而真正动账户余额的时候曾经提早了,所以可能会呈现账户透支的状况。

另外一个设立影子账户的计划,其实和咱们本次的申请合并的主题是另外一个不同的方向。

它的思维是拆分。

热点账户说到底还是一个单点问题,那么对于单点问题,咱们用微服务的思维去解决的话是什么计划?

就是拆分。

假如这个热点账户上有 100w,我设立 10 个影子账户,每个账户 10w,那么是不是咱们的流量就扩散了?从一个账户变成了 10 个账户。

压力也就进行了摊派。

这个计划就有点相似于秒杀场景中的库存了,库存咱们也能够拆多份。

然而带来的问题也很显著。

一是获取账户余额的时候须要进行汇总操作。

二是假如用户要扣 11w 呢?咱们总余额是够的,然而每个影子账户上的钱是不够的。

三是你的影子账户抉择的算法是很重要的,是用随机?轮训?加权?这些对于账务成功率都是有比拟大的影响的。

另外这个思维,我在之前的文章中也提到过,有趣味的能够看看其在 JDK 源码中的利用:我从 LongAdder 中窥探到了高并发的秘籍,下面只写了两个字 …

好了,回到本次的主题:多笔合一笔记账。

有个网红店,生意十分的好,每天很多人在店外面生产。

当用户扫码领取后,申请会发送到这个店对接的第三方领取公司。

当领取公司收到申请,并实现记账操作后才会告知商户用户领取胜利。能够给用户商品了。

随着店里生意越来越好,带来的问题是第三方领取公司的零碎压力减少,扛不住这么大的并发了。导致用户领取成功率的降落或者用户领取胜利后很长时间才告诉到商户。

那么针对这个商户的账户,咱们就能够做多笔合一笔解决。

当记录进入缓冲流水记录表之后,咱们就能够告诉商户用户领取胜利了,至于钱,你释怀,我有定时工作,一会就到账:

所以当用户下单之后,咱们只是先记录数据,并不去理论动账户。等着定时工作去触发记账,进行多笔合并一笔的操作。

比方上面的这个示意图:

商户理论有 5 个用户领取记录,然而这 5 笔记录对应着一条账户流水。咱们拿着账户流水,也是能够追溯到这 5 笔交易记录的。

这样的益处是吞吐量上来了,告诉及时,用户体验也好了。然而带来的弊病是余额并不是一个精确的值。

假如咱们的定时工作是一小时汇总一次,那么商户在后端看到的交易金额可能是一小时之前的数据。

而且这种计划对于账户收钱的场景十分的适宜,然而减钱的场景,也是有可能会呈现金额为负的状况。

不晓得你有没有看出多笔合一笔解决计划的机密。

如果咱们把缓冲流水记录表看作是一个队列。那么这个计划形象进去就是队列加上定时工作。

所以,_申请合并的关键点也是队列加上定时工作_。

文章看到当初,申请合并咱们应该是大略的理解到了,也的确是有实在的利用场景。

除了我下面的例子外,比方还有 redis 外面的 mget,数据库外面的批量插入,这玩意不就是一个申请合并的实在场景吗?

比方 redis 把多个 get 合并起来,而后调用 mget。屡次申请合并成一次申请,节约的是网络传输工夫。

还有实在的案例是转账的场景,有的转账渠道是按次免费的,那么作为第三方公司,咱们就能够把用户的申请先放到表里记录着,等一小时之后,一起汇总发动,假如这一小时内产生了 10 次转账,那么 10 次免费就变成了 1 次免费,尽管让客户等的略微久了点,但还是在能够承受的范畴内,这操作节约的就是真金白银了。

高并发的申请合并

了解了申请合并,那咱们再来说说当他后面加上高并发这三个字之后,会产生什么变动。

首先不论是在申请合并的后面加上如许狂拽炫酷吊炸天的形容词,说的如许的天花乱坠,它也还是一个申请合并。

那么队列和定时工作的这个根底构造必定是不会变的。

高并发的状况下,就是申请量十分的大嘛,那咱们把定时工作的频率调高一点不就行了?

以前 100ms 内就会过去 50 笔申请, 我每收到一笔就是立刻解决了。

当初咱们把申请先放到队列外面缓存着,而后每 100ms 就执行一次定时工作。

100ms 到了之后,就会有定时工作把这 100ms 内的所有申请取走,对立解决。

同时,咱们还能够管制队列的长度,比方只有 50ms 队列的长度就达到了 50,这个时候我也进行合并解决。不须要期待到 100ms 之后。

其实写到这里,高并发的申请合并的答案曾经进去了。关键点就三个:

一是须要借助队列加定时工作实现。

二是管制定时工作的执行工夫.

三是管制缓冲队列的工作长度。

计划都想到了,把代码写进去岂不是很容易的事件。而且对于这种面试的场景图,个别都是探讨技术计划,而不太会去探讨具体的代码。

当探讨到具体的代码的时候,要么是对你的计划存疑,想具体的探讨一下落地的可行性。要么就是你答对了,他要筹备从代码的交易开始衍生另外的面试题了。

总之,大部分状况下,不会在你给了一个面试官感觉谬误的计划之后,他还和你探讨代码细节。你们都不在一个频道了,连忙换题吧,还聊啥啊。

切实要往代码实现上聊,那么大概率他是在等着你说出一个框架:Hystrix。

Hystrix 框架

其实这题,你要是晓得 Hystrix,很容易就能给出一个比拟完满的答复。

因为 Hystrix 就有申请合并的性能。给大家演示一下。

假如咱们有一个学生信息查问接口,调用频率十分的高。对于这个接口咱们须要做申请合并解决。

做申请合并,咱们至多对应着两个接口,一个是接管单个申请的接口,一个解决把单个申请汇总之后的申请接口。

所以咱们须要先提供两个 service:

其中依据指定 id 查问的接口,对应的 Controller 是这样的:

服务启动起来后,咱们用线程池联合 CountDownLatch 模仿 20 个并发申请:

从控制台能够看到,霎时承受到了 20 个申请,执行了 20 次查问 sql:

很显著,这个时候咱们就能够做申请合并。每收到 10 次申请,合并为一次解决,联合 Hystrix 代码就是这样的,为了代码的简洁性,我采纳的是注解形式:

在下面的图片中,有两个办法,一个是 getUserId,间接返回的是 null,因为这个办法体不重要,基本就不会执行。

在 @HystrixCollapser 外面能够看到有一个 batchMethod 的属性,其值是 getUserBatchById。

也就是说这个办法对应的批量解决办法就是 getUserBatchById。当咱们申请 getUserById 办法的时候,Hystrix 会通过肯定的逻辑,帮咱们转发到 getUserBatchById 上。

所以咱们调用的还是 getUserById 办法:

同样,咱们用线程池联合 CountDownLatch 模仿 20 个并发申请,只是变换了申请地址:

调用之后,神奇的事件就呈现了,咱们看看日志:

同样是承受到了 20 个申请,然而每 10 个一批,只执行了两个 sql 语句。

从 20 个 sql 到 2 个 sql,这就是申请合并的威力。申请合并的处理速度甚至比单个解决还快,这也是性能的晋升。

那假如咱们只有 5 个申请过去,不满足 10 个这个条件呢?

别忘了,咱们还有定时工作呢。

在 Hystrix 中,定时工作默认是每 10ms 执行一次:

同时咱们能够看到,如果不设置 maxRequestsInBatch,那么默认是 Integer.MAX_VALUE。

也就是说,在 Hystrix 中做申请合并,它更加偏重的是工夫方面。

性能演示,其实就这么简略,代码量也不多,有趣味的敌人能够间接搭个 Demo 跑跑看。看看 Hystrix 的源码。

我这里只是给大家指几个关键点吧。

第一个必定是咱们须要找到办法入口。

你想,咱们的 getUserById 办法的办法体外面间接是 return null,也就是说这个办法体是什么基本就不重要,因为不会去执行办法体中的代码。它只须要拦挡到办法入参,并缓存起来,而后转发到批量办法中去即可。

而后办法体下面有一个 @HystrixCollapser 注解。

那么其对应的实现形式你能想到什么?

必定是 AOP 了嘛。

所以,咱们拿着这个注解的全门路,进行搜寻,啪的一下,很快啊,就能找到办法的入口:

com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect#methodsAnnotatedWithHystrixCommand

在入口处打上断点,就能够开始调试了:

第二个咱们看看定时工作是在哪儿进行注册的。

这个就很好找了。咱们曾经晓得默认参数是 10ms 了,只须要顺着链路看一下,哪里的代码调用了其对应的 get 办法即可:

同时,咱们能够看到,其定时性能是基于 java.util.concurrent.ScheduledThreadPoolExecutor#scheduleAtFixedRate 实现的。

第三个咱们看看是怎么管制超过指定数量后,就不期待定时工作执行,而是间接发动汇总操作的:

能够看到,在 com.netflix.hystrix.collapser.RequestBatch#offer 办法中,当 argumentMap 的 size 大于咱们指定的 maxBatchSize 的时候返回了 null。

如果,返回为 null , 那么阐明曾经不能承受申请了,须要立刻解决,代码外面的正文也说的很分明了:

以上就是三个要害的中央,Hystrix 的源码读起来,须要下点功夫,大家本人钻研的时候须要做好心理准备。

最初再贴一个官网的申请合并工作流程图:

打完出工。

面试题

后面说的深圳的,两年教训的小伙伴把面试题汇总了一份给我,我也分享给大家吧。

Java 根底

  • volatile 关键字底层原理
  • 线程池各个参数含意
  • lock、synchronized 区别
  • ReentrantLock 锁偏心与非偏心实现、重入原理
  • HashMap 扩容机会(容量初始化为 1000 和 10000 是否触发扩容)、机制、1.7 与 1.8 的差别
  • ConcurrentHashMap1.7、1.8 的优化与差别,size 办法实现差别
  • ThreadLocal 原理与危险、为什么会内存泄露
  • 阻塞队列的用处、区别
  • LinkedBlockingQueue 对列的 add、put 区别,理论过程中如何应用
  • 乐观锁、乐观锁、自旋锁的应用场景、实现形式、优缺点
  • Class.forName、loadClass 区别;
  • 线程生命周期、死锁条件与死锁防止、状态转换关系(源码级别);
  • String intern 办法;
  • cas 的优缺点与解决方案、ABA 问题;

JVM 相干

  • CMS 垃圾回收的碎片解决形式
  • 罕用的垃圾回收器
  • JVM 垃圾回收器 CMS 的优缺点、与 G1 的区别、进入老年代的机会
  • JVM 内存模型
  • JVM 调优思路
  • GC Root、ModUnionTable
  • 偏差锁、轻量级锁、重量级锁底层原理、降级过程
  • jmap、jstat、top、MAT
  • CMS 与 G1 对别
  • GC Root、ModUnionTable;

Redis 相干

  • Redis 高性能起因
  • Redis 的部署模式
  • RedisCluster 底层原理
  • Redis 长久化机制
  • 缓存淘汰机制
  • 缓存穿透、缓存雪崩、缓存击穿产生场景与解决方案

SQL 相干

  • MyBatis 拦截器的用处
  • MyBatis 动静 SQL 原理
  • 分库分表方案设计
  • MySQL 怎么解决幻读、原理(源码级别)
  • Gap 锁的作用域原理
  • RR、RC 区别
  • MySQL 默认的事务隔离级别、Oracle 默认的事务隔离级别
  • MySQL 为啥应用 B + 树索引
  • redo log、binlog、undo log 写入程序、别离保障了 ACID 的什么个性
  • 数据库乐观锁
  • MySQL 优化
  • MySQL 底层原理

Spring 相干

  • @Bean 注解、@Component 注解区别
  • Spring Aop 原理
  • @Aspect 和一般 AOP 区别
  • 自定义拦截器和 Aop 那个先执行
  • web 拦截器
  • DispatchServlet 原理

Dubbo 相干

  • Dubbo 负载平衡、集群容错
  • Dubbo SPI 机制、Route 重写应用场景
  • Dubbo RPC 底层原理
  • 全链路监控实现原理

分布式相干

  • 分布式锁的实现形式
  • 漏斗算法、令牌桶算法
  • 事务最终一致性解决方案
  • SLA
  • 分布式事务实现形式与区别
  • Tcc Confirm 失败怎么办?
  • 分布式锁的各种实现形式、比照
  • 分布式 ID 的各种实现形式、比照
  • 雪花算法时钟回拨问题与应答计划
  • 红锁算法

设计模式

  • 罕用的设计模式
  • 状态模式
  • 责任链模式解决了什么问题
  • 饿汉式、懒汉式优缺点、应用场景
  • 模板办法模式、策略模式、单例模式、责任链模式

Zookeeper

  • Zookeeper 底层架构设计
  • zk 一致性

MQ

  • Kafka 程序音讯
  • MQ 音讯幂等
  • Kafka 高性能秘诀
  • Kafka 高吞吐原理
  • Rocket 事务音讯、延时队列

计算机网络

  • 浏览器输出一个 url 产生了什么
  • Http 1.0、1.1、2.0 差别
  • IO 多路复用
  • TCP 四次挥手过程、状态切换
  • XSS、CRSF 攻打与预防
  • 301、302 区别

Tomcat

  • Tomcat 大略原理

代码

  • 手写公布订阅模式
  • 大数(两个 String)相加

场景问题

  • 打赏排行榜实现
  • 高并发下的申请合并
  • CPU 100% 解决教训
  • 短链零碎设计
  • 左近的人我的项目实现
  • 10w 个红包秒级发送计划
  • 延时工作的实现计划与优缺点比照

说来惭愧,有些题我也答不上来,所以和大家一起查漏补缺吧。

哦,对了,那个小伙子最终收割了好几个大厂 offer,跑来问我哪个 offer 好。

你说这问题对我来说那不是超纲了吗?我也没在大厂体验过啊。所以我狐疑他不讲武德,来骗,来偷袭我这个老实巴交的小号主,我心愿他能耗子尾汁,在鹅厂好好倒退:

满腹经纶,难免会有纰漏,如果你发现了谬误的中央,能够提出来,我对其加以批改。

感谢您的浏览,我保持原创,非常欢送并感谢您的关注。

我是 why,一个被代码耽搁的文学创作者,不是大佬,然而喜爱分享,是一个又暖又有料的四川好男人。

还有,欢送关注我呀。

正文完
 0