后端知识点记录

目录

1.java基础

2.高并发

3.jvm

4.算法和数据结构

5.数据库

6.redis专题栏

7.消息中间件

8.框架

9.网络传输

10.微服务

11.设计模式

12.服务器调优

13.linux

14.向面试官提问


1.java基础

1.HashMap和HashTable区别
  1.1 HashMap是非线程安全,HashTable是线程安全,所以性能上HashMap要高
  1.2 HashMap允许空值空键,HashTable不允许空值空键
  1.3 初始容量大小和扩容大小不同:HashMap初始化大小为16,每次扩容,容量变为原来的2倍;HashTable的初始化大小为11,每次扩容,容量变为原来的2n+1

2.String、StringBuffer、StringBuilder
  2.1 String被final修饰过,长度不可变,拼接字符的时候用+=,每次拼接的字符串都是一个新的内存空间
  2.2 StringBuffer是可变字符串变量,被synchronized修饰过,是线程安全的
  2.3 StringBuilder和StringBuffer都是可变字符串变量,StringBuilder是非线程安全的

3.List和set区别
  3.1 List和Set都是接口,它们都继承于接口Collection
  3.2 List是有序可重复的,Set是无序可重复的
  3.3 List中有ArrayList、LinkedList和vector、ArrayList和Vector都是基于数组实现的,数组都有下标,查询速度较快,而进行添加和删除的时候要比LinkedList慢,链表结构删除和添加的时候只需要改变指针位置即可。

4.运行时异常和一般异常
  4.1 运行时异常由虚拟机接管,可以不用手动捕获异常。出现运行时异常,系统会把异常一直往上抛,一直遇到处理代码,如果一直没有遇到处理代码,则出现运行时异常,线程终止,主程序停止
  4.2 一般异常,是需要程序员通过catch捕获进行处理的异常

5.==和equals区别(它们的区别主要集中在引用数据类型上)  
  5.1 ==比较的是内存地址,equals默认比较的也是内存地址,重写后比较的是对象的值是否相等
  5.2 ==两侧可以都为null,equals左侧如果为null,则会报空指针

6.Integer和int区别
  6.1 Integer是int封装类,int是基本数据类型
  6.2 Integer的默认值是null,int默认值是0
  使用场景:如果需要区别出未参加考试和考试成绩为0的区别,则用Integer,Integer可以区别出未赋值和值为0的区别

7.JDK和JRE区别
  7.1 JDK:java开发工具,包含了类库和工具
  7.2 JRE:java程序运行环境

8.System.out.println(3|9)输出什么?
  8.1 '|'除了可以用来当成或运算来用,符号两边的都会运算,也可以当成位运算
  8.2 '||'短路或,如果符号左边的为true,则为true
  8.3 结果:3|9=0011|1001=1011(有1个为1则为1,1位真)=11

6.HashMap底层原理
  6.1 存放数据:底层通过hashCode()方法计算出哈希值,将哈希值转换成下标,下标位置上如果没有任何元素,则将节点存放在这个位置上;如果下标位置上存在链表,则使用equals()比较key和链表中的元素是否相等,如果发现key相等,则这个节点上的value值被覆盖掉;如果可以在链表中不存在,则将value加入到链表的末尾。
  6.2 查询数据:通过hashCode()计算出哈希值,将哈希值转换成为下标,去下标的位置上找元素,如果这个位置上什么都没有,则返回null;如果这个位置上存在链表,则遍历这个链表使用equals()方法比较这个key在链表中是否存在,如果整个链表中都找不到这个key,则返回null;如果可以在链表中找到这个key,则范围链表中查到到的这个位置上的元素对应的value。
  6.3 多线程情况下会出现死锁,因为每次插入数据的时候,HashMap会进行扩容。扩容的时候,是将原来链表里面的元素进行遍历,依次写入到一张新的链表中。eg:A节点写入到新的链表,此时还没设置他的指针,另外一个线程抢占了资源,新写进来一个B结点,B结点指针

7.Arraylist和LinkedList区别
  7.1 数据结构不同:ArrayList底层是数组,LinkedList是双向链表结构。
  7.2 查询性能ArrayList更高、因为是用的数组结构,直接精确定位要要查询的数据;LinkedList需要遍历链表。
  7.3 插入和更新性能LinkedList更高,因为是采用的链表结构,只需要修改相邻元素的指针即可,ArrayList需要移动插入/删除元素后面的元素(如果是针对最后一个元素做插入或删除,那性能和LinkedList是一样的)
  7.4 都是线程不安全的
  7.5 内存占用空间:ArrayList更小,它的空间主要浪费在要为列表结尾的空间预留一定的空间,LinkedList需要存储前驱和后继节点

8.Object常用方法
  8.1 getClass:被final修饰,因此不能被重写
  8.2 hashCode:返回对象的哈希值
  8.3 equals:默认比较两个对象的内存地址。String类重写后,比较的是字符串的值是否相等,所以要看具体重写后的方法里怎么实现的。
  8.4 toString:返回类的名字@实例的哈希值
  8.5 notify:被native修饰,不能被重写。唤醒一个被等待的线程
  8.6 notifyAll:被native修饰,不能被重写。唤醒所有被等待的线程
  8.7 wait:被native修饰,不能被重写。线程等待,释放了锁。sleep没有释放锁
  8.8 finalize:实例被垃圾回收器回收的时候触发的

9.为什么重写equals时必须重写hashCode
  9.1 重写equlas目的:默认情况equals比较的是内存地址是否相等,但是实际业务中通常只是比较两个变量的值是否相等。
  9.2 两者同时重写原因
       a)没重写之前,如果两个对象用equals比较内存地址,发现两个对象相同,则他们的hashCode一定相同,因为hashCode是根据对象内存地址计算出来的。得出结论,对象相同,则他们的hashCode一定相同。
       b)如果只重写equals(比较对象值),那么会出现两个对象相同(其实内存地址是不相同的),此时获取hashCode调用的是object里面的方法,它是计算出来两个对象的hashCode肯定是不相同的。那么就和前面的结论冲突。
       c)另外,有很多集合里面为了提高执行效率,在判断对象是否相等之前,都是先比较哈希值是否相同,如果哈希值不同,对象肯定不相同,就不再执行equals;只有哈希值相同,才执行equals方法比较。

10.HashMap多线程下的问题
  10.1 死循环
       a)HashMap底层使用的链表结构存储数据
       b)多线程情况下在修改链表的指针的时候会出现并发,造成环形链表
       c)当用户查询一个不存在的元素,就会不停的遍历这个链表,始终无法跳出循环
  10.2 替换方案:使用ConcurrentHashMap,加了同步锁
  10.3 环形表的形成(扩容前A--->B)
       a)线程1,扩容后结果是B--->A
       b)线程2,拿到的是扩容前的元素A---B,此时也进行扩容,先将A写入到新的链表中,A的指针指向的是老的table表中第一个元素(即线程1扩容后的第一个节点,即B)
       c)线程2,A写入到新链表后(指针指向B),此时新的table表就是A-->B,现在将B元素写入到新的链表中,B的指针指向的是老的table表中第一个元素,即A。

11.finally和return执行顺序
  11.1 正常情况:finally里面不包含return,优先执行finally里面内容,执行完后再执行try或catch里面的return内容
  11.2 特殊情况:finally里面包含return了,那么直接return回去

12.a>b和a-b>0区别
  12.1 结论:a>b成立,a-b>0不一定成立
  12.2 推理
       a)int a = Integer.MAX_VALUE = 2147483647 = 01111111111111111111111111111111(2^31-1,高位上的0表示符号位,是正数)
       b)int b = -1(十进制)= 11111111111111111111111111111111(高位上的1表示符号位,1是负数)
       c)a - b = 2147483647 + 1 = 01111111111111111111111111111111 + 01111111111111111111111111111111(次数因为是1,所以高位上的符号是0) = 10000000000000000000000000000000 = -2147483648(十进制)

13.实现serializable接口的类中为什么要指定serialVersionUID
  11.1 如果不指定serialVersionUID,序列化的时候会自动生成一个serialVersionUID,将其和属性一起序列化,然后进行持久化或网络传输;JVM会根据属性生成一个新的serialVersionUID,此时会对比两个serialVersionUID是否相同,相同则序列化成功,否则报错。

2.高并发

1.多线程原理
  同一时间,CPU只能处理1条线程,多线程并发执行其实是CPU快速的在多条线程中进行任务调度(切换),只要CPU调度线程的时间足够快,就会给人职高多个线程并发的假象

2.线程池原理
  应用启动的时候会根据设置的核心线程数初始化线程池的大小,当一个请求过来了会去判断当前线程数是否小于核心线程数,如果小于核心线程数,就提交任务并执行线程;如果当前线程大于或等于核心线程数,就会检查缓冲队列是否满了,如果队列没满,就会将线程加入到队列中;如果满了就会看当前线程数是否超出最大线程数量,如果超出了就会执行hander里面自定义的异常处理;如果当前线程数没有超过最大线程数就会创建一个线程。另外我们可以设置空闲连接最长空闲时间,超过这个时间就会自动销毁。
  2.1 减少对象的创建、销毁带来的内存开销
  2.2 可有效的控制最大并发量,避免过多资源竞争
  2.3 提供定时执行、单线程和并发数控制
       a)newCachedThreadPool:支持缓存的线程池
       b)newFixedThreadPool:可控制并发数量,超出的线程加入到队列中
       c)newScheduledThreadPool:定时执行的线程
       d)newSingleThreadExecutor:只会创建一个线程、保证所有的任务都是按照先进先出执行

3.volatile关键字作用,它如何保证原子性,可见性
  不能保证原子性,可以保证可见性(线程A对变量进行修改后,会将其它线程内的缓冲区里面关于这个变量的值置为无效,这样其它线程就会去主存中读取最新的变量值)
  3.1 被volatitle修饰过的变量是共享变量,在多线程环境下,如果被其中一个线程修改了,其它线程去读取这个变量就会去内存中读取,不会去自己的工作空间中读取。
  3.2 被比喻为轻量级的synchronized,执行效率比synchronized高,不具有互斥性和原子性
  3.3 使用volatitle禁止指令重排

4.多线程有序打印
  4.1 使用读写锁ReentrantLock,每次执行方法前先上锁【执行lock()】,执行完以后再执行解锁【解锁unLock()】。中间可能会用到Conditon.await()【Conditon对象是通过ReentrantLock.newCondition()获取到的】将锁加入到等待队列中,如需唤醒使用Conditon.singal()
  4.2 使用synchronized关键字对方法上锁。如需加入到等待队列,可使用object.wait(),如需重新唤醒执行object.notify()或objectAll();

5.进程与线程的区别
  5.1 线程是最小单位,一个进程可以包含多个线程
  5.2 与进程不同的是,线程在同类的多个线程中是共用一个内存空间和一组资源的。所以系统在产生一个线程或多个线程来回切换后,对服务器的负担来说要小一些。因此线程也称为轻量级的进程

6.synchronized和ReentrantLock的区别  
  6.1 释放锁的区别:synchronized可以自动释放锁,ReentrantLock不能自动释放锁
  6.2 获取锁的区别:synchronized会一直等待,ReentrantLock可以根据返回手动释放锁否则会死锁
  6.3 类型的区别:synchronized是非公平锁,ReentrantLock默认也是非公平锁,但是可以设置为公平锁

