我的新课《C2C 电商系统微服务架构120天实战训练营》在公众号儒猿技术窝上线了,感兴趣的同学,可以长按扫描下方二维码了解课程详情:
课程大纲请参见文末
说明:本文整理自石杉架构班学员LEO同学在儒猿技术交流群的面经分享⼤家好,⾃我介绍⼀下:10年经验,普本毕业,坐标北京,这次跳槽进⼊了阿⾥。分享⼀下这次⾯试经验,以及平时学习的积累。
我的⼯作年限算是⽐较⻓,都有中年危机了,跟着石杉⽼师的架构课学习了两年,做技术⼀路⾛过只有脚踏实地的学习总结还有多积累、多思考才能有所进步,本次跳槽其实我是整整准备了⼀年半,充分利⽤周末和休假的时间学习提⾼,看⽯杉⽼师的课程的同时⼀定同步的做笔记,重要部分标红,我还看了很多相关书籍,书籍⾥的例⼦也是每个都必须敲⼀遍,看书的同时也做笔记把重要的记下来并标红,⾯试前⼀周做突击⽤
阿⾥巴巴、快⼿、滴滴、京东数科,拿到了哪些公司的offer:阿⾥巴巴、快⼿。由于已经拿到了⼼仪的offer,就没有继续约其他⼤⼚的⾯试了
java基础,代表的有原⽣的List、Map、并发和线程池、TCP、⽹络等知识点对应的⽼师的课程:
- java架构课程的JDK源码剖析系列,还有架构课程⾥⾯的其他专题,
这个⼀集不漏的需要看完看懂,⽼师画的图看⾃⼰再⼿动默写⼏遍理解原理,这些基础知识太重要,必!问!
这些问题我都会结合⽂字+流程图/原理图,做⾮常深⼊的解答问题:简述HashMap的底层原理
(1) hash算法:为什么要⾼位和低位做异或运算?答:让⾼位也参与hash寻址运算,降低hash冲突
(2) hash寻址:为什么是hash值和数组.length - 1进⾏与运算?答:因为取余算法效率很低,按位与运算效率⾼
(3) hash冲突的机制:链表,超过8个以后,红⿊树(数组的容量⼤于等于64)
(4) 扩容机制:数组2倍扩容,重新寻址(rehash),hash & n - 1,判断⼆进制结果中是否多出⼀个bit的1,如果没多,那么就是原来的index,如果多了出来,那么就是index + oldCap,通过这个⽅式。就避免了rehash的时候,⽤每个hash对新数组.length取模,取模性能不⾼,位运算的性能⽐较⾼,JDK 1.8以后,优化了⼀下,如果⼀个链表的⻓度超过了8,就会⾃动将链表转换为红⿊树,查找的性能, 是O(logn),这个性能是⽐O(n)要⾼的
(5) 红⿊树是⼆叉查找树,左⼩右⼤,根据这个规则可以快速查找某个值
(6) 但是普通的⼆叉查找树,是有可能出现瘸⼦的情况,只有⼀条腿,不平衡了,导致查询性能变成O(n),线性查询了
(7) 红⿊树,红⾊和⿊⾊两种节点,有⼀⼤堆的条件限制,尽可能保证树是平衡的,不会出现瘸腿的情况
(8) 如果插⼊节点的时候破坏了红⿊树的规则和平衡,会⾃动重新平衡,变⾊(红 <-> ⿊),旋转,左旋转,右旋转
问题:volatile关键字底层原理,volatile关键字是否可以禁⽌指令重排以及如何底层如何实现的指令重排
(1) 这⾥贴下⽯杉⽼师在讲volatile关键字底层原理画的图:硬件级别的原理:
(2) 主动从内存模型开始讲起,原⼦性、可⻅性、有序性的理解,volatile关键字的原理java内存模型:
(3) 可⻅性:⼀个线程修改了变量,其他线程能⻢上读取到该变量的最新值read(从主存读取),load(将主存读取到的值写⼊⼯作内存),use(从⼯作内存读取数据来计算),assign(将计算好的值重新赋值到⼯作内存中),store(将⼯作内存数据写⼊主存),write(将store过去的变量值赋值给主存中的变量) 这个是流程图:
(4) volatile读的内存语义如下:当读⼀个volatile变量时,JMM会把该线程对应的本地内存置为⽆效。线程接下来将从主内存中读取共享变量。
(4-1)当读flag变量后,本地内存B包含的值已经被置为⽆效。此时,线程B必须从主内存中读取共享变量,线程B的读取操作将导致本地内存B与主内存中的共享变量的值变成⼀致。(4-2)volatile写和volatile读的内存语义总结:
- 线程A写⼀个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所做修改的)消息。
- 线程B读⼀个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改的)消息。
- 线程A写⼀个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过主内存向线程B 发送消息。
(5) 锁的释放和获取的内存语义:当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新 到主内存中。
当线程获取锁时,JMM会把该线程对应的本地内存置为⽆效。从⽽使得被监视器保护的临界区代码必须 从主内存中读取变量。(6) 有序性:基于happens-before原则来看volatile关键字如何保证有序性这个是流程图:http://note.youdao.com/s/BPU2J7te
(6-1)程序顺序规则:⼀个线程中的每个操作,happens-before于该线程中的任意后续操作。(6-2)监视器锁规则:对⼀个锁的解锁,happens-before于随后对这个锁的加锁。(6-3)volatile变量规则:对⼀个volatile变量域的写,happens-before于任意后续对这个volatile域的读(6-4)传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
(6-5)start()规则:如果线程A执⾏操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。(6-6)join()规则:如果线程A执⾏操作ThreadB.join()并成功返回,那么线程B中的任意操作happens- before与线程A从ThreadB.join()操作成功返回。(7) 原⼦性:volatile关键字不能保证原⼦性,唯⼀的场景就是在32位虚拟机,对long/double变量的赋值写是原⼦的,volatile关键字底层原理,lock指令以及内存屏(8) lock指令:volatile实现的两条原则(8-1)Lock前缀指令会引起处理器缓存回写到内存。(8-2)⼀个处理器的缓存回写到内存会导致其他处理器的缓存失效。
Java线程在运⾏的声明周期中可能处于6种不同的状态,在给定的⼀个时刻,线程只能处于其中的⼀个状态。这⾥我整理了⼏张图:
问题:简述线程池的原理,⾃定义线程池的参数以及每个参数的意思,线程池有哪⼏种,分别的应⽤场景举例
corePoolSize:线程池⾥应该有多少个线程
maximumPoolSize:如果线程池⾥的线程不够⽤了,等待队列还塞满了,此时有可能根据不同的线程池的类型,可能会增加⼀些线程出来,但是最多把线程数量增加到maximumPoolSize指定的数量keepAliveTime + TimeUnit:如果你的线程数量超出了corePoolSize的话,超出corePoolSize指定数量的线程,就会在空闲keepAliveTime毫秒之后,就会⾃动被释放掉
workQueue:你的线程池的等待队列是什么队列threadFactory:在线程池⾥创建线程的时候,你可以⾃⼰指定⼀个线程⼯⼚,按照⾃⼰的⽅式创建线程出来RejectedExecutionHandler:如果线程池⾥的线程都在执⾏任务,然后等待队列满了,此时增加额外线 程也达到了maximumPoolSize指定的数量了,这个时候实在⽆法承载更多的任务了,此时就会执⾏这个东上⾯的基本参数的意义以外,我还推荐⼤家看下美团技术团队写的《Java线程池实现原理及其在美团业务中的实践》https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html 这篇⽂章,写的⾮常⼲。问题:简述OSI七层⽹络模型,TCP/IP四层⽹络模型OSI七层⽹络模型,⽹络的七层加⼯从下到上主要包括物理层,数据链路层,⽹络层,传输层,会话层, 表示层,应⽤层
问题:简述TCP三次握⼿以及四次挥⼿TCP三次握⼿的过程如下:(1)客户端发送SYN(seq=x)报⽂给服务器端,进⼊SYN_SEND状态。(2)服务器端收到SYN报⽂,回应⼀个SYN(seq=y)和ACK(ack = x+1)报⽂,进⼊SYN_RECV状态。(3)客户端收到服务器端的SYN报⽂,回应⼀个ACK(ack=y+1)报⽂,进⼊Established状态。TCP三次握⼿的过程图:
学习资料:⽯杉⽼师在架构班讲的:《讲给Java⼯程师听的⼤⽩话⽹络课程》推荐书籍: 《⽹络是怎样连接的》《图解TCP/IP》 《图解⽹络硬件》 《图解HTTP》
这⾥援引下儒猿群群友根据《从 0 开始带你成为JVM实战⾼⼿》专栏 总结出来的图,分享给⼤家https://www.processon.com/view/link/5e69db12e4b055496ae4a673
CMS的⼯作机制相对复杂,垃圾回收过程包含如下4个步骤(1) 初始标记:只标记和GC Roots直接关联的对象,速度很快,需要暂停所有⼯作线程。(2) 并发标记:和⽤户线程⼀起⼯作,执⾏GC Roots跟踪标记过程,不需要暂停⼯作线程。(3) 重新标记:在并发标记过程中⽤户线程继续运⾏,导致在垃圾回收过程中部分对象的状态发⽣变化, 为了确保这部分对象的状态正确性,需要对其重新标记并暂停⼯作线程。(4) 并发清除:和⽤户线程⼀起⼯作,执⾏清除GC Roots不可达对象的任务,不需要暂停⼯作线程。问题:G1与CMS的区别,你们公司使⽤的是哪个,为什么?(这个需要结合⾃⼰的业务场景回答) 相对于CMS垃圾收集器,G1垃圾收集器两个突出的改进。(2) 可以精确地控制停顿时间,在不牺牲吞吐量的前提下实现短停顿垃圾回收。问题:JVM参数举例,讲讲为什么这么设置,为了避免fullGC的停顿对系统的影响,有哪些解决⽅案?由于⽂本不⽅便贴代码,贴在在了有道云笔记⾥⾯:
为解决应⽤在午⾼峰发⽣ full gc ⽽影响系统响应时间问题, 考虑低峰期主动进⾏ full gc 对 old 区进⾏释放.确保启动参数中 -XX:+DisableExplicitGC 项被删除, 该参数作⽤是禁⽌ System.gc() 调⽤. (启动参数⼀般配在 start 脚本中)在启动参数中加⼊ -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses, 该参数的作⽤是主动 System.gc() 时调⽤ CMS 算法进⾏ gc 操作.JVM 分为堆区和栈区,还有⽅法区,初始化的对象放在堆⾥⾯,引⽤放在栈⾥⾯, class 类信息常量池(static 常量和 static 变量)等放在⽅法区(1) ⽅法区:主要是存储类信息,常量池(static 常量和 static 变量),编译后的代码(字 节码)等数据(2) 堆:初始化的对象,成员变量 (那种⾮ static 的变量),所有的对象实例和数组都要 在堆上分配(3) 栈:栈的结构是栈帧组成的,调⽤⼀个⽅法就压⼊⼀帧,帧上⾯存储局部变量表,操 作数栈,⽅法出⼝等信息,局部变量表存放的是 8 ⼤基础类型加上⼀个应⽤类型,所 以还是⼀个指向地址的指针(4) 本地⽅法栈:主要为 Native ⽅法服务问题:JVM内存分那⼏个区,每个区的作⽤是什么?java 虚拟机主要分为以下⼀个区:1. 有时候也成为永久代,在该区内很少发⽣垃圾回收,但是并不代表不发⽣ GC,在这⾥ 进⾏的 GC 主要是对⽅法区⾥的常量池和对类型的卸载2. ⽅法区主要⽤来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后 的代码等数据。4. ⽅法区⾥有⼀个运⾏时常量池,⽤于存放静态编译产⽣的字⾯量和符号引⽤。该常量池具有动态性,也就是说常量并不⼀定是编译时确定,运⾏时⽣成的常量也会存在这个常量池中。1. 虚拟机栈也就是我们平常所称的栈内存,它为 java ⽅法服务,每个⽅法在执⾏的时候都会创建⼀个栈帧,⽤于存储局部变量表、操作数栈、动态链接和⽅法出⼝等信息。2. 虚拟机栈是线程私有的,它的⽣命周期与线程相同。3. 局部变量表⾥存储的是基本数据类型、returnAddress 类型(指向⼀条字节码指令的地 址)和对象引⽤, 这个对象引⽤有可能是指向对象起始地址的⼀个指针,也有可能是代表 对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定4. 操作数栈的作⽤主要⽤来存储运算结果以及运算的操作数,它不同于局部变量表通过索 引来访问,⽽是压栈和出栈的⽅式 5.每个栈帧都包含⼀个指向运⾏时常量池中该栈帧所属⽅法的引⽤,持有这个引⽤是为了 ⽀持⽅法调⽤过程中的动态连接.动态链接就是将常量池中的符号引⽤在运⾏期转化为直接 引⽤。本地⽅法栈和虚拟机栈类似,只不过本地⽅法栈为 Native ⽅法服务。堆:java 堆是所有线程所共享的⼀块内存,在虚拟机启动时创建,⼏乎所有的对象实例都在这 ⾥创建,因此该区域经常发⽣垃圾回收操作。程序计数器内存空间⼩,字节码解释器⼯作时通过改变这个计数值可以选取下⼀条需要执⾏的字节码 指令,分⽀、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内 存区域是唯⼀⼀个java 虚拟机规范没有规定任何 OOM 情况的区域。问题:堆⾥⾯的分区:Eden,survival (from+ to),⽼年代,各⾃的特点。堆⾥⾯分为新⽣代和⽼⽣代(java8 取消了永久代,采⽤了 Metaspace),新⽣代包 含 Eden+Survivor 区,survivor 区⾥⾯分为 from 和 to 区,内存回收时,如果⽤的是复 制算法,从 from 复制到 to,当经过⼀次或者多次 GC 之后,存活下来的对象会被移动 到⽼年区,当 JVM 内存不够⽤的时候,会触发 Full GC,清理 JVM ⽼年区 当新⽣区满了之后会触发 YGC,先把存活的对象放到其中⼀个Survice 区,然后进⾏垃圾清理。因为如果仅仅清理需要删除的对象,这样会导致内存碎 ⽚,因此⼀般会把 Eden 进⾏完全的清理,然后整理内存。那么下次 GC 的时候,就会使⽤下⼀个 Survive,这样循环使⽤。如果有特别⼤的对象,新⽣代放不下, 就会使⽤⽼年代的担保,直接放到⽼年代⾥⾯。因为 JVM 认为,⼀般⼤对象的存 活时间⼀般⽐较久远。
问题:如何判断⼀个对象是否存活?(或者GC对象的判定⽅法) 判断⼀个对象是否存活有两种⽅法:所谓引⽤计数法就是给每⼀个对象设置⼀个引⽤计数器,每当有⼀个地⽅引⽤这个对象时,就将计数器加⼀,引⽤失效时,计数器就减⼀。当⼀个对象的引⽤计数器为零时,说明此对象没有被引⽤,也就是“死对象”,将会被垃圾回收. 引⽤计数法有⼀个缺陷就是⽆法解决循环引⽤问题,也就是说当对象 A 引⽤对象 B,对象 B ⼜引⽤者对象 A,那么此时 A,B对象的引⽤计数器都不为零,也就造成⽆法完成垃圾回 收,所以主流的虚拟机都没有采⽤这种算法。该算法的思想是:从⼀个被称为 GC Roots 的对象开始向下搜索,如果⼀个对象到 GC Roots 没有任何引⽤链相连时,则说明此对象不可⽤。在 java 中可以作为 GC Roots 的对象有以下⼏种: • 虚拟机栈中引⽤的对象,⽅法区类静态属性引⽤的对象 • ⽅法区常量池引⽤的对象本地⽅法栈 JNI 引⽤的对象 虽然这些算法可以判定⼀个对象是否能被回收,但是当满⾜上述条件时,⼀个对象⽐不⼀定会被回收。当⼀个对象不可达 GC Root 时,这个对象并 不会⽴⻢被回收,⽽是出于⼀个死缓的阶段,若要被真正的回收需要经历两次标记,如果对象在可达性分析中没有与 GC Root 的引⽤链,那么此时就会被第⼀次标记并且进⾏ ⼀次筛选,筛选的条件是是否有必要执⾏finalize()⽅法。当对象没有覆盖 finalize()⽅法或者已被虚拟机调⽤过,那么就认为是没必要的。如果该对象有必要执⾏ finalize()⽅法,那么这个对象将会放在⼀个称为 F-Queue 的对队 列中,虚拟机会触发⼀个 Finalize()线程去执⾏,此线程是低优先级的,并且虚拟机不会承诺⼀直等待它运⾏完,这是因为如果 finalize()执⾏缓慢或者发⽣了死锁,那么就会造成 F- Queue 队列⼀直等待,造成了内存回收系统的崩溃。GC 对处于 F-Queue 中的对象进⾏ 第⼆次被标记,这时,该对象将被移除”即将回收”集合,等待回收。
如果服务出现⽆法调⽤接⼝假死的情况,⾸先要考虑的是两种问题
(1) 第⼀种问题,这个服务可能使⽤了⼤量的内存,内存始终⽆法释放,因此导致了频繁GC问题。也许每秒都执⾏⼀次Full GC,结果每次都回收不了多少,最终导致系统因为频繁GC,频繁Stop the World,接⼝调⽤出现频繁假死的问题(2) 第⼆种问题,可能是这台机器的CPU负载太⾼了,也许是某个进程耗尽了CPU资源,导致你这个服务的线程始终⽆法得到CPU资源去执⾏,第⼀种,是内存使⽤率居⾼不下,导致频繁的进⾏Full GC,gc带来的stop the world问题影响了服务。第⼆种,是内存使⽤率过多,导致JVM⾃⼰发⽣OOM。第三种,是内存使⽤率过⾼,也许有的时候会导致这个进程因为申请内存不⾜,直接被操作系统把这个进问题:如何在JVM内存溢出的时候⾃动dump内存快照?-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/usr/local/app/oom第⼀个参数意思是在OOM的时候,⾃动dump内存快照出来,第⼆个参数是说把内存快照放到哪去⾃⼰阅读的书籍举例:《实战Java虚拟机:JVM故障诊断与性能优化(第2版)》Netty知识点对应的⽼师的课程:《Netty核⼼功能精讲以及核⼼源码剖析》 问题:NIO开发的话为什么选择netty(1) NIO的类库和API的繁杂,使⽤麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。(2) 需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程。这是因为NIO编程涉及到Reactor模 式,你必须对多线程和⽹络编程⾮常熟悉,才能写出⾼质量的NIO程序。(3) 可靠性能⼒补⻬,⼯作量和难度都⾮常⼤。例如客户端⾯临重连、⽹络闪断、半包读写、失败缓存、⽹络拥塞和异常码流的处理的问题,NIO编程的特点就是功能开发相对容易,但是可靠性能⼒补⻬⼯作量和难度都⾮常⼤(4) JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%(2) 功能强⼤,预置了多种编解码弄能,⽀持多种主流协议;(3) 定制能⼒强,可以通过ChannelHandler对通信框架进⾏灵活地扩展;(4) 性能⾼,通过与其他业界主流的NIO框架对⽐,Netty的综合性能最优;(5) 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发⼈员不需要再为NIO的BUG⽽烦恼;(6) 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加⼊;(7) 经历了⼤规模的商业应⽤考验,质量得到验证。
假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端⼀次读取到的字节数是不确定的,故可能存在以下4种情况
(1) 服务端分两次读取到了两个独⽴的数据包,分别是D1和D2,没有粘包和拆包;(2) 服务端⼀次接收到了两个数据包,D1和D2粘合在⼀起,被称为TCP粘包;(3) 服务端分两次读取到了两个数据包,第⼀次读取到了完整的D1包和D2包的部分内容,第⼆次读取到 了D2包的剩余内容,这被称为TCP拆包;(4) 服务端分两次读取到了两个数据包,第⼀次读取到了D1包的部分内容D1_1,第⼆次读取到了D1包的 剩余内容D1_2和D2包的整包。(1) 应⽤程序write写⼊的字节⼤⼩⼤于套接⼝发送缓冲区⼤⼩;(2) 进⾏MSS(Maxitum Segment Size 最⼤分段⼤⼩)⼤⼩的TCP分段;(3) 以太⽹帧的payload⼤于MTU(Maxitum Transmission Unit 最⼤传输单元)进⾏IP分⽚。(1) 消息定⻓,例如每个报⽂的⼤⼩为固定⻓度200字节,如果不够,空位补空格;(2) 在包尾增加回⻋换换符进⾏分割,例如FTP协议;(3) 将消息分为消息头和消息体,消息头中包含表示消息总⻓度(或者消息体⻓度)的字段,通常设计思 想为消息头的⼀个字段使⽤int32来表示消息的总⻓度;
问题:简述Netty的线程模型(这个最好画图,显示出⾃⼰思路清新) 现场画图:
问题:Netty解决了java原⽣NIO哪些问题(空轮询的bug,这个⼀定要说出来)⼤家看下这个博客写的挺好的:
https://blog.csdn.net/baiye_xing/article/details/73351330
传统数据从Socket⽹络中传送,需要4次数据拷⻉和4次上下⽂切换:• 数据从⽤户空间缓冲区拷⻉到内核的socket⽹络发送缓冲区;• 数据从内核的socket⽹络发送缓冲区拷⻉到⽹卡接⼝(硬件)的缓冲区,由⽹卡进⾏⽹络传输。
传统⽅式,读取磁盘⽂件并进⾏⽹络发送,经过的4次数据拷⻉和4次上下⽂切换是⾮常繁琐的。实际IO读 写,需要进⾏IO中断,需要CPU响应中断(带来上下⽂切换),尽管后来引⼊DMA来接管CPU的中断请求,但四次拷⻉仍在存在不必要的环节。
零拷⻉的⽬的是为了减少IO流程中不必要的拷⻉,以及减少⽤户进程地址空间和内核地址空间之间因为上 下⽂切换⽽带来的开销。由于虚拟机不能直接操作内核,因此它的实现需要操作系统OS的⽀持,也就是需要kernel内核暴漏API。1. Direct Buffers:Netty的接收和发送ByteBuffer采⽤直接缓冲区(Direct Buffer)实现零拷⻉,直接在内存区域分配空间,避免了读写数据的⼆次内存拷⻉,这就实现了读写Socket的零拷⻉。如果使⽤传统的堆内存缓冲区(Heap Buffer)进⾏Socket读写,JVM会将堆内存Buffer拷⻉到直接内存中,然后才写⼊Socket中。相⽐堆外直接内存,消息在发送过程中多了⼀次缓冲区的内存拷⻉。2. CompositeByteBuf:它可以将多个ByteBuf封装成ByteBuf,对外提供统⼀封装后的ByteBuf接⼝。CompositeByteBuf并没有真正将多个Buffer组合起来,⽽是保存了它们的引⽤,从⽽避免了数据的拷⻉,实现了零拷⻉。传统的ByteBuffer,如果需要将两个ByteBuffer中的数据组合到⼀起,我们需要⾸先创建⼀个size=size1+size2⼤⼩的新的数组,然后将两个数组中的数据拷⻉到新的数组中。但是使⽤Netty提供的组合ByteBuf,就可以避免这样的操作。3. Netty的⽂件传输类DefaultFileRegion通过调⽤FileChannel.transferTo()⽅法实现零拷⻉,⽂件缓冲区的数据会直接发送给⽬标Channel。底层调⽤Linux操作系统中的sendfile()实现的,数据从⽂件由DMA 引擎拷⻉到内核read缓冲区,;DMA从内核read缓冲区将数据拷⻉到⽹卡接⼝(硬件)的缓冲区,由⽹卡进⾏⽹络传输。
把⽼师这张图熟记于⼼,这个流程最好能在⾯试中画出来。Redis知识点对应的⽼师的课程:亿级流量电商详情⻚系统实战https://gitee.com/shishan100/Java-Interview-Advanced/blob/master/docs/high-concurrency/redis-single-thread-model.md (⽼师的⾯试训练营)
问题:缓存雪崩以及穿透的解决⽅案?缓存雪崩发⽣的现象,缓存雪崩的事前事中事后的解决⽅案事前:redis⾼可⽤,主从+哨兵,redis cluster,避免全盘崩溃事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL被打死事后:redis持久化,快速恢复缓存数据
缓存穿透现象以及解决⽅案:
https://gitee.com/shishan100/Java-Interview-Advanced/blob/master/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md ⽼师的⾯试训练营
ZooKeeper
知识点对应的⽼师的课程:《08_ZooKeeper顶尖⾼⼿课程:从实战到源码》问题:2PC与3PC是什么,两者的流程,以及优缺点2PC, 即⼆阶段提交, 为了是基于分布式系统架构下的所有节点在进⾏事务处理过程中能够保持原⼦性和⼀致性⽽设计的⼀种算法。通常,⼆阶段提交协议也被认为是⼀种⼀致性协议,⽤来保证分布式系统数据的⼀致性。⽬前绝⼤部分的关系型数据库都是采⽤⼆阶段提交协议,来完成分布式处理的,利⽤该协议能够⾮常⽅便地完成所有分布式事务参与者的协调,统⼀决定事务的提交或回滚,从⽽能够有效地保证分布式数据⼀致性,因 此⼆阶段提交协议被⼴泛地应⽤在许多分布式系统中。(3) 各参与者向协调者反馈事务询问的响应。阶段⼆:执⾏事务提交
⼆阶段提交协议的缺点:同步阻塞,单点问题,脑裂,太过保守这个是流程图:
3PC,即三阶段提交,是2PC的改进版,其将⼆阶段提交协议的提交事务请求过程⼀分为⼆,形成了由CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议
三阶段提交协议的优点:相较于⼆阶段提交协议,三阶段提交协议最⼤的优点就是降低了参与者的阻塞范围,并能够在出现单点故障后继续达成⼀致。三阶段提交协议的缺点:三阶段提交协议在去除阻塞的同时也引⼊了新的问题,那就是在参与者接收到PreCommit消息后,如果⽹络出现分区,此时协调者所在的节点和参与者⽆法进⾏正常的⽹络通信,在这种情况下,该参与者依然会进⾏事务提交,这必然出现数据的不⼀致。
(1) 群⾸向所有追随者发送⼀个PROPOSAL消息p。(2) 当⼀个追随者接收到消息p后,会响应群⾸⼀个ACK消息,通知群⾸其已接受提案(proposal)(3) 当收到仲裁数量的服务器发送的确认消息后(该仲裁数包括群⾸⾃⼰),群⾸就会发送消息通知追随者进⾏提交(COMMIT)操作。问题:强⼀致性和最终⼀致性的区别,ZooKeeper的⼀致性是怎样的?强⼀致性:只要写⼊⼀条数据,⽴⻢⽆论从zk哪台机器上都可以⽴⻢读到这条数据,强⼀致性,你的写⼊ 操作卡住,直到leader和全部follower都进⾏了commit之后,才能让写⼊操作返回,认为写⼊成功了,此时只要写⼊成功,⽆论你从哪个zk机器查询,都是能查到的,强⼀致性最终⼀致性:写⼊⼀条数据,⽅法返回,告诉你写⼊成功了,此时有可能你⽴⻢去其他zk机器上查是查不 到的,短暂时间是不⼀致的,但是过⼀会⼉,最终⼀定会让其他机器同步这条数据,最终⼀定是可以查到的研究了ZooKeeper的ZAB协议之后,你会发现,其实过半follower对事务proposal返回ack,就会发送commit给所有follower了,只要follower或者leader进⾏了commit,这个数据就会被客户端读取到了那么有没有可能,此时有的follower已经commit了,但是有的follower还没有commit?绝对会的,所以 有可能其实某个客户端连接到follower01,可以读取到刚commit的数据,但是有的客户端连接到follower02在这个时间还没法读取到,所以zk不是强⼀致的,不是说leader必须保证⼀条数据被全部follower都commit了才会让你读取到数据,⽽是过程中可能你会在不同的follower上读取到不⼀致的数据,但是最终⼀定会全部commit后⼀致,让你 读到⼀致的数据的因此zk是最终⼀致性的,但是其实他⽐最终⼀致性更好⼀点,出去要说是顺序⼀致性的,因为leader⼀定会保证所有的proposal同步到follower上都是按照顺序来⾛的,起码顺序不会乱。但是全部follower的数据⼀致确实是最终才能实现⼀致的如果要求强⼀致性,可以⼿动调⽤zk的sync()操作
在整个分布式锁的竞争过程中,⼤量的“Watcher通知”和“⼦节点列表获取”两个操作重复运⾏,并且绝⼤多数的运⾏结果都是判断出⾃⼰并⾮是序号最⼩的节点,从⽽继续等待下⼀次通知,这看起来显然不怎么科学。客户端⽆端地接收到过多和⾃⼰不相关的事件通知,如果在集群规模⽐较⼤的=情况下,不仅会对ZooKeeper服务器造成巨⼤的性能影响和⽹络冲击。更为严重的是,如果同⼀时间有多个节点对应的客户端完成事务或是事务中断引起节点消失,ZooKeeper 服务器就会在短时间内向其余客户端发送⼤量的事件通知,这就是⽺群效应。这个ZooKeeper分布式锁实现中出现⽺群效应的根源在于,没有找准客户端真正的关注点。我们再来回顾⼀下上⾯分布式锁的竞争过程。
它的核⼼逻辑在于:判断⾃⼰是否是所有⼦节点中序号最⼩的。于是很容易可以联想到,每个节点对应的 客户端只需要关注⽐⾃⼰序号⼩的那个相关节点的变更情况就可以了,⽽不需要关注全局的⼦列表变更情况。
我本次⾯试阿⾥,⾯试总共经历了6轮,前3轮技术⾯试都做了算法题,第四轮技术最终⾯试没有做算法题,聊项⽬和离职原因等。
HR我⾯了2轮,第⼀轮HR⾯试主要聊⼊职阿⾥要做的产品以及我本⼈的⼀个职业发展规划,第⼆轮HR⾯试是HRBP⾯的,主要是谈薪资和股票等。⼀些技术的问题⼤概就是上⾯列举出来的,重点是问我⼯作经历中做的项⽬,以及项⽬中的设计,遇到的问题以及解决⽅案,还有就是⾯试官会给出⼀个他们产品中遇到的问题让你通过你过往的⼯作经验给⼀个解决⽅案,也就是技术探讨。我本次⾯试的重点说的项⽬是⾃研API⽹关,apollo配置中⼼的⼆次开发,运单系统的分库分表⽅案等。要想进⼊例如阿⾥这样的⼤⼚,必须要⾃⼰有⼀些含⾦量⽐较⾼的项⽬拿出来给⾯试官讲解,并且要讲解细节,例如项⽬整体的架构,整体流程,项⽬部署的机器配置,平时与活动的QPS峰值,流量估算经验,⾃定义扩展开发或者⾃研的原 因,踩过哪些坑,以及解决的⽅案是什么⽯杉⽼师的互联⽹java⼯程师⾯试突击训练第⼀季、第⼆季、第三季都要看完并且理解,⼒扣注册⼀个会员,刷题,我⽤了1年半刷了140道题,题量不要求多,但要有代表性,例如:链表、递归、迭代等,然后充分理解解题思路即可,平时没事的时候,对着题能把代码写出来
END
长按扫描下方二维码,了解课程详情