配合xiaolincoding,JavaGuide 使用。
知识体系
Java
基础
集合类
动态代理
反射
反射的应用场景
- RPC调用、AOP面向切面,动态代理的实现也依赖于反射、还有各种框架里比如Spring/SpringBoot、Mybatis这些也用到了反射,反射其实就是一面镜子,通过反射可以获取到类的定义、类的属性和方法、调用的方法和构造的对象,都可以通过反射获取到这些信息
数据类型
- byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。
- short:16位,最大数据存储量是65536,数据范围是-32768~32767之间。
- int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。
- long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。
- float:32位,数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F。
- double:64位,数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加。
- boolean:只有true和false两个取值。
- char:16位,存储Unicode码,用单引号赋值。
面试题
说说你对面向对象的理解
- 面向对象是一种设计思想,在Java中万物皆对象,万物都可以抽象成对象,对象都有属性、方法、行为这些特征,面向对象就是会把问题抽象成对象,然后用对象执行的方式去解决问题
面向对象有三个特性:继承、封装、多态
- 继承:子类继承父类,并能复用父类的所有属性和方法,只有私有属性和方法无法访问,子类也可以拥有自己的方法和属性,所以子类可以对父类进行扩展,这就是继承的作用
- 封装:就是把对象的状态隐藏在对象的内部,不允许外部对象之间访问对象内部的信息,只能通过外部的访问访问来操作属性
- 多态:一个对象具有多种状态,具体表现为父类引用指向子类的实例,对象间可以用继承和接口来实现多态
说说包装类型的常量池技术
- 说常量池之前先说下常量吧,常量就是用final修饰的变量,值一旦给定就不能修改,常量池就是用来存放常量的容器,主要用来避免频繁创建或者销毁对象(会消耗性能)用的,还实现了对象之间的共享,Java的包装类型基本上都实现了常量池技术,比如:Byte、Integer、Long、Character、Boolean这些,这5个包装类都默认创建了[-127-128]这个区间的缓存数据,超过了还是会创建新的对象,具体用的话就是是这些包装类,它就默认使用常量池的对象而不会去创建新的对象,这大概就是我的一个理解
接口和抽象类的区别是什么
- 接口可以多实现也就相当于多继承,而抽象类就不行
- 接口可以支持default方法,抽象类就不支持
- 接口不能函数实现,抽象类可以
- 接口只能包括public函数和public static final常量,抽象类就没有这种限制
说说String、StringBuffer、StringBuilder的区别
- String是用来构造字符串的类,声明后属性会变成final,由于这种不可变型,拼接、裁剪字符串会产生新的String对象,所以操作少量的数据可以用String
- StringBuffer是一个线程安全的可以修改字符的类,它对方法和调用的方法加了同步锁,因此是线程安全的,这个解决了String拼接产生太多对象的问题,因为保证线程安全,所以也会带来一定的性能开销,所以一般来说,除非有线程安全的必要保障,不然还是会使用StringBuilder,不过多线程情况下用StringBuffer比较合适
- StringBuilder和StringBuffer没有本质的区别,只是去掉了线程安全的部分,减少了性能的开销而已,所以单线程情况下,一般使用StringBuilder
说说泛型擦除的原理
- 所谓泛型擦除就是Java中的泛型只在编译中有效,在运行中会被删除,Java的泛型是伪泛型,所有的泛型参数在编译后都会被清除掉,原理就是在编译成字节码时,首先进行类型检查,然后进行类型擦除
异常的类型,具体的例子有哪些
- 异常的类型有Error、Exception、RuntimeException,Error就是系统异常,我们处理不了的不需要我们处理的,Exception可以catch捕获处理,RuntimeException可以捕获处理也可以不捕获
- Error的例子就比如:OutOfMemoryError和StackOverflowError这种
- Exception的例子就比如:IOException、SQLException这种
- RuntimeExeption的例子就比如:NullPointerException这种
Lambda表达式有什么好处,什么坏处,应用场景有哪些
- 好处就是使用Lambda表达式可以让代码变得更加紧凑,让Java支持函数式编程,它就是个匿名函数;
- 坏处是如果Java应用启动过程中引入了很多的Lambda表达式,会导致启动过程变慢
- 应用场景有替代匿名内部类、传递方法或者构建函数引用,还可以去访问变量
IO流
BIO:同步阻塞IO
- BIO:同步阻塞IO,就是应用程序发起read调用后,它会一直阻塞,直到内核把数据拷贝到用户空间,适合客户端连接数不高的情况,高并发常见下不适用
NIO:同步非阻塞IO
- NIO:同步非阻塞IO,引入select机制,就是应用程序会一直发起read调用,等数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到内核把数据拷贝到用户空间,它是一种IO多路复用模型
AIO:异步IO
- AIO:异步IO,采用回调机制,但是对于代码难度更高,所以没有广泛流行
什么是IO多路复用模型
- 线程首次会发起select调用,会询问内核数据是否准备就绪,等内核把数据准备好了,用户线程会再发起read调用,read调用过程还是阻塞的,目前支持IO多路复用的系统调用有select、epoll等。IO多路复用模型,能通过减少无效的系统调用,能减少对CPU资源的消耗
什么是select调用,什么是epoll调用?
- select调用:内核提供的系统调用,支持一次查询多个系统调用的可用状态,几乎所有的操作系统都支持
- epoll调用:属于select调用的增强版本,优化了IO的执行效率,Linux内核2.6以上支持
多线程
三种使用线程的方法
- 实现 Runnable 接口
- 实现 Callable 接口
- 继承 Thread 类
守护线程
- 程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分
- 当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程
- main() 属于非守护线程
- 使用 setDaemon() 方法将一个线程设置为守护线程 Thread thread = new Thread(new MyRunnable()); thread.setDaemon(true);
Thread
- sleep:sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。
- yield:对静态方法 Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。
- interrupt:通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。
- join:在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
- wait() notify() notifyAll()
调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
它们都属于 Object 的一部分,而不属于 Thread。
JUC
Concurrent
Executor
Caller&Future
Queue
locks
ReentrantLock
- ReentrantLock默认情况下也是非公平的,但是也可以是公平的。
- ReentrantLock可以同时绑定多个Condition对象
CAS
atomic
ThreadLocal
- 保存线程独享的数据
Volatile
- 对多线程数据可见性的保证
Fork/Join
- 用于大任务的分割与汇总
Synchronized
Synchronize是如何保证同一时刻只有一个线程可以进入到临界区呢
- Synchronize的是对对象进行加锁,在JVM中,对象在内存中分为三块区域,对象头、实例数据和对齐填充,在对象头中保存了锁标志位和指向monitor对象的起始地址,当Monitor被某个线程占用后,就会处于锁定状态,Monitor的Owner部分会指向持有Monitor对象的线程,另外Monitor还有两个队列用来存放进入以及等待获取锁的线程,Synchronize应用在方法上时,在字节码中是通过方法的ACC_SYNCHRONIZED的标志来实现的,Synchronize的应用在同步块上时,在字节码中是通过monitorenter和monitorexit来实现,针对Synchronize获取锁的方式,JVM使用的锁升级的优化方式,就是先使用偏向锁优先同一线程再次获取锁,如果失败就升级为CAS轻量级锁,如果再失败会进行短暂的自旋,防止线程被系统挂起,最后如果以上都失败就会升级为重量级锁。
JVM实现
线程池
核心线程数,默认情况下,核心线程会一直存活
最大线程数,决定线程池最多可以创建多少线程
线程的空闲时间,空闲时间的单位,当线程闲置超过空闲时间时就会被销毁
线程缓存队列
- 有界队列
- 无界队列
- 同步队列
线程池满时拒绝策略
- 抛出异常(默认)
- 丢弃
- 提交失败时,由提交任务的线程直接执行任务
- 丢弃最早提交的任务
面试题
为什么要使用多线程,有什么好处,有什么坏处?具体场景
- 说多线程前得说下进程才能体现其好处,进程是程序的执行的基本单位,是一种动态的概念,也是资源分配的最小单位,系统资源是有限的,使用进程可以提高资源的利用率和提高系统的运行效率,而线程,它可以相当于是轻量级的进程,是程序执行的最小单位
- 好处就是它在线程间调度和切换的成本远远小于进程,多个CPU的话意味着多线程可以同时运行,这就减少了线程上下文的切换,现在的系统动不动就要求百万级别甚至千万级别的并发量,而多线程就是开发高并发系统的基础,使用多线程可以提高系统的并发能力
- 坏处就是在并发编程下会遇到很多问题比如:内存泄漏、死锁、线程不安全等问题
- 使用多线程的场景就是异步、分布式这些场景
什么是并发?什么是并行?
- 在一段时间内执行了多个任务,这叫并发,多个任务可以被不同核心的CPU同时执行,这叫并行
说说线程的生命周期以及每个周期进入和结束的标志
- 线程的生命周期也就是它的几种状态,有new创建、ruunable运行、blocked阻塞、waiting等待、time_waiting超时等待、terminated终止这几个
- 进入new创建状态的标志是实例化,在调用start()方法后结束new状态
- 调thread.start()方法进入runnable运行状态,这个状态有运行中running和就绪ready两个子状态,拿到CPU的时间片就进入running状态,没拿到就进入ready状态,runnable的结束标志是调用了wait()方法或者sleep()方法进入waiting等待状态或者time_waitting超时等待状态
- 调thread.wait()方法进入waiting等待状态,结束标志是调用了thread.notify()或者notifyAll()方法,调用后重新回到runnable状态
- 调thread.sleep()方法进入time_waiting超时等待状态,结束标志是调thread.notify()或者notifyAll()方法,调用后重新回到runnable状态
- 线程执行完成后进入terminated终止状态
说说避免死锁的三个必要条件
- 三个必要条件是:请求与保持条件、不剥夺条件、循环等待条件
- 1、破坏请求与保持条件:一次性申请所有资源
- 2、破坏不剥夺条件:占有部分资源的线程申请其它资源时,如果申请不到,就主动释放它占有的资源
- 3、破坏循环等待条件:按某一顺序申请资源,释放资源时则反序释放
简述哲学家进餐问题和银行家算法
- 哲学家就餐问题就是:几位哲学家围坐在一张圆桌子旁,桌子中央有一盘通心面,每人面前有一只空盘子,每两人之间放一把叉子;每位哲学家思考、饥饿,然后吃通心面;为了吃面,哲学家必须获得两把叉子,并且每个人只能直接从紧邻自己左边或右边去取叉子,这个问题反映了死锁的现象,也就是说,如果每个哲学家先取右边叉子,再拿左边叉子,就造成死锁了。
- 银行家算法是著名的死锁避免算法,但缺乏实用价值,就是:假如小城镇银行家拥有资金数量为N,它被M个客户共享,银行家对客户提出这些约束条件:
1、每个客户每次提出部分资金量的申请并获得分配
2、如果银行满足客户对资金的最大需求量,那么客户在资金运作后,要在有限的时间内全部归还银行
只要客户遵守上面的约束条件,银行家将保证做到:
1、若一个客户所要的最大资金量不超过N,银行一定会接纳此客户并满足其资金需求;
2、银行在收到一个客户的资金申请时,可能会因为资金的不足而让客户等待,但能保证在有限的时间内让客户获得资金
在银行家算法中,客户可以看作进程,资金看作资源,银行家看作操作系统,虽然能避免死锁,但是实现起来会受到种种限制,要求所涉及的进程不相交,也就是不能有同步要求,要事先知道进程的总数和每个进程请求的最大资源数,这就很难办到了
- 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
- 因为调start()方法的时候,才能创建新线程并且执行run()方法里的代码,如果直接调run()方法,它就不会创建线程也不会执行调用线程的代码,只会把run()方法当作普通的方法去执行
- volatile的用法
- volatile是一种轻量级别的线程同步机制,保证可见性和有序性,不保证原子性,在变量更新操作时能够通知到其它的线程,常用于DCL双重检查锁以及状态标记量这两个
volatile用cas机制实现的线程同步
- synchronized和ReentrantLock的区别
- 都是可重入锁,S自动释放锁,R要手动释放锁;
- S是个关键字,用于修饰代码块和方法,依赖于JVM,R是一种JDK层面的锁机制,依赖于API
- 它们都是独占锁,S容易操作不够灵活,不容易操作但是够灵活
- S不可响应中断,一个线程获取不到锁会一直等待,R可响应中断
- R比S多了一些高级功能,比如:等待可中断、公平锁
- 乐观锁和悲观锁的底层实现
- 乐观锁就是线程每次操作数据时会认为该线程不会去修改,也就不会上锁,其实现方式可以加版本号或者时间戳实现和CAS实现
- 悲观锁就是线程每次操作数据时会认为该线程会去修改数据,每次操作数据都会上锁,别的线程就会阻塞,其实现就是synchronized
- 不加锁如何保证线程安全
- 1、通过CAS无锁算法保证线程安全,CAS就是CompareAndSwap,比较并交换,其机制就是比较比较并交换+乐观锁+锁自旋;它是一种非阻塞同步,是unsafe类通过JNI调底层C/C++指令实现的,CAS有三个东西:内存位置、旧预期值、新值,当内存位置等于旧预期值时,就会用新值去更新内存位置的地址
- 2、乐观锁保证线程安全,用乐观锁得加版本号或者时间戳,一旦数据变化就去修改版本号,如果版本号对不上就是版本冲突了
- synchronized是可重入锁么
- 是的,可重入,不可中断,默认非公平锁
- 使用了vector就一定能保证线程安全吗
- 正常来说是保证的,因为vector里的所有公共方法都加了synchronized,所以能保证线程安全,而如果进行复合操作形成组合方法就不一定能保证线程安全了,这个时候就需要用锁来保证线程安全
- 高并发的情况下保证数据的一致性怎么做到
- 如果使用大量缓存,必定引入读写分离,执行写操作后,延迟删除缓存,当然不可能完全消除数据一致性问题,除非要牺牲吞吐量,但是不满足高并发要求,合理的控制延时参数,能够解决绝大多数问题
- 先更新数据库,再删除缓存 ,如果还出现
延迟双删,防止缓存失效时(读写分离架构下,读从库延迟问题),存入旧数据,第二次删除可以异步执行等待删除
- ThreadLocal的原理
- ThreadLocal是线程局部变量,它会给每个线程开辟独立的空间,存储是存到ThreadLocalMap里的,这个Map是K,V形式存储的<Thread, Object>,K是Thread,V是Object,它能让各个线程绑定自己的值,可以把它当作一个盒子,这个盒子里存储每个线程的私有数据,一般用get()方法获取当前线程的默认值,set()方法修改当前线程所存储的数据,它可以避免线程不安全和线程之间竞争的问题
- 扩展:你知道ThreadLocal内存泄漏的问题吗?知道的话可以说说
- ThreadLocalMap的key是弱引用,而value是强引用,当JVM垃圾回收的时候,key会被回收,而value不会,如果不做措施的话,value永远不会被回收,也就出现了key为null的map,这时候就会出现内存泄漏,而ThreadLocalMap也考虑到了这种情况,在调用get()、set()、remove()这些操作的时候,会把key为null的清理掉
- 场景题:在生产者消费者问题中怎么保证当消费者用完产品后生产者会马上得知,不能使用while循环去解决
- 生产者-消费者问题是进程同步问题,是一个对产品生产和消费的过程,对于生产者,如果缓冲区没满,它就会生产产品并将其放入缓冲区,如果缓冲区已满,它就会进行等待;对于消费者,如果缓冲区不空,它就会取走产品并释放缓冲区,如果缓冲区已空,它就会进行等待。
- 不用while循环,可以用管程去解决,可以用if去判断缓冲区是否满了,如果满了就休眠等待,再唤醒等待的消费者,消费者消费完了缓冲区就空了,如果空了就再去唤醒生产者
- 线程池处理任务的流程是怎样的
- 向线程池提交任务,判断是否大于核心线程数,不大于则直接创建线程执行,大于则判断缓冲队列是否已经满了,没满则进入队列,然后执行,满了则判断是否大于最大线程数,不大于则创建线程并执行 ,大于则执行拒绝策略。
- wait与sleep的由什么不同
- wait属于Object类,sleep属于Thread类
- wait会释放对象锁,而sleep不会
- wait需要在同步块中使用,sleep可以在任何地方使用
- sleep需要捕获异常、wait不需要
- Synchronized和ReentranLock有什么不同
- 1. 锁的实现,synchronized是JVM实现的,而ReentrantLock是JDK实现的
- 2. 由于新版本Java对synchronizated进行很多优化,所以与ReentrantLock大致相同
- 3. ReentrantLock可以中断,synchronized不行
- 4. synchronized中的锁是非公平的,ReentrantLock默认情况下也是非公平的,但是也可以是公平的
- 5. ReentrantLock可以同时绑定多个Condition对象
- 读写锁适用于什么场景?ReentrantReadWriteLock是如何实现的
- 适用于读多写少的场景
- ReentrantReadWriteLock是Lock的另一种实现方式,我们已经知道了ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。相对于排他锁,提高了并发性。在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,这时ReentrantReadWriteLock能够提供比排他锁更好的并发性和吞吐量。
读写锁内部维护了两个锁,一个用于读操作,一个用于写操作。所有 ReadWriteLock实现都必须保证 writeLock操作的内存同步效果也要保持与相关 readLock的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。 ReentrantReadWriteLock支持以下功能: 1)支持公平和非公平的获取锁的方式; 2)支持可重入。读线程在获取了读锁后还可以获取读锁;写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁; 3)还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不允许的; 4)读取锁和写入锁都支持锁获取期间的中断; 5)Condition支持。仅写入锁提供了一个 Conditon 实现;读取锁不支持 Conditon ,readLock().newCondition() 会抛出 UnsupportedOperationException。
容器
List
ArrayList
数组实现的,线程不安全,适合查询,支持动态数组
1、ArrayList每次扩容是原来的1.5倍
2、数组进行扩容时,会将老数组的元素重新拷贝一份到新数组中,每次数组容量增长大约是原容量的1.5倍
3、扩容代价很高,最好尽可能创建数组时指定它的容量,应尽量避免数组扩容的发生
扩展:为什么扩容因子是1.5?
- 因为源码里写的:当前ArrayList大小+当前ArrayList大小右移一位,大概就是1.5的样子
LinkedList
- 链表实现的,适合插入或删除
Vector
- 数组实现的,用来做线程同步
CopyOnWriteArrayList
- 线程安全的ArrayList,有两个机制,一个是动态数组,一个是线程安全,动态数组是copy数据到新的数组,线程安全是ReentrantLock volatile
Set
HashSet
- 基于HashMap
TreeSet
LinkedHashSet
ConcurrentSkipListSet
CopyOnWriteArraySet
Map
HashMap
1.7是数组+链表
1.8是数组+链表+红黑树
数组中的元素为一个链表,通过计算存入对象的hashcode,确认存入位置,用链表解决散列冲突,链表的节点存入的是键值对
填充因子的作用
Map扩容的rehash机制
容量是二的幂次方
- 是为了方便按位与操作计算余数,比求模更快
多线程风险的原因
- 对线程put时,会在超过填充因子的情况下rehash.HashMap为避免尾部遍历,链表插入采用头插法,多线程场景下可能产生死循环
TreeMap
HashTable
LinkedHashMap
ConcurrentHashMap
- 1.7是segment+HashEntry+Unsafe
- 1.8是Synchronized+cas+node+Unsafe 采用CAS自旋锁(一种乐观锁实现模式)提高性能.但在并发度较高时,性能一般
引入红黑树解决hash冲突时的链表查找问题.在链表长度大于8且总容量大于64时启用.扩容后链表长度小于6时重新转为一般链表.(8,6,64为默认参数)
线上问题查看和处理
jinfo: jvm参数信息工具示例: jinfo -flags pid
jstat:查看虚拟机各种运算状态示例: jstat -gcutil pid
- SO:新生代中Survivor space 0区已使用空间的百分比
- S1:新生代中Survivor space 1区已使用空间的百分比
- E:新生代已使用空间的百分比
- O:老年代已使用空间的百分比
- M:元数据区已使用空间的百分比
- CCS:压缩类空间利用率百分比
- YGC: 从应用程序启动到当前,发生Yang GC的次数
- YGCT:从应用程序启动到当前,Yang GC所用的时间【单位秒】
- FGC:从应用程序启动到当前,发生Full GC的次数
- FGCT:从应用程序启动到当前,Full GC所用的时间
- GCT: 从应用程序启动到当前,用于垃圾回收的总时间【单位秒】
jstack:线程快照工具示例:jstack -l pid
jmap: HeapDump工具
jmap -heap pid查看堆信息
jmap -dump:format=b,file=heapDump.hprof pid导出堆文件并用jhat查看
jhat -port 8899 heapDump.hprof
- 浏览器访问:http://ip:8899
线上OOM问题排查
java -Xms48m -Xmx48m -XX:+HeapDumpOnOutOfMemoryError XX:HeapDumpPath=./heapdump.hprof -jar mianshi.jar
- 使用jprofiler查看dump文件及call tree分析
框架
Spring
IOC/DI
- 对象的属性由自己创建,为正向流程,而由Spring创建,为控制反转
- DI(依赖注入)为实现IOC的一种方式,通过配置文件或注解包含的依赖关系创建与注入对象
- 正向流程导致了对象与对象之间的高耦合,IOC可以解决对象耦合的问题,有利于功能的复用
Context&Beans
- 所有由Spring创建,管理,用于依赖注入的对象,称为Bean
- 所有Bean创建并完成依赖注入后,都会放入Context上下文中进行管理
AOP(Aspect Oriented Programming 面向切面编程)
- 以功能进行划分,对服务顺序执行流程中的位置进行横切,完成各服务共同需要实现的功能
Core
- Spring组件的核心
Spring Context初始化流程
prepareRefresh();
对刷新进行准备,包括设置开始时间,设置激活状态,初始化Context中的占位符,子类根据其需求执行具体准备工作,而后再由父类验证必要参数
obtianFreshBeanFactory();
,刷新并获取内部的BeanFactory对象
prepareBeanFactory(beanFactory);
,对BeanFactory进行准备工作,包括设置类加载器和后置处理器,配置不能自动装配的类型,注册默认的环境Bean
postProcessBeanFactory(beanFactory);
为Context的子类提供后置处理BeanFactory的扩展能力,如想在bean定义加载完成后,开始初始化上下文之前进行逻辑操作,可重写这个方法
invokeBeanFactoryPostProcessors(beanFactory);
,执行Context中注册的BeanFactory后置处理器,有两张处理器,一种是可以注册Bean的后置处理器,一种的针对BeanFactory的后置处理器,执行顺序是先按优先级执行注册Bean的后置处理器,而后再按优先级执行针对BeanFactory的后置处理器
SpringBoot中会进行注解Bean的解析,由ConfigurationClassPostProcessor触发,由ClassPathDefinitionScanner解析,并注册到BeanFactory
registerBeanFactoryProcessor(beanFactory();
,按优先级顺序在BeanFactory中注册Bean的后置处理器,Bean处理器可在Bean的初始化前后处理
initMessageSource();
初始化消息源,消息源用于支持消息的国际化
initApplicationEventMuticaster();
初始化应用事件广播器,用于向ApplicationListener通知各种应用产生的事件,标准的观察者模型
onRefresh();
,用于子类的扩展步骤,用于特定的Context子类初始化其他的Bean
registerListeners();
,把实现了ApplicationListener的类注册到广播器,并对广播其中早期没有广播的事件进行通知
finishBeanFactoryInitialization(beanFactory);
,冻结所有Bean描述信息的修改,实例化非延迟加载的单例Bean
finishRefresh();
,完成上下文的刷新工作,调用LifecycleProcessor.onRefresh(),以及发布ContextRefreshedEvent事件
resetCommonCaches();
在finally中执行该步骤,重置公共的缓存,如ReflectionUtils中的缓存,AnnotationUtils等
Spring中Bean的生命周期
事务
隔离级别
- ISOLATION_DEFAULT
- ISOLATION_READ_UNCOMMITTED
- ISOLATION_READ_COMMITTED
- ISOLATION_REPEATABLE_READ
- ISOLATION_SERIALIZABLE
传播行为
- PROPAGATION_REQUIRED
- PROPAGATION_SUPPORTS
- PROPAGATION_MANDATORY
- PROPAGATION_REQUIRED_NEW
- PROPAGATION_NOT_SUPPORTED
- PROPAGATION_NEVER
- PROPAGATION_NESTED
核心接口/类
- ApplicationContext
- BeanFactory
- BeanWrapper
- FactoryBean
scope
- Singleton
- Prototype
- Request
- Session
- Global-session
- Application
- Websocket
事件机制
- ContextRefreshedEvent
- ContextStatedEvent
- ContextStoppedEvent
- ContextClosedEvent
- RequestHandledEvent
面试题
说说你对Spring的理解,它的特性和优势是什么
- Spring是轻量的,基本版本大约2MB,其特性就是控制反转和依赖注入了,优势就是可以很方便的对数据库进行访问,可以很方便的集成第三方组件(比如:电子邮件、任务、调度、缓存这些),对单元测试支持也很好,支持restful风格的开发
Spring有哪些组件(模块)
- 核心组件的话有三个,没有它们就没有AOP、web这些特性
Spring-Core:Spring的核心组件,包含Spring的IOC和DI
Spring-Beans:bean包,核心是beanFactory,实现了对bean的动态注入
Spring-Context:维护Bean的上下文,相当于Spring的IOC容器
其它组件就比如:
Spring-context-support:提供第三方库集成到Spring-Context,比如电子邮件、任务、调度、缓存这些
Spring-aop:基于动态代理的切面编程,进行切面管理用的
- 场景题:如果实现一个你自己的Spring框架需要考虑那些方面,简单聊聊
- 要轻量,对代码无侵入性,支持restful风格开发,同样支持IOC和AOP以及对持久化层的访问,提供对外扩展的接口
- IOC 和 AOP 具体介绍一下
- IOC就是控制反转,它是一种设计思想,而DI依赖注入就是实现IOC的一种方法,个人理解就是获取对象的方式反转了,就比如对象的创建交给了第三方,而不是个人去创建
- AOP面向切面,Spring的AOP是基于动态代理的,能为业务无关但是把业务模块共同调用的逻辑封装起来,比如事务处理、日志处理、权限控制这些,利用AOP可以减少重复代码,降低模块间的耦合,有利于维护
- IOC 配置的三种方式
- 1、第一种,加载配置文件+在配置文件的applicationContext中配置bean的标签
- 2、加载配置文件+在applicationContext.xml中开启注解扫描+注解
- 3、IOC纯注解方式(编写配置类)+加载配置类
- 依赖注入的三种方式
- 1、构造器注入
- 为什么推荐构造器注入方式
- 构造器注入有无参构造器注入和有参构造器注入,更推荐构造器注入方式是因为它能在xml配置文件加载的时候,它里面管理的对象就都已经初始化了
- 2、setter方法注入
- 3、命名空间注入
- 说说Spring的循环依赖,如果出现循环依赖该怎么解决?
- 循环依赖就是A依赖于B,B也依赖于A或者自己依赖自己,Spring通过三级缓存来解决循环依赖,当A,B两个类出现循环依赖时,在A完成实例化后,用实例化对象去创建一个对象工厂并添加到三级缓存里,然后调用对象工厂的getObject()方法获取到相应的对象,得到这个对象注入到B中,B会走完它对象的生命周期,当B创建完后,再把B注入到A中,再让A走完自己对象的生命周期,这样,循环依赖就解决了
- 构造器循环依赖和setter循环依赖两部分解答,构造器通过使用创建Bean中的标识池判断是否产生了循环创建,setter通过引入ObjectFactory解决
- Spring中Bean的生命周期介绍一下
- bean的生命周期有:bean定义->bean注册->实例化->依赖注入->初始化->销毁
- bean定义:就是xml或者注解定位资源加载读取bean的元数据并定义一个BeanDefinition对象
- bean注册:就是将BeanDefinition对象根据对于规则放到缓存池的map中
- 实例化:将BeanDefinition实例化成真正的bean,就是调用构造函数去实例化
- 依赖注入:也就是属性赋值,去调用setter()方法
- 初始化:初始化对象,调initializeBean()这个方法
- 销毁:在容器关闭时调用,将对象销毁掉
- Bean构造方法,@PostConstuct注解,InitiatingBean,init-method的执行顺序
SpringMVC
面试题
说说SpringMVC的一个好处和作用
- SpingMVC是controller层的一个组件,我们使用它可以根据MVC三层架构进行开发,并可以对日常开发进行解耦,还可以和Spring无缝衔接
Spring MVC底层实现原理
- 当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。
说说SpringMVC请求一次的全过程
- 1.首先,用户发起请求
- 2.然后,中央控制器dispatcherServlet开始处理
- 3.dispatcherServlet调用处理器映射器handlerMapping
- 4.handlerMapping找到对应处理器,并返回对应的注解名handler给中央控制器
- 5.dispatcherServlet将handler给handlerAdapter处理器适配器
- 6.handlerAdapter调用handler处理器(controller)
- 7.controller调用业务层
- 8.业务层调用dao层
- 9.dao层调用jdbc或Mybatis对数据库操作返回给业务层
- 10.controller得到业务层返回的数据,返回modelandview
- 11.dispatcherServlet调用视图解析器ViewResolve解析modelandview
- 12.ViewResolve返回view
- 13.dispatcherServlet将view给jsp进行渲染呈现给用户
工作流程
- 1、 用户发送请求至前端控制器DispatcherServlet
- 2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器
- 3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet
- 4、 DispatcherServlet调用HandlerAdapter处理器适配器
- 5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器
- 6、 Controller执行完成返回ModelAndView
- 7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
- 8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器
- 9、 ViewReslover解析后返回具体View
- 10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中
- 11、 DispatcherServlet响应用户
SpringBoot
注解
类型类
- @Controller
- @service
- @Repository
- @Component
- @Configuration
- @Bean
设置类
- @Required
- @Autowired && @Qualifier
- @Scope
Web类
- @RequestMapping && @GetMapping @ PostMapping
- @PathVariable && @RequestParam
- @RequestBody && @ResponseBody
功能类
- @ImportResource
- @ComponentScan
- @EnableCaching && Cacheable
- @Transactional
- @Aspect && Poincut
- @Scheduled
面试题
自定义一个你自己的starter怎么实现(开共享屏幕实现)
- 自定义starter参考网站:https://blog.csdn.net/Simle_Hi/article/details/120869602
SpringSecurity
SpringWebFlux
SpringData
MyBatis(Plus)
Mybatis处理流程
面试题
简述MyBatis的层次结构
- sqlSession -> Excutor -> statementHandler -> parameterHandeler | ResultSetHadler -> TypeHandler
sqlSessionFactory 与 SqlSession介绍一下
- sqlSessionFactory,它就像数据库连接池一样,主要功能是创建SqlSession对象,这个对象是全局的,也就是避免了每次访问mybatis都要创建对象
- SqlSession,它是连接到连接池的一个请求,表示和数据库交互的会话,完成必要的增删改查功能
Excutor的概念
- Excutor是mybatis的执行器,它是mybatis调度的核心,负责SQL语句的生成和查询缓存的维护
动态SQL有哪些,都有什么用途
- 动态SQL就是根据不同的条件生成不同的SQL,有if、choose(when、otherwise)、trim(where、set)、foreach
- if:能够实现模糊查询,根据某个字段就能查询整条的记录
- choose(when、otherwise):有时候我们不想使用所有条件,而是想从多个条件中选一个使用,就可以用choose,它相当于Java中的switch
- trim(where、set):trim和set等价,可以通过自定义trim来定制where的功能
- foreach:对集合遍历的使用用这个
- foreach怎么批量插入数据?
- 在insert标签里写入插入语句,最后的value用foreach遍历要插入的字段就行
${}和 #{} 的区别
- ${}是Properties文件的变量占位符,可以替换标签的属性值和sql语句,就比如${driver}可以被替换为
- #{}是sql的参数占位符,sql语句中#{}表示一个占位符,即?,它会将传入的数据当成一个字符串
Netty
Netty线程模型
- 其中ChannelPiepline的设计模型采用的是Handler组成的责任链模型
面试题
Netty中有哪些重要的对象,它们之间的关系是什么
- Channel,Socket,NioEventLoop,ChannelPipeline等
Java虚拟机
线程独占
栈
- 又称方法栈,线程私有的,线程执行方法是都会创建一个栈帧,用来存储局部变量表(这里面有八大引用类型、对象的引用),操作栈,动态链接,方法出口等信息.调用方法时执行入栈,方法返回式执行出栈.
本地方法栈
与栈类似,也是用来保存执行方法的信息.执行Java方法是使用栈,执行Native方法时使用本地方法栈.
- Hospot虚拟机直接把本地方法栈和Java虚拟机栈合二为一
程序计数器
- 保存着当前线程执行的字节码位置,每个线程工作时都有独立的计数器,只为执行Java方法服务,执行Native方法时,程序计数器为空.
常用参数
- Xms:设置初始堆大小
- Xmx:设置最大堆大小
- Xmn:设置年轻代大小
- Xss:设置每个线程的堆栈大小
JMM与内存可见性
- Java内存模型,定义程序中变量的访问规则。
- 在多线程进行数据交互时,例如线程A给一个共享变量赋值后由线程
B
来读取这个值,线程A修改变量只修改在自己的工作内存区中,线程B
是不可见的,只有从A
的工作内存区写回到工作主内存,B
在从主内存读取到自己的工作内存区才能进行进一步的操作。 - 由于指令重排序的存在,写和读的顺序可能会被打乱,因此
JMM
需要提供原子性、可见性、有序性的保证。
GC
常用GC算法
分代回收
- 分代管理主要是为了方便垃圾回收,这样做是基于两个事实:
- 大部分对象很快都不在使用
- 还有一部分不会立即无用,但也不会持续很长时间
大部分对象在Eden区中生成,Eden区满时,还存活的对象会在两个Suivivor区交替保存,达到一定次数后对象会晋升为老年代。
- 垃圾回收器
- CMS
- 1.7前主流垃圾回收算法,为标记-清除算法,优点是并发收集,停顿小
- `初始标记`会StopTheWorld,标记的对象是root即最直接可达的对象.
并发标记
GC线程和应用线程并发执行,标记可达的对象. 重新标记
第二个StopTheWorld,停顿时间比并发标记
小很多,但比初始标记
稍长.主要对对象重新扫描并标记. 并发清理
进行并发的垃圾清理. 并发重置
为下一次GC重置相关数据结构.
- G1
- 1.9后默认的垃圾回收算法,特点保持高回收率的同时减少停顿.采用每次只清理一部分,而不是清理全部的增量式清理,以保证停顿时间不会过长
- 其取消了年轻代与老年代的物理划分,但仍属于分代收集器,算法将堆分为若干个逻辑区域(region),一部分用作年轻代,一部分用作老年代,还有用来存储巨型对象的分区.
- 同CMS相同,会遍历所有对象,标记引用情况,清除对象后会对区域进行复制移动,以整合碎片空间.
- 年轻代回收
- `并行复制`采用复制算法,并行收集,会StopTheWorld
- 老年代回收
- 会对年轻代一并回收
初始标记
完成堆root对象的标记,会StopTheWorld. 并发标记
GC线程和应用线程并发执行. 最终标记
完成三色标记周期,会StopTheWorld. 复制/清楚
会优先对可回收空间加大的区域进行回收
- ZGC
- 11中提供的高效垃圾回收算法,针对大堆内存设计,可以处理TB级别的堆,可以做到10ms以下的回收停顿时间
- 着色指针
- 读屏障
- 并发处理
- 基于region
- 内存压缩(整理)
- `roots标记`标记root对象,会StopTheWorld.
并发标记
利用读屏障与应用线程一起运行标记,可能会发生StopTheWorld. 清除
会清理标记为不可用的对象. roots重定位
是对存活的对象进行移动,以腾出大块内存空间,减少碎片产生.重定位最开始会StopTheWorld,却决于重定位集与对象总活动集的比例. 并发重定位
与并发标记
类似.
- 垃圾回收策略
- 引用计数法
- 引用计数法会给每个对象添加一个引用计数器,每当有一个地方引用它的时候,计数器值就+1,引用失效时,计数器值就-1,计数器值为0就表示这个对象是垃圾
- 可达性分析算法
- 当一个程序启动后,需要马上找到根对象,它会从根上开始向下搜索来找到根对象,也就是要找到GC Root,这个搜索的路径叫引用链,当一个对象到GC Root没有任何引用链的话,那么这个对象就是不可用的
- 扩展:哪些对象可以作为GC Root呢?
- 1、虚拟机栈(里面栈帧的本地变量表)中引用的对象
- 2、本地方法栈中引用的对象
- 3、方法区中类静态属性和常量引用的对象
- 4、所有被同步锁持有的对象
执行模式
- 解释模式
- 编译模式
- 混合模式
编译器优化
- 公共子表达式的消除
- 指令重排
- 内联
- 逃逸分析
- 方法逃逸
- 线程逃逸
- 栈上分配
- 同步消除
Java类加载机制详解
- 加载:是文件到内存的过程,通过类的完全限定名查找此类字节码文件,并利用字节码文件创建一个Class对象;
- 验证:验证是堆文件类内容验证,目的在于当前类文件是否符合虚拟机的要求,不会危害到虚拟机安全,主要包括四种:文件格式验证、元数据验证、字节码、符号引用;
- 准备:准备阶段是进行内存分配,为类变量,也就是类中由static修饰的变量分配内存并设置初始值,初始值是0或null,而不是代码中设置的具体值,代码中设置的值在初
- 解析:解析主要是解析字段、接口、方法,主要是将常量值中的符号引用替换为直接引用的过程,直接引用就是直接指向目标的指针或相对偏移量等;
- 初始化:最后是初始化,主要是完成静态块执行与静态变量的赋值,这是类加载最后阶段,若被加载类的父类没有初始化,则先对父类进行初始化。
- 只有对类使用是才会初始化,初始化的条件包括访问类的实例,访问类的静态方法和静态变量的时候,使用Class.forName()反射类的时候,或者某个子类被初始化的时候。
类加载器
BootStrap ClassLoader
:启动类加载器加载JAVA_HOME/lib
下的类ExtClassLoader
:扩展加载器加载JAVA_HOME/lib/ext
下的类AppClassLoader
:应用加载器加载加载classpath
指定目录下的类自定义类加载器
双亲委派模型
- 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。
- 每一个层次的类加载器都是如此。因此,所有的加载请求最终都应该传送到顶层的启动类加载器中。
- 只有当父加载器反馈自己无法完成这个加载请求时(搜索范围中没有找到所需的类),子加载器才会尝试自己去加载。
- 采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子
ClassLoader
再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer
的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
线程共享
堆
- JVM内存管理最大的一块,对被线程共享,目的是存放对象的实例,几乎所欲的对象实例都会放在这里,当堆没有可用空间时,会抛出OOM异常.根据对象的存活周期不同,JVM把对象进行分代管理,由垃圾回收器进行垃圾的回收管理
方法区
- 又称非堆区,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器优化后的代码等数据.1.7的永久代和1.8的元空间都是方法区的一种实现
面试题
什么情况下会触发FullGC
- Minor GC:回收新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快。 Full GC:回收老年代和新生代,老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多。
- 老年代空间不足
双亲委派机制的加载流程是怎样的,有什么好处?
- 一个类加载器首先将类加载请求传到父类加载器,只有当父类加载器无法完成类加载请求时才尝试加载
1.8为什么用Metaspace替换掉PermGen?Metasapce保存在哪里?
- PermGen 内存空间将全部移除。JVM的参数:PermSize 和 MaxPermSize 会被忽略并给出警告(如果在启用时设置了这两个参数)。
- 元空间(Metaspace): 一种新的内存空间的诞生。JDK8 HotSpot JVM 使用本地内存来存储类元数据信息并称之为:元空间(Metaspace);这将是一个好消息:意味着不会再有java.lang.OutOfMemoryError: PermGen问题,也不再需要你进行调优及监控内存空间的使用,但是新特性不能消除类和类加载器导致的内存泄漏。你需要使用不同的方法以及遵守新的命名约定来追踪这些问题。
- Metaspace 内存分配模型 (最大区别)大部分类元数据都在本地内存中分配。 用于描述类元数据的“klasses”已经被移除。
- 参数设置 默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性: -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
更新原因 1、字符串存在永久代中,容易出现性能问题和内存溢出。 2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。 3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。 4、Oracle 可能会将HotSpot 与 JRockit 合二为一。
编译期会对指令做哪些优化?(简单描述编译器的指令重排)
- 指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序
简单描述一下volatile可以解决什么问题?如何做到的?
- 强制主内存读写同步,防止指令重排序
G1垃圾回收算法与CMS的区别有哪些
- G1(Garbage-First),它是一款面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。 G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。 每个 Region 都有一个 Remembered Set,用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set,在做可达性分析的时候就可以避免全堆扫描。 如果不计算维护 Remembered Set 的操作,G1 收集器的运作大致可划分为以下几个步骤:
初始标记
并发标记
最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。
筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。 具备如下特点:
空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。
- CMS(Concurrent Mark Sweep),Mark Sweep 指的是标记 - 清除算法。 分为以下四个流程:
初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。
并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。
重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。
并发清除:不需要停顿。 在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。 具有以下缺点:
吞吐量低:低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率不够高。
无法处理浮动垃圾,可能出现 Concurrent Mode Failure。浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾,这部分垃圾只能到下一次 GC 时才能进行回收。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
标记 - 清除算法导致的空间碎片,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前触发一次 Full GC。
对象引用有哪几种方式,有什么特点
- 强引用被强引用关联的对象不会被回收。 使用 new 一个新对象的方式来创建强引用。
- 软引用被软引用关联的对象只有在内存不够的情况下才会被回收。 使用 SoftReference 类来创建软引用。
- 弱引用被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。 使用 WeakReference 类来实现弱引用。
- 虚引用又称为幽灵引用或者幻影引用,一个对象是否有虚引用的存在,不会对其生存时间造成影响,也无法通过虚引用得到一个对象。 为一个对象设置虚引用的唯一目的是能在这个对象被回收时收到一个系统通知。 使用 PhantomReference 来实现虚引用。
使用过哪些JVM调试工具,主要分析哪些内容
- JMC(JVM浏览器,JFR-Java Flight Recoeder,JMX-Java Management Extensions):内存信息,代码信息,线程信息,I/O信息,系统信息
- btrace-线上调试神器:方法调试信息,定时执行,对象创建信息,类加载信息,内存统计,死锁检测,同步快执行,线程栈信息,异常信息,文件/网络I/O
- jps:查看Java进程信息
- jmap:查看JVM中对象的统计信息
- jstat:对JVM的资源和性能进行实时监控
- jinfo:动态查看、调整JVM参数
- jcmd:1.7提供的综合工具
- 还有jconsole,jProfiler,jvisualVM为监控与性能分析工具,建议使用JMC替代
如何判断一个类是无用的类
- 1、这个类的实例都已经被回收了,Java的堆中不存在这个类的任何实例
- 2、加载这个类的classloader也已经被回收了
- 3、这个类对应的java.lang.Class对象没有任何地方被引用,也无法在任何地方通过反射访问这个类的方法
G1回收器与之前的回收器相比最大的不同是什么
- 比之前的回收器更加全能,能极高满足GC停顿时间要求的同时,还具备吞吐量性能的特征,整体上看还是基于标记-整理算法实现,局部上看基于标记-复制算法实现,其回收的过程是:初始化标记-并发标记-最终标记-清理标记
类加载的过程是什么,每个过程具体做到了什么事
- JVM把类的数据加载到内存里,并对类的数据进行校验,初始化,最好变成能被虚拟机直接使用的class对象,其加载过程是:加载-验证-准备-解析-初始化-使用-卸载;这个过程也叫它的生命周期
- 加载过程有三步:
1、通过类的全限定性类名获取该类的二进制流
2、将该二进制流的静态存储结构转为方法区的运行时数据结构
3、在堆中为该类生产一个class对象
验证:验证该class文件的字节流信息是否符合虚拟机的标准,不会威胁到JVM虚拟机的安全
准备:为class对象的静态变量分配内存,并初始化这个对象的初始值
解析:这个阶段会把符号引用转成直接引用
初始化:这是调用类加载器的过程,在初始化过程时才会执行类中定义的Java代码
使用:也就是这个类终于被开始使用了
卸载:卸载会把这个类的class对象GC回收
JVM调优常用的手段是什么
- 1、调整最大堆的和最小堆的内存
- 2、设置新生代和老年代的大小
- 3、如果出问题了可以用jstack或者jmap查看堆栈信息
数据库
MySQL
特点
- 互联网行业中最流行的数据库
常被查询的区分度高的列做索引
最左原则
回盘排序
覆盖索引
小表驱动大表
索引
可大幅增加数据库的查询性能,适合读多写少的场景 代价:需要额外空间保存索引,插入更新删除时,由于更新索引增加额外的开销
类型
唯一索引
- 索引列中的值唯一,允许出现空值
主键索引
- 特殊的唯一索引不允许出现空值
普通索引
- 索引列中的值不唯一
联合索引
- 多个列按顺序组成索引,相同列不同顺序为不同索引
全文索引
- 只能在char varchar text等类型使用
实现
B-Tree
- 最常用
R-Tree
- 用于处理多维数据的数据结构,可对地理数据进行空间索引
Hash
- 效率比B-Tree高,不支持范围查找,排序等功能
FullText
- 适用于全文索引
读快照和当前读
- 当前读:select xx for update
- 快照读:没有加for update或者没有lock mode
当前读的锁机制
行锁
- 命中主键索引,锁定对应的行
- 命中普通索引,锁定对应的行
表锁
- 无索引字段,锁表
间隙锁
- 通常发生在普通索引上
调优
表结构与索引
- 分库分表,读写分离
- 为字段选择合适的数据类型
- 将字段多的表分拣成多个表,增加中间表
- 混合范式与反范式,适当冗余
- 为查询创建必要索引,但避免滥用
- 字段尽可能设置为NOT NULL
SQL语句优化
寻找最需要优化的语句:分析慢查询日志
- 使用频繁或效率最低的
利用查询工具:explain,profile
避免使用SELECT *, 只取需要的列
尽可能使用prepared statements
使用索引扫描来排序
MySQL参数优化
硬件及系统配置
面试题
说说你对数据库的理解,有多少说多少
- 数据库是一种数据存储技术,我们主要用它来存数据,它在操作系统层面也是一个进程,里面的各个操作都是进程里的线程,MySQL的基本架构在server层有连接器、查询缓存、分析器、优化器、执行器这些,还有个存储引擎,我们一般用MySQL都会选择一个存储引擎来建库建表,根据需求来设计字段,存储引擎我们最常用的就是InnoDB了,除此之外还有MyISAM和Bob等等,MySQL还有日志和索引,它的日志我们日常使用到的有binlog、redolog和unlog,索引的话有普通索引、主键索引、覆盖索引、联合索引等等,MySQL还有事务,事务是可以保证对数据的操作不会不一致以及遇到报错的数据可以回滚,它有四种特性:原子性、一致性、隔离性和持久性,事务还有四个隔离级别:读未提交、读已提交、可重复读和串行化,除了这些之外,MySQL也可以用来做集群存储更多的数据以及更能适合并发场景下的使用,比如主库写,从库读,做读写分离,分库分表这种,这就是我对MySQL的一个理解
数据库设计通常分为哪几步
- 需求分析
- 根据数据库范式和需求设计字段
- 建库
- 建表
- 建索引
- 维护数据库
说说数据库的增加和修改语句,怎么用命令进行增加和修改?
- 增加:insert into(字段) values (值)
- 修改:update tables set col = xxx
MyISAM和InnoDB的区别
- MyISAM只支持表锁,InnoDB支持行锁和表锁默认行锁
- MyISAM不支持MVC,InnoDB支持
- MyISAM不提供事务支持,InnoDB提供事务支持,默认四个隔离级别(ACID)
- MyISAM不支持外键,InnoDB支持
- MyISAM不支持数据库异常崩溃后的恢复,InnoDB支持
- 索引实现也不一样,MyISAM索引数据和文件是分离的,InnoDB数据文件本身就是索引文件
MySQL日志都有什么,分别有什么作用
- 最常见的就是binlog、redolog、undolog
- binlog记录的是数据库的写入操作,不包括查询信息,以二进制的形式存储在磁盘中,作用就是如果数据库表中数据被删了,可以使用binlog将其恢复,使用binlog要事先在配置文件中开启,binlog还可以用于主从复制,主端开启binlog,然后将binlog发送到从端,达到数据同步的目的
- redolog重做日志,它可以保证事务的持久性以及可以防止发生故障时,有脏页没有写入磁盘
- undolog回滚日志,主要用于回滚操作,undolog实现了事务的原子性和mvcc的读,比如insert一条语句,undolog就会生成一条对应的delete语句
读锁和写锁实现的底层原理
- 所谓读锁也叫共享锁,能够对同一份数据,多个读操作可以同时进行而不会互相影响,加锁方式就是select ... lock in share mode
- 所谓写锁也叫排它锁,当前写操作没有完成之前,它会阻断其它写锁和读锁,加锁方式是select... for update
表级锁和行级锁的实现原理
- 表级锁会锁住整张表,开销少,所以它发生锁冲突的概率最大,一般用于查询为主
- 行级锁只允许事务读一行数据,开销大,加锁慢,行锁在存储引擎层实现
事务的特性
- 原子性、一致性、隔离性、持久性
- 原子性:要么全部执行,要么全部不执行,实现原理就是靠的undolog
- 一致性:事务执行结束后,数据库的完整性约束不会被破坏,事务执行前后状态保持一致,像原子性、持久性和隔离性都是为了保证一致性,保证了一致性的话,假如转账,转账双方的余额得到保证
- 隔离性:事物内部的操作与其它事务是隔离的,也就是各个事务之间不能互相干扰,隔离性有四个隔离级别:读未提交、读已提交、可重复读和串行化,隔离性是通过加锁和MVCC来保证的
- 持久性:持久性就是一旦事务提交后,它对数据库的改变就是永久性的,即使数据库发生故障也不会出现变化,它的实现原理就是redolog
并发事务带来的问题有哪些,能说说看吗
- 死锁、脏读、不可重复读、幻读、丢失更新
- 死锁:就是两个或两个以上的事务都在互相等待对方释放锁,或者因为加锁顺序不一致导致的循环等待锁资源时,就会出现死锁;怎么解决?等待事务超时自动回滚,进行死锁检测,杀死死锁进程
- 脏读:就是不同事物下,当前事务可以读到另外事务还没有提交的数据,比如A事务修改数据,B事务随后读取这个数据,如果A回滚撤销这次数据的修改,B再读读到了A修改了的数据,那么B读到的就是脏数据
- 不可重复读:就是同一个事物多次读到同一个数据时,读到的数据不一样,比如B事务读到一个数据,A事务修改了数据,如果B再读到这个数据就和第一次读到的数据不一致;怎么解决?InnoDB中,select不可重复读问题通过MVCC解决,update/delete通过记录锁解决,insert通过临键锁解决
- 幻读:幻读是一种特殊的不可重复读问题,在同一事务中,运行两次同样的SQL可能返回不同的结果,第二次SQL可能会返回之前不存在的数据,同样的,幻读也可以通过临键锁next-key解决
- 丢失更新,就是一个事务的更新操作会被另一个事务的更新操作覆盖,比如A,B两个事务都对一个数据进行修改,A先修改,B后修改,B的修改覆盖了A的修改;怎么解决?通过select加排它锁解决
事务隔离级别有哪些
- 读未提交、读已提交、可重复读、串行化
- 读未提交,限制了两个事务不能同时修改,即使事务没有提交数据也可能被其它事务读到,它的级别最低,容易出现幻读、脏读、重复读问题
- 读已提交,当前事务只能读到其它事务已提交的数据,这个级别解决了脏读问题,但是还是会有幻读问题
- 可重复读,限制了事务读数据时不能进行修改,解决了不可重复读问题,但是还是可能存在幻读问题,它是MySQL默认的隔离级别
- 串行化,所有事务按顺序执行,可以避免脏读、不可重复读、幻读等问题,就是事务执行很耗费性能
场景题:MySQL对于千万级的数据库或者大表怎么处理
- 分库分表、读写分离、建集群
MVCC解决什么问题
- MVCC,多版本并发控制,特点是读不加锁,因此读写不冲突,主要应用在读已提交和可重复读这两个隔离级别;主要解决了并发读写时,可以做到读操作不阻塞写操作,写操作也不阻塞读操作,以及可以解决脏读、幻读、不可重复读等问题
索引用命令怎么建?怎么用命令查看索引?
创建索引:
- CREATE <索引名> ON <表名> (<列名> [<长度>] [ ASC | DESC])
命令查询索引:
- SHOW INDEX FROM <表名> [ FROM <数据库名>]
索引有什么作用
- 可以提高数据库查询的效率,降低数据库的IO成本,索引还可以对数据进行排序,降低CPU的消耗
索引的底层数据结构
- 有Hash索引、B- 索引、B+树索引、B*树索引、R-树索引
为什么InnoDB使用B+树作为索引的数据结构
- 简单说一下,就是因为B+树的单次请求涉及的磁盘IO少,查询效率比较稳定以及遍历效率高
为什么会出现回表查询,怎么避免
- 先说下什么是回表查询,就是如果查询的字段是主键索引也就是根据主键去查就不需要回表,但是如果你查的字段不是主键索引是普通索引的话,那么就需要回表,回表的话它会先查一次这个普通索引,根据普通索引查到主键索引的值,然后再通过主键索引去查,底层进行了两次索引查询,这个过程就是回表
- 避免的话,尽量就是你要什么字段就只查那个字段或者使用主键查询,不要全查就行了,能使用覆盖索引就使用覆盖索引,所谓覆盖索引就是你要查的索引就只查那几个索引字段,要查的时候按最左原则,从左到右排好查就行了
解释一下最左(匹配)前缀法则
- 就是如果你创建一个联合索引,那这个索引的任何前缀都会用于查询,比如(col1,clo2,col3)这个联合索引的所有前缀就是(col1)、(col1,col2)、(col1,col2,col3),包含这些列的查询都会用索引查询
- 其它所有不再最左前缀里的列就不会用索引,即使包含了联合索引的部分列也不行,就比如(col2)、(col3)、(col2,col3)就不会用索引去查询
- 注意,(col1,col3)会用(col1)的索引去查询
覆盖索引是什么意思
- 覆盖索引就是,如果一个索引包含(或者说覆盖)所有需要查询字段的值,就叫这个索引为"覆盖索引"。
- 在InnoDB中,如果不是主键索引,B+树的叶子节点存储的是主键+列值,查询其它索引会通过主键再查一次,这就是回表。这样就比较慢,而覆盖索引就是把要查询的字段正好是索引的字段,那么之间根据该索引就可以查询数据了,就不需要回表了
MySQL读写分离方案
- 读写分离就是为了将对数据库的读写操作分散到不同的数据库节点上
- 一般情况下,我们会选择一主多从的形式做读写分离,也就是一台主库负责写,其它从库负责读,主库和从库之间会进行数据同步,不过读写分离会带来数据不一致的问题也就是主从同步延迟
MySQL主从复制是什么意思
主从复制就是再实现读写分离的时候,要保证主库和从库之间的数据是实时同步的,这个过程就是主从复制
一般用binlog来实现主从复制,再主库开启binlog,然后将binlog发送到各个从库中,从库重新执行binlog从而达到主从数据同步的目的,具体过程就是:
- 1、主库将数据库的数据变化写到binlog里
- 2、从库连接主库
- 3、从库会创建一个I/O线程,并向主库请求更新的binlog
- 4、主库会创建一个binlog dump线程来发送binlog,从库的I/O线程负责接收binlog
- 5、从库的I/O线程将接收的binlog写入道relay log中
- 6、从库的SQL线程读取relay log同步数据本地,也就是再执行一遍SQL
MySQL主从的延迟怎么解决
- 1、强制将最新数据的读请求交给主库处理
- 2、完成写请求后,避免立刻进行请求操作
你知道MySQL是怎么进行优化的吗?
- 1、通过慢查询日志找出执行效率慢的SQL,然后通过explain具体分析
- 2、可以通过建联合索引优化,联合索引建好就不需要回表操作
Redis
redis内存分配采用jemalloc,将内存划分为small,large,huge三个范围,并在其中划分了不同大小的内存块,存储数据时选择大小合适的内存块进行存储,有利于减小内存碎片
数据结构
字符串(strings)
类型介绍
- 字符串是Redis最简单的储存类型,它存储的值可以是字符串、整数或者浮点数,对整个字符串或者字符串的其中一部分执行操作;对整数或者浮点数执行自增(increment)或者自减(decrement)操作。
- Redis的字符串是一个由字节组成的序列,跟java里面的ArrayList有点类似,采用预分配冗余空间的方式来减少内存的频繁分配,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。
应用场景
- 字符串类型在工作中使用广泛,主要用于缓存数据,提高查询性能。比如存储登录用户信息、电商中存储商品信息、可以做计数器(想知道什么时候封锁一个IP地址(访问超过几次))等等。
操作指令
- mset key1 value1 key2 value2:添加多条String类型数据
- mget key1 key2:获取多条String类型数据
- incrby key step:按照步长(step)自增
- decrby key step:按照步长(step)递减
- incr key:自增(+1)
- decr key:自减(-1)
散列(hashes)
类型介绍
散列相当于Java中的HashMap,内部是无序字典。实现原理跟HashMap一致。一个哈希表有多个节点,每个节点保存一个键值对。
与Java中的HashMap不同的是,rehash的方式不一样,因为Java的HashMap在字典很大时,rehash是个耗时的操作,需要一次性全部rehash。Redis为了高性能,不能堵塞服务,所以采用了渐进式 rehash策略。
渐进式 rehash 会在rehash的同时,保留新旧两个hash结构,查询时会同时查询两个hash结构,然后在后续的定时任务中以及hash操作指令中,循序渐进地将旧hash的内容一点点迁移到新的hash结构中。当搬迁完成了,就会使用新的hash结构取而代之。
当hash移除了最后一个元素之后,该数据结构自动被删除,内存被回收。
ziplist 和 hashtable 实现
- 当
hash
中的K/V字符串长度小于64B且hash长度小于512时使用ziplist
,超过时使用hashtable
- 当
应用场景
- Hash也可以同于对象存储,比如存储用户信息,与字符串不一样的是,字符串是需要将对象进行序列化(比如json序列化)之后才能保存,而Hash则可以讲用户对象的每个字段单独存储,这样就能节省序列化和反序列的时间。如下: 此外还可以保存用户的购买记录,比如key为用户id,field为商品id ,value为商品数量。同样还可以用于购物车数据的存储,比如key为用户id, field为商品id, value为购买数量等等。
操作指令
- hset userlnfo username zhangsan age 18 address bj
- hget userlnfo username
- hget userlnfo age
- hmget userlnfo username age
- hgetall userlnfo
- hlen userlnfo
- hincrby userlnfo age 2
- hincrby userlnfo age 2
- hincrby userlnfo age 2
- hdel userlnfo age
- del userlnfo
列表(lists)
类型介绍
Redis中的lists相当于Java中的LinkedList,实现原理是一个双向链表(其底层是一个快速列表),即可以支持反向查找和遍历,更方便操作。插入和删除操作非常快,时间复杂度为o(1),但是索引定位很慢,时间复杂度为o(n).
linkedlist 和 ziplist 实现
- ziplist 存储在连续位置上,存储效率高,不利于修改操作,适用于数据较少的情况
- linkedlist 再插入节点上复杂度低,但内存开销大,节点地址不连续,容易产生内存碎片
- 3.2后增加了 quicklist ,其本身是双向无环链表,每个节点是ziplist
应用场景
- lists的应用场景非常多,可以利用它轻松实现热销榜;可以实现工作队列(利用lists的push操作,将任务存在lists中,然后工作线程再用pop操作将任务取出进行执行) ;可以实现最新列表,比如最新评论等。
操作指令
- lpush student zhangsan lisi wangwu
- rpush student tianqi
- lpop student
- rpop student
- lrange student 0 1
集合(sets)
类型介绍
集合类似Java中的HashSet,内部实现是一个value永远为nulI的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。
由 hashtable 和 intset 实现
- 当set中的值都为数值且set长度小于512时使用
intset
,超过则使用hashtable
- 当set中的值都为数值且set长度小于512时使用
应用场景
- redis的sets类型是使用哈希表构造的,因此复杂度是o(1),它支持集合内的增删改查,并且支持多个集合间的交集、并集、差集操作。可以利用这些集合操作,解决程序开发过程当中很多数据集合间的问题。比如计算网站独立ip,用户画像中的用户标签,共同好友等功能
操作指令
sadd nums 1 1 2 2 3 3
- 添加成员,返回添加的条数
smembers nums
srem nums 2
spop nums
- 随机移除
sadd nums1 1 2 3
sadd nums2 2 3 4
sinter nums1 nums2
- 计算 num1 num2 的交集
sdiff nums1 nums2
- 计算 num1 num2 的差集,返回 nums2 不在num1中的数据,示例结果返回1
sunion nums1 nums2
- 计算 num1 num2 的并集,示例结果返回1 2 3 4
有序集合(sorted sets)
类型介绍
sorted sets是Redis类似于SortedSet和HashMap的结合体,一方面它是一个set,保证了内部value的唯一性,另一方面它可以给每个value赋予一个score,代表这个value的排序权重。内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。sorted sets中最后一个value被移除后,数据结构自动删除,内存被回收。
由 ziplist 和 skiplist 实现
- 当
zset
中元素长度小于64B且zset
长度小于128时使用ziplist
,超过时会使用skiplist
- 当
应用场景
- 主要应用于根据某个权重进行排序的队列的场景,比如游戏积分排行榜,设置优先级的任务列表,学生成绩表等。
操作指令
zadd rank 66 zhangsan 88 lisi 77 wangwu 99 zhaoliu
- 添加元素
zrange rank 0 3
- 返回从小到大4个
zrangebyscore rank 77 99
- 返回有序集 key 中,所有 score值介于 min和 max之间(包括等于 min或 max )的成员。有序集成员按score值递增(从小到大)次序排列,最小的是最上面
zrem rank zhaoliu
- 移除zhaoliu的排名
zcard rank
- key为rank的元素数量
zcount rank 77 88
- 77分到88分的元素的数量
zrank rank wangwu
- wangwu的排名,示例返回0
zrevrank rank wangwu
- wangwu的排名倒叙排,示例返回2
功能
bitmap
支持按位存取信息,可用于实现bloomfilterhyperLogLog
提供不精确的去重统计功能,适合用作大规模数据的去重统计geospatial
可用于保存地理位置,并作位置距离计算或根据半径计算位置等Sub/Pub
订阅发布功能,可用于简单的消息队列pipeline
可批量执行一组指令,一次性返回全部结果,另一种方式是使用脚本- 事务功能是串行执行,但失败不会回滚
Redis应用
- Feed流
- 用户签到
- TopN排行榜
- 附近的人
持久化
RDB
- 将数据集以快照形式写入磁盘,通过fork子进程执行,采用二进制压缩存储
- redis数据保存在单一文件中,适合用作灾害备份,但是在快照写入磁盘之前宕机会丢失数据,保存快照时会时服务停顿
AOF
- 以文本日志的形式,记录redis的每一个操作
- 有灵活以保持同步(每秒,每次操作,不同步)
- 磁盘文件与RDB方式比大,效率低于RDB
淘汰策略
voltile-
对设置的生存时间的key进行lru,最小生存时间,随机剔除allkeys-
则是对所有keyno-eviction
则是不进行剔除,读取正常,写入则会报异常
缓存常见问题
缓存不一致
- 原因:同步更新失败,异步更新 解决方案:增加重试,补偿任务,最终一致
缓存穿透
- 原因:恶意攻击 解决方案:空对象缓存,bloomfilter过滤器
缓存击穿
- 原因:热点key失效 解决方案:互斥更新,随机退避,差异失效时间
缓存雪崩
- 原因:缓存宕机 解决方案:快速失败熔断,主从模式,集群模式
面试题
如何使用Redis实现延时队列,如何使用Redis实现分布式锁?
- 可使用sortedset实现延迟队列,使用时间戳做score,使用zrange by score命令获取指定延迟时间之前的数据.
- 可是使用setnx设置key,返回1则获取锁成功,返回0则获取锁失败
- redisson
如何保证Redi的高并发和高可用
- 高并发:主从读写分离,多从库,多端口实例以及cluster集群部署
- 高可用:sentinel保证主库宕机时,重新选住并完成从库的变更
说说你对Redis的理解,有多少说多少
- Redis是一种存储技术,在操作系统层面它也是一个进程,一般我们用Redis来做缓存存储数据,用缓存的目的是为了减少数据库的访问压力,一些数据如果在缓存里就不需要再去走一次数据库了,而且Redis也很适合并发常见下的使用,最大QPS能达到8w+,Redis6.0前都是单线程的,说它是单线程是因为它里面有个文件事件处理器,这个是单线程的,它可以通过IO多路复用来监听大量的客户端连接,所以它快,此外Redis还有常用的数据结构,比如:String字符串、List列表,Set集合、Hash哈希、Zset有序集合五种基本数据类型,还有三种特殊数据类型:地理位置geo、位图bitmap、基数统计,此外Redis还有两种持久化机制:AOF和RDB,它默认是RDB快照存储,AOF是以文本存储,它也可以记录Redis的一个状态,还有Redis有两种删除key的策略,定时删除和惰性删除,还有一些内存淘汰算法,以及事务,它的事务是不支持回滚的,当命令执行错时才会触发事务,Redis在使用过程中也会出现一些问题,如缓存穿透、缓存雪崩、和数据库数据不一致,这些也有对应的解决方法,还有就是Redis也支持集群,在高并发场景下可以做Redis集群来减少系统负载压力,比如一主二从这种,还有就是Redis在6.0后就支持多线程了,这就是我对Redis的理解
为什么Redis 是单线程的
- 因为Redis基于Reactor模型来设计开发了一套自己的高效事件处理模型,这个事件模型对应的是Redis的文件事件处理器,由于文件事件处理器是单线程的,所以一般就说Redis是单线程的,虽然它是单线程,但是它可以通过IO多路复用来监听大量的客户端连接
那你说说这个文件事件处理器有哪些东西
- 主要包含4个部分:
- 1、多个Socket;2、IO多路复用程序;3、文件事件分发器;4、事件处理器
Redis 一般有哪些使用场景
- 用Redis主要是图它快,因为最常用的就是用来做缓存了,除此之外还可以用来做分布式锁以及消息队列
Redis 有哪些数据类型,每一种都有什么实际应用
数据结构有5种基本数据结构:字符串String、列表List、哈希Hash、集合Set、有序集合Zset
三种特殊数据结构:基数统计HyperLogLogs、位存储Bitmap、地理位置Geospatial
各个数据类型应用场景:
- String:缓存、分布式锁、全局ID、计数器、限流
- Hash:购物车,String可以做的事情,Hash都可以做
- List:用户的消息列表、网站的公共列表、活动列表、博客的文章列表、评论列表等、还可以做栈和队列
- Set:抽奖、点赞、打卡、签到、商品标签、商品筛选
- Zset:排行榜
- HyperLogLogs:统计网站的UV、应用的日活、月活
- Bitmap:连续七天在线用户
- Geospatial:增加地理信息、获取地理信息、计算两个位置的距离、获取指定范围内的地理位置
为什么会设计Redis Stream
- Redis Stream是5.0推出的数据类型,能够支持可持久化的消息队列,设计它是为了能实现发布订阅这种功能,它借鉴了Kafka的设计
Redis Stream消费者崩溃带来的会不会消息丢失问题
- 不会,如果消费者崩溃出现异常没有处理成功之前的消息,Redis还是会保留这部分未处理成功的消息的,等到消费者重新上线后再把没处理的消息重新发给上线的消费者,这样就不会出现消息丢失的问题了
Redis Steam 坏消息问题,死信问题
- 当消息不能被消费者处理并累加到临界值的时候,就可以认为这些消息是坏消息了,也叫死信,怎么处理呢?Redis判断出死信消息后会给它删除掉,这样就处理掉了
Redis 的持久化机制是什么
- RDB会创建Redis快照,并以二进制的形式保存在磁盘中,快照持久化也是Redis默认的持久化方式
- AOF以协议文本方式,将所有对Redis写入过的命令和参数记录到AOF文件,从而记录Redis的状态
AOF是写前日志还是写后日志
- 写后日志,Redis先执行命令,把数据写入内存,然后再将日志文件写入磁盘
Redis 过期键key的删除策略有哪些
- 定期删除和惰性删除
- 定期删除每隔一段时间会取一些设置了过期时间的key来检查是否过期,如果过期就删除,但是会存在设置了过期时间的key定期删除策略没有检查到,那就会一直存在,所以就有了惰性删除策略
- 惰性删除策略默认在主线程删除并释放key的,4.0之前是同步删除,4.0后是异步删除,主要用来删除定期删除没有删除到的key
Redis 内存淘汰算法有哪些
Redis提供6种内存淘汰机制:
- volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中随机选择数据淘汰
- allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
4.0 版本后增加以下两种:
- volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
- allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
Redis如何做内存优化
- 1、减少key和value的长度,key值越短越好
- 2、字符串优化
- 3、编码优化
- 4、控制key的数量
说说Redis事务相关命令
- Redis事务相关命令有:MULTI、EXEC、DISCARD、WATCH等命令
- NULTI:开始事务
- EXEC:执行事务
- DISCARD:取消事务
- WATCH:监听指定的key
Redis事务中出现错误的处理
- Redis事务在出现错误的情况下,除了执行过程出现错误的命令,其它命令都能正常执行,并且Redis是不支持回滚操作的
为什么 Redis 不支持回滚
- 因为Redis开发者们觉得没必要支持回滚,这样更简单便捷,而且性能更好,而且Redis 开发者觉得即使命令执行错误也应该在开发过程中就被发现而不是生产过程中
Redis缓存有哪些问题,如何解决
- 缓存穿透、缓存雪崩、缓存与数据库一致性问题
- 缓存穿透就是大量请求的key不存在缓存中,导致请求到了数据库里,怎么解决?
- 1、做好参数校验,不合法的参数直接抛出异常;
- 2、设置key的过期时间,尽量将无效key的过期时间设置的短一点,比如一分钟;
- 3、引入布隆过滤器,把不存在的key放到过滤器中,用户请求过来会判断请求的值是否在布隆过滤器,不存在的话就把请求参数错误信息返回给客户端,存在的话才继续走下去
缓存雪崩就是缓存在同一时间大面积的失效,后面的请求直接落在了数据库上,造成了数据库接受了大量的请求,就跟雪崩一样,怎么解决?
- 1、采用Redis集群
- 2、限流
数据一致性问题怎么解决?
- 更新数据库,然后直接删除缓存,如果更新数据库成功,而删除缓存这一步失败的情况的话,可以增加缓存更新重试机制来解决
说说Redis哨兵模式
- redis哨兵是要用集群去做的,比如一主二从,主节点挂了就会哨兵就会重新选一个节点作为领导者去切换
Redis6.0之前为什么一直不用多线程
- 我觉得有三个原因:
- 1、单线程编程比多线程编程简单并且更容易维护
- 2、Redis性能瓶颈不在于CPU,而在于内存和网络
- 3、多线程会存在死锁、线程上下文切换的问题,甚至会影响自身性能
说说Redis的二进制不安全?
什么是脑裂机制?
Oracle
特点
- 功能强大,缺点贵
MariaDB
特点
- MySQL的分支,由开源社区维护
PostgreSQL
特点
- 类似于Oracle的多进程模型,可支持高并发的应用场景,几乎支持所有SQL标准,适合严格的企业应用场景
MongoDB
特点
- 基于分布式文件存储的数据库
- 将数据存储为一个文档,数据结构由键值对组成
- 适用于表结构不明确,数据结构不断发生变化的场景
- 不适合有事务和复杂查询的场景
TiDB
特点
- 开源分布式关系型数据库
- 几乎完全兼容MySQL
- 支持水平弹性扩展,ACID事务,标准SQL,MySQL语法和MySQL协议
- 具有数据强一致性的高可用性
- 既适合在线事务处理,也适合在线分析处理
OceanBase
特点
- 蚂蚁金服所有,满足金融级数据可靠性以及数据一致性要求的数据库系统
- 以商业化不再开源
数据库范式
第一范式(最低)
- 要求表中的字段不可再拆分
第二范式
- 在满足第一范式的基础上,要求每条记录由主键唯一区分,记录中的所有属性都依赖与主键
第三范式
- 在满足第二范式的基础上,要求所有属性直接依赖于主键,不允许间接依赖
巴斯-科德范式(一般满足至此即可)
- 在满足第三范式的基础上,要求联合主键的各字段之间互不依赖
第四范式
第五范式
Linux
awk
top
netstat
grep
less
tail
netstat
vmstat
iostat & iotop
ifstat & iftop
dstat
strace
GDB
lsof
tcpdump
traceroute
分布式
框架
SpringCloudNetflix
SpringCloudAlibaba
Dubbo
面试题
RPC与HTTP的区别,以及相对应的使用场景
- HTTP使用C/S方式调用,RPC使用动态代理方式调用
- 在使用方式方面,HTTP使用Client,RPC通过动态代理;从请求模型看,HTTP一般会经过DNS解析,4/7层代理等中间环节,而RPC是点对点直连;从服务治理能力来看,RPC提供丰富的服务治理功能,例如熔断 、负载均衡,HTTP对跨语言处理比较方便
网关
- OpenResty
大数据
Hadoop
Hive
Trino
Presto
Hbase
- 是在hdfs(Hadoop Distributed File System hadoop分布式文件系统)中分布式面向列的数据库,类似于Google的Bigtable
- 可提供快速随机访问海量结构化数据,在表中由行排序,一个表中有多个列族,每个列族有任意数量的列
- 依赖于hdfs,可以实现海量数据的可靠存储,适用于数据量大,写多读少,不需要复杂查询的场景
搜索引擎
ElasticSearch
消息队列
Kafka
有Scala开发的跨语言高性能分布式消息队列,单机吞吐量在十万级,消息延迟在毫秒级.完全的分布式系统,blocker,producer,consumer都是原生自动支持分布式,依赖ZooKeeper做分布式协调.支持一写多读,可被多个消费者消费.消息皆不会丢失,但可能重复
基本概念
- 分布式流处理平台
- 提供发布订阅及 Topic 支持
- 吞吐量高但不保证消息有序
- Kafka消费者组是Kafka消费的单位
- 单个Partition只能由消费者组中某个消费者消费
- 消费者组中的单个消费者可以消费多个Partition
Kafka架构
- Kafka集群有多个server组成,每个server称为一个Broker,为消息代理
- Kafka中消息是按topic进行划分的,一个topic就是一个queue,实际应用中不同数据可设置为不同topic
- 一个topic可以有多个consumer,当producer发送数据到topic中时,订阅了该topic的consumer都能接收到消息
- 为提高并行能力,维护了多个portion分区,每个portion保证id唯一且有序,新消息会储存在队尾,
- portion持久化时会分段,保证对较小的文件进行写操作,以提高性能
- 每个topic会被分为多个portion,存于多个broker上,以保证容灾
Kafka消息生产/生产流程
对consumer进行分组管理,以支持消息的一写多读
producer有多种方式选择portion,轮循(默认),指定,根据key值得hash选择portion
消息得发送有三种
- 同步(默认):producer发送消息时,同步获得反馈
- 异步:producer以batch的方式push消息,可以极大地提高性能,也增加消息丢失风险
- oneway:只发送消息,不返回结果
Kafka确保每个group中只能有一个consumer消费
通过group coordinator管理哪个consumer负责消费哪个portion.默认支持range和轮循分配
在zookeeper中保存了每个topic的每个portion的消费偏移量offset.通过更新offset,以保证每条消息都被消费
ps.每个consumer线程相当于一个consumer实例,当consumer group中的consumer数量大于portion时,有的consumer会读取不到数据
常见命令
1、启动Kafka
- bin/kafka-server-start.sh config/server.properties &
2、停止Kafka
- bin/kafka-server-stop.sh
3、创建Topic
- bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic jiangzh-topic
4、查看已经创建的Topic信息
- bin/kafka-topics.sh --list --zookeeper localhost:2181
5、发送消息
- bin/kafka-console-producer.sh --broker-list 192.168.220.128:9092 --topic jiangzh-topic
6、接收消息
- bin/kafka-console-consumer.sh --bootstrap-server 192.168.220.128:9092 --topic jiangzh-topic --from-beginning
客户端操作类型
AdminClient API 允许管理和检测Topic、broker以及其它Kafka对象
- AdminClient: AdminClient客户端对象
- NewTopic: 创建Topic
- CreateTopicsResult: 创建Topic的返回结果
- ListTopicsResult: 查询Topic列表
- ListTopicsOptions: 查询Topic列表及选项
- DescribeTopicsResult: 查询Topics
- DescribeConfigsResult: 查询Topics配置项
Producer API 发布消息到1个或多个topic
Producer发送模式
- 同步发送
- 异步发送
- 异步回调发送
构建 KafkaProducer步骤
- MetricConfig
- 加载负载均衡器
- 初始化Serializer
- 初始化RecordAccumulator ——类似于计数器
- 启动newSender ——守护线程
KafkaProducer
- Producer是线程安全的
- Producer并不是接到一条发一条
- Producer是批量发送
KafkaProducer send(record) 方法
- 计算分区 —— 消息具体进入哪一个partition
- 计算批次 —— accumulator.append
- 1、创建批次 2、向批次中追加内容
消息传递保障
- 最多一次:收到0到1次
- 至少一次:收到1到多次
- 正好一次:有且仅有一次
- acks 配置 producer需要server接收到数据之后发出的确认接收的信号,此项配置就是指procuder需要多少个这样的确认信号。此配置实际上代表了数据备份的可用性。以下设置为常用选项:
(1)acks=0: 设置为0表示producer不需要等待任何确认收到的信息。副本将立即加到socket buffer并认为已经发送。没有任何保障可以保证此种情况下server已经成功接收数据,同时重试配置不会发生作用(因为客户端不知道是否失败)回馈的offset会总是设置为-1;
(2)acks=1: 这意味着至少要等待leader已经成功将数据写入本地log,但是并没有等待所有follower是否成功写入。这种情况下,如果follower没有成功备份数据,而此时leader又挂掉,则消息会丢失。
(3)acks=all: 这意味着leader需要等待所有备份都成功写入日志,这种策略会保证只要有一个备份存活就不会丢失数据。这是最强的保证。
(4)其他的设置,例如acks=2也是可以的,这将需要给定的acks数量,但是这种策略一般很少用。
- Consumer APl
订阅一个或多个topic,并处理产生的消息
- 单个分区的消息只能由ConsumerGroup中某个Consumer消费
- Consumer从Partition中消费消息是顺序,默认从头开始消费
- 单个ConsumerGroup会消费所有Partition中的消息
RocketMQ
- 阿里开源的消息中间件,单机支持十万级的吞吐量,使用Java开发具有高吞吐量,高可用性的特点,适合在大规模分布式系统中使用
RabbitMQ
- Erlang开发的开源消息队列,通过Erlang的Actor模型,实现了数据的稳定可靠传输.支持AMQP,XMPP,SMPP等多种协议,因此也比较重量级,由于采用broker代理架构,发送给客户端时,先在中间队列进行排队.RabbitMQ单机吞吐量在万级不算很高
ActiveMQ
- 可部署于代理模式和P2P模式,同样支持多种协议,单机吞吐量在万级.但是不够轻巧,对于队列较多的时候支持不是很好,并且有较低概率丢失消息
构建工具
Maven
Gradle
网络工具
PostMan
WireShark(网络包分析工具)
Fiddler(只针对HTTP进行抓捕)
Charies
面试
自我介绍
- 简短有力3-4分钟较好
- 内容要有结构性:经历简介、项目经历,技术总结
- 凸显能力:技术基础能力扎实,喜欢探究原理
反问面试官问题
- 部门技术栈
- 工作内容和细节
- 成员数量
- 未来发展方向
CICD
云效
Coding持续集成
Jenkins
Kotlin
Python
前端
HTML
jQuery
CSS
Vue
算法
分治
动态规划
贪心
回溯
分支界定
时间复杂度
空间复杂度
排序
插入
- 希尔
- 直插
交换
- 冒泡
- 快排
选择
- 简单选择
归并
基数
查找
- 二分查找
- 二叉排序树
- B树
- BloomFilter
字符串匹配
- BF算法
- BM算法
- Sundady算法
- KMP算法
- Tire树
面试题
- 各种排序算法实现和复杂度、稳定性
- 二叉树的前、中、后序遍历
- 翻转句子中单词的顺序
- 用栈模拟队列(或用队列模拟栈)
- 堆10亿个数进行排序,限制内存位1G
- 去掉(或找出)两个数组中重复的数字
- 将一颗二叉树转换成其镜像
- 确定一个字符串中的括号是否匹配
- 给定一个开始词,一个结束词,一个字典,如何找到从开始词到结束词的最短单词接龙路径
- 如何查找两个二叉树节点的最近公共祖先
数据结构
队列
栈
表
- 数组
- 单链表
- 双链表
- 循环链表
- 散列表
图
- 有向图
- 无向图
- 带权图
多叉树
- B、B+树
- 字典树
二叉树
- 平衡二叉树
- 红黑树
- 哈夫曼树
- 堆
设计模式
创建型
单例模式
单例模式就是一个类只有一个对象实例,并且提供一个访问它的全局访问点
实现的常见三种方法
- 静态初始化(饿汉).不管是否使用都会创建
- 双检锁(懒汉).单例变量必须要用volatile修饰.
- 单例注册表.spring中bean的单例模式就是用该方法实现.
工厂方法模式
- 它提供了一种创建对象的最佳方式。在工厂模式我们创建对象不会对客户端暴露创建逻辑,也是使用一个共同的接口来指向新的对象
- 创建不同类型实例常用的方式,spring中的bean都是由不同工厂类创建的
抽象工厂模式
建造者模式
- 建造者模式一般在项目里可以替代setter方法注入
- 适用于一个对象拥有很多复杂的属性,需要根据不同情况创建不同的具体对象
- 创建Protocol Buffer对象时,需要用到Builder
原型模式
结构型
适配器模式
- 类似于转接头,将两种不匹配的对象进行适配,也可以起到对两个不同的对象进行解耦的作用
- SLF4J可使项目与Log4、logback等具体日志实现框架进行解耦,其通过不同适配器与不同框架进行适配,完成日志功能的使用
装饰器模式
代理模式
- 可以通过代理控制对象的访问以及在原有代码也业务不变的情况下,直接在业务流程切入代码增加新功能,代理模式有静态代理和动态代理两种
- 在不适合或不能直接引用另一个对象的场景,可以用代理模式对被代理的队形进行访问行为的控制。Java的代理模式分为静态代理和动态代理,静态代理是指在编译时就创建好的代理类,例如在源代码中编写的类,动态代理指在JVM运行过程中动态创建的代理类,如JDK动态代理,CDLIB,javaasist等。
- 例如,在Mybatis中getMapper时会通过MapperProxyFactory及配置文件动态生成的Mapper代理对象,代理对象会拦截Mapper接口的方法调用,创建对应方法的MapperMethod类并执行execute方法,然后返回结果.
外观模式
桥接模式
组合模式
享元模式
行为型
策略模式
模板方法模式
观察者模式
- 也可称为发布订阅模式,适用于一个对象某个行为需要触发一系列操作的场景
- GRPC中stream流式请求的处理
迭代器模式
责任链模式
- 类似工厂流水线,其中的每个节点完成对对象的某一种处理
- Netty框架的处理消息的Pipeline就是采用的责任链模式
命令模式
备忘录模式
状态模式
访问者模式
中介者模式
解释器模式
计算机基础
操作系统
进程与线程
区别联系:进程是资源分配的最小单位,线程是程序执行的最小单位;进程使用独立的数据空间,线程共享进程的数据空间
线程调度:时间片轮转调度、先来先服务调度、优先级调度、多级反馈队列调度、高响应比优先调度
线程切换步骤:线程的上下文切换、线程切换的代价
Linux下的IPC(进程间通讯)
- Pipe
- MessageQueue
- 共享内存
- UnixSocket
- Signal
- Semaphore
协程
网络
OSI七层模型
- 物理层
- 数据链路层
- 网络层
- 传输层
- 会话层
- 表示层
- 应用层
TCP/IP四层模型
- 应用层
- 传输层
- 网络层
- 应用层
HTTP
HTTP和HTTPS的区别是什么
- HTTP是超文本传输协议,HTTPS是超文本传输安全协议,其区别就是HTTPS比HTTP多了一个SSL证书,有这个证书后,它就比HTTP要安全,而且HTTPS和HTTP的端口号也是不同的,HTTP是80,HTTPS是443
HTTPS的加密手段是什么
- HTTPS采用混合加密机制,也就是说它把对称加密和非对称加密混合起来使用,它会在密钥安全交换环节使用非对称加密方式,之后使用对称加密方式,这样做的目的是对称加密比非对称加密处理速度更快
HTTP 如何保存用户状态
- 用coolie或者session保存
HTTP状态码有哪些
- 200:请求成功
- 301:永久重定向
- 400:客户端请求错误
- 404:未发现页面异常
- 500:服务器异常
HTTP2
TCP
特点
基于链接(点对点)
- 每次发送数据先要建立连接
双工通信
- 连接建立后可进行双向通信
可靠传输
- 通过对数据包编号,并按序号接受,可确保数据的完整性和有序性
拥塞控制
- 主要通过慢启动,拥塞避免,拥塞发生,快速恢复算法实现
基于字节流而非报文(保证数据的可靠性和完整性)
实现细节
- 8种报文状态
- 滑动窗口机制
- KeepAlive
- Bagel算法
三次握手
第一次,客户端发送syn建立连接的请求给服务端,发送完请求后客户端状态SYS_CENT状态,服务端收到状态变成SYS_RCVD
第二次,服务端发送SYN和ACK这些信息的请求给客户端,确认这次连接是有效的,客户端收到后状态会变成established
第三次,客户端再发送个ACK请求给服务端,表示这次连接已经建立成功了,服务端在收到客户端的请求后也会变成established,至此三次握手建立连接成功
拓展
syn洪水攻击
- 即请求端只发送SYN包,而不对接收端的SYNACK包进行回复,使得接收端大量连接处于半连接状态,影响其他正常请求的建连. 解决方法:设置linux的tcp参数 tcp_synack_retries=0,加快对于半连接的回收速度,或加大tcp_max_syn_backlog应对少量的syn洪水攻击
四次挥手
第一次,客户端发送FIN关闭连接的请求给服务端,表示不会再有数据发送了,希望断开连接,发送完后,客户端状态变成FIN_WAIT_1,然后等待服务端确定
第二次,服务端收到客户端的FIN请求后,进入了CLOSE_WAIT状态,然后发个ACK确认信息给客户端,表示也不会再接收客户端的请求了,客户端在收到这个请求后状态变成FIN_WAIT_2
第三次,服务端再发一个FIN请求给客户端,告诉客户端可以关闭连接了,发送完后状态会变成TIME_WAIT
第四次,客户端收到服务端的FIN请求后,会再发送一个ACK信息给服务端表示我这边连接关闭了也确认了你关闭连接的信息,发送后客户端状态变成CLOSE,服务端在收到ACK后状态也会变成CLOSE,至此四次挥手关闭连接成功
拓展
为何HOSTA要在2MSL(Maximum Segment Lifetime 最大报文生存时长)后才关闭连接?
- 要保证TCP的全双工连接能可靠关闭
- 要保证这次连接中重复的数据段从网络中消失,防止端口重用时可能的数据混淆
有可能大量socket会处于TIME_WAIT和CLOSE_WAIT的问题
- 解决TIME_WAIT过多,可通过开启Linux的tcp参数tw_reuse或tw_recycle能加快TIME_WAIT状态的回收. CLOSE_WAIT过多则可能是被动关闭的一方存在代码BUG,没有正确关闭连接导致的
TCP协议中的流量控制起到什么作用?
- 流量控制是为了控制发送方的发送速率,能够保证接收方来得及接收
介绍一下ARQ协议
- ARQ协议是自动重传请求(Automatic Repeat-reQuest)的缩写,是OSI模型中的错误纠正协议之一,能够通过使用确认和重传这两个机制,在不可靠的服务上实现可靠的信息传输,ARQ协议包括停止等待ARQ协议和连续ARQ协议
头结构
- 16位端口号:标示该段报文来自哪里(源端口)以及要传给哪个上层协议或应用程序(目的端口)。进行tcp通信时,一般client是通过系统自动选择的临时端口号,而服务器一般是使用知名服务端口号或者自己指定的端口号。
- 32位序号:表示一次tcp通信过程(从建立连接到断开)过程中某一次传输方向上的字节流的每个字节的编号。假定主机A和B进行tcp通信,A传送给B一个tcp报文段中,序号值被系统初始化为某一个随机值ISN,那么在该传输方向上(从A到B),后续的所有tcp报文断中的序号值都会被设定为ISN加上该报文段所携带数据的第一个字节在整个字节流中的偏移。例如某个TCP报文段传送的数据是字节流中的第1025~2048字节,那么该报文段的序号值就是ISN+1025。
- 32位确认号:用作对另一方发送的tcp报文段的响应。其值是收到对方的tcp报文段的序号值+1。假定主机A和B进行tcp通信,那么A发出的tcp报文段不但带有自己的序号,也包含了对B发送来的tcp报文段的确认号。反之也一样。
- 4位头部长度:表示tcp头部有多少个32bit字(4字节),因为4位最大值是15,所以最多有15个32bit,也就是60个字节是最大的tcp头部长度。
UDP
特点
- 非链接
- 非可靠传输
- 效率高
QUIC(基于UDP,但是提供了基于UDP的可靠性保障)
- 避免前序抱阻塞(HOL阻塞)
- 零RTT建联
- FEC前向纠错
QUIC
题目
从输入URL到页面加载发生了什么
- 1、DNS解析域名
- 2、TCP建立连接
- 3、发送HTTP请求
- 4、服务端处理请求并返回HTTP报文
- 5、浏览器解析报文并渲染页面
- 6、渲染完毕,页面加载结束,连接结束
Cookie 的作用是什么
- 1、保存用户登录信息,下次用户再登录就会自动把登录信息填在网站
- 2、保持登录,用户下次登录时就不用再重复登录了
- 3、登录网站一次后访问其它网站页面不需要重新登录
Cookie 和 Session 有什么区别
- Cookie存在客户端也就是浏览器上,Session存在服务器上,相对来说Session安全性更高,如果要再cookie存一些敏感信息,最好对cookie进行加密,使用时去服务端解密即可
GET请求和POST请求的区别
从三个层面来解答
- Http报文层面:GET将请求信息放在URL,POST放在报文体中
- 其他层面:GET可以被缓存、被存储,而POST不行
- 数据库层面:CET符合幂等性和安全性,POST不符合
forward和redirect的区别
1.forward 和redirect forward又叫转发,表示转发,当请求来到时,可以将请求转发到其他的指定服务,用户端不知晓。redirect又叫重定向,表示转发,当请求发给A服务时,服务A返回重定向给客户端,客户端再去请求B服务。
3.使用redirect注意事项 1.redirect不支持post请求 2.redirect需要携带请求参数,需要在url地址中进行编码防止中文乱码。
4.两者的区别
- 1.从地址栏显示来说:forword是服务器内部的重定向,服务器直接访问目标地址的 url网址,把里面的东西读取出来,但是客户端 并不知道,因此用forward的话,客户端浏览器的网址是不会发生变化的。
redirect是服务器根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,所以地址栏显示的是新的地址。
- 2.从数据共享来说:由于在整个定向的过程中用的是同一个request,因此forward会将request的信息带到被重定向的jsp或者 servlet中使用。即可以共享数据,redirect不能共享
- 3.从运用的地方来说 forword 一般用于用户登录的时候,根据角色转发到相应的模块 redirect一般用于用户注销登录时返回主页面或者跳转到 其他网站
- 4.从效率来说:forword效率高,而redirect效率低
- 5.从本质来说:forword转发是服务器上的行为,而redirect重定向是客户端的行为
- 6.从请求的次数来说:forword只有一次请求;而redirect有两次请求
版本控制
GIT
Git合并代码有那两种方法?有什么区别
git Merge
- 这种合并是将两个分支的历史合并到一起,现有的分支并不会被更改,它会比对双方不同的文件缓存下来,生成一个commit,去push
- 优点: 安全,现有分支不会被修改
- 缺点: 或多或少都会污染一点分支历史,在回看项目时会增加理解项目历史的难度
git Rebase
- 这种合并通常称之为“衍合”,他是修改提交历史,比对双方的commit,然后找出不同的去缓存,然后在去push,修改你的commit历史。
- 优点: 项目历史会非常整洁
- 缺点: 安全性和可跟踪性很差,你将无法知晓你这次合并做了那些修改用处: 绝不要在公共的分支上使用它。
容器
Docker
作用
- 构建,部署,运行服务
- 服务版本管理
- 屏蔽环境差异
- 隔离服务
- 提高资源利用率
特点
- 开源容器技术
- 基于LXC,高效虚拟化
- 适合大规模构建
- 灵活可拓展
- 管理简单
概念
- 镜像(Images
- 容器(Container
- 守护进程(Daemon
- 客户端(Client
- 镜像仓库(Repository
Docker原理
docker通过对不同运行进程进行隔离实现虚拟化
docker运用三种方式以实现进程的隔离:
- Namespace
- 进程:docker运用三种方式以实现进程的隔离利用Linux的Namespace隔离进程之间的可见性,不同的服务进程属于不同的Namespace,互相无法感知对方的存在.
- 网络:docker运用三种方式以实现进程的隔离实现了host,container,null和bridge(默认)四种网络模式.每个容器创建时都会创建一对虚拟网卡,一个在容器中一个在docker0的网桥中,组成了数据的通道.docker0的网桥通过iptables中的配置与宿主机的网卡相连,所有符合条件的请求都会通过iptables转发到docker0,再有网桥分发给相应的容器网卡.
- 挂载点(文件目录):为防止容器进程修改宿主机的文件目录,docker通过修改进程访问文件目录的根节点结合namespace来隔离不同容器进程可以访问的文件目录
Control Groups
UnionFS
- docker的镜像是分层结构,存在操作系统层,技术环境层,web容器层,服务代码层,层与层相互依赖通过UnionFS把Image的不同分层作为只读目录
- Container是在Image的只读目录上创建的科目可写的目录
- docker有AUFS, Btrfs, overlay, Devicemapper, zfs等多种不同的存储驱动实现
Kubernetes
容器集群管理系统,不是PaaS平台
作用
容器集群管理
自动化部署
- 自动扩缩容
- 应用管理
概念
- Master:管理节点,负责协调集群中所有节点的行为与活动(例,应用的运行,修改,更新等)
- Node:运行容器,可有多个Pod
- Pod:Kubernetes可创建部署的基本单位,可运行多个Container
- Container:为运行中的服务镜像,共享所属Pod的网络存储
- Service:为Pod添加标签,将其划分为不同的Service
- Deployment:表示对Kubernetes集群的一次操作(例,创建,更新,滚动升级等)
Kubernetes架构
Master
api server:用户资源操作的唯一入口
- 创建应用部署
- 管理部署状态
- 认证授权
- 访问控制
- api注册和发现
controller manager:维护集群状态,包含多个controller(例,node controller,route controller,service controller)
- 故障检测
- 自动扩展
- 滚动更新
- 等
scheduler:资源调度
etcd:保存集群状态
kubectl:运行命令的管理工具,与Master中的api server进行交互,通过api server下达指令
Node
容器运行时(container runtime):可以不是docker
- Pod:可看作虚拟服务器,可运行多个Container
kubelet:负责人与Master通信,周期性访问api server,进行检查和报告,执行容器的操作,维护容器的生命周期,也负责volume和网络的管理
kube-proxy:处理网络代理和容器的负载均衡,通过改变iptables规则,控制容器上的tcp和udp包
Kubernetes把所有被管理的资源看作对象,使得对资源的管理变成对对象属性的设置,配置文件使用yaml格式,对象可大致分为四类:
- 资源对象
- 配置对象
- 存储对象
- 策略对象