7.高并发带来什么问题,如何支撑高并发
  7.1 问题
       a)系统压力过大
       b)服务响应时间变成
  7.2 解决方案
       a)业务应用做集群部署,使用ngnix做负载均衡,将压力均分到每台业务应用上。
       b)针对一些并发量过高的接口/服务通过限流,去掉一些非法请求。eg:一个用户一分钟访问库存服务1000次是不可能存在的,那么可通过redis计数器实现对流量的控制。
       c)数据库主从分离,主库承担写入操作,从库承担读操作。另外业务表可根据业务规则拆分。
       d)将一些数据变更较少,但是查询频率较高的数据写入到redis中,优先查询redis,查询不到再查数据库,查完数据库后将数据写入到reids中。redis需要做集群部署。
       e)一些可以异步处理的业务,可以使用消息中间件来处理。

8.在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
  8.1 优势:lock分为读写锁,同一时刻读锁可以被多个线程共享进行读操作,但是不能被写操作;同一时刻写锁只能被当前线程写,其它线程不能进行操作。
  8.2 用读写锁实现:ReadWriteLock readWriteLock = new ReenTranReadWriteLock();

9.为什么执行线程方法要通过start()方法调用run()?
  9.1 因为调用start()方法会新建一个线程,然后才会调用run方法,否则就和调用普通方法一样

10.countDownLatch和CyclicBarrier区别
  10.1 countDownLatch:使一个线程等待其它线程工作完以后才恢复工作。通过计数器实现的,我们可以设置线程的个数为计数器的个数,当线程执行完一个则调用countDown()减1,直到计数器变为0,等待的这个线程才能使用await()唤醒线程工作
  10.2 CyclicBarrier:当做个线程同时到达才一起工作,也是通过计数器实现的。

11.什么是不可变对象,它对写并发应用有什么帮助?
  11.1 对象一旦创建,它的属性就不能被改变,不可变对象对于线程来说永远都是安全的。

12.线程的状态
  12.1 初始状态:线程还未开始运行
  12.2 运行状态:又分就绪和运行
  12.3 阻塞状态:多个线程之间抢占锁资源,当前线程没抢到锁,则进入到阻塞状态。
  12.4 等待状态:当前线程已经抢占到了锁,但是在执行业务块的时候,因为等待原因,例如wait()方法导致等待
  12.5 超时等待状态:会按照用户设置的时间等待,不会出现线程阻塞
  12.6 终止状态:线程出现异常或正常执行完的状态

13.BIO和NIO区别
  13.1 BIO:同步线程阻塞,收到请求后就会一直堵塞,直到请求执行完
  13.2 NIO:同步非线程阻塞,收到请求后就会立即返回,然后轮询去检查是否执行完

14.AQS
  14.1 全称:抽象队列同步器
  14.2 思想:如果当前请求的资源是空闲状态,则将当前资源设置为有效的工作线程,并且锁定状态;如果当前资源被其它线程占用,这个时候就需要一套完整的线程堵塞和唤醒的机制,此时会将暂时获取不到锁的线程放入到队列里。

15.synchronized的工作原理
  15.1 通过对二进制文件进行反编译后,会发现被synchronized修饰的代码块,生成了两个指令monitorenter和monitorexit。当线程需要获取对象的锁时,monitorenter指令尝试获取锁,如果获取锁失败了,则进行线程堵塞;如果获取锁成功了,则将计数器加1;如果获取锁的时候,发现当前线程已经获取到锁了(重入锁),那么计数器加1。当代码块执行完以后,monitorexit指令执行时会将计数器减1,直到计数器减为0,则其它线程可以获取到对象的锁。
  15.2 synchronized是悲观锁,不管是否存在资源竞争,都会上锁。

16.用户态和内核态
  16.1 用户态:一个进程在执行用户自己的代码
  16.2 内核态:一个进程调用系统内核的代码

16.jdk对锁的优化(synchronized的加锁方式)
  16.1 前景:jdk6之前,如果需要将线程进行阻塞和唤醒都需要操作系统的协助,需要对用户态和内核态之间进行切换,处理器损耗较大。实现原理完全是使用操作系统的互斥性来实现,就是上面获取/释放锁的逻辑(monitorenter和monitorexit指令)。
  16.2 偏向锁
       a)概念:它会偏向第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程在访问,则线程不会触发同步,也就不会有上锁和解锁的过程,这种情况会给线程加一个偏向锁。如果在多线程情况下,JVM会将持有偏向锁的线程挂起,将其恢复成标准的轻量级锁
       b)实现
      1.首先检查对象头,看是否可以设置偏向锁,如果可以设置,则设置偏向锁
      2.检查当前线程是否指向对象头的线程ID,如果是,则执行同步代码
      3.如果对象头线程ID并未指向当前线程,通过CAS算法竞争锁,如果成功,将对象头的线程ID更新为当前线程ID,则执行同步代码
      4.竞争锁失败,将获得偏向锁的线程挂起,升级为轻量级锁
  16.3 自旋锁
       a)概念:线程在进行阻塞之前,先让线程自旋等待一段时间让其等待锁的释放,可能在等待期间其它线程已经释放锁,这样就避免了让线程进行阻塞。
       b)优缺点
          1.优点:减少线程堵塞,避免用户态和内核态之间切换,增加处理器的耗时
      2.缺点:消耗CPU
       c)那种场景使用:如果持有锁的线程执行时间比较短,比自旋等待的时间短,则建议用这种锁。

  16.4 轻量级锁
       a)概念:在有资源竞争的情况下,线程会尝试着获取锁,如果获取不到才会自旋一定次数后(1.6后JVM自己控制次数),如果还是获取不到锁就会升级为重量级锁
       b)实现
          1.首先会将对象头复制到栈帧中的一个新开辟的空间(锁记录空间)
      2.将对象头的指针指向锁记录空间,如果指向成功,则获取锁成功,执行同步代码
      3.如果指针指向失败,则存在多个资源竞争锁,此时会通过自旋获取锁,如果获取一定次数后还是拿不到锁,则升级为重量级锁
  16.5 重量级锁
       a)概念:每次都执行上锁,同步代码执行完后再解决
       b)实现:通过计数器去实现加解锁,详细可参考步骤15

3.jvm

1.jvm中类的加载过程
  1.1 装载
      a)通过类的全路径名获取到文件的二进制流
      b)将二进制流转换成运行时数据结构
      c)在内存中生成class对象
  1.2 检查(为了保证文件中的字节流不会危害JVM)
      a)文件格式验证:是否符合class文件规范
      b)元数据验证:对字节码进行语义分析,eg:是否继承了不能被继承的类,如A继承了一个接口
      c)字节码验证:整个验证过程中最复杂的一个验证,验证数据流和控制流的分析,确定语义的正确性。eg:类型转换是否正确
      d)符号引用验证:这个过程在后面的解析过程中发生,主要是为了保证解析动作能正常执行(符号引用:通过A指定定位到B指令,B指令记录的是一个指针,最终A指令指向的结果就是一个指针。)
  1.3 准备:给类中的静态常量分配空间(实例变量是在对象实例化的时候在堆上创建),分配空间的动作是在方法区进行的。
  1.4 解析:将符号引用转换成直接应用(这一步可选)
  1.5 初始化:对静态变量和静态代码块进行初始化工作

2.jvm内存分配
  2.1 方法区(Method Area):存放类信息(全路径名、属性、方法名)、常量和静态变量数据
  2.2 堆(heap):唯一目的就是存放对象实例,几乎所有的对象实例(和数组)都在这里分配内存
  2.3 虚拟机栈(Vm Stack):每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用的过程就 对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
  2.4 本地方法栈(Native Method Stack):本地方法栈与Java虚拟机栈非常相似。本地方法栈为虚拟机执行 Native 方法服务
  2.5 程序计数器(Program Counter Register):记录当前虚拟机字节码的地址
  2.6 内存分配
      a)如果开启了逃逸分析(jdk1.6默认开启的),确定一个对象的作用域不会超出方法之外,则在栈上分配。这样对象可以随着栈帧的出栈而销毁,不需要垃圾回收器来回收,减少了GC负载。
      b)如果对象作用域超出方法之外,则会在TLAB(线程本地分配缓存区)上分配。在给对象分配内存的时候,每个线程都有自己的TLAB,TLAB是占用Eden区空间,默认是1%。
      c)如果TLAB空间也不足,则会分配在堆上,如果堆的内存不足,则会触发Minnor GC,GC以后如果空间还是不足,那么就会内存溢出

3.内存溢出
  3.1 StackOverFlowError:栈内存溢出,一般出现在递归调用某个函数
  3.2 OutofMemoryError
      a)Java heap space:堆内存溢出,一般出现在频繁的创建大对象,并且对象没有及时回收,超出堆的内存
      b)GC overhead limit exceeded:GC回收时间过长,并且回收到的内存很小。

4.堆分为新生代和年老代(新生代内存=1/3堆内存,老年代内存=2/3)
  4.1 堆内存大小:新生代占堆内存1/3,老年代占堆内存2/3
  4.2 堆内存分为
       a)新生代
          1.Eden区(8/10新生代空间):一般存放新创建的对象
      2.To区(1/10新生代空间)和From区(1/10新生代空间):这两个区都称为Survivor区大小相同。当我们的新生代在GC(Minnor GC)一次并且存活,年龄计数器加1岁,将对象移动到Survivor区,以后新生代每次GC后存活一次,对象就加1,默认加到15岁(可以修改默认值),就会将对象移动到老年代。另外一种情况,Survivor区年龄相同的所有对象占用的内存大小占Survivor区的一半以上,则将超过年龄大于等于这个的对象全部移到老年代。
       b)老年代
  4.3 堆可以出现在逻辑上连续,而物理上不是连续的内存空间

5.Java堆的结构是什么样子的?什么是堆中的永久代(Perm Genspace)
  5.1 JVM的堆在运行时数据区,堆里面存的是new出来的对象和数据,所有的内存分配都是在堆上进行的
  5.2 堆是在JVM启动的时候创建,堆内存是由GC回收
  5.3 堆上的对象分为存活和死亡的,死亡的是应用访问不到的,存活的是应用会访问到的。死亡的对象只有在GC回收之后才会释放空间

6.GC是什么?为什么要有GC
  6.1 GC是垃圾回收器
  6.2 GC会自动监测超出作用域的对象,达到自动回收的目的
  6.3 GC只有在虚拟机空闲或堆内存不足的情况下,才会自动回收那些没有被引用的对象

7.如果判断一个对象是否存活
  7.1 引用计数器:给每个对象设置一个引用计数器,当对象被引用的时候加1,引用失效后减1。当计数器为0是,GC就会回收这个对象。存在一个循环应用的问题无法解决,所以多数虚拟机不会采用这个方法
  7.2 可达性算法(引用链法):从一个GC ROOTS的对象开始向下搜索,如果没有任何引用,就说明对象不可用。
  注:虽然满足上述条件,GC也不会立即回收对象,需要对对象进行2次标记后才会回收对象。

