滴滴1面面经
之前公司带过的一个实习生,想换工作,就投递了这家公司,总体来说面试难度不高。 滴滴这个面试官水平有点低,感觉啥都没问出来。
我这个小师弟背景: 985本硕毕业,美团实习一年,在一线大厂工作了2年,目前薪资32K*15。这家公司的薪资还是可以的,工作2-3年,最高也可以给到67W,并且是非常核心的C端部门,面试时长60分钟。
面试结果:通过。 二面会邀如下:
一面面试会邀:
二面会邀:
面试官第一个问题:Java有哪一些支持并发的组件,从整个面上介绍一下Java在并发上做了哪些事情?
候选人回答:
首先是一台服务器进程的并发处理方式,为什么会出现线程安全问题,因为操作系统工程师和硬件工程师为了提升程序性能,比如线程切换、添加CPU缓存、多核、还有编译优化,会导致多线程读写同一个内存数据的时候会有并发问题。
既然有并发问题,那么就有解决并发问题的一些工具类、组件和设计思想,Java常见的解决并发安全的类有,synchronized、Lock、ThreadLocal、乐观锁、CopyOnWrite思想、voliate、String类的一些不变性设计模式。 但是实际开发过程中,因为我们都是跟数据库交互,我在美团实习的时候,我当时喜欢用锁,觉得学了锁出现并发的时候就可以用,但是一个同事和我说,你到底有多么复杂的业务场景需要使用synchronized,因为synchronized大家还是比较忌讳的,synchronized会将线程由并行改成串行,但是对于流量高的系统,本身现在流量就很高,加锁,流量会将服务器打挂。
因为创建线程和销毁线程是比较消耗资源的,JDK就引入了线程池。我也看了很多源码,并发concurrent包里面的锁,线程池源码我也都看过。 但是我现在做的业务,肯定不是一台服务器运行代码,是一个集群的形式,那肯定要有分布式锁。
分布式锁,就是让多台机器访问同一个资源,而将不同服务器的线程拦截住,现在主要使用的是Redis锁,当然ZK也可以实现锁,但是ZK是强一致性的组件,对高频的写操作支持的并不友好,所以用的也不是很多,用的多的还是Redis锁。
这个问题不难。 候选人首先讲了为什么会出现并发安全问题,然后列举了一些解决并发安全问题的办法,候选人也讲述了自己实际工作为什么不使用JDK的synchronized和Lock锁,并且也说明了自己也看了很多源码,比如锁和线程池源码。 因为JDK的synchronized和Lock只能在一个进程中使用,但是真实场景都是分布式系统,需要使用分布式锁,Redis和ZK可以实现分布式锁,但是ZK写操作慢,所以实际工作中Redis锁使用的更多。
总结:虽然并发类有一些知识没有说,但是这么回答有什么大的问题吗? 技术出现的背景也说了,并且也表达了自己也看了很多源码,并且实际工作中是怎么使用锁的,因为对于流量高的业务,除了线程池(线程池也没啥用,现在都用动态线程池)和一些线程安全的集合类(ConcurrentHashMap)外,synchronized、Lock等几乎没啥使用场景。有的面试官问一个问题,喜欢考察候选人的技术深度和实际工作怎么使用这个技术的。 有的面试官喜欢考察候选人八股文。
八股文回答:
在我看来,并发领域可以抽成3个核心问题:分工、同步、互斥。
分工是指:当我们需要使用异步线程提高程序性能时,拆分任务,并将这些任务交给不同的线程运行。Java SDK中支持分工的工具类有Executor、Fork/Join、Future等。
同步是指: 在方法执行过程中,线程之间是有依赖的,一个线程结束后,依赖它的后续线程就可以运行相应的任务。Java SDK 并发包里的 Executor、Fork/Join、Future 本质上都是分工方法,但同时也能解决线程协作的问题。例如,用 Future 可以发起一个异步调用,当主线程通过 get() 方法取结果时,主线程就会等待,当异步执行的结果返回时,get() 方法就自动返回了。主线程和异步线程之间的协作,Future 工具类已经帮我们解决了。除此之外,Java SDK 里提供的 CountDownLatch、CyclicBarrier、Phaser、Exchanger 也都是用来解决线程协作问题的。
互斥是指: 同一时刻,只允许一个线程访问共享变量。Java常见的解决并发安全的类有,synchronized、Lock、ThreadLocal、乐观锁、CopyOnWrite思想、voliate、Stirng类的一些不变性设计模式。
还有线程池,阻塞队列,线程安全的集合等,这就是我理解的Java并发体系。 回答完毕。
不谈技术出现的背景,不谈实际工作是怎么使用的,和这个技术有什么问题。把自己知道的全部说出来就行。也是另一种面试的回答思路。
最好的回答是八股+扩展。 简单的知识,八股文讲出来,然后有深度或者有业务含义的,可以适当扩展。
面试官第二个问题:你刚刚也说了你们使用比较多的是Redis锁,你可以结合你们的业务场景说一下是怎么使用的吗?
候选人回答:
我在美团使用的Redis锁比较多。我可以说一下使用场景,我们在做派单调度的时候,因为要更新订单状态,害怕订单状态出问题(多个角色会更新订单状态),所以要给订单加锁。Redis的锁有setnx(),这个是最早版本Redis锁的实现。 后面使用set(NX,PX,EX,过期时间)实现的Redis锁。现在的话,使用tryLock()实现Redis锁。
候选者业务中为什么会出现并发问题说的有点简单,可以适当扩展一下。
面试官想问的是,你们业务中是否遇到过分布式系统下,多个角色同时更新同一条数据而出现数据一致性问题,然后接下来就是锁什么,也就是Redis锁的key跟哪个业务数据关联上。
Redis锁的实现以及锁实现的技术变更,候选人已讲解。
扩展:
如果你是学生或者实际工作没有做过流量高的业务,你就找你项目会产生并发的数据,然后以一种合理的方式回答出来.
这个是一个应届生的项目(话说你们从哪里找的项目,项目假的厉害),第一条工作职责:
我实习(或者学校)做的项目,项目为游戏账号交易系统,因为会出现多个玩家购买一个账号的问题,我这里就是使用Redis的锁解决的,锁的是账号id。
但是需要注意的是:有的面试官会问, 多个用户同时读写同一份数据时,没有获取到锁的线程干嘛? 等待锁还是抛异常还是直接返回等。
比如你回答完后,面试官问你:
1.没有获取到锁的用户,你们的处理方案是什么? 2.这里除了使用Redis锁,还有没有其他解决方案?
一定要根据自己的业务特点整理清楚。
面试官第三个问题: 你们除了锁之外,还有一些其他的并发场景吗?
这个问题要考察什么,我也有点懵。面试官想问的是,多个用户同时读写同一个数据,你们有其他解决方案吗? 还是你有做过流量高的系统吗? 然后这些流量高的系统,你们是怎么解决并发问题的。 完全是2个不一样的解决方案,没有get到他想问的点。
候选人回答:
流量很高的业务算吗? 这也算并发,但是我们的业务更多的是读操作,主要是处理高流量,用户请求时,我们怎么样保证数据能快速的返回,并保证我们服务的稳定性。
扩展:
1.除了Redis锁之外,分布式系统下,是否有其他办法可以保证数据的一致性? 可以使用数据库的行锁+版本号来实现。
2.如果面试官问你高流量系统如何应对并发请求的?
如果是读请求,解决方案是将数据放到Redis中,让Redis承担读流量。 如果流量再高,就是本地缓存+Redis缓存的解决方案,本地缓存作为一级缓存,Redis作为二级缓存。当然系统肯定也要配置限流。
如果是写请求,可以使用MQ去削流。
面试官第四个问题,你们的系统如何保证数据快速返回的?你们做了哪些工作?
候选人回答: 我做的主要是营销类业务和排行榜业务。 现在应对高流量和减小接口耗时都是一些通用的解决方案。
现在都是全量缓存,MySQL里面有的数据,Redis一旦没有,就有可能会出现缓存击穿、缓存穿透等问题。但是我们的业务体量,不会让你出现缓存穿透,缓存击穿的,而且公司对Redis的内存消耗也不是那么在意,也不是不在意吧,只是这些内存消耗对于营销类的业务,完全是可以接受的。
接下来需要考虑的就是MySQL里面的数据(或者下游系统的数据)怎么和Redis的数据保持一致,使用的就是定时任务,轮训MySQL里面的数据,然后放到缓存当中。
但是Redis在使用过程中,最怕的就是热点数据和bigkey数据,解决方案就是把数据打散,一些热点数据我们就使用本地缓存。 我们的系统架构就是本地缓存+Redis缓存。 流量来了,我们发现本地缓存有数据就返回,没有的话,请求Redis,然后有的话,放到内存当中,主要是这样的一个系统架构。
候选人说的,除了啰嗦外,没有什么问题。
想回答好这个问题,一定要跳出,你在博客或者其他视频看到的知识,他们整理的很多知识只适合流量低的应用。 对于很多核心服务,没这么在意内存的,多缓存1G数据,这点内存消耗对于业务正常运行来说不算什么的。缓存击穿、缓存穿透、缓存雪崩出现的原因都是,MySQL有的数据Redis没有,并且卡业务的临界过期时间引起的。 但是互联网公司的核心业务缓存解决方案都是全量缓存,过期时间在业务合理的范围内足够长,Redis和MySQL(或者下游业务方)的数据一致性是通过MQ或者定时任务实现的。
注意:如果你只回答使用缓存就有点简单了。 Redis会的和不会的,写的代码的效率和稳定性差距是很大的。 这里还是可以体现出你的技术深度的,要适当扩展。
去掉这些啰嗦的话,一个更好的回答。
为了能快速的返回数据给用户,不可能请求数据库获取数据。 我们的解决方案就是将数据全量缓存到Redis中,如果流量更高或者有一些热key的情况时,采用的是本地缓存+Redis缓存的解决方案。 用户请求数据时,如果本地缓存有数据,从缓存获取数据即可。 如果本地缓存没有该数据,从Redis缓存读取数据。
Redis缓存和数据库(或者下游系统)数据如何保持一致呢? 如果对实时性要求比较高的应用,使用MQ同步数据,实时性要求没这么高的应用,可以使用定时任务。当然还有热key和bigkey需要考虑。
回答到这里应该就ok了。 如果面试官对数据一致性方案,或者热key,bigkey的解决方案感兴趣,他会问。 引出问题但是又不说解决方案,面试过程要学会引导面试官问问题。
面试官第五个问题,主要是缓存是吧。
候选人回答:因为我之前做的是骑手派单,这个业务对数据的一致性要求高,只能读写数据库。如果数据量多, 就可以使用分库分表来提高数据的查询速度。
面试官是想让候选人扩展吗?
那就多了。 首先服务器要够用,一台机器的流量过高时,肯定慢,使用多台服务器承担这部分流量。 CDN,网络带宽,固态硬盘等等等等。
面试官第六个问题,你刚刚也提到了MySQL,你能介绍一下MySQL的3个日志吗?
候选人回答:
第一个日志是binlog。 其主要作用的数据备份,数据回滚和主从同步用的。
第二个日志是redo.log。 redo.log是Innodb引擎里面特有的,Innodb引擎为了提高读写数据,先不和MySQL的全表数据进行交互,Innodb引擎先将数据写入到内存中,但是当服务器异常重启后,内存数据会丢失,Innodb引擎就将数据也写入到redo.log中,当服务器异常重启后,读取redo.log中的数据就可以回复内存中的数据,提升性能的同时,又保证了数据的不丢失。
第三个日志是undo.log。 Innodb引擎为了实现事务的隔离级别,不能直接更新表中的数据,只能使用CopyOnWrite思想,更新数据前,先copy一份老的数据,当前事务在copy的数据上进行变更,老的数据和copy的数据使用链表关联, 这个链表就是undo.log。
当然MySQL也有其他日志,比如回放日志。
面试官问的这个问题,Innodb引擎的事务和事务隔离级别有很多设计思想的,但是面试官也没细问,一笔待过,
因此就是一个纯八股文面试题了。 其实从面试到现在,面试官也没问什么,所有问的问题都没啥技术含量,一问一答就结束。
面试官第七个问题,你也学了Redis,你们使用Redis除了实现锁,还使用了缓存对吧? 缓存主要使用了Redis的哪个数据结构?
候选人回答:
我们使用最多的字符串类型的数据结构。 Redis其他数据结构要不是集合,要不是Map,使用这些数据结构很容易出现热key,bigkey问题。 所以我们需要将数据打散,使用String的数据结构保存数据,然后分散到Redis的不同分片,不同机器上,这样流量相对来说会更均匀。 其他数据结构容易出现问题,所以我们使用的比较少。
面试官想问的问题是Redis有哪些数据结构。
候选人回答了Redis有哪些数据结构。 并说出了流量高时需要使用哪种数据结构缓存数据。 无论是List,Set,还是Map,还是SortSet,里面都保存着批量数据,既然有多个数据,那么这个集合或者Map的职责就变多,必然保存该数据的服务器流量就会偏多,容易出现热key,bigkey问题。 所以流量高的应用,使用最多的数据结构就是String。
面试官第八个问题,你们常用的组件,基于SpringBoot搭个项目,然后用Redis做个缓存,MySQL做个存储,还有其他的一些组件吗?
候选人回答:我们使用SpringBoot启服务,然后服务与服务之间调用用的RPC,接下来服务之间为了削流和解耦,使用MQ,数据库用的MySQL,缓存用的Redis,因为有灰度和开关和diff,所以需要使用动态配置中心,还有限流等。 其实我懂的还是很多的。
面试官第九个问题,你知道有哪些消息队列?
候选人回答:我知道的MQ有kafka和RocketMQ,我在美团和快手使用的是kafka。
面试官接着问:那为什么他们选择kafka而不选择RocketMQ?
候选人回答: 我也问过我leader为什么公司选择使用kafka,而不选择RocketMQ。 我leader给我的解释是,kafka对跨语言的支持要更友好一些,因为公司不止只有后端,还有算法和数据组,kafka对于他们的支持要很友好些。
而且kafka对大数据量的发送要更快一些,吞吐量要更高一些。
这个回答有点问题: 不是kafka对跨语言支持的更友好一些,kafka和RocketMQ都支持Java、Go、python等主流编程语言。 但是kafka吞吐量更高,对水平扩展的支持更友好,更满足大公司对大数据的处理速度。 而且大公司有数据组,kafka对大数据的处理速度更快,数据组只知kafka而非RocketMQ。 数据组与后端进行交互时,更加方便。
面试官第十个问题,你可以介绍一下kafka,说的宽泛一些,kafka的一些整体设计理念和架构是什么?
候选人回答:
首先发送消息有2种模式,点对点的模式,一个发送,一端接收。 比如电话就是点对点的模式。 还有一种是1对多的模式,一个发送方,多个消费方。 比如订单数据,可能会有多个消费者, kafka支持2种发送形式。
kafka的架构是,首先是发送者,发送消息,发送的消息存储到kafka集群中,kafka集群是有多个broker,可以认为一台机器就是一个broker,然后一个broker里面有多个数据分片,然后就是消费者端,消费kafka集群里面的消息。
候选人回答到这里,面试官打断了,问了候选人另一个问题,你刚刚提到了分片,为什么kafka集群要设计这么一个分片呢?
候选人回答:
我认为的MQ的分片类似于MySQL的分表,比如我们现在有1TB的数据,一台服务器没有办法保存这么多的数据,肯定要分到多张表里面保存。
补充:可以将多张表的数据放到不同的服务器上面保存,从而提高数据的处理数据。
接下来是主从。kafka使用分片来实现主从之间的数据同步。但是kafka的主从和MySQL的主从不一样的点是,kafka的主来提供读写,从只从主同步数据,然后备份数据。 MySQL的从可以提供读操作。
接下来开始聊项目:
面试官问:我看你写了3个项目,你觉得哪个项目你觉得参与度比较高或者比较重要的项目。
候选人回答:商家排行榜需求是我第一个owner的需求。 其他的需求都是组里面的leader带着我一起做,我觉得这个项目有一定的设计思想,并且对我成长比较多的一个项目。
面试官第一个问题:你说这个项目你是owner,那么这个项目是你自己开发,然后设计,推进的吗?
候选者: 这个需求的代码开发量不是特别多,这个推进的话,肯定是产品拉我们需求评审,我写技术方案,leader觉得没有什么问题后,我写代码,然后开发,测试,上线。
面试官第二个问题:你刚刚也说了,这个需求有一些设计思考点,你可以大概得说一下吗?
后面的都是一些项目的问答知识了,没有太多参考价值。
后面又做了一道算法题。算法题也简单,最长回文子串。 候选人也做出来了。