2021春招面经
蚂蚁2021春招
一面
面试问题
关于进程和线程区别
进程是资源分配的最小单位,而线程是程序执行的最小单位。进程有自己的独立地址,系统会为他分配地址空间。而线程作为轻量级的进程,同一个进程中的线程共享该进程的共享内存。且线程切换的CPU花费和线程通信也比进程少得多。操作系统的内存管理
操作系统的内存管理分为连续分配管理和非连续分配管理,连续分配比如块式管理,非连续分配有页式管理和段式管理。页式管理将主存分为固定的一页一页,页面较小,减少了碎片。而段式管理把主存分为一段一段(比页小),同时段是有意义的,如主程序段,数据段等。
两者相同点:都提高了内存利用率,减少碎片。段/页间离散,段/页内有序。
两者区别:页是固定大小,而段不固定;且段具有逻辑含义。
段页式管理总结了两者的优点,先把主存分为若干段,再对段内进行分页。并发中你都用过哪些锁?
我的答案:Synchronized和volatile。volatile是轻量级的同步方法,只能修饰变量,保证了数据的可见性,但却不能保证数据的原子性。synchronized主要解决多个线程访问资源的同步性,而volatile保证了多线程数据的可见性。
答案:重入锁:支持重进入的锁,表示该锁能支持一个线程对资源的重复加锁。 排他锁:锁在同一时刻只允许被一个线程访问。 读写锁:读写锁在同一时刻可以被多个线程访问,但在写线程访问时,其他写线程将被阻塞。读写锁通过分离读锁和写锁,并发性得到了提高。HashMap的实现机制
有两部分组成,首先一个hash数组用来存储对应hash key的数据。每个数组元素后接一个链表,当相同hash值的数据到来(冲突)时,其会被放到该链表尾部。JDK1.8后新增当链表长度超过16时会转为红黑树存储。数组和链表的区别
略。JVM的GC算法
简单的有标记整理/清除法,复制算法。现在用的是分代收集法,将堆内存分为新生代和老年代。新生代中会有大量对象死去,因此可以选择复制算法,仅用少量的成本就可以完成垃圾回收。老年代由于对象存活几率较高,可以使用标记清除/整理法。水印(watermark)的作用
水印是一个timestamp,相当于一段时间的截点,其主要解决流数据的时间乱序的问题,防止后到的流数据比先前的流数据先到达流系统,为此需要对流数据进行排序,而水印就是收集一段时间流数据的终止截点。零拷贝技术了解吗
在网络通信中,用户希望读取文件向socket里面发送数据,通常需要两个阶段:从磁盘读入文件到用户态,从用户态到socket。然而由于操作系统的文件IO的策略,文件读写中都要经过内核态,也就是中间进行了不必要的文件拷贝和上下文切换。零拷贝技术在磁盘拷贝到内核态时(DMA),在内核态中直接进行了文件的复制并从内核态发送给了socket(DMA)。零拷贝的实现可以直接调用linux的sendfile指令即可。算法题
上游有五个节点,每个节点每秒产生一段句子,下游节点统计五个节点的wordcount。如何实现?
除去Mapreduce和流的背压等方法,面试官的答案好像是每个节点建立一张单词的hash表,再直接合并hash表做wordcount。嗯,这不就是hashset的做法吗,经典空间换时间的方法,学到了。
二面
经历了一面后,感觉要把自己的项目/框架思路理清楚一点。
简介
面试官好,我是今年来自华中科技大学研二的学生,我的名字叫徐阳,很高兴能来参加蚂蚁这次的面试。简单介绍一下我的情况:我的主要技术方向在于JAVA后端方向,对spring,kafka,kafka-stream,netty等框架有所了解。本科在校期间主要参与了两个项目,第一个是Android和树莓派构建的快递服务项目,我的主要工作是Android客户端和服务器代码的开发,服务器端外接了短信验证,物流查询等SDK对外提供REST服务接口,主要涉及的技术就是简单的android开发和socket编程。第二个是基于JavaWeb的网站项目,使用的是Servlet技术,我的主要工作是后台MVC逻辑的搭建和一些常规的CRUD操作。读研阶段我接触到了我们实验室的知识图谱项目,该项目主要解决大规模图数据的存储和计算问题,我在其中主要负责的是对图谱数据时间和空间纬度的信息建立索引,供系统可以快速查询到对应时空范围的图数据信息。然后我使用了kafka作为中间件使我们的系统与上层父系统解耦。听说贵部门主要做实时计算方向,正好我也对实时计算比较感兴趣,之前也了解过一些实时计算的内容和kafka-stream的知识,所以来参加了本次面试,谢谢。
图数据库项目
我主要的工作是对图谱内的时空数据建立索引。空间索引技术发展到今天已经非常成熟,时空索引的关键在于空间索引的选择和时空索引的混合策略。常见的空间索引有建立R树索引,空间曲线填充,和geohash算法等。目前主流的时空数据索引处理方案是geomesa平台,其内部主要运用了空间填充曲线的z2/z3算法,其优势点在于可以对实时加入的数据快速加入到索引中进行查找。而我在项目中主要使用了R树的方法,当然上次听讲座我也了解到我们阿里做时空数据的也在主要采用该方法。其特点在于结构简单易嵌入到其他模型中,但相对的需要一段进行索引的构建,不能对实时数据做查找。使用R树也符合我们项目的需求,使用离线计算预先搭建好时空索引。当然R树只是简单的空间索引模型,我参考HR树的思想,在索引中对时间进行了分片索引,在相邻的时间内如果数据的空间信息变化不大,则可以考虑合并相邻的空间索引以减少内存开销。同时针对GB级的图数据,我也解决了其heap溢出和OOM等问题,以及针对R树的两个参数通过实验进行了调参和剪枝,优化了索引的查询效率。
kafka框架
kafka框架作为优秀的消息中间件,其拥有很多优秀的特性。我们项目主要使用了kafka作为消息代理将我们的子系统与上层系统进行解耦。我觉得kafka最主要的特点主要有两点:第一个特点是作为消息代理,kafka具有高的吐吞量(TPS)和传输速率,这主要依赖于kafka的几个设计特点:一个是其topic设置多个partition,其横向增加了消息代理的并行度。每个partition中消息顺序写入。还有kafka使用了零拷贝技术作为网络传输,减少了文件拷贝的开销,当然这点在netty中也有使用。另外kafka也配置了消息批量发送和日志压缩等方案,也都是保障kafka高吞吐量的策略。举一个例子,是我们隔壁实验室一个朋友的项目,他做的是网卡采集项目,通过将网卡采集的信息传入kafka再做持久化或其他处理,其使用kafka就是看中了它的高吞吐量。第二个是作为分布式日志功能,kafka也是一款很优秀的日志系统。其内部使用了zookeeper做集群管理和topic管理,保证了分布式下系统的一致性。kafka可以通过ack机制选择消息的发送规则,数据的冗余策略,主从节点的分配策略,日志的压缩方式等等一系列的可选项。
kafka-stream使用
聊完了kafka,我还在此基础上了解kafka-stream。kafka-stream是在kafka上的流计算平台。使用kafka-stream需要先构建一个拓扑结构,也就是streambuilder,包括源节点,处理节点,分裂/合并节点,输出节点等等,输入输出节点也要考虑键值序列化的问题。之后使用StreamBuilds启动该拓扑结构。比如一个购物系统,将用户的消费订单输入,其可以首先经过屏蔽节点屏蔽用户关键信息,再提取其中信息分裂成多个流,如购买金额流,购买地区流等等。最后再将分支流的结果写入新的主题。其中也涉及了很多细节操作,如用户信息设为键值(保证了用户数据分配到同一个分区)与流的重新分区(though函数),流的基本操作(如mapValue,filter,flatMap等)。同时kafka-stream也提供了流的状态存储,比如计算购买总额需要存储之前流的状态信息。kafka-stream其内部使用StateStore类型存储,其底层用的是RocksDB内存数据库,通过transformValues等函数即可完成流的状态存储。在更新流的基础上kafka-stream演化成了KTable,其提供了聚合,窗口等流操作。使用kafka的timestamp作为流链接和窗口计算的时间,在完成流操作的同时也可以用于kafka自己的日志压缩策略。kafka-stream还提供了底层的处理器API,供实现更细节的流操作。最后KTable也发展有专业的平台confluent,做专门的KSQL和优化。
Netty框架
Netty是一款高性能的网络框架,其支持千万用户的并发访问,也是异步NIO的集大成者,其主要有Channel,Future,EventLoop,ChannelHandler等组件构成。其核心在于用EventLoop绑定线程来管理线程的生命周期与事件,同一个Thread可以被多个channel绑定。我觉得Netty的优势主要在于其使用也很简单,其封装了底层的JAVA网络编程模型并进行了性能优化,只需要少量的代码就可以完成客户端和服务器代码的编写(传统的NIO/AIO编程难度较高),如创建一个Bootstrap,绑定EventLoopGroup,指定channel类型,然后就可以运行其中channelHandler,并使用ChannelFuture来异步回调。同时其中也封装了很多实用的handler实例,如httpIn/DecodeHandler,可以把http的拆封包快速解决而仅关注于数据的处理,再比如实现SSL加密,升级Websocket协议等等,都可以用一个handler解决,十分方便。
面试问题
问到了kafka如何高效索引日志信息的,或者说kafka如何通过时间戳来索引到日志。我以为是插入了某个关键节点(如checkpoint)这样的方法,但最后看了下里面大有学问。在kafka的/index文件中会生成三份文件.log,.index,.timeindex。其中timeindex记录的是时间戳和offset,而index记录的是offset和postion。log文件并不对应所有索引里的offset,而是采用二分查找的方式从最近的offset出发顺序遍历。
另外又又问到了零拷贝技术,我按照之前准备的答结果又说太浅了。这里深入总结一下。网络的零拷贝技术是从FileChannel中衍生的。在文件读写中,从磁盘读取文件需要从磁盘->内核缓冲区->用户缓冲区(用户内存),这里涉及到了程序的上下文切换(也就是系统态到内核态的切换),为此在NIO中使用了内存缓冲区到用户缓冲区的地址映射,也就是调用filechannel.map()方法,其原理类似于操作系统的内存映射方法,这种方法很适合大文件的读取和修改,其中的类称为MappedByteBuffer。而在网络编程中,Channel就类似于内存缓冲区,Buffer就类似于用户缓冲区,零拷贝直接将fileChannel的内容通过transferTo方法映射到socketChannel,避免了中间Buffer的缓存。transferTo方法底层是通过系统调用sendfile实现的。
结果
一面啥都没准备,奈何面试官人太好过了。二面纯纯的技术面,比较大boss问的太深了,答得很一般。本来还是有机会的,可惜笔试没考好,GGEZ。
腾讯春招
一面(后台端)
面试问题
这次的面试可谓是各种问题。首先继续是kafka的问题,让说一下kafka的用法。当我说到partition顺序读写时,直接抛来一个问题。
- kafka如何做到读和写的顺序一致性的?
- 你用到了socket编程,谈一谈你对NIO的理解吧?
- 知道epoll吗,聊一聊epoll和select的区别吧。
- 你后台是用http协议吧,讲一讲为什么http要四次挥手?
- spring和spring-boot有什么区别?
- 算法1:如何判断链表有环? 算法2:如何找出频率大于n/2的单词?
- JAVA的XMS和XMX的含义,什么是OOM,如何判断在OOM时系统哪里出错?
- 经典GC
- 你用的JDK1.8,它和1.7有什么不同?
- 试图想问我redis和mysql的问题…
- 线程和进程的区别?如何保证线程的安全性?
- 最最后后,又又又是零拷贝,真有你的。
这里主要记录下不会的。
- kafka如何做到读和写的顺序一致性的?
顺序问题是网络传输中最常见的问题,如发送的1 2 3需要有序,但1因为发送失败而重发,结果到达2 3 1。为此需要保证消息的有序性。实际上,实现全局有序是十分困难的,为此kafka保证了消息的局部有序性。在写入阶段,客户端使用write(topic,partition,key),当key或partition相同时,kafka就可以保证partition内部的有序性。至于读入端,使用offset读来保证读入的有序性。但是在多个Partition时,不能保证Topic级别的数据有序性。可以使用key保证某一局部性数据的有序性。
如何判断在OOM时系统哪里出错?
处理OOM问题的关键在于拿到Heap Dump(堆转储文件),它保存了某一时刻JVM堆中对象对内存的使用情况。首先通过jps获取到Java进程的pid,然后通过jmap输出该进程的Dump文件,拿到dump文件后可以使用MAT/VirtualVM进行分析,根据内存分配图可以看到哪个类本身占用的内存,它引用的类的内存等等信息。在确定了发生OOM的类后,之后再回到程序内部分析代码。比如在java的ThreadPool中,其会把多的task扔到一个block的linkedlist中,这时task本身如果又new了一些变量,那么当task增多时就会出现OOM的情况。解决方法是每个task操作的变量是一致的,可以使用单例模式保证每个进程操作一组变量。如何找出频率大于n/2的单词?
摩尔投票法,方法如下:假设一个队列中总存在一个大于n/2频率的元素。那么每次从中挑选出两个不同的元素进行抵消,那么最后剩余的元素就是大于n/2的元素。使用major和count两个变量遍历一次数组就好,时间复杂度为线性,空间复杂度为常量。
一面(客户端)
先笔试了半个小时,题很简单。
面试问题
讲一讲对spring的依赖注入和AOP的认识,AOP除了动态代理还有什么实现
Ioc:控制反转是一种设计思想,可以将对象的创建交给spring框架实现。Ioc容器是spring实现Ioc的载体,通过将对象的依赖关系交给容器来管理,并由Ioc容器完成对象的注入。当需要某一个对象时,仅需要配置文件或注解,即可完成对象的创建。
Aop:面向切面的编程,将业务模块所共有的逻辑封装起来,减少系统的重复代码。AOP是靠动态代理实现的,如果代理的对象实现了某接口,则会使用JDK Proxy创建代理对象。另外Spring继承了AspectJ.Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。如何保证两个事务运行后再调用下一个事务(reactor框架,反应式编程)
在spring的事务管理中,如果仅有两个事务先后关系,可以复写Executor的afterCommit(),指定事务提交后的操作。(面试官这道题好像是想问spring中的reactor框架。)
reactor框架主要由reactor-core和reactor-ipc两个模块,前者用于reactive编程的核心api,后者实现高性能网络通信(netty实现)。在core中,主要有Flux(0:N) 和 Mono(0:1)两个publisher类。当消息通知产生时,订阅者的对应的方法 onNext(), onComplete()和 onError()会被调用。
言归正传,可以对两个事务的onComplete()设置回调函数,设置标志位来保证两个事务的状态并执行第三个事务。android端有什么了解
netty的优势
分布式RMI的使用
进程间的通信手段
http的三次握手和四次挥手
然后简单说了下他们项目上flutter和nodejs的东西,主要是win/mac/android/ios端的腾讯文档的开发,主要使用nodejs和C++做后台数据处理。没有用过java做后台。
结果
后台自认为很多问题感觉答得还可以,但一面就被刷了,挺难受的。客户端一面人很好,虽然很多没答上来但还是很亲和,方向差的确实很多,客户端主要做的是移动端和桌面端的东西(android ios flutter nodejs),外加tx好像没有java后台,基本GG了。