8.java中存在一直无法回收的对象吗?
  8.1 产生原因:长生命周期的对象引用了短生命周期的对象
  8.2 实例:定义了一个全局的对象A,全局对象A引用了一个对象B,对象B创建了以后就再也没有用过。
  8.3 检查内存泄漏:检查对象A的分支,发现分支对象没有使用,那么A就会存在内存泄漏

9.浅拷贝和深拷贝
  9.1 浅拷贝:对对象中的数据成员进行简单的赋值,如果存在动态的成员或指针就会报错
  9.2 深拷贝:对对对象中的数据成员进行赋值,如果存在动态成员或指针就开辟新的内存空间

10.finalize() 方法什么时候被调用?析构函数 (finalization) 的目的是什么?
  10.1 finalize:GC回收内存之前调用这个方法完成的一些工作。如果内存充足或者虚拟机一直没有空闲的时候,这个方法永远不会被调用
  10.2 主要用途:回收特殊渠道申请的内存,JNI(Jave Native Interface)调用non-java(C或C++),finalize()的工作就是回收这部分的内存

11.如果对象被置为null,GC会立即回收吗?
  不会立即回收,会在下个回收周期回收

12.简述Java 内存分配与回收策率以及Minor GC 和 Major GC(Full GC)。
  12.1 对象分布:对象优先在新生代的Eden区;对象大的直接进老年代;对象存活长的也会进入到老年代
  12.2 GC时间:当Eden区内存满了,会触发一次新生代GC回收(Minor GC),频率高,回收速度快;Full GC是发生在老年代,但是可以通过配置在Full GC之前可以触发一次Minor GC

13.JVM中的永久代总会存在垃圾回收吗?
  13.1 不会存在。如果永久代满了或超过临界值了,会触发一次Full GC。
  13.2 Java 8 中已经移除了永久代,新加了一个叫做元数据区的

14.什么是类加载器?有哪几类加载器
  14.1 概念:通过类的全路径名获取类的二进制流
  14.2 启动类加载器(Bootstrap ClassLoader):用来加载java核心类库,无法被java程序引用
  14.3 扩展类加载器(Extentions ClassLoader):用来加载java的扩展类库。
  14.4 系统加载器(System ClassLoader):根据java类路径加载java类型。一般可以使用ClassLoader.getSystemClassLoader()来获取
  14.5 自定义加载器:继承java.lang.ClassLoader类

15.类加载器双亲委派机制
  15.1 当一个类收到了类加载的请求,不会自己先去加载这个类,会先委派父类去加载,如果父类不能加载,反馈给子类,由子类加载。

16.JVM内存调优
  16.1 注意事项:不能简单的看操作系统和java进程占用的内存,因为GC回收后这个值是不会变的。因此内存调优更多看的是jdk自带的一些工具,eg:jconsole、java visualVM
  16.2 调优目的:减少GC的频率和Full GC的次数,过多的GC和Full GC会占用过多的CPU资源,影响系统吞吐量。
  16.3 导致FULL GC的情况:
        a)老年代空间不足
       1.尽量让对象在新生代GC时被回收
       2.让对象在新生代多存活一段时间
       3.不要创建过大的对象和数据,因为过大对象或数组会直接进入到老年代
        b)Pemanet Generation空间不足
       1.增加Perm Gen空间,避免太多静态对象
       2.控制好新生代和老年代的比例
        c)system.gc()显示调用:请求jvm调用垃圾回收
    d)选择合适的垃圾回收策略
  16.4 调优方法
        a)合理设置新生代大小、老年代大小(一般新生代1/3堆内存大小,老年代2/3大小)
       1.新生代设置太小,会造成频繁Minor GC
       2.新生代设置太大,会造成老年代太小,老年代太小就可能会造成触发FULL GC
       3.Survivor区设置太小,当Survivor中的对象还没超过年龄,里面的对象就不会进入到老年代,此时Eden中对象可能因为内存满了,会直接将对象移到老年代
       4.survivor区设置太大,Eden区就会很小,这样加快新生代GC回收的频率
        b)选择合理的垃圾回收策略
       1.串行收集器:
       2.并行收集器:
       3.并行年老代收集器:
       4.并发收集器:
     
17.jdk、jre和jvm
  17.1 jdk:java开发工具包,eg:javac.exe、java.exe、jar.exe
  17.2 jre:JVM的标准实现和java的一些基本类库
  17.3 jvm:识别class文件中的字节码,调用操作系统的API完成结果的输出

18.Survivor区为什么要划分from和to区
  18.1 原因:解决内存碎片化造成分配堆内存没有得到充足的使用 
  18.2 分析:当Eden区中的对象经过了第一次Minor GC后,会将还在引用的对象从Eden区里面的对象复制到Survivor s0区(from区),年龄计数器会自动加1;下次再进行Minor GC的时候,Eden区和S0区清空,将Eden区和S0区的对象复制到S1,如此来回反复,保证Eden区和Survivor区的内存不会存在碎片化。

19.指令重排序
  19.1 概念:计算机指令在执行的过程中,实际的指令执行顺序和我们代码编写的顺序会存在差异,为了提高执行的执行效率,对相互间没有依赖的指令顺序进行重新编译。
  19.2 实例
       a)代码顺序:int a = 10;int b = 15,int c = a + 5;
       b)正常执行顺序(按照代码顺序执行)
          1.加载变量a,赋值100,存储a
      2.加载变量b,赋值15,存储b
      3.加载变量a和c,对a执行加5操作,存储c
       c)指令重排顺序(通过分析发现变量a加载2次,降低了执行效率,可以只加载1次即可,顺序可调整为:int a = 10;int c = a + 5;int b = 5)
          1.加载变量a和c,对a赋值10,和5进行加法运算后,存储c
      2.加载变量b,赋值5,存储b
  19.3 多线程情况下的问题
       a)问题:影响执行结果的正确性
       b)示例(假设初始值a = 0,flag = false,预期B方法执行结果是15,即A方法执行完后,再执行B方法)
          1.方法A的方法体:int a = 5;boolean flag = true;方法B的方法体:if(flag){int a = a + 10}
      2.指令重排:优先执行boolean flag = true;然后执行int a = 5;当第一个线程调用方法A,执行boolean flag = true,还未执行int a = 5,第二个线程执行方法B,执行后的结果a = 10,和预期结果不一致
    
20.内存溢出和内存泄漏区别
  20.1 内存溢出:内存大小设置不合理,导致内存不够用
  20.2 内存泄漏:对象使用过程中未及时释放,导致内存一直被占用

21.执行引擎
  21.1 解释器
       a)作用:将字节码转换成机器码(二进制串)
       b)流程:jvm启动的时候,会根据热点算法对每一行字节码进行计数,当达到一定次数后,认为这段代码就是热点代码,将热点代码交给编译器处理,下次再遇到这个字节码的时候,直接将其交给编译器进行处理。如果不是热点代码,会找到对应的机器码后,然后执行机器码,效率比较差。
  21.2 编译器:针对热点代码,经过中间代码生成器、代码优化器和目标代码生成器将其生成优化后的机器码,提供执行效率。
  21.3 垃圾回收器

22.java代码执行流程
  22.1 词法分析--》语法分析--》语法\抽象语法树--》语义分析--》注解抽象语法树--》字节码生成器--》生成字节码

4.算法和数据结构

1.常用算法
  参见整理的数据结构思维导图
  1.1 查找算法
       a)顺序查找:从头到尾遍历一遍
       b)折半查找:又称二分查找,假设表中元素是一个已经排好序的集合,待查元素和表中中间位置的元素对比,等于中间元素,则查找结束;小于中间元素,则待查元素属于左侧集合,接着将待查元素和左侧集合中的中间位置对比,依次按照这个逻辑查找,直到找到这个元素位置。时间复杂度:二分查找每次排除掉一半的不适合值,第一次二分剩下n/2次,第二次二分剩下n/2/2=n/4=n/(2^2),m次二分剩下n/2^m,最坏的情况是排除到最后一次结果才找到元素n/2^m=1,即n=2^m,复杂度就是log2(n)
       c)二叉排序树查找:左子树上的子结点均小于根结点,右子树上的子节点均大于右根结点,它的左右子树也分别为二叉排序树。待查元素如果是等于根结点,则查找结束;待查元素如果是小于根结点,则数据查询范围在左子树范围,反之在右子树范围。
       d)哈希散列法:根据key值和哈希函数定义一张散列表(哈希表),然后根据键值去散列表中定位到数据的位置
  1.2 排序算法
       a)直接插入排序:假设待排序集合中第一个元素为已经排好序的集合,将第二个元素和已排序集合对比,插入到待已排序集合中,此时已排序集合中就有2个元素,接着第3个元素和已排序集合对比,插入到已排序集合中,依次类推直到最后一个元素插入到已排序集合中。时间复杂度:最好的情况是待排序集合已经是有序集合,需要对比n-1次,不需要移动元素,时间复杂度O(n);最坏的结果是待排序集合都是逆序,需要对比n-1次,需要移动n-1,夹在一起就是O(n^2)
       b)sheel希尔排序:假设待排序元素为n(n=8),先计算出增量序列n/2,一直计算到序列=1位置,此处增量序列为【8/2=4,8/2/2=2,8/2/2/2=1】,即4,2,1.按照增量4划分成多个子集合,每个集合由4个元素组成,相邻集合中下标相同的元素进行插入排序,同理增量2也是这个逻辑,最后增量1执行直接插入排序的逻辑。时间复杂度:和增量序列有关系,通常是O(n),是不稳定的排序算法
       c)简单选择排序:每次都是从未排序集合中选择最小/大元素和已经排序的集合中最后一个元素对比,知道未排序集合中元素都已经排序。时间复杂度:最好情况就是已经排好序,不需要移动。通常最坏的情况选择次数是O(n^2),比较次数是O(n)
       d)冒泡排序:从第一个元素开始,将其和第二个元素对比,将大的元素移到后面,接着第二个和第三个对比,将大的元素移动到后面,一轮结束后最后一个元素就是最大的元素,又重复前面的操作,从第一个元素开始比较,只是此时最后一次比较就是和倒数第二个元素对比,依次类推直到所有的元素都排序好。时间复杂度:比较次数是(n-1)+(n-2)+……+2+1=n*(n-1)/2,所以时间复杂度是O(n^2)
       e)快速排序:设置第一个元素为基准值,设置第二个元素为低位,最后一个元素为高位。从高位上的元素开始往前查找,找到第一个比基准值小的元素,高位的下标更新为找到的这个元素的下标,将其和基准值位置互换,紧接着从低位上的元素开始往后查找,找到第一个比基准值大的元素,低位下标更新为找到的这个元素的下标,将其和基准值位置互换;接着又从高位开始往低位查找,直到最后低位和高位相邻,也就是找到最后一个元素,此时基准值左侧的元素都比基准值小,基准值右侧的元素都比基准值大。最后使用递归调用基准值左侧和右侧的子集合进行排序。时间复杂度:最坏的情况就是基准值右侧的集合是已经排好序的集合,此时查询次数(n-1)+(n-2)+……+2+1=n*(n-1)/2,所以时间复杂度是O(n^2)
       f)归并排序:采用的是分治法的思想,将待排序集合划分为多个子集合。时间复杂度:第一次n/2,第二次n/4=n/2^2,m次就是n/2^m,假设第m次就是将集合拆分成一个个的,即n/2^m=1,即第m次复杂度是log2^n,那一共有n次就是n*log2^n
       d)堆排序:先按照完全二叉树的建立规则,将排序元素初始化成完全二叉树。找到最后一个叶子节点的父子节点,将父节点和叶子节点对比,组成大顶堆(父节点比它的子节点都大称为大顶堆,反之为小顶堆),当子树发生变化后,上级子树也要按照这个规则做调整。

