前言
通过平时积累,汇总一些面试常用的题解
数据结构
- 二叉排序树
左子树必比根小,右子树必比根大。《二叉排序树(BST)》
最差的情况会变成线性链表
- 平衡二叉树AVL
为了提高查找效率,避免二叉排序树线性情况。 平衡二叉树通过旋转的方式保持左右子树深度相差1,《平衡二叉树AVL》。但此时插入效率却大打折扣
- TreeMap红黑树
不追求“完全平衡”——它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能, 请阅读《红黑树》
- 跳跃表skipList
Skip list是一个分层结构多级链表,最下层是原始的链表.通过概率的方式建立上层索引链表《跳跃表skipList》
- B,B+树
平衡的多路查找树, 相对的减少了树的层级。且又针对磁盘设计减少读出/写入页次数。《B,B+树》
- 散列表HashMap
详细阅读《散列hash表》总结部份
- 字典树
又叫前缀树,提取字符串公共部份。相对散列而言节省了空间,增加了一点查询效率。《字典树(trie)》《双数组字典树DoubleArrayTrie》
JAVA基础
- 面向对象的三大基本特征
Java对象具备面向对象的三大基本特征:继承,封装,多态.而触成三大特征的核心就是 静态分派(重载)及动态分派(重写).
- 静态分派:重载是编译器利用参数的静态类型而不是实际类型作为判定依据的。
- 动态分派:重载是根据运行期对象实际类型来确定方法的执行版本
- == 和 equals 的区别是什么?
关于“==”
- 基本类型:比较的是值是否相同;
- 引用类型:比较的是引用是否相同;
关于“equals”
- equal只在是对象的一方法,默认情况下是引用比较
- 只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较
- equals方法和hashcode的关系
hashCode通常作用于散列存储结构中确定对象的存储大致地址。然后通过equals方法来比较两个对象是否相等。
- equals方法重写的话,一起重写hashcode方法。
- hashcode算法要超散,来减少equals方法比较的次数
- Java的自动装箱与拆箱
Integer total = 99;
//执行上面那句代码的时候,系统为我们执行了:
Integer total = Integer.valueOf(99);
int totalprim = total;
//执行上面那句代码的时候,系统为我们执行了:
int totalprim = total.intValue();
//如果i小于-128或者大于等于128,就创建一个Integer对象,否则执行SMALL_VALUES[i + 128]缓存好的
public static Integer valueOf(int i) {
return i >= 128 || i < -128 ? new Integer(i) : SMALL_VALUES[i + 128];
}
- "i"与 new String(“i”)一样吗,以及string.intern()的作用?
主要考查对JVM常量池的理解《JVM常量池》
IO
- IO 流分为几种?
- 按功能来分:输入流(input)、输出流(output)。
- 按类型来分:8 位传输的字节流和16 位传输的字符流。
- IO技术
- DMA技术:操作系统为了避免CPU完全被各种IO接口调用占用,引入了DMA(直接存储器存储)。当应用程序对操作系统发出一个读写请求时,会由DMA先向CPU申请权限,申请到权限之后,内存地址空间与磁盘之间的IO操作就全由DMA来负责操作。
- 管道的技术:程序与文件之间建立一个通道用于连接,然后通过缓冲区进行数据存取,通过通道进行连接传递。批量发送,不在是传统的一个字节一个字节发送。
- 零拷贝
程序主要过程是:磁盘-内核空间-用户空间(修改文件)-内核空间-网卡
零拷贝则是打通用户和内核: 磁盘-内核空间(修改文件)-网卡
- mmap技术就是用户空间直接分配虚拟内存,映射到内核空间
- 也通过直接内存方式
详情参考《内核概述与NIO》
- BIO、NIO、AIO 有什么区别?
BIO:同步阻塞式 IO 应用线程阻塞等待数据到达,从内核中复制出来。
non-BIO:同步非阻塞式 IO,应用线程不断去询问内核是否数据已到达,如果没到达应用线可做其它任务,如果已到达则从内核中从内核中复制出来。
NIO:New IO 同步非阻塞 IO,是传统non-BIO的升级,内核提供了select/poll模型,应用线程只需要告诉内核所关心的事件,如果事件(数据是否到达)没发生,则可做其它任务,如果发生了,则从内核中复制出来。
- select模型:内核端搞了个数组,把文件描述符收集起来。最后把就绪状态的连接一次性返回用户空间。(减少用户空间和内核的交换次数,数组最大容量为1024)
- epoll模型:结构上采用红黑树+队列的形式。把文件描述符内在红黑树中并注册callback, 等callback响应为就绪状态则从红黑树放到队列中。采用共享内存的技术实现零拷贝来返回到用户空间。(因为采用callback机制,在全是活跃节点的时候,可能存在性能问题。)
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO的操作基于事件和回调机制。应用线程向内核注册信号处理程序(处理数据到达后的情况),当内核接收到数据到达信息,则触发信号处理程序
详情参考《网络IO模型》
线程
- java中的线程模型
SUN JDK采用了一对一线程模型。一个Java线程对应一个内核线程。
- JMM和线程安全
其中工作内存是一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。避免工作内存影响,必须保存程序:原子性,可见性,有序性《线程概述与JMM》
- volatile的内存语义
对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
- synchronized的锁原理
synchronized是Java自带的对象锁。常见三种使用方法:
- 普通同步方法,锁是当前实例;
- 静态同步方法,锁是当前类的Class实例,Class数据存在永久代中,是该类的一个全局锁;
- 对于同步代码块,锁是synchronized括号里配置的对象。
如果多线线程激烈的抢锁,会进行锁优化过程:无锁-偏向锁-轻量锁-重量锁,详情查看《隐式锁:synchronized》
- 线程有哪些状态?
线主要有5个状态《线程的概述》
主要关注:
- wait会挂起线程等待notify。
- sleep只是让出cpu时间片
- bio, 会在其时间片内时不时的查看io状态
- 创建多少个线程合适
单CPU的最佳线程数 = (1/CPU利用率) = 1 + (I/O耗时/CPU耗时)
详情查看《线程的概述》
- ThreadLocal类的实现原理
通过Thread内的ThreadLocalMap对象,结合弱引用的设计自动回收,程序中被置空的值《线程的概述》
JUC
- ConcurrentHashMap实现原理
相对于《散列hash表》,ConcurrentHashMap引入了分段的锁的概念,使之可以并发操作。
- jdk1.7中,ConcurrentHashMap拥有Segment数组对象,Segment继承了ReentrantLock的子哈希表。
- Segment数组多大,理论上支持几线程并发执行,不同Segment的操作相互独立—分段锁技术
- 根据传入的key 通过segmentShift和segmentMask这两个全局变量定位Segment
- Segment内维护着hash,其查找,插入,扩容都与hash一样
- jdk1.8中,不再采用 Segment 实现,而采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。
- 通过hash定位到数组的索引坐标(桶),如果没有则使用CAS进行添加(链表的头结点),有则用synchronized锁住。
- 扩容方式和HashMap1.8差不多。
- 桶在迁移中
- 有get操作,因为采用复制式迁移,不影响。
- 有put操作,迁移时有锁,会阻塞
- 桶在迁移完成,头节点变成ForwardingNode,负责转发请求
- 所有桶迁移完成,舍弃老的数据
- 线程池的原理
- 作为线程池,肯定要有核心线程数量corePoolSize,没任务也不会被回收的线程数量,及生成线程的threadFactory,
- 当任务数过多,肯定要有一个存任务的地方,就是等待队列workQueue,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)
- 当等待队列满了,肯定要加大线程数加快消费,但也不能无限制,maximumPoolSize就是线程池中可以容纳的最大线程的数量
- 加大消费线程数后,仍消费不了的任务,我们需要一个无法执行任务处理策略handler
- CallerRunsPolicy:当任务被拒绝的时候,直接让提交的线程执行
- DiscardOldestPolicy:当被拒绝的时候,线程池会放弃队列之中等待最长时间的任务,并且将被拒绝的任务添加到队列之中
- DiscardPolicy:当任务添加拒绝的时候,将直接丢弃此任务
- AbortPolicy:java默认,当任务添加到线程池之中被拒绝的时候,这个时候会抛出RejectedExecutionException异常
- 当任务数为空线程数,如果allowCoreThreadTimeOut 为true,所有线程会被回收;为flase,>corePoolSize线程会被回收,他们肯定有回收标准。keepAliveTime,就是线程池中除了核心线程之外的其他的最长可以保留的时间;util,就是计算这个时间的一个单位。
- 线程的销毁有二种,一种是上述的超时销毁,另一种是用户代码异常销毁
- shutDown先自己销毁一波,然后利用线程的中断机制一个一个传递唤醒销毁,来确保所有线程都中断
详情查看《线程池Executor原理及生命周期》
- CAS与atomic的原理
CAS(Compare_and_swap)是一种系统原语,在执行过程中不允许被中断。主要有3个操作数,内存值M,预期值E,新值U,如果M==E,则将内存值修改为B,否则啥都不做。其实缺点:
- 循环时间长开销很大 (可以几次cas后直接挂起解决)
- 只能保证一个共享变量的原子操作(用一个CAS来获取共享变量的操作权限,从而达到控制多个变量的效果)
- ABA问题(增加一个版本号的共享变量)
Atomic包下的类,就是利用自旋CAS,从而保证多个线程同时对单个(包括基本类型及引用类型)变量进行操作的安全性。
主要参考《CAS与Unsafe》
- AQS原理
利用unsafe与LockUtil类,根据synchronized重量锁原理优化。自我实现锁队列,及条件等待(如上图)。并对业务场景充分考虑,设计独占及共享两种模式。《显示锁:AQS》
- AQS创造的线程同步工具类
- 闭锁CountDownLatch相当于一扇门,在闭锁到达结束状态之前,这扇门一直是关闭的,任何线程都不能通过这扇门,当闭锁到达结束状态时,这扇门会打开并允许所有线程通过。充分利用AQS的共享模式,state的值就是闭锁的值。
- 计数信号量Semaphore用来控制同时访问某个特定资源的操作数量,或者同时执行某个制定操作的数量。主要实现也是利用AQS的共享模式,许可permits就是state
- 栅栏(Barrier)主要能阻塞一组线程直到某个事件发生,它和闭锁的的区别是所有的线程都必须同时到达栅栏时,才能继续执行,而闭锁是用于等待事件(人等事),栅栏是用于等待其他线程(事等人)。主要利用ReentrantLock和Condition
- synchronized和Lock的区别
- synchronized作用于代码块,Lock可灵活上锁和释放锁
- synchronized不能够保证进入访问等待的线程的先后顺序,属于非公平锁,Lock可公平可非公平
- Synchronized只有一个condition,Lock可关联多个
- Synchronized被JVM直接管理,Lock要自己管理
JVM
- jvm主要组成部分及其作用
- 类加载器(Class Loader):加载类文件到内存。Class loader只管加载,只要符合文件结构就加载,至于能否运行,它不负责。
- 执行引擎(Execution Engine):也叫解释器,负责将Class loader加载类解释成命令,交由操作系统执行。
- 运行时数据区(Runtime Data Area):提供Execution Engine执行命令时,上下文环境。
- 本地库接口(Native Interface):本地接口的作用是融合不同的语言为java所用
- JVM类加载器
java中采用双亲委派模型。《类加载机制》
如果有业务需要,可以能守 继承ClassLoader类,重写loadClass和findClass方法打破双亲委派模式。
- JVM运行时区域
1.程序计数器
2.Java虚拟机栈
3.本地方法栈
4.元数据区 (JDK7叫方法区)
5.堆区
6.运行时常量池(堆中)
7.直接内存
- JVM内存池结构
- 堆区:
- 对象,字符串常量池(字面量,且以对象形式生存)
- 非堆区
- 元数据区
- 常量池(类信息,符号引用),
- 本地区域(codeCache, 线程栈)
- 字节码编译后的机器码(codeCache),线程的栈及本地线程栈(线程栈),程序计数器(线程栈),jvm代码及JVM本生的对象及native调用(其它),JAVA通过直接闪存的方式申请的内存(其它)
主要参考《JVM内存池》
- 对象的创建
为了解决在Eden区分配内存时锁竞争导致性能下降的问题。在Eden区域为每个线程分配了私有的内存区域(Thread-local Allocation Buffers),简称TLABS。这里的私有具体是指只能由该线程在这块内存区域中分配对象,但是已经分配的对象其他线程也可以正常访问.《JVM内存池》
- JAVA对象结构
采用oop-klass二分模型。《JAVA对象结构》《Java对象内存表示机制》
- Oop表示Java实例,主要用于保存实例数据,不提供任何虚函数功能,Oop保存了对应Kclass的指针
- Klass保存类型信息,方法信息,类的继承关系等
- JAVA类的生命周期
- 加载。把字节码class加载到内存中,在过程中会对类文件做格式检查
- 链接。
- 验证。确保类或接口的二进制信息有效性。
- 准备。final static 声明的会直接设置,其它的静态字段设置默认值。此阶段在类创建之后的任何时间,任何时间都可以执行准备,但一定要保证在初始化阶段前完成
- 解析。将常量池中的符合引用解析成正确的内存地址调用
- 初始化。类的静态属性设置值
- 使用。
- 卸载:垃圾回收
主要参考《Hotspot 类文件加载、链接和初始化》
- java栈的结构
栈是每个线程私有的内存空间由多个栈帧组成,每个栈帧代表一个方法无法满足内存,需求的时候,会报栈溢出异常:StackOverflowError
它分为四个部份:
- 局部变量表。栈帧的底部的一段连续的内存空间.在开启一个新的Java栈帧时(调用一个方法)就会初始化完成(其大小是固定的,编译是就确定了)。
- 操作栈.是一个后出先入的LIFO结构,用于存储命令参数。它借用还没使用的栈区域使(因此是和下一个栈帧局部变量表重合的)
- 动态连接.栈帧中保存着常量池的引用,随时可以链接.
- 返回地址。
主要参考《JVM方法调用》
- 如何判断一对象是否存活
- 引用计数算法,关注两对象互相引用无法回收
- 可达性分析算法,关注4大引用
- 强引用: 不回收
- 软引用: 内存不足时回收
- 弱(虚)引用: GC时回收
主要参考《JAVA对象引用》
- 有哪些垃圾收集算法
- 标记-清除。出现大量的内存碎片
- 标记-整理。效率不高
- 复制。对内存要求较高
- 对象如何晋升到老年代
- 对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态。
- 大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年态;
- 空间分配担保,新生代使用复制算法,当Minor GC时如果存活对象过多,无法完全放入Survivor区,就会向老年代借用内存存放对象,以完成Minor GC。在触发Minor GC时,虚拟机会先检测之前GC时租借的老年代内存的平均大小是否大于老年代的剩余内存,如果大于,则将Minor GC变为一次Full GC,如果小于,则查看虚拟机是否允许担保失败,如果允许担保失败,则只执行一次Minor GC,否则也要将Minor GC变为一次Full GC。
- 什么时候触发MinorGC?什么时候触发FullGC?
- MinorGC
1. Eden区域满了
2. 新创建的对象大小 > Eden所剩空间
3. Full GC的时候会先触发Minor GC- FullGC
1. System.gc()方法的调用
2. 老年代空间不足
3. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
4. 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
- CMS收集器
CMS 全称为 Concurrent Mark Sweep。一般和ParNew新生代收集器配合使用用的老年代收集器。以获得最短回收停顿时间为目标的收集器,使用标记-清除算法.主要思想是把 GC roots 相关的对象标记,并清理。《JVM垃圾回收》其主要缺点:
- 标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽。老年代堆内存用尽但触发Serial Old收集器回收老年代,造成长时间STW.
- G1收集器
G1收集器采用了不同的方法,默认将整堆划分为2048个分区(年轻代+老年代).G1是采用标记-整理. 《JVM垃圾回收》
- 年轻代主要采用STW方案 , 并传递GC-ROOT
- 老年带在每个区域记录其它区域的引用集合Rset,然后通过Rset检测和GC-Root相关联。判断否回收
- CMS收集器和G1收集器的区别
- CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
- CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片;G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
- 最大区别:CMS收集器以最小的停顿时间为目标的收集器;G1收集器可预测垃圾回收的停顿时间(Region区块概念,可对回收价值和成本进行排序回收,根据GC期望时间回收)
JVM线上故障排查
- java进程cpu占比过高,怎么排查
- 使用top命令查看线程最高的那个
- 使用top -Hp 查看线程cpu占比,比较高的那个获取pid
- 把线程pid转化为16进制,获取jstack获取java线程堆栈信息
- 通过cpu占比过高的线程的栈信息,定位代码具体分析
- 如何分析资源等待
- 获取jstack获取java线程堆栈信息
- 使用grep命令查看lock状态的线程,并统计lock同一资源最多(资源ID相同)
- 再通过栈信息,定位代码,具体分析资源等待的原因
- java OOM问题排查
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/home/admin/logs/java.hprof
或者
jmap -dump:format=b,file=文件名 [pid]
- java进程启动进添加参数
- 导入eclipse安装MAT工具进行分析
- 如果程序挂掉了,但是没有找到任何这个操作的日志记录。这时查看一下/var/log/messages文件。操作系统由于内存使用率过高,直接杀死了评分最高的进程,Linux对于每个进程有一个OOM评分,这个评分在/proc/pid/oom_score文件中。
- 如何定位磁盘IO
1.生成 5g 大小的文件并输出时间,执行速度等信息
time dd if=/dev/zero of=test.file bs=1G count=5
2.测试写入20G数据,数据量越大,测试值应该更精确
sync;/usr/bin/time -p bash -c "(dd if=/dev/zero of=test.dd bs=1M count=20000)"
sync;/usr/bin/time -p bash -c "(dd if=/dev/zero of=test.dd bs=1000K count=20000;sync)"
dd bs=1M count=20000 if=/dev/zero of=test.dd conv=fdatasync dd命令测试是IO的顺序写和读方
- 使用上述命令测出磁盘io的峰值
- 用iotop定位io比较高的进程(线程)
- 用lsof查看进程所连接的文件资源
- 如何定位网络IO
Iperf可以测试TCP和UDP带宽质量。Iperf可以测量最大TCP带宽,具有多种参数和UDP特性。Iperf可以报告带宽
- 使用上述工具查看带宽峰值
- 用iftop定位io比较高的连接
- 用netstat定位io比较高的连接是哪个进程
- 用lsof查看进程所连接的tcp/upd资源
- GC性能分析
- 通过printGCDetails参数,获取GC时的明细信息
- 通过jstat获取GC的统计信息
- 现结果jmap 的dump信息分析
- 不要忘记Java的应用有时候会因为各种原因Crash-XX:ErrorFile=/var/log/java/java_error_%p.log
MySQL
- MySQL中varchar与char的区别
- char(N)如果存储的字节数超过 N,那么 char( N)将和 varchar( N)没有区别。 如果存储的字节数少于 N,那么存储 N 个字节,后面补空格,补到 N 字节长度。
- char类型通常要比varchar类型占用更多的空间,所以从减少空间占用量和减少磁盘i/o的角度,使用varchar类型反而更有利.
- varchar每次存储都要有额外的计算,得到长度等工作,如果一个非常频繁改变的,那就要有很多的精力用于计算
- varchar(50)中50的涵义
- 最多存放50个字符,varchar(50)和(200)存储hello所占空间一样,但后者在排序时会消耗更多内存,因为order by col采用fixed_length计算col长度。
- 当列的长度小于255字节,用1字节表示,若大于255个字节,用2个字节表示,理想情况中varchar的最大长度为65535.当发生行溢处时,则这个存放行溢处的页类型为Uncompress BLOB Page
- int(20)中20的涵义
是指显示字符的长度,不影响内部存储。
- mysql主键顺序插入
mysql 对 B+树索引的进行分裂优化
如果一方向进行插入的,Mysql会将分裂点的记录为定位到的记录后的第三条记录。减少B+树分裂造成的空间浪费主要参考《InnoDB存储引擎索引概述》
- sql优化
- explain分析
一般来说,要看到尽量用 index(type 为 const、 ref 等, key 列有值),避免使用全表扫描(type 显式为 ALL)- 字段是否可加索引分析
- 高选择性,则此时使用B+树索引是最适合的
- 覆盖索引,减少io
- profile分析
用来分析 sql 性能的消耗分布情况。当用 explain 无法解决慢 SQL 的时候,需要用profile 来对 sql 进行更细致的分析,找出 sql 所花的时间大部分消耗在哪个部分,确认 sql的性能瓶颈。
- mysql中事务的4大特性
- 原子性
- 一致性
- 隔离性
- 持久性
- mysql的锁算法
- Record Lock : 单个行记录上的锁
- Gap Lock : 间隙锁,锁定一个范围,单不包含记录本身
- Next-Key Lock : Gap Lock + Record Lock,锁定一个范围,并且锁定记录本身
以上三种算法作用于每个索引,要注册使用到的索引更加锁顺序。另外关注数据库的隔离特别加锁算法是不一样的《mysql锁机制》
- mysql mvcc原理
通过记录链中的事务ID,加上read-view窗口。给其它事务形成快照读《mysql事务》
- 事务及恢复实现原理
- Undo log:回滚而记录的,称之为撤销日志。
- redo :记录页修改的时的日志.
- 通过redo日志恢复undo日志,undo日志可用来回滚没完成的事务。
- 如上图,当page页被刷新到磁盘的时候,redo log 会记录checkpoint, 这样checkpoint之前的数据就可以擦除。当发生死机需要恢复时,只要拿磁盘上page数据+checkpoint到write_post的数据。
具体参考《mysql事务》
- binlog的几种格式
- ROW(行模式):记录record修改前,修改后的数据。日志会非常大,mysql主从同步时,会产生大量磁盘IO
- Statement(语句模式): 记录每一条会修改数据的sql。由于记录的只是执行语句,无法保证slave得到和在master端执行时候相同 的结果
- Mixed(混合模式):一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog
行模式最能保障数据的准确性。
- 2pc之redo与binlog
redo log是InnoDB引擎特有的; binlog是MySQL的Server层实现的.它们要实现2pc必须要选择TM (MySQL的Server层)
- prepare阶段:数据改变写入到redo log设置为Prepared状态,binlog不做任何操作。
- commit阶段:将事务(包括其xid)写入Binlog中,然后将redo log中的对应事务打上commit标记。
- 如果commit阶段,redo log打标记失败了,TM (MySQL的Server层)扫描最后一个Binlog文件,提取其中事务的xid。再次提交redo log中prepare的事务。《mysql事务》
http协议
- HTTP的过程与原理
- 用户在浏览器中键入需要访问网页的 URL或者点击某个网页中链接;
- 浏览器根据 URL中的域名,通过 DNS 解析出目标网页的 IP 地址;
- 浏览器与网页所在服务器建立 TCP 连接;
- 浏览器发送 HTTP 请求报文,获取目标网页的文件;
- 服务器发送 HTTP 响应报文,将目标网页文件发送给浏览器;
- 释放 TCP 连接;
- 浏览器将网页的内容包括文本、图像、声音等显示呈现在用户计算机屏幕
- 为什么tcp三次握手
因为TCP是双工的,可以双向发数据
- A向B,请求连接
- B返回A,已经准备好了,随时发数据
- A向B,我也准备好了,可以发数据
- 什么是https
https = http + ssl协议。SSL协会作用在会话层主要通过第三方证书链,加上公开密钥+ 共享密钥 两加载方式保证数据传输的安全性《https概述》
- 什么是http2
http是基于文本的,http2是基于二进制的。采用“多路复用”通讯方式,并在协议上增加《https概述》:
- 二进制分帧
- 流优先级
- 头部压缩
- 服务器推送
分布式理论
- 什么是幂等
就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。一般使用方法:
- 常用的负载均衡方式
- 手动选择,页面做只个按钮让用户选择
- DNS
- 一个域名解析多个ip,DNS服务器将解析请求按照顺序,随机分配到不同的ip上,来实现负载均衡
- 智能DNS解析能够根据用户本地设置的DNS服务器线路和地区,将对同一个域名请求解析到不同的IP上
- 传输层
- 硬件的交换机方式,比较有代表性的有F5 BIG-IP、Cisco CSS、NetScaler
- 软件四层负载均衡的代表作品是LVS,采用IP负载均衡技术和基于内容请求分发技术,调度器具有很好的吞吐率,将请求均衡地转移到不同的服务器上执行
- 应用层, 基于HTTP反向代理,最具有代表性的就是大家熟悉的Nginx
- 强一致性2pc与3pc
- 2pc:主要依赖TM,tm接受到commit请求后,所有RM发送commit,如果中间意外,TM重启后继续执行。
- 3pc:中间相对2pc多了个对齐步骤,这步骤主要是告诉RM,XX时间内如果我失联请回滚。过了这时间则自动提交。
主要参考《分布式事务与一致性》
- 基于消息的最终一致
- 基于MQ的事务功能方式
MQ Server 向 MQ producer询问提交状态- 基于消费端回调方案
MQ consumer 向 MQ producer询问提交状态- 基于BD的本地消息表方案
将业务数据和事务消息直接写入数据库,启用线程从消息表取数投递主要参考《分布式事务与一致性》
- 线性一致性Raft
- 获取Leader结点特性
- 有最大的Term;
- 如果Term相同,则有最大的Index;
- 如果Term相同,Index也相同,就看谁最先发起(随机时间);
- 日志同步
leader 就会把 nextIndex 递减进行重试,直到遇到匹配到node正确的日志,然后一一同步- 线性一致性读
将请求发给Leader, Leader把请求以日志形式提交至状态机并记录 ReadIndex, 当状态机执完成便返回主要参考《日志一致性协议Raft》
Dubbo
- dubbo的十层架构
- serivce服务层:包含Provider,Consumer
- Config配置层:配置一些基本服务的基本信息
- Registry注册层:向注册中心注册,及提取注册信息
- Proxy代理层:生成接口代理对象,Filter等拦截功能实现,主要采用spi+asm(字节码增加技术)实现
- Cluster集群层:提供方可能不止一个服务隐藏在代理层后面,这层是均衡策略和容错策略的开发地
- monitor监控层:主要收集调用数据,给后台及均衡策略提供相应的数据
- Protocol协议层:把服务对象协议化
- Exchange信息交换成:用Fature处理request和response
- transport传输层:用netty,nima等框架实现传输
- 序列化层:将数据序列化
- dubbo的均衡策略
作用在Cluster层《dubbo之集群》表示如果分压到每台服务器的策略:
- Random LoadBalance
随机,按权重设置随机概率。- RoundRobin LoadBalance
轮循,按公约后的权重设置轮循比率。- LeastActive LoadBalance
最少活跃调用数,相同活跃数的随机,活跃数指调用前后时间计数差。- ConsistentHash LoadBalance
一致性Hash,相同参数的请求总是发到同一提供者。其中Random,RoundRobin,LeastActive,刚启动时dubbo使用预热warmup功能,对刚上机子权重放小
- dubbo的容错策略
作用在Cluster层《dubbo之集群》表示调用失败时采取的策略:
- Failover Cluster(缺省)
失败自动切换,当出现失败,重试其它服务器。- Failfast Cluster
快速失败,只发起一次调用,失败立即报错。- Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。- Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。- Forking Cluster
并行调用多个服务器,只要一个成功即返回。- Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。如果服务器启动token用随机,重试的时候会报错,因为会话中token没变
- dubbo的优雅停机
- 主要Jdk提供的关闭钩子,以下场景会触发钩子
- 程序正常退出
- 使用System.exit()
- 终端使用Ctrl+C触发的中断
- 系统关闭
- 使用Kill pid命令干掉进程
- dubbo中主要通过勾子执行
- 注锁注销注册中心
新的请求不能再发往正在停机的 Dubbo 服务提供者。- 关闭 Server
若关闭服务提供者,已经接收到服务请求,需要处理完毕才能下线服务。- 关闭 Client
若关闭服务消费者,已经发出的服务请求,需要等待响应返回。主要参考《dubbo之服务配置及启停》
Rocketmq
- Rocketmq结构及作用
主要参考《rocketmq概述》
- NameServer集群
- Broker都会向NameServer集群每一个NameServer注册。
- Producer,Consumer通过Broker代理向NameServer集群每一个NameServer注册
- NameServer集群中NameServer互不通信
- Broker集群
- 每个Broker都可采用master/slave模式,主要同步方式有同步双写和异步复制
- 每个Broker将所在数据存储一个文件中,然后通过索引的方式关联Queue
- 每个Queue只会被一个Consumer消费,如果Consumer数>Queue数,则可能存在Consumer无消费的现象。反之,Consumer可消费多个Queue
- Producer集群
- Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,主要拉取最新的broker topic queue数据。
- Producer和与其关注topic的broker保持长连接。默认情况下消息发送采用轮询方式,会均匀发到对应Topic的所有queue中。
- Consumer集群
- Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,主要拉取最新的Consumer与broker topic Queue数据。
- Consumer和与其关注topic的broker保持长连接,在用相同的算法来分配Queue(保证Queue只对应一个Consumer)。当有个Consumer失去心跳后,并向该消费者分组的所有消费者发出通知,分组内消费者重新分配Queue继续消费。
- 消费者消费模式有几种
- 集群消费
一个 Consumer Group 中的各个 Consumer 实例分摊去消费消息,即一条消息只会投递到一个 Consumer Group 下面的一个实例。- 广播消费
消息将对一 个Consumer Group 下的各个 Consumer 实例都投递一遍。即即使这些 Consumer 属于同一个Consumer Group ,消息也会被 Consumer Group 中的每个 Consumer 都消费一次。
消费者获取消息有几种模式?
- PushConsumer
推送模式的消费者。消息的能及时被消费。其原理是长轮询 + pull 模式结合的方式。因为每台服务器的消息能力是不一样的,pull是可以根据服务器的能力自己拉取。- PullConsumer
拉取模式的消费者。应用主动控制拉取的时机,怎么拉取,怎么消费等。主动权更高。但要自己处理各种场景。
- ACK——消息确认机制
- consumer与对应的Queue连接
- consumer向Queue,pull数据10条,offset 不变
- consumer会创建一个线程池来消息(所以不能保证消息的顺序消费)
- consumer会创建一个TreeMap, 按消息的 offset 升序排序
- 如果对应offset消息被消费了,直打上标签
- 如果1-10的,offset 1,2,5被打上标签,consumer会把1,2offset的消息同步给Queue。Queue向前+2.consumer继续等待offset为3的消息消费
优缺点:
- 优点:防止消息丢失(也就是没有消费到)。
- 缺点:会造成消息重复消费(所以要做幂等操作)。
lucence
- 构建文件索引的过程
- 分词
- 倒排索引 (词典树+压缩) 可以将更多索引load到内存,减少磁盘查询次数,因此速度快
- 主要关系索引参数
- DocValues 有助于排序
- store 冗余存储想要的数据
- 合并段,lucence索引数据不更新,只新增。会把新的数据在新段,最后merge
主要参考《Lucene简介》
Redis
- redis 有哪些数据类型
- 字符串(String):
Redis最基本的数据类型,一个键最大能存储512MB,可以包含任何数据。比如jpg图片或者序列化的对象,json。- 列表(List):
简单的字符串列表,按照插入顺序排序- 哈希表(Hash):
是一个string类型的field和value的映射表- 集合(Set):
Redis的Set是string类型的无序集合。底层通过哈希表实现的- 有序集(zset):
和set一样,不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。
- Redis有哪几种持久化方式及对比
- RDB(快照): 内存中数据dump下来
- AOF: 顺序的redis指令序列
采用 RDB + AOF的方式
- AOF指令,可靠性高,实时性高,恢复性能
- RDB基于间隔时间,恢复性能好, 实时性差
- 缓存穿透,缓存雪崩
- 缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
解决方案:布隆过滤器,缓存空对象
- 缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
解决方案:数据预热,就是在部署启动之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。为key设置不同的缓存失效时间。使redis高可用,不会造成所有key同时失效的情况。
- 缓存击穿: 是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。。
解决方案:限流降级,对某个key只允许一个线程查询数据和写缓存,其他线程等待
- 热点(热key)问题:就是瞬间有几十万的请求去访问redis上某个固定的key,从而压垮缓存服务的情情况
解决方案:本地端利用二级缓存技术。redis端则备份热key,客户端使用轮询或者随便对其分流
- 怎么实现redis分布式锁
基于redis是单线程,setnx设置值
如果事务执行长度大于锁的expire怎么解决
- 不断的延长锁的时效性,开启一个守护线程,进行更新。如果宕机,后台线程也死了
- 控制方法的调用时长。
- Redis的过期键的删除策略
- 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
- 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
- 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
- redis数据淘汰策略
redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略
- volatile-lru:从设置了过期时间的数据集中,选择最近最久未使用的数据释放;
- allkeys-lru:从数据集中(包括设置过期时间以及未设置过期时间的数据集中),选择最近最久未使用的数据释放;
- volatile-random:从设置了过期时间的数据集中,随机选择一个数据进行释放;
- allkeys-random:从数据集中(包括了设置过期时间以及未设置过期时间)随机选择一个数据进行入释放;
- volatile-ttl:从设置了过期时间的数据集中,选择马上就要过期的数据进行释放操作;
- noeviction:不删除任意数据(但redis还会根据引用计数器进行释放),这时如果内存不够时,会直接返回错误。
- redis高可用
- 采用Master/Slave主备的方式,使用AOF方式
- 增加Sentinel(哨兵),负责持续监控主从节点的健康,当主节点挂掉时,自动选择一个最优的从节点切换为主节点
- 客户端来连接集群时,会首先连接 sentinel,通过 sentinel 来查询主节点的地址
- 然后再去连接主节点进行数据交互。
- 当主节点发生故障时,客户端会重新向 sentinel 要地址,sentinel 会将最新的主节点地址告诉客户端。
如此应用程序将无需重启即可自动完成节点切换。Sentinel 会持续监控已经挂掉了主节点,待它恢复后,原先挂掉的主节点现在变成了从节点,从新的主节点那里建立复制关系。
- hash一致性算法扩容,均衡
先构造一个0到2^32的整数环,将真实服务使用(虚拟节点)平均分配负责环上的节点。当一台服务器挂了,直接将其负责虚拟节点移到新机子上.
codis将上述虚拟节点与真实节点的关系维护在zk上,实现集群的动态扩容与缩容《redis概述》
SSM基础框架
- mybatis 架构原理
参考《Mybatis概述》主要关注点
- mybatis的执行流程
- #和$的区别
- 一级缓存
- srping中AOP
aop面向切面编程,关键在于代理模式,Spring AOP使用的动态代理.可用于权限认证、日志、事务处理.《浅谈AOP》主要有两种方式
- JDK动态代理
基于接口的代理,不支持类的代理。- CGLIB动态代理
是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP. 底层利用ASM 只是一个字节码操作的工具。spring aop利用上述两种方式实现AspectJ规范 《spring-aop 扩展代理》
- spring如何解决循环依赖
采用了三级缓存《spring-beans核心容器》
- singletonObjects保存完成实例
- earlySingletonObjects保存的不完整的Bean(装配中的Bean)
- singletonFactories创建Bean对象的工厂,(即将装配的Bean)
为什么用三级缓存
- 一级缓存就可以解决循环依赖的动态代理问题
- 二级缓存解决并发getBean导致已走完和未走完bean生命周期引起的安全问题
- 三级缓存更多是为了符合设计问题,让未发生循环依赖的bean在初始化阶段动态代理
- spring-bean的生命周期
构造-属性注入-初始化-执行-销毁 《spring-context 容器上下文》
- SpringMVC流程
- 用户发送请求至前端控制器 DispatcherServlet。
- DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
- HandlerMapping处理器映射器找到具体的处理器(之前根据 xml 配置,注解加载的),生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。
- DispatcherServlet 调用 HandlerAdapter 处理器适配器
- HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller 执行完成返回 ModelAndView。
- HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet。
- DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器
- ViewReslover 解析后返回具体 View。
- DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet 响应用户。
主要参考《spring-web 网络开发》
- springboot启动流程
- 通过@EnableAutoConfiguration注解,触发@Import(ImportSelector)
- ImportSelector,加载spring.factiries方式。
从而实现springboot版SPI.至使加载spring.factiries文件中指定的类去配置想要的插件。主要参考《spring boot启动流程》