2.完全二叉树、满二叉树、平衡二叉树、B树
  2.1 完全二叉树:一个深度为k有n个节点的二叉树,从上往下从左往右依次编号,除了第k层外,其它层的节点都达到了最大个数,第k层的节点都集中在最左边
  2.2 满二叉树:深度为k,每一层都达到了最大节点数,有2^k-1个节点
  2.3 平衡二叉树:任意节点的子树高度都小于等于1
  2.4 哈夫曼树(最优二叉树):是带权路径长度最短的树,权值较大的结点离根较近。
  2.5 B树
       a)所有的叶子节点都在同一层
       b)节点关键字都遵循左小右大的原则
       c)查找原理:eg:获取根结点关键字进行比较,如果比根结点小,则在左子节点(eg:左子节点存了2个节点B和E),如果待查的节点比B节点小,则待查元素在左侧;比B节点大比E节点小,则指向B和E中间的节点;比E节点大,则执行E节点右侧节点;同理,知道查到到待查元素结束

3.如何判断一个单链表是否是环形链表
  判断依据:最后一个结点的指针指向首节点的位置,但是因为是环形表,所以没办法判断哪个是首节点哪个是尾结点,所以只能想办法找到链表中是否有结点的下一个指针是空
  3.1 哈希表法
       a)将链表中的元素,从前到后依次遍历一遍,将未出现过的节点存储到map中
       b)由上会出现两种情况:如果某个节点的指针是null,说明是单链表;如果某个节点已经在map中出现过,说明是环形链表
  3.2 快慢指针法
       a)链表在遍历的过程中,用两个指针,快指针每次遍历结点的时候,指针跳一个结点,慢指针每次遍历的时候,一次只走一步
       b)当快指针或慢指针有一个出现null,说明是单链表
       c)当快指针和慢指针出现重叠的时候,说明是环形表
       d)之所以用快慢指针是为了解决死循环,因为在遍历的过程中,总会有重叠的时候,此时可以跳出循环

4.数组和链表的区别
  4.1 数组查询的时候,数组是根据索引能直接定位到元素,链表是遍历表中每个元素,将元素的key和待查元素对比,发现一致则查询结束,数组查询速度比链表快
  4.2 数据插入/删除效率比链表低。插入元素的时候,数组需要将插入元素之后的数据全部往后移动;删除元素的时候,需要将删除元素之后的数据往前移动,链表不存在这类问题,只需要修改相邻元素的指针即可
  4.3 数组的存储空间在物理地址上是连续的,链表是逻辑连续物理上没有要求
  4.4 数组需要初始化大小,不便于扩展。eg:提前定义数据大小为10,来了第11条数据则需要重新定义一个数组,链表不存在这类问题
  4.5 时间复杂度有区别

5.用链表实现栈
  栈:先进后出
  5.1 进栈操作(在首节点位置插入数据):将需要插入的元素作为头节点存储,插入元素的指针指向原头结点
  5.2 出栈操作:(首节点的指针往右移动即可):将链表的头指针移动到下一个节点上即可
  5.3 获取栈顶元素:返回头节点值即可

6.一致性hash算法
  6.1 应用场景:假设我们要将图片上传到服务器上,现在服务器做了集群部署,如果每台服务器都上传,则浪费服务器磁盘空间。那么,我们可以通过文件的名称和服务器的台数取模,计算出来的模值就是具体哪台服务器。下次需要获取图片,就仍然按照这个方法计算出图片上传到了哪台服务器上,直接去这台服务器上获取图片。但是现在我们又增加了两台服务器,如果按照之前的计算规则查询图片上传到哪台服务器,那肯定就会出错。因为服务器台数发生了变更,取模的值也会发生变更,这样原本存在A服务器上的图片,计算出来的模值可能是指向B服务器,就会找不到图片。
  6.2 解决办法
       a)原来计算规则:图片名字取哈希值,和服务器台数取模,根据取模的值判断图片缓存在哪台服务器
       b)现在计算规则:我们想象有一个圆环,圆环上的点是顺时针从1开始,一直到2^32-1个结束。我们将每台服务器的IP取哈希值和2^32取模,得出每台服务器在圆环上的分布位置。那么图片缓存的位置,可以根据图片的名称取哈希值后,和2^32取模得出。这样图片的位置肯定也会分布在这个圆环上,那么图片属于哪台服务器就看,这个图片顺时针看遇到的第一台服务器就是图片缓存的服务器位置。假设现在增加了一台服务器,那么就根据新的ip和2^32取模,计算出新的服务器分布在哪个位置上,相应图片的位置就重新划分,这样只影响一小部分服务器上的缓存。
       c)存在问题:服务器分布不均匀,造成图片不是均匀的缓存在每台服务器上。可以通过增加虚拟节点,例如,围着圆环上A/B/C服务器节点后面,顺时针接着追加A/B/C虚拟节点,这些虚拟节点对应的就是真实的服务器节点。虚拟节点追加越多,图片分布越均匀。

7.实现LRU
  如果一个数据最近没有被访问到,那么在将来访问的可能性都很小,这个时候如果空间满了会先将这种数据给移除掉
  7.1 使用链表实现,新进来的数据都在链表头部新增,当链表满了以后,就删除链表尾部数据
  7.2 用数组存储,每次给这条数据设置一个版本号,新进来的数据版本号设置为0,新进来一条数据,将旧的数据版本号加1,当数据满了就从最大的版本号开始删除数据

8.合并两个有序链表
  8.1 归并算法
  8.2 小顶堆

9.CAS算法
  9.1 CompareAndSwap:比较并替换。它有3个操作数,内存地址V,旧的预期值是A,新的预期值是B,如果需要将A修改成B,需要比较内存中的V和A是否是同一个,如果相同,则修改,否则就什么都不做。

10.删除链表中某个节点(这个节点不是首尾节点)
  10.1 思路:通常情况下,如果要删除某个节点,我们只需要将这个节点之前的节点指针指向到删除的节点的下一个节点即可,但是实际操作中我们是无法知道需要删除的这个节点之前的这个节点。基于这个考虑,我们可以将需要删除的这个节点替换这个节点的下一个节点,然后将需要删除的这个节点删除。

11.获取两个有序集合的中位数
  11.1 首先将两个有序集合合并成一个集合,并进行排序
  11.2 使用二分查找算法,将集合分为2个集合,如果两个集合长度相等,则中位数=(A集合.最后一个元素+B集合.第一个元素)/2;如果A集合比B集合多一个元素,则中位数为A集合最后一个元素;如果A集合比B集合少一个元素,则中位数为B集合第一个元素

12.一个数组,找出最大的两数之和,且这两个数间隔大于等于2
  12.1 思路
       a)首先遍历整个数组,找到每个数字的取值范围,记录这个数组的下标。eg:第一位数可以和哪些位置上的数字进行对比,
       b)对每个操作数的取值范围进行排序,取每个数字和取值范围排序后的集合中最后一个元素进行相加操作,并记录下来。
       c)最后针对每个操作数对应的计算结果对比,取最大的值

13.按照权重实现负载均衡(A服务器命中率50%,B服务器30%,C服务器20%)
  12.1 思路
       a)将A、B、C服务器的ip按照50%、30%、20%的比例存储到HashMap中
       b)

5.数据库

1.sql优化
  1.1 避免在where字句对有索引的字段进行运算,会导致索引失效,进行全表扫描
  1.2 外键必须加索引
  1.3 在wehre和order by涉及的列上加索引,尽量避免全表扫描
  1.4 在设计表时要避免字段出现null的情况,通常在建表或程序里设置默认值
  1.5 SQL语句尽量大写,因为sql引擎在执行的过程中会将sql转换成大写
  1.6 尽量避免写select *,做到按需查找字段,减少数据库解析sql语句的时间

2.索引
  2.1 定义
      表的索引目录,在查找内容之前先查目录中索引位置,从而快速定位到查询数据
  2.2 优点
      a)创建唯一索引,保证数据的唯一性;
      b)使用索引,加大检索速度;
      c)使用索引能直接定位到查询的数据,减少磁盘IO操作
  2.3 缺点
      a)创建索引和维护索引都需要耗时,会随着数据的增加而增加;
      b)索引需要占用额外的物理空间
      c)对表中的数据进行增删改的时候,索引也要动态维护(以外键为例),降低了维护速度

3.分区和分表
  3.1 分表:按照一定的规则将一些大表拆分多张小表,数据库中看到的是多张表。
  3.2 分区:按照一定的规则将一张大表划分成不同的区块,数据库中看到的是一张表。

4.防止sql注入
  4.1 前台js对特殊字符进行屏蔽处理(eg:or、单引号、=等)
  4.2 后台可以增加拦截器对特殊字符进行处理

5.数据库范式
  5.1 1NF:强调列的原子性,即列不能再被拆分。(eg:手机号就是不能再被拆分、姓名可以被拆分成姓氏和名)
  5.2 2NF:非主属性列完全依赖主属性,且存在传递依赖
  5.3 3NF:非主属性列完全依赖主属性,不存在传递依赖

6.oracle和mysql区别
  6.1 oracle没有自增的数据类型,需要借助序列完成自增;mysql有自增类型的数据类型
  6.2 mysql针对字符串使用双引号,oracle使用的单引号
  6.3 分页sql:mysql使用方法简单,直接用limit即可;oracle翻译比较麻烦,使用rownum
  6.4 针对长字符串的处理,oracle单个字符串最大支持4000个字符,超出这个范围使用clob
  6.5 like的区别,使用like '%内容%',mysql是走索引的,oracle是不走索引

7.事务的特性(ACID)
  7.1 原子性:事务是一个不可分割的单位,一个事务中的操作要么都成功,要么都失败
  7.2 一致性:事务必须使数据库从一个一致性状态变换为另外一个一致性状态(参考银行转换,A账户往B账户转钱,A账户的钱减少,B账户的增加,两边操作必须保持一致)
  7.3 隔离性:多个事务之间的操作是相互隔离的,不存在相互干扰
  7.4 持久性:一个事务一旦提交,它对数据库的改变就是永久性的

8.事务的隔离级别
  不可重复读和幻读都是一个事务中两次读取的结果不一致,前者强调的列的变化,后者强调的是行的变化。
  8.1 读未提交(read_uncommited):一个事务读取到了另外一个事务未提交的数据。不能避免脏读、不可重复读、幻读,并发性能最高。
  8.2 读已提交(read_commited):一个事务不可能读另外一个事务未提交的数据。可以避免脏读,不可能避免不可重复读、幻读。
  8.3 可重复读(repeatable_read):能避免脏读、不可重复读,不能避免幻读
  8.4 串行化(serializable):隔离级别最高,消耗资源最低,能避免脏读、不可重复读、幻读

9.事务的传播属性(spring默认事务是使用数据库的事务,mysql是可重复读、oracle是读已提交)
  支持当前事务的情况:
  9.1 propagation_required:如果一个事务存在,则支持当前事务,如果不存在,则创建新的事务
  9.2 propagation_supports:如果一个事务存在,则支持当前事务,如果不存在,则非事务的方法运行
  9.3 propagation_mendatory:如果一个事务存在,则支持当前事务,如果不存在,则抛出异常
  不支持当前事务的情况:
  9.4 propagation_requires_new:总是要开启一个新的事务,如果事务存在,将该事务挂起
  9.5 propagation_not_supported:总是非事务方法运行,并挂起所有的事务
  9.6 propagation_never:总是非事务方法运行,如果事务存在则抛出异常
  其它情况:
  9.7 propagation_nested:某一个事务存在,则运行在一个嵌套的事务中

10.数据库的索引结构
  10.1 为什么要使用索引
       a)使用唯一索引保证数据库中每一行数据的唯一性
       b)使用索引提升查询速度,因为可以减少检索的数据量
       c)随机性IO变为顺序IO
       d)可以加速多表之间的连接,提升性能
  10.2 为什么不对表中的每一个列创建一个索引
       a)索引除了占用表空间外,还需要占用一部分的物理空间
       b)创建索引和维护索引会随着数据量的增加而变得越来越耗时
       c)更新和删除数据,也要动态的维护索引,降低了数据的维护速度
  10.3 如何提高查询速度:将无序的数据调整成相对有序
  10.4 注意事项
       a)where条件中不要用函数,避免无法命中索引
       b)尽量使用自增主键,不要使用业务主键
       c)索引列设置默认值,禁止使用null,因为null比空字符串占用更大的空间
       d)删除长期不使用是索引,造成不必要的性能损耗
  10.5 mysql主要使用的两种数据结构(https://blog.csdn.net/zhou920786312/article/details/72714422)
       a)哈希索引:底层数据结构就是哈希表,针对单条记录查询的时候可以用哈希索引,查询性能最快。
       b)B树索引
          1)B树索引结构类似于一颗树,主要数据集中在叶子节点上
          2)如果是主键索引,叶子节点上存储的是记录行rowid对应的物理地址+索引的列值;基于二分查找的概念,根结点左侧的子节点都比根结点小,根结点右侧的子节点都比根结点大,当需要根据索引去检索数据的时候,会去看索引的列值是在左子树范围还是,还是右子树范围,定位索引值对应的具体位置后,就可以找到记录行的具体的物理地址,根据物理地址就能获取到这一行的数据。
      3)如果是非主键索引,叶子节点存储的是主键+索引的列值。根据索引列值查询索引的时候,首先根据列值定位到索引的具体位置,根据位置上存储的主键查询这一行的数据。
  10.6 覆盖索引:一个索引包含所有需要查询的字段的值,称为覆盖索引。因为根据非主键索引查询数据的时候,我们根据索引的列值找到索引所在的位置,这个位置上存的是索引的列值+主键,数据库引擎会根据主键再次查询一下这条 记录,称之会回表,这样查询速度较慢。覆盖索引就是将查询出来的字段加入到索引中去,保证索引中包含所有需要查询的字段的值。

11.mysql怎么避免不可重复读和脏读的问题
  11.1 mysql默认是可重复读
  11.2 大部分数据库都是基于乐观锁为基础的MVCC(Multi-Version Concurrency Control多版本控制并发)来避免不可重复读和脏读问题
  11.3 针对每条数据都有默认的两列,一个创建的版本号、一个删除的版本号
       a)新增数据的时候,会有个默认的版本号(假设创建版本号为1)
       b)更新数据的时候,先将当前数据的删除版本号+1,标记为删除(删除版本号为2);然后再新增一条数据,新增数据的版本号在原有版本号的基础上加1(创建版本号变为2)
       c)删除数据的时候,直接将当前事务的版本号更新为删除的版本号
       d)查询数据的时候,为了避免查询到已经删除的数据或其它事务修改过的数据,必须满足条件为:当前事务的版本号大于或等于创建的版本号,小于删除的版本号

12.死锁问题产生原因,怎么避免?
  12.1 产生:两个线程发生相互等待,线程A占用资源a,等待资源b,线程B占有b,等待a
  12.2 避免
       a)一次性分配资源
       b)银行家算法:在每次访问资源之前,先检查所有的资源是否都满足采取执行。

13.InnoDB和MyISAM区别
  13.1 事务
       a)InnoDB支持事务:每条sql默认分装成sql,自动提交,影响速度。多条数据如果需要放在一个事物,可以用begin和commit
       b)MyISAM不支持事务
  13.2 外键
       a)InnoDB支持外键:一个包含外键的InnoDB表转换成MyISAM表会失败
       b)MyISAM不支持外键
  13.3 索引结构
       a)InnoDB是聚集索引,数据文件和索引是绑定在一起的。主键索引中记录数据文件和索引列值,根据记录行的物理地址找到我们的记录。非主键索引记录的是主键和索引的列值,根据主键再次查询数据。
       b)MyISAM是非聚集索引。数据文件和索引是分开存在的。叶子节点是存的数据文件的地址指针。

14.mysql工作原理
  14.1 查询原理
       a)查询请求会放在连接池中,并且交由处理器进行管理
       b)当请求从待处理进入到处理队列,管理器会将sql给到SQL接口
       c)SQL接口会去缓存中看是否有这条sql(通过hash算法),如果有这条sql直接返回结果,否则执行d)
       d)SQL接口将sql给到解释器,解释器对SQL语法进行检查
       e)解释器处理完以后,会将sql交给优化器,他会产生多种优化计划,选择最合适的一条计划执行
       f)确定了执行计划以后,SQL语句交给数据库引擎处理,处理后的结果返回回去
  14.2 数据库引擎如何缓存数据:存储引擎处理完数据以后,会将查询结果返回回去的同时,将查询结果、sql进行hash,保留在cache中,留待下次查询
  14.3 buffer与cache的区别:buffer是写缓存,cache是读缓存

15.mysql的悲观锁和乐观锁
  15.1 悲观锁
       a)共享锁:在sql语句后面加上lock in share mode。自身和其他人都可以读这这条数据,但是不能修改这条数据,会报锁等待超时。
       b)排它锁:在sql语句后面加上for update。自身可以对其进行增删改查,其它人不能对其做任何操作。
  15.2 乐观锁:数据库中增加一个版本号的字段,每次更新数据库增加这个版本号

16.hash索引优缺点
  16.1 优点:如果元素存储的时候,哈希值几乎不冲突,那么定位数据性能很高,O(1)
  16.2 缺点:不支持范围查找(查找原理和hashMap一样)

17.b树和b+树区别
  17.1 b树最后一级的叶子节点,如果不是同一个父类下面的叶子节点之前没有维护指针,b+树维护了指针
  17.2 每一层的叶子节点存储空间是16K,如果父类节点下面的子节点分叉越多,那么就意味着这棵树的高度越低,高度越低,那就意味着查询的次数就越少,性能会更好,所以b+树的非叶子节点只存储了索引列值,没有存储对应的数据。而b树是每一层的节点都存了索引列值和数据,因为一层只能存16K数据,所以一层存放的节点就会少,树的高度就高,查询次数就会多。

18.联合索引底层结构
  18.1 和普通的非主键索引存储结构一样,采用B+树结构存储,即叶子节点存储的是索引列值和主键,索引列值是联合索引的列值(多个字段的值)。需要注意的点是查询的过程中采用最左策略,即先比较第一个索引的列值,如果相等,则比较第二个值,依次类推,知道找对需要查找的值。
  18.2 示例:字段A=? and 字段B=?,则先根据字段A去索引列值中比较,A相等的话,再找B的索引列值
  18.3 注意:假设A、B、C是联合索引,查询的时候,只有以下情况才走索引(最左前缀法则)
       a)根据A查找
       b)根据A和B查找
       c)根据A/B/C查找走索引
       注:根据A/C查找,只有A走索引,C是不走的,因为支持最左前缀法则,先比较A,在比较B,再比较C,这种情况下A是走索引的,因为缺少B,所以C是不走的。索引的列值排序的过程中,是先根据A排序,再根据B排序,依次类推。
  
19.mysql为什么建议用自增主键
  19.1 这个和索引存储的数据结构有关系,如果主键是自增,那么数据插入后,响应的索引也会动态维护,维护的过程中叶子节点是插入到B+树的最后一个结点中。如果是非自增索引,例如用uuid,那么响应的叶子节点就会插入到树的中间,插入后整颗树为了保持平衡,需要继续动态调整,影响效率。

6.redis专题栏

1.redis常见问题
  1.1 缓存穿透问题
      问题描述:查询一条数据库和缓存中都没有的数据,如果一直访问这条数据,就会对数据库造成压力,这种现场称为缓存穿透问题
      解决办法:针对这类数据,可以在缓存中设置一个标识(eg:标记为false),查询redis的时候,可以根据这个标识返回一个空的给调用方,就避免了反复查数据库。当这条数据在数据库中存在后或发生变更后再更新缓存。  
  1.2 并发问题
      问题描述:当我们大量访问都是查询一个缓存中没有的数据时,这样就会都去数据库中进行查询,可能会造成数据库的宕机
      解决办法:查询的时候,加一个同步锁,只有第一次读取这条数据并且返回到redis以后才能查询缓存
  1.3 雪崩问题
      问题描述:大量数据访问时间失效,就会查询数据库,第一台数据库崩溃了,访问就会查询第二台,第二台数据库也会崩溃
      解决办法:访问失效有效期合理设置,可以在访问量较少的时候失效
  1.4 高并发快原因
      a)CPU不是redis的瓶颈,内存和网络带宽才是最有可能的瓶颈,所以redis是单线程的。单线程结构避免了多个线程上下文切换和资源竞争带来的开销。
      b)redis是纯内存数据库,为了提高读写的性能,所有的数据都会写入到内存中,同时异步写入到磁盘中。如果不将数据写入到内存中,磁盘的I/O速度严重影响redis性能。
      c)redis支持主从的模式,主数据库数据会同步到从数据库上。我们可以利用主数据库插入数据,从数据库查询数据,提供访问性能。
  1.5 redis cluster集群如何实现高可用
      a)redis从主从---》哨兵---》集群不断完善。主从解决读写分离问题,但是无法解决主从故障自动转移问题(有一个redis服务挂掉,不能自动切换到另外一台redis服务上)。哨兵能管理多个主从同步方案,实现主从故障自动转移,但是无法解决单个节点性能问题,集群则能解决前面的所有问题。
      b)读写分离、主从复制、分布式集群

2.redis持久化
  2.1 快照持久化
      是默认开启的,会自动保存数据,当启动时会在文件夹中生成dump.rdb文件,存放持久化后的数据。当然我们也可以设置持久化的频率,在redis.conf文件中通过save进行设置。如果频繁的快照持久化,会降低性能和效率。
      存在问题:数据化未及时持久化,在提交之前停电了,这样就会丢失掉这些数据
  2.2 精细持久化
      把用户执行的每个“写”指令(添加/修改/删除)都备份到文件中,还原数据的时候就是执行具体写指令而已。AOF持久化开启之后,之前的数据都会丢失,所以要在一开始使用时就要配置好。开启的方法就是在配置redis.conf,将appendonly 改为yes,同时还可以更改文件名称,然后重新启动服务,这时精细化持久化就启动好了。

3.redis主从同步/复制原理
  3.1 redis只能有一个主数据库,可以有多个从数据库,只需要配置从数据库的redis.conf文件,指定主数据库的ip和端口,及用户密码
  3.2 主从复制分为全量同步和增量同步
      a)全量同步
         1.当一个从数据库启动时,会向主数据库发出同步请求
     2.主数据库接收到请求后,在后台对主数据库数据进行快照保存,并且在缓存记录区记录后续的所有写操作
     3.主服务器保存完快照后,会给从服务器发送快照信息
     4.从服务器接收到快照信息后,会覆盖掉从数据库中当前所有的数据。
     5.主服务器发送快照完毕后,会将缓存记录区里面的所有写命令发送给从服务器
     6.从服务器接收到缓存记录区里面的写命令后,确认之前的快照保存完毕后,执行写命令同步最新的数据
      b)增量同步
         从数据库初始化完成后,主数据库的每次完成写入操作的同时,都会给从数据库发送一次写操作命令,完成从数据库的写操作。

4.redis和memcached区别
  4.1 redis会将数据持久化到文件中,memcached只能将数据放在内存中。
  4.2 一旦停电,redis从一定程度上来说是避免了数据丢失(只丢失未持久化的那一部分数据),memcached直接全部丢失。
  4.3 redis支持的数据格式比较多,支持string、set、list、sortd set、hash等,memcached只支持string。
  4.4 设置过期时间不同,redis是通过expire来设置,memcached是设置key的时候直接设置【memcache.set(key,value,new Date(50)】

5.redis应用场景
  5.1 解决session共享
  5.2 缓存锁
  5.3 普通存储
  5.4 redis计数器

6.redis线程竞争
  6.1 概念:多个线程间,对同一个key进行写入操作,造成写入顺序不可控
  6.2 解决
        a)使用redis分布式锁(setnx),保证同一个时间点只有一个线程可以写入成功
        b)借鉴cas原理,保存一个时间戳,每次更新之前先比较传过来的时间戳和redis中的时间戳大小,如果时间戳比缓存中存在的时间戳新,则更新,否则跳出
        c)使用消息队列,保证消息的有序消费

7.redis过期策略
  7.1 定时删除策略
      a)redis会将设置了过期时间的key放入到一个独立在字典里,每过100ms后,定时扫描key
      b)随机抽取20个key,找到里面过期的key
      c)删除过期的key,如果过期的key超过1/4,则继续执行步骤b
      d)为了避免每次扫描的过期key都超过1/4,从而出现不停循环卡死线程,redis对扫描时间增加了上限时间,默认是25ms
  7.2 从库的过期策略
      a)主库key到期后,会在AOF文件中增加一条del指令,同步到所有的从库,从库通过这条指令删除过期的key
      b)因为指令同步是异步进行的,会在短期内出现主从不同步的情况。

7.消息中间件

1.消息队列MQ的套路
  1.1 好处
       a)针对高并发场景可以起到削峰的作用,可以缓解数据库瞬时的压力,通过消息队列提升系统的响应速度
       b)降低系统耦合性,扩展性好。生产者不用关心,有多少个消费者,可根据实际业务需要增加消费者
  1.2 工作原理
       a)生产者生产消息,发送给消息队列服务器
       b)消息队列服务器接收到消息后,如果是部署的集群,则会将消息的副本备份到每台服务上,备份成功后消息队列服务器就会给生产者服务器发送一个回执
       c)消息队列服务器存储好消息后,会给消费者推送消息,消费者接收到消息后,如果消费都是正常会自动回执给到消息队列服务器
       d)消息队列服务器删除存储的消息
  1.2 问题
       a)使用消息队列后,需要调整我们的业务逻辑。原本同步返回业务处理结果的,需要更新业务逻辑为异步通知调用者结果
       b)丢消息
          1)生产者可能由于网络问题,消息没有发送出去。解决方案:此时生产者(消息中间件)会自动触发重试机制。
      2)消息队列服务器接收到消息后,保存副本消息失败。解决方案:通常情况下,我们需要通过集群部署,保存多个副本文件。eg:kafka中某个服务器宕机了,他会有个选举机制,从剩下的机器中选择一个leader负责消息的写入以及协调其它机器的数据备份。可以通过配置,设置所有的机器备份成功后,才视作消息队列服务器接收消息成功。
      3)消费者在消费的过程中遇到服务重启丢失消息。解决方案:比较暴力的做法:kafka是支持事务的,当我们消费成功后才提交我们的事务,如果消费不成功就不提交事务。但是这种做法会存在问题,如果消费一直不成功,就会造成消息拥堵,所以这种做法需要结合实际业务来。推荐做法是,不论消费成功与否,可以给生产者发送一个回执。
       c)消息重复发送。解决方案:kafka针对每条消息会生成一个消息id,消费者可以根据这个id判断唯一性,也可以在业务上加乐观锁。
       d)对同一条数据进行多次变更,怎么保证从消息队列里拿到的数据按顺序执行。解决方案:通常kafka的做法是,发送消息的时候,针对同一条数据,不管变更多少次,可以设置一个固定的key,kafka底层会通过这个key计算出哈希值,根据这个哈希值和分区取模,计算出消息应该分发到哪台机器上。
       e)一致性问题。解决方案:可通过定时对账检查系统的一致性
       g)高可用性如何保证。解决方案:一般部署集群服务
       h)消息队列满了以后该怎么处理。解决方案:kafka支持动态控制,例如磁盘空间达到了80%,清楚超过超时时间的数据。
       i)有几百万消息持续积压几小时,说说怎么解决。解决方案:新建一个临时的topic,这个topic可以扩多个分区,原有的topic收到消息后将请求转发到这个topic上。另外如果条件允许的话,可以增加应用的服务节点。

2.kafka原理
  2.1 组成
      a)以topic为单位来汇总消息
      b)以集群方式运行,一台机器称为一个broker
      c)一个topic对应多个partition,同一个partition中的消息是有序的,kafka可通过partition实现负载均衡,一个partition对应一个broker
  2.2 怎么判断节点还活着:kafka将每个结点注册到zookeeper中,zookeeper会通过心跳连接检查节点是否存活

3.kafka consumer是否可以消费指定分区消息
  3.1 consumer发出fetch命令给broker的时候,可以设置偏移量offset,从指定位置开始消费消息

4.kafka消息是采用的pull还是push机制
  4.1 producer将消息推送到broker中,consumer从broker中拉消息
 

8.框架

1.springMVC运行原理
  1.1 用户发送一个请求过来,前端控制器DispatcherServlet接收到请求
  1.2 前段控制器DispatcherServlet请求处理器映射器HandlerMapping去查找处理器controller
  1.3 处理器映射器HandlerMapping返回处理器给前端控制器DispatcherServlet
  1.4 前段控制器DispatcherServlet调用处理器适配器HandlerAdapter执行处理器controller
  1.5 处理器执行完以后,返回ModelAndView,处理器适配器HanderAdapter将ModelAndView返回给前端控制器
  1.6 前端控制器DispatcherServlet请求视图解析器ViewResolver解析视图
  1.7 视图解析器ViewResolver返回view给前端控制器DispatcherServlet
  1.8 前端控制器DispatcherServlet对视图进行渲染
  1.9 前端控制器DispatcherServlet将响应结果返回给用户

2.$和#区别(mybatis)
  2.1 #:将传入的数据当成一个字符串,会自动将传入的数据加上双引号;$直接将传入的数据显示在sql中
  2.2 #能防止注入,$不能防止注入

3.springIOC底层实现原理(工厂+反射+dom4j解析xml)
  3.1 IOC(控制反转):上层类控制下层,将原本程序员手动new出来的对象,交给spring容器去管理
  3.2 DI(依赖注入):将底层类作为参数传递给上层类,实现上层类对下层类的控制

4.springAOP底层原理
  4.1 面向切换编程,是运行时织入增强的代码,运行时织入是通过代理完成的。找到切入点,结合反射针对指定的业务做特殊处理
  4.2 代理分为静态代理和动态代理,静态代理:由程序员或特殊工具生成的源代码,再对其编译,在程序运行前,代理类class文件已经生成。动态代理:在程序运行时,通过反射机制生成代理类class文件。
  4.3 静态代理的每个代理类只为一个接口服务,缺点是程序开发过程中会产生很多的代理类。
  4.4 动态代理是在程序运行时由java反射机制动态生成,简化了编程工作,提升了程序的扩展性,而且java反射机制可以生成任何类型的动态代理类
  4.5 常用概念
       a)Joinpoint(连接点):指那些被拦截到的点,eg:对哪些方法进行拦截?
       b)Pointcut(切入点):将连接点设置到执行完那个通知后执行,eg:@Round环绕通知,此处的含义就是设置连接点是在方法前执行一次,方法后执行一次
       c)Advice(通知):表示连接点之后要做的事情,是在哪个环节做。分为:前置通知、后置通知、环绕通知、异常通知等
       d)Weaving(织入):将切换应用到目标程序中
       e)Proxy(代理):一个类被AOP织入增强后,就会产生一个代理类
       f)Aspect(切面):切入点和通知的结合,表示何时何地做什么事

5.Autowired和resource区别
  相同点:都可以用来做bean的注入
  5.1 @Autowird是spring的注解,@Resource是javax包下的注解
  5.2 注入方式不一样
      a)@Autowird默认是按照类型装配(byType),默认情况下依赖注入的对象必须存在,否则会报空指针,如果允许为null,可以加上属性requried=false。如果想按照名称装配(byName),可以结合@Qualifter注解一起用。
      b)@Resource默认是按照名称装配(byName),它有两个属性name和type;如果指定name就是按照名称装配,指定type就是按照类型装配;如果name和type都指定了,则从spring上下文中找到唯一匹配的bean进行装配。

6.rpc框架原理
  6.1 remote procedure call(远程过程调用),即调用远程计算机上的服务就像调用本地服务一样便捷。RPC是一个概念,一个完整的RPC框架包含四大组件,client、client stub、server、server stub
  6.2 运行原理(stub就是一个存根,相当于一个副本)
       a)客户端client发送一个请求
       b)client stub收到client请求后,底层使用动态代理,首先序列化请求参数,然后使用socket发送网络,发送网络请求之前需要知道ip和port,一般会使用注册中心这类组件对元数据进行管理。
       c)server stub端收到网络请求后,反序列化请求参数,调用真正的服务提供者server
       d)server执行业务方法后返回server stub
       e)server stub将返回的参数序列化返回给client stub
       f)client stub收到消息后,将返回参数反序列化,再将最终结果返回给client

7.如果一个接口有多个实现类,在自动注入时,使用的是哪一个实现类
  7.1 可以在实现类上指定不同的名字,这样每个类都会被注入进去
  7.2 类注入到spring容器后,具体使用哪个实现类就取决于我们在使用的地方注解上指定的是哪个实现类

8.bean作用域
  8.1 spring的bean默认是单例,在多线程的情况下,针对每个action(controller)请求都是单例的,另外针对托管的service是通过spring容器(BeanFactory)保证service在容器内是单例的。
  8.2 bean大致分为4步:实例化---》属性赋值---》初始化(eg:解析XML获取spring容器方便后期通过容器调用getBean()/注册一些监听器事件/国际化等)---》销毁
  8.3 生命周期(bean标签中通过scope指定)
       a)singleton:默认是单例,在spring容器的生命周期内,都只有这一个实例
       b)prototype:多例,针对每次bean的请求时创建一个实例(将其注入到另外一个bean中,或每次通过spring容器getBean()时都会创建一个实例)
       c)request:针对每个Http请求都会创建一个bean,一个request请求结束,bean即销毁
       d)session:针对每个会话创建一个bean,会话结束即销毁
       e)globalSession:针对整个应用程序内有效,知道应用程序被销毁

9.spring循环依赖
  9.1 现象:A依赖B,B依赖A
  9.2 情况
      a)构造器注入:实例化A的时候,通过构造器将B注入给A,同理实例化B的时候,通过构造器将A注入给B。无法解决,启动就会报错。
      b)参数注入:通过三级缓存解决,参见9.4
  9.3 解决办法(三级缓存)
      a)一级缓存:singletonObjects(ConcurrentHashMap<String,Object>,存放已经实例化好的bean);二级缓存:earlySingletonObjects(Map<String,Object>,存放未填充属性的对象),三级缓存:singletonFactories(Map <String,ObjectFactory>,存放对象工厂)
      b) A开始实例化,调用A的构造方法,将A放入到三级缓存SingletonObjectFactories里面
      c)依赖注入A的时候发现A依赖了B,则调用B的构造方法,将B放入到三级缓存SingletonObjectFactories里面
      d)依赖注入B的时候发现B依赖了A,发现A在三级缓存SingletonFactories中存在,则直接获取A,并将A放入到二级缓存earlySingletonObjects中,同时把A从三级缓存中删除。
      e)到此B的依赖注入完成,将B放入到一级缓存singletonObject里面,把B从三级缓存SingletonObjectFactories中移除
      f)将B依赖注入给A后,继续注入A的其它依赖,知道A注入完成,将A从二级缓存中移除,加入到一级缓存中
  9.4 第三级缓存存在的价值
      a)如果不使用AOP,则第三级缓存没有意义
      b)假设我们对A使用AOP后,如果不需要使用三级缓存,则按照上面注入的流程,首先对A进行实例化,实例化完后立即就要对A生成代理对象,代理对象存储在二级缓存中。接着注入A的时候,发现依赖了B,就会实例化B且将B放入到二级缓存中。再接着发现B依赖了A,A又在二级缓存中存在,则去二级缓存中找到了A,但是A是代理对象,如果将A注入给B和预期是违背的,正确情况应该注入原始的bean。如果使用三级缓存,A应该是存在三级缓存中,当给B填充属性的时候,在三级缓存中找到A的代理对象,然后就会调用getObject方法获取原生的bean,将其注入给B
      

10.BeanFactory和Application区别
  10.1 BeanFactory:只提供了实例化bean和拿bean的功能,实现了控制反转功能。
  10.2 ApplicatonContext:应用上下文,继承BeanFactory接口。它封装了更为高级的功能:
       a)国际化(XML中定义ReloadableResourceBundleMessageSource,后台代码通过ApplicationContext.getMessage("key"))
       b)访问资源,如URL和文件(ApplicationContext acxt = new ClassPathXmlApplicationContext("/applicationContext.xml"))
       c)载入多个(有继承关系)上下文(xml中通过bean标签载入多个xml)
       d)消息发送、响应(ApplicationEventPublisher,事件机制是观察者模式,通过实现ApplicationEvent类和ApplicationListener接口,可以实现对ApplicationEeven事件的处理)
          1.容器事件:继承ApplicationEevent类
      2.监听事件:实现ApplicationListener接口,同时在xml中配置bean
       e)AOP(拦截):实现HandlerInterceptor
  10.3 区别
       a)启动的时候不会去实例化bean,只有在容器中拿bean的时候才会实例化
       b)在启动的时候会将所有的bean实例化
  
11.bean和对象的区别
  11.1 bean就是一个对象,对象不一定是个bean
  11.2 bean在实例化以后,会自动填充属性,对象不会

12.spring生命周期
  源代码入口:doCreateBean()
  12.1 将xml中定义的bean封装成BeanDefinition对象
  12.2 将BeanDefinition对象注册到BeanFactory中(ConcurrentHashMap中,key:bean的name,value:BeanDefinition对象)
  12.3 实现BeanFactoryPostProcessor后置处理器(可选)
  12.4 实例化bean(createBeanInstance)
  12.5 实现BeanPostProcessor后置处理器(可选)
  12.6 属性赋值(依赖注入)
  12.7 spring调用回调函数(可选,实现awar接口,eg:BeanNameAware、BeanFactoryAware)
  12.8 初始化(调用applicationContext.xml中init方法)
  12.9 执行AOP操作(可选)
  12.10 销毁

13.BeanDefinition/BeanFactory/BeanFactoryPostProcessor/BeanPostProcessor
  13.1 BeanDefinition:描述bean的一些属性定义
  13.2 BeanFactory(bean的工厂类)
       a)记录bean的定义
       b)获取bean的时候如果bean不存在则创建(BeanFactory.getBean的时候才创建bean)
       c)记录bean的别名
       d)记录bean的名字
       e)bean的单例池
  13.3 BeanFactoryPostProcessor:BeanFactory组建完成后需要做的一些工作可以定义在里面(BeanFactory后置处理器)
  13.4 BeanPostProcessor:bean创建完成后的后置处理器

14.spring和mybatils怎么结合的
  14.1 解析applicationContext.xml配置文件,找到bean标签
  14.2 bean标签中的class属性定义了MapperScannerConfigurer类,扫描basePackage包下面所有的接口类,将mapper接口封装成BeanDefinition对象
  14.3 根据BeanDefinition对象将bean注入到工厂类中
  14.4 BeanFactory对bean进行实例化

15.mybatis一级缓存和二级缓存
  15.1 一级缓存:即在一个sqlSession会话中,执行两次查询条件相同的sql,第一次查询的数据库,第二次查询的是缓存。注:如果一个sqlSession会话中,第一次查询和第二次查询中间执行了修改操作,则第二次查询的是数据库。
  15.2 二级缓存:多个sqlSession中共享数据。在相同查询条件的情况下,第一个sqlSession范围内查询的数据库,第二个sqlSession范围内查询的是缓存。注,如果第一次和第二次中间环节执行了修改,则第二个sqlSession查询的是数据库。

9.网络传输

1.解决跨域问题
  1.1 不要在js直接调用跨域的网址,先调用自己服务器,在自己的服务器上再调用跨域的网址
  1.2 使用jsonp,js里面设置请求的数据格式为jsonp

2.HttpClient
  2.1 创建HttpClient实例
  2.2 创建连接方式实例,有两种方式GetMethod和PostMethod
  2.3 执行HttpClient的execute方法
  2.4 读取response方法
  2.5 处理返回的结果。HttpEntity response = response.getEntity();
  2.6 关闭连接,释放资源

3.序列化和反序列化
  3.1 序列化:将对象持久化成本地文件的过程。在网络传输的过程中,是不支持对象传递的,所以需要通过序列化,将对象转换成被传输的流,方便对象的传递
  3.2 反序列化:将文件中的对象信息读取后写入到内存中
  3.3 序列化过程:类实现Serializable接口---》通过ObjectOutputStream.writeObject()将对象写入到文件中

4.http请求格式
  4.1 请求方式、URI、协议和协议的版本
  4.2 请求头可以生命浏览器用的语言、文本的长度
  4.3 请求正文,一般是用户请求的参数

5.转发和重定向
  5.1 从地址栏显示来说:转向是服务器直接请求目标资源,将返回结果直接响应给浏览器,地址栏信息不变;重定向是客户端发送请求给服务端,服务端返回一个重定向的状态码(eg:301或302),同时返回重定向后的URL地址给浏览器,浏览器收到状态码后判断需要重定向,重新请求重定向的地址,浏览器地址栏更新成重定向后的地址信息
  5.2 从数据共享来说:转发可以将当前请求的参数往后传递,重定向则不能
  5.3 从效率上来说,转发只存在一次请求,重定向存在多次请求,转发效率高

6.CSRF攻击(cross site request forgery:跨站请求伪造)
  6.1 工作流程
       a)用户浏览器发送一个请求到A服务器,A服务器业务处理好以后,将cookie返回给用户浏览器,用户未清除cookie
       b)用户访问B服务器,B服务器要求访问A服务器的cookie,用户在不知情的情况下,确认了该操作
       c)B服务器就模拟了正常的用户请求了A服务器,A服务器无法识别B服务器过来的 请求时伪造的
  6.2 预防
       a)用户对A服务器的每次请求增加token验证
       b)验证浏览器的来源(referer)

7.TCP三次握手和四次挥手
  7.1 三次握手流程
       a)发送端给接收端发送一个同步序列编号
       b)接收端收到后,回给发送端一个确认字符
       c)发送方收到确认字符后,再回执给接收端确认自己收到过
  7.2 三次握手目的:建立可信的通信机制,确保双方确认自己与对方的接收和发送都正确
  7.3 任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。
 

10.微服务

1.分布式事务怎么控制
  eg:用户下单后,需要调用订单服务下单、库存服务扣减库存,同时需要调用支付服务扣减账户余额
  1.1 TCC方案(try-confirm-cancel):借助TCC框架,基本思想是用户发送try请求,调用订单服务生成订单信息、库存服务增加占用库存、支付服务冻结支付的金额,中间有任意一方失败,则调用cacel操作,取消响应服务的业务操作,如果都成功,则调用订单服务、库存服务、支付服务的confirm操作,扣减实际库存和支付金额。该方案实现的业务逻辑太复杂,工作量大
  1.2 通过消息队列,每次执行完一个服务,则增加回执信息,如生产者未收到回执,则定时重推消息。
  1.3 GTS中间件
       a)组成
          1.GTS Client(客户端):事务的发起与结束
      2.GTS RM(资源管理器):完成分支事务的开启、提交、回滚等操作
      3.GTS Server(事务协调器):负责分布式事务的整体推进,事务生命周期的管理
       b)原理
          1.当业务应用发起服务调用时,首先会通过GTS Client向GTS Server注册新的全局事务
      2.GTS Server会给业务应用返回全局唯一的事务编号xid
      3.业务应用调用微服务时会将xid传播到服务端
      4.微服务在执行数据库操作时会通过GTS RM向GTS Server注册分支事务,并完成分支事务的提交
      5.如果A、B、C三个服务均调用成功,GTS Client会通知GTS Server结束事务
      6.假设C调用失败,GTS Client会要求GTS Server发起全局回滚。然后由各自的RM完成回滚工作

2.hystrix熔断怎么实现的,滑动窗口怎么实现的
  2.1 作用:当服务提供者不可用,服务调用者依旧大量请求服务提供者,最后造成服务调用者也不可用,服务出现雪崩情况。此时熔断器会根据我们的配置及时断开调用服务提供者。
  2.2 功能
       1.隔离(线程池隔离):某一个调用的服务不可用,不会影响其它服务的调用
       2.优雅的降级机制:超时降级、资源(线程或信号量)不足降级,可配合降级接口返回自定义的数据(通过回调函数指定处理的逻辑)
       3.熔断:当某个服务的提供者因为某种原因造成响应过慢或服务不可用时,从保证自己服务整理的可用性出发考虑,将不再调用服务提供者,而直接返回成功,释放资源。当服务提供者恢复正常后,再调用服务提供者。
       4.支持实时监控、报警和控制
  2.3 原理
       1.当用户请求正常被执行时,不适用熔断器
       2.当用户请求失败(eg:超时),失败的比例达到了设置的阈值,熔断器会自动打开,请求降级处理,执行我们定义的回调函数。
       3.延时一段时间后(默认5S),熔断器会放开一部分请求,判断请求是否成功,如果成功,则关闭熔断器。如果不成功,则继续统计失败比例,重新计时,经过一段时间后又重复步骤3的动作

3.秒杀系统怎么设计
  3.1 设计理念
       a)限流:鉴于只有少部分用户能够秒杀成功,所以要限制大部分流量,只允许少部分流量进入服务后端
       b)削峰:利用缓存和消息中间件达到这个目的
       c)异步处理:其实也是削峰的一种处理方式
       d)内存处理:使用内存数据库,对变更很少但是频繁查询的操作使用redis中间件
       e)可扩展:增加机器节点
  3.2 设计思路
       a)针对IP或者用户限流
       b)页面数据能静态化的全部静态化,减少和后台的交互,减轻服务器压力
       c)采用消息队列缓存请求,例如只有100个库存,可以在缓存中先扣减库存,扣减成功后直接返回秒杀成功。后面再异步去数据库里面扣减库存
       d)针对变更少查询频率较高的数据,缓存到redis中

4.springBoot文件加载顺序
  优先级从高到低依次加载,优先级高的覆盖优先级低的配置,所有配置形成互补
  4.1 命令行参数,eg:可以在程序启动的时候指定tomcat端口,这样配置文件中指定的端口就失效了。
  4.2 来自java:comp/env的JNDI属性
  4.3 java系统属性
  4.4 优先加载带profile的文件
       a)jar外部的application-{profile}.properties或application-{profile}.yml(带spring.profile)
       b)jar内部的application-{profile}.properties或application-{profile}.yml(带spring.profile)
  4.5 再来加载不带profile的文件
       a)jar外部的application.properties或application.yml
       b)jar内部的application.properties或application.yml
  4.6 @Configuration注解类上的@PropertySource
  4.7 在启动类中设置默认属性(eg:SpringApplication.setDefaultProperties)

5.springBoot配置文件加载顺序
  优先级从高到低依次加载
  5.1 通过启动命令指定:spring.config.location
  5.2 文件根目录下面的config文件夹下的配置文件
  5.3 根目录下面的配置文件
  5.4 classpatch下面的config文件夹下面的配置文件
  5.5 classpatch下面的配置文件

6.如何使用SpringBoot实现异常处理
  6.1 定义一个全局异常拦截器,其实就是一个普通的类上加上@ControllerAdvice
  6.2 异常处理类中可以定义异常捕获的方法,方法上通过@ExceptionHandler指定需要捕获的异常

7.SpringCloud优势
  7.1 集成网关组件,完成路由分发、鉴权控制,避免外部系统直接参与和内部应用之间的交互,减轻内部应用的压力。
  7.2 集成注册中心,完成服务的发现和注册,实现动态的维护应用节点,同时支持负载均衡,保证每台服务器的压力尽可能的平衡。
  7.3 使用配置中心,完成配置文件的动态和集中维护。
  7.4 使用feign简化http接口的调用,调用外部服务可以像调用本地服务接口一样。
  7.5 使用熔断器,避免服务造成雪崩问题,及时熔断出问题的服务,保证单个服务是正常
  7.6 使用消息总线,动态的将我们配置中心的信息同步到所有的configClient

8.bus消息总线原理
  8.1 客户端应用发送一个刷新请求
  8.2 配置中心configServer去git中读取配置信息,发送消息给消息总线bus
  8.3 消息总线收到消息后,同步通知所有的configClient
  8.4 configClient去configServer中读取最新配置

9.微服务拆分规则
  9.1 单一职责、高内聚低耦合
  9.2 以业务模型为切入口

10.springBoot获取属性有哪几种方式
  10.1 @Value
  10.2 @ConfigurationPropertis,可是设置需要读取的配置文件中属性值的前缀
  10.3 将Environment注入到需要使用的地方,然后通过env.getProperties()方法获取

11.hystrix原理
  11.1 如果经过短路的流量超过一定的阈值(默认是10s内20个请求)。HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()
       eg:要求在10s内,经过短路器的流量为20个;那么10s内,经过短路器的流量小于20个,则根本不会判断要不要短路
  11.2 如果短路器统计到的异常调用的占比超过一定的阈值。HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
  eg:达到了短路流量阈值后,同时异常的访问数量达到了一定的占比(默认50%),这个时候就会开启短路。
  11.3 短路器从close状态变为open状态
  11.4 短路器开启后,所有经过短路器的请求全部要求短路,直接走fallback降级处理
  11.5 经过一段时间(默认5s)后,会进入到半开状态,会让一部分请求经过短路器,看能否正常调用,如果调用成功了,那么就会自动回复,转到close状态

12.hystrix线程池隔离
  12.1 每个外部依赖都用一个单独的线程池隔离,当某个服务出现严重的延迟,只会影响耗尽那一个服务的线程池资源,不会影响其它依赖的调用。

11.设计模式

1.单例模式
  1.1 概念:保证系统中只有一个对象的实例(构造器必须使私有的,防止子类通过构造器构建父类实例,提供给外部一个静态方法获取类的实例)
  1.2 优点
      a)节省对象创建带来的时间上的开销
      b)减少频繁创建对象带来的对内存上的开销,减轻GC压力
  1.3 懒汉和饿汉区别
       a)懒汉是采用的延时加载,是在需要用到的时候才创建对象;饿汉是在类创建的时候就已经初始化好对象的实例。
       b)懒汉如果不使用同步锁,会存在线程不安全的问题;饿汉是线程安全的,因为类创建的时候对象已经实例化了。 

2.工厂模式
  2.1 概念:定义一个接口,让子类实现这个接口,同时构建一个工厂类,由工厂类决定调用具体哪个子类(工厂类中可以定义不同的标识,根据标识创建不同的子类,返回的类型都是接口的类型)。
  2.2 优点
      a)调用者不用关心具体的子类实现细节,只需要通过标识决定获取那个子类的对象
      b)便于扩展,如果需要扩展工厂类的功能,只需要增加一个子类,然后增加工厂类的逻辑即可。

3.适配器模式
  3.1 概念:结合了多个独立接口的功能,使其可以兼容原本不能在一起工作的类可以在一起工作。简言之就是将多个接口中的部分功能组合到一起,实现接口的兼容,使其不能在一起工作的类可以在一起工作。
  3.2 优点
      a)可以使多个没有关联的类可以一起工作(springMVC适配器执行controller)
      b)提高了类的复用

4.策略模式
  4.1 概念:为了解决多个算法相似的情况,减少复杂度,多个算法可以自由切换,减少if/els的判断。
  4.2 优点
      a)算法可以自由切换
      b)避免多重条件判断
  4.3 实现
      a)增加接口A,定义一个方法
      b)增加实现类B和C,实现A
      c)增加一个策略切换类,定义构造方法,创建接口A的实例。同时,定义一个方法,方法内部调用接口A的方法
      d)调用者通过策略切换类调用具体的哪个方法(入参:传类B或类C)

5.代理模式
  5.1 概念:不将目标对象直接暴露给调用方,调用方通过一个代理类去实现目标对象的调用,代理类中可能会做一些逻辑的控制,例如通过了权限控制后,才能调用目标对象
  5.2 优点
      a)职责清晰
      b)高扩展性

12.服务器调优

1.服务器调优
  1.1 通过纵向扩充,提升服务器硬件配置
  1.2 增加服务节点
  1.3 使用ngnix+tomcat进行负载均衡,所有的请求经ngnix分发到多台tomcat进行处理,降低每台tomcat的压力

2.ngnix负载均衡策略
  2.1 轮询:按照请求的时间逐一分配到每台服务器上
  2.2 ip_hash:按照IP的哈希值分配,每个ip过来的请求访问固定的一台服务器
  2.3 fair:按照后端服务器响应时间来分配请求,响应时间短的优先分配
  2.4 url_hash:按照请求的url的哈希值分配,每个URL的请求访问固定一台服务器

3.Nginx介绍
  3.1 反向代理:正向代理是用户访问服务器,需要手动的设置服务器的ip和端口,例如VPN。反向代理是,接收用户的请求,然后将请求转发给内网网络的服务器,从服务器上得到结果返回给客户。
  3.2 负载均衡:在高并发的环境下,将多个请求分发到多台服务器上执行,减少每台服务器的压力。
  3.3 动静分离:将动态的网页中的静态数据拆分出来,将其缓存起来。
  3.4 优点
       a)支持高并发,增加吞吐量(负载均衡能增加吞吐量)
       b)高可用性,可持续运行数年
       c)支持热部署,可以在不停止nginx服务器的情况下升级nginx

4.代理使用
  4.1 概念:目的是代码的复用,不直接使用对象,而使用代理对象对功能进行一种增强
  4.2 使用场景
       a)某个功能逻辑比较复杂,为了避免直接修改逻辑造成问题,使用代理类对该功能进行增强
       b)我们在使用RPC框架的时候,框架本身是不知道要调用那个业务方法,这个时候可以使用代理类对功能进行增强
       c)AOP使用的动态代理机制对功能进行的增强

5.tomcat调优
  5.1 JVM参数调优:catalinna.bat中设置堆初始化大小和堆最大值(JAVA_OPTS='-Xms256m-Xmx512m')。其中堆最大值一般设置为可用内存的80%
  5.2 禁用DNS查询:域名解析需要占用网络资源,影响性能,将server.xml中的enableLookups参数修改为false
  5.3 调整线程数

13.linux

1.linux命令
  1.1 创建文件夹:mkdir
  1.2 建文件:touch
  1.3 查看tomcat进程:ps -ef | grep 'tomcat'
  1.4 分页查看命令:more
  1.5 实时查看系统进程占用情况:top
  1.6 查看test.log中的第1行到第5行日志:sed -n '1,5p' test.log

14.向面试官提问

1.向技术面试官提问
  1.1 技术分享:公司或团队会不定期举行一些技术分享吗?
  1.2 团队人员比例:开发和测试占比?
  1.3 日常的工作流程是怎么样的?

2.向hr提问
  2.1 岗位晋升:之前在这个岗位上工作过的同事,一般多久可以得到晋升?
  2.2 薪资体系:工资和资料的比例
  2.3 交金比例
 

自愿打赏

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿猴乐园

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值