Java常见面试题(实习篇)
迪丽瓦拉
2024-06-03 08:08:36
0

未完待续 !!!

本篇为本人找实习所准备的面试题,如有问题,请帮提出,谢谢…

一.Java基础篇

1.接口和抽象类的区别

相似点:

(1)接口和抽象类都不能被实例化

(2)实现接口或继承抽象类的普通子类都必须实现这些抽象方法

不同点:

(1)抽象类可以包含普通方法和代码块,接口里只能包含抽象方法,静态方法和默认方法,

(2)抽象类可以有构造方法,而接口没有

(3)抽象类中的成员变量可以是各种类型的,接口的成员变量只能是 public static final 类型的,并且必须赋值

2.重载和重写的区别

重载发生在同一个类中,方法名相同、参数列表、返回类型、权限修饰符可以不同

重写发生在子类中,方法名相、参数列表、返回类型都相同,权限修饰符要大于父类方法,声明异常范围要小于父类方法,但是final和private修饰的方法不可重写

3.==和equals的区别

==比较基本类型,比较的是值,==比较引用类型,比较的是内存地址

equlas是Object类的方法,本质上与==一样,但是有些类重写了equals方法,比如String的equals被重写后,比较的是字符值,另外重写了equlas后,也必须重写hashcode()方法

4.异常处理机制

(1)使用try、catch、finaly捕获异常,finaly中的代码一定会执行,捕获异常后程序会继续执行

(2)使用throws声明该方法可能会抛出的异常类型,出现异常后,程序终止

5.HashMap原理

1.HashMap在Jdk1.8以后是基于数组+链表+红黑树来实现的,特点是,key不能重复,可以为null,线程不安全

2.HashMap的扩容机制:

HashMap的默认容量为16,默认的负载因子为0.75,当HashMap中元素个数超过容量乘以负载因子的个数时,就创建一个大小为前一次两倍的新数组,再将原来数组中的数据复制到新数组中。当数组长度到达64且链表长度大于8时,链表转为红黑树

3.HashMap存取原理:

(1)计算key的hash值,然后进行二次hash,根据二次hash结果找到对应的索引位置

(2)如果这个位置有值,先进行equals比较,若结果为true则取代该元素,若结果为false,就使用高低位平移法将节点插入链表(JDK8以前使用头插法,但是头插法在并发扩容时可能会造成环形链表或数据丢失,而高低位平移发会发生数据覆盖的情况)

6.想要线程安全的HashMap怎么办?

(1)使用ConcurrentHashMap

(2)使用HashTable

(3)Collections.synchronizedHashMap()方法

7.ConcurrentHashMap原如何保证的线程安全?

JDK1.7:使用分段锁,将一个Map分为了16个段,每个段都是一个小的hashmap,每次操作只对其中一个段加锁

JDK1.8:采用CAS+Synchronized保证线程安全,每次插入数据时判断在当前数组下标是否是第一次插入,是就通过CAS方式插入,然后判断f.hash是否=-1,是的话就说明其他线程正在进行扩容,当前线程也会参与扩容;删除方法用了synchronized修饰,保证并发下移除元素安全

8.HashTable与HashMap的区别

(1)HashTable的每个方法都用synchronized修饰,因此是线程安全的,但同时读写效率很低

(2)HashTable的Key不允许为null

(3)HashTable只对key进行一次hash,HashMap进行了两次Hash

(4)HashTable底层使用的数组加链表

9.ArrayList和LinkedList的区别

ArratList的底层使用动态数组,默认容量为10,当元素数量到达容量时,生成一个新的数组,大小为前一次的1.5倍,然后将原来的数组copy过来;因为数组在内存中是连续的地址,所以ArrayList查找数据更快,由于扩容机制添加数据效率更低

LinkedList的底层使用链表,在内存中是离散的,没有扩容机制;LinkedList在查找数据时需要从头遍历,所以查找慢,但是添加数据效率更高

10.如何保证ArrayList的线程安全?

(1)使用collentions.synchronizedList()方法为ArrayList加锁

(2)使用Vector,Vector底层与Arraylist相同,但是每个方法都由synchronized修饰,速度很慢

(3)使用juc下的CopyOnWriterArrayList,该类实现了读操作不加锁,写操作时为list创建一个副本,期间其它线程读取的都是原本list,写操作都在副本中进行,写入完成后,再将指针指向副本。

11.String、StringBuffer、StringBuilder的区别

String 由 char[] 数组构成,使用了 final 修饰,对 String 进行改变时每次都会新生成一个 String 对象,然后把指针指向新的引用对象。

StringBuffer可变并且线程安全

StringBuiler可变但线程不安全。

操作少量字符数据用 String;单线程操作大量数据用 StringBuilder;多线程操作大量数据用 StringBuffer。

12.hashCode和equals

hashCode()和equals()都是Obkect类的方法,hashCode()默认是通过地址来计算hash码,但是可能被重写过用内容来计算hash码,equals()默认通过地址判断两个对象是否相等,但是可能被重写用内容来比较两个对象

所以两个对象相等,他们的hashCode和equals一定相等,但是hashCode相等的两个对象未必相等

如果重写equals()必须重写hashCode(),比如在HashMap中,key如果是String类型,String如果只重写了equals()而没有重写hashcode()的话,则两个equals()比较为true的key,因为hashcode不同导致两个key没有出现在一个索引上,就会出现map中存在两个相同的key

13.面向对象和面向过程的区别

面向对象有封装、继承、多态性的特性,所以相比面向过程易维护、易复用、易扩展,但是因为类调用时要实例化,所以开销大性能比面向过程低

14.深拷贝和浅拷贝

浅拷贝:浅拷贝只复制某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存
深拷贝:深拷贝会创造一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对对象。

15.多态的作用

多态的实现要有继承、重写,父类引用指向子类对象。它的好处是可以消除类型之间的耦合关系,增加类的可扩充性和灵活性。

16.什么是反射?

反射是通过获取类的class对象,然后动态的获取到这个类的内部结构,动态的去操作类的属性和方法。
应用场景有:要操作权限不够的类属性和方法时、实现自定义注解时、动态加载第三方jar包时、按需加载类,节省编译和初始化时间;
获取class对象的方法有:class.forName(类路径),类.class(),对象的getClass()

17.Java创建对象得五种方式?

(1)new关键字 (2)Class.newInstance (3)Constructor.newInstance

(4)Clone方法 (5)反序列化


18.为什么重写了equals要重写hashcode

  1. 如果没有重写equals()方法, x.equals(y)==true, 那么hashcode的值一定相同。因为源码里面equals()比较的是地址, 同一个对象的hashcode的值一定是相同的。
  2. 如果只重写equals()方法, 没有重写hashcode()方法, x.equals(y)==true, 那么hashcode的值可能不相同(因为重写了equals之后可能比较的不是地址了,也就是说 x.equals(y)==true也不一定是同一个对象)。这样的话,这个类就不能和某些集合类一起使用了(比如:set,hashmap), 因为这些集合是通过hash判断存储的。

所以在开发里面用个约定俗成, 重写了equals一般会重写hashcode。

19.ArrayList扩容机制怎样?

  1. ArrayList每次扩容是原来的1.5倍。
  2. 数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。
  3. 代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。尽可能,就指定其容量,以避免数组扩容的发生。
  4. 创建方式方式不同,容量不同

20.说下ConcurrentHashMap?

  1. 在ConcurrentHashMap中,无论是读操作还是写操作都能保证很高的性能
  2. 在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。
  3. 在理想状态下,ConcurrentHashMap 可支持16个线程执行并发写操作,及任意数量线程的读操作。
  4. 关于它的存储结构
    1. JDK 1.7 中使用分段锁(ReentrantLock + Segment + HashEntry),相当于把一个 HashMap 分成多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于 Segment,包含多个 HashEntry。
    2. JDK 1.8 中使用 CAS + synchronized + Node + 红黑树。锁粒度:Node(首结点)(实现 Map.Entry)。锁粒度降低了。

21.final, finally, finalize的区别?

final

用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。内部类要访问局部变量,局部变量必须定义成final类型。java中的final类有java.lang.String、java.lang.Math、java.util.Scanner、java.net.URL、java.lang.reflect.Parameter、java.time.Year等。

finally

finally是异常处理语句结构的一部分,表示总是执行。

finally中的return会覆盖try和catch中的return值。

finally语句在return语句执行之后return返回之前执行的。

​ finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变,也可能不变。

try或catch代码块中(return/throw)的返回值保留,再来执行finally代码块中的语句,等到finally代码块执行完毕之后,在把之前保留的返回值给返回出去。

​ finally代码块执行不到的情况:

​ 在执行到try catch finally前就抛出了异常

​ try中有 System.exit(0) 代码,会退出JVM

​ 线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),或宕机

finalize

finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。该方法最多只被调用一次,但JVM不保证此方法总被调用,

22.List、Set、Map三个接口的区别以及常见子类?

  1. List、Set单列,Map是双列的键值对
  2. List可重复,set不可重复
  3. List有序的,set是无序
  4. List中最常用的两个子类:ArrayList(基于数组,查询快)和LinkedList(基于链表,增删快)
  5. Set中最常用的两个子类:HashSet和TreeSet
  6. Map中最常用的两个子类:HashMap和TreeMap

二.计网/OS篇

1.TCP/IP模型

20200904160558176.png

2.浏览器输入地址后做了什么

img

3.TCP三次握手

20200904160558436.png

4.为什么TCP不能两次握手

假设是两次握手,若客户端发起的连接请求阻塞在网络中,会造成该报文的重传,这时服务收到连接请求后会立刻进入连接状态,当双方传输完数据结束连接后,第一次阻塞的请求突然又到达了服务端,此时服务端又进入连接状态,而客户端不会响应服务端的连接确认报文

5.TCP四次挥手

20200904160558691.png

6.为什么要进入时间等待状态?

若客户端发送确认释放包后直接关闭,而服务端因为某种原因没有收到客户端的确认释放包,就会一直发送确认请求,而客户端永远不会再响应该请求。

7.TCP 滑动窗口

TCP 流量控制,主要使用滑动窗口协议,滑动窗口是接受数据端使用的窗口大小,用来告诉发送端接收端的缓存大小,以此可以控制发送端发送数据的大小,从而达到流量控制的目的。如果TCP发送方收到接收方的零窗口通知后,会启动持续计时器。计时器超时后向接收方发送零窗口探测报文,如果响应仍为0,就重新计时,不为0就打破死锁

8.TCP拥塞控制

发送方会维护一个拥塞窗口大小的状态变量,大小取决于网络的拥塞程度。发送方的发送窗口大小是取接收方接收窗口和拥塞窗口中较小的一个

拥塞控制有四种算法:

慢开始:从小到大主键发送窗口,每收到一个确认报文窗口大小指数增长

拥塞避免:当窗口大小到达一定阈值时,转为拥塞避免,每收到一个确认报文窗口大小+1。若此时网络超时,就把阈值调小一半,重新慢开始

快重传:要求接收方收到请求后要立即回复

快恢复:发送方连续收到多个确认时,就把拥塞避免阈值减小,然后直接开始拥塞避免

9.TCP超时重传

发送方在发送按数据后一定时间内没有收到接收方响应报文,就会重新发送刚刚的报文,接收到收到报文后会对该报文的序列号进行检验,已存在就抛弃

10.TCP可靠传输的实现

TCP是靠滑动窗口协议和连续ARQ协议配合流量控制和拥塞控制来保证的可靠传输。

ARQ是停止等待协议和自动重传请求,它规定TCP要为每一次传输的包编号,每发送一个包,要等待对方确认后才能发送下一个分组,若一段时间对方没有确认,就重新发送刚刚的报文。接收方会对数据包排序,把有序数据传给应用层,返回缺失的第一个ACK确认序列号给发送方,接收到收到报文后会对该报文的序列号进行检验,重复就丢弃。

流量控制是…拥塞窗口上…(上面已经说了)

11.状态码

1xx:请求正在处理

2xx:请求成功处理

3xx:请求重定向 301:永久重定向 302:临时重定向 304:使用本地缓存

4xx:客户端错误 400:请求格式错误 403:没有访问权限 415:请求体过大

5xx:服务端错误

12.socket通信流程

(1)服务端创建socket并调用bind()方法绑定ip和端口号

(2)服务端调用listen()方法建立监听,此时服务的scoket还没有打开

(3)客户端创建socket并调用connect()方法像服务端请求连接

(4)服务端socket监听到客户端请求后,被动打开,调用accept()方法接收客户端连接请求,当accept()方法接收到客户端connect()方法返回的响应成功的信息后,连接成功

(5)客户端向socket写入请求信息,服务端读取信息

(6)客户端调用close()结束链接,服务端监听到释放连接请求后,也结束链接

三.JVM篇

1.JVM运行时数据区(内存结构)

线程私有区:

(1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧

(2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一

(3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码

线程共享区:

(4)堆内存:Jvm进行垃圾回收的主要区域,存放对象信息,分为新生代和老年代,内存比例为1:2,新生代的Eden区内存不够时时发生MinorGC,老年代内存不够时发生FullGC

(5)方法区:存放类信息、静态变量、常量、运行时常量池等信息。JDK1.8之前用永久代实现,JDK1.8后用元空间实现,元空间使用的是本地内存,而非在JVM内存结构中

2.什么情况下会内存溢出?

堆内存溢出:(1)当对象一直创建而不被回收时(2)加载的类越来越多时(3)虚拟机栈的线程越来越多时

栈溢出:方法调用次数过多,一般是递归不当造成

3.JVM有哪些垃圾回收算法?

(1)标记清除算法: 标记不需要回收的对象,然后清除没有标记的对象,会造成许多内存碎片。
(2)复制算法: 将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。用在新生代
(3)标记整理算法: 与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。用在老年代

4.GC如何判断对象可以被回收?

(1)引用计数法:已淘汰,为每个对象添加引用计数器,引用为0时判定可以回收,会有两个对象相互引用无法回收的问题

(2)可达性分析法:从GCRoot开始往下搜索,搜索过的路径称为引用链,若一个对象t没有任何的GCRoo引用链,则判定可以回收

GCRoot有:虚拟机栈中引用的对象,方法区中静态变量引用的对象,本地方法栈中引用的对象

5.典型垃圾回收器

CMS:以最小的停顿时间为目标、只运行在老年代的垃圾回收器,使用标记-清除算法,可以并发收集。

G1 :JDK1.9以后的默认垃圾回收器,注重响应速度,支持并发,采用标记整理+复制算法回收内存,使用可达性分析法来判断对象是否可以被回收。

6.类加载器和双亲委派机制

类加载器:

从父类加载器到子类加载器分别为:

BootStrapClassLoader 加载路径为:JAVA_HOME/jre/lib

ExtensionClassLoader 加载路径为:JAVA_HOME/jre/lib/ext

ApplicationClassLoader 加载路径为:classpath

还有一个自定义类加载器

双亲委派机制:

如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把这请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传说到顶层的启动类加载器中,只有当父类加载器返回自己无法完成这个加载请求(它的搜索返回中没有找到所需的类)时,子类加载器才会尝试自己去加载

7.JVM中有哪些引用?

强引用:new的对象。哪怕内存溢出也不会回收

软引用:只有内存不足时才会回收

弱引用:每次垃圾回收都会回收

虚引用:必须配合引用队列使用,一般用于追踪垃圾回收动作

8.类加载过程

(1)加载 :把字节码通过二进制的方式转化到方法区中的运行数据区

(2)连接:

验证:验证字节码文件的正确性。

准备:正式为类变量在方法区中分配内存,并设置初始值,final类型的变量在编译时已经赋值了

解析:将常量池中的符号引用(如类的全限定名)解析为直接引用(类在实际内存中的地址)

(3)初始化 :执行类构造器(不是常规的构造方法),为静态变量赋初值并初始化静态代码块。

9.JVM类初始化顺序

父类静态代码块和静态成员变量->子类静态代码块和静态成员变量->父类代码块和普通成员变量->父类构造方法->子类代码块和普成员变量->子类构造方法

10.对象的创建过程

(1)检查类是否已被加载,没有加载就先加载类

(2)为对象在堆中分配内存,使用CAS方式分配,防止在为A分配内存时,执行当前地址的指针还没有来得及修改,对象B就拿来分配内存。

(3)初始化,将对象中的属性都分配0值或null

(4)设置对象头

(5)为属性赋值和执行构造方法

11.对象头中有哪些信息

对象头中有两部分,一部分是MarkWork,存储对象运行时的数据,如对象的hashcode、GC分代年龄、GC标记、锁的状态、获取到锁的线程ID等;另外一部分是表明对象所属类,如果是数组,还有一个部分存放数组长度

12.JVM内存参数

-Xmx[]:堆空间最大内存

-Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的

-Xmn[]:新生代的最大内存

-xx:[survivorRatio=3]:eden区与from+to区的比例为3:1,默认为4:1

-xx[use 垃圾回收器名称]:指定垃圾回收器

-xss:设置单个线程栈大小

一般设堆空间为最大可用物理地址的百分之80

13.GC的回收机制和原理

GC的目的实现内存的自动释放,使用可达性分析法判断对象是否可回收,采用了分代回收思想,
将堆分为新生代、老年代,新生代中采用复制算法,老年代采用整理算法,当新生代内存不足时会发生minorGC,老年代不足时会发送fullGC

14.java堆和栈的区别

  1. 栈解决程序的运行问题,堆解决的是数据存储的问题
  2. 栈内存是线程私有的,而堆内存是线程共有的。
  3. 栈内存一般会用来存储局部变量和方法调用,但堆内存是用来存储Java对象和数组的。
  4. 堆会GC垃圾回收,而栈不会。

15.堆内存模型是怎样的

JDK1.7

  1. 新生 Young -》Eden、From Survivor和To Survivor
  2. 老年 Old
  3. 永久

JDK1.8

  1. 新生 Young -》Eden、From Survivor和To Survivor
  2. 老年 Old
  3. metaspace

16.JVM的类加载机制主要有如下3种

  1. 全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
  2. 双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。
  3. 缓存机制。缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

17.常见垃圾回收器

  1. 新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge
  2. 老年代收集器使用的收集器:Serial Old、Parallel Old、CMS

18.简述分代垃圾回收器是怎么工作的

  1. 老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3
  2. Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1
  3. 把 Eden + From Survivor 存活的对象放入 To Survivor 区;
  4. 清空 Eden 和 From Survivor 分区; From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。
  5. 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。
  6. 老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。

19.为什么要废弃1.7中的永久代

  • 字符串存在永久代中,容易出现性能问题和内存溢出。原因永久代固定内存大小,字符串或常量过多则导致oom
  • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

20.Minor GC、Major GC、Full GC是什么

  • Minor GC 发生在新生代的垃圾回收,暂停时间短
  • Major GC 老年代区域的垃圾回收,老年代空间不足时,会先尝试触发Minor GC。Minor GC之后空间还不足,则会触发Major GC,Major GC速度比较慢,暂停时间长
  • Full GC 新生代 + 老年代完整垃圾回收,暂停时间长,应尽力避免

21.JVM 调优的参数都有哪些

  1. -Xms256m: 设置堆的初始化大小为256m

  2. -Xmx: 设置堆的最大大小

  3. -XXSurvivorRatio=3,表示年轻代中的分配比率:survivor:eden = 2:3

    设置年轻代中Eden区和两个Survivor区的大小比例。默认比例为8:1:1

  4. -XX:newSize 设置年轻代的初始大小

    年轻代和老年代默认比例为1:2。可以通过调整二者空间大小

  5. -XX:MaxNewSize 设置年轻代的最大大小

    初始大小和最大大小两个值通常相同

  6. -Xss 对每个线程stack大小的调整,-Xss128k

    设置线程栈的大小,默认为1M。用于存放栈帧、调用参数、局部变量等

  7. -Xmn设置年轻代的大小

22.说一下 JVM 调优的工具?

  1. jps,(Java Process Status)

    输出JVM中运行的进程状态信息(现在一般使用jconsole)

  2. jstack 查看java进程内线程的堆栈信息

  3. jmap 用于生成堆转存快照

  4. jstat 显示垃圾回收信息、类加载信息、新生代统计信息等

    1. jstat -gcutil pid
    2. jstat -gc pid 垃圾回收统计
  5. jconsole 用于对jvm的内存,线程,类 的监控,是一个基于 jmx 的 GUI 性能监控工具

  6. VisualVM: 故障处理工具。

23.你听过直接内存吗?

它又叫做堆外内存,不受 JVM 内存回收管理,是虚拟机的系统内存,常见于 NIO 操作时,用于数据缓冲区,分配回收成本较高,但读写性能高,不受 JVM 内存回收管理

24.JVM由那些部分组成,运行流程是什么?

在JVM中共有四大部分,分别是

  1. ClassLoader(类加载器)
  2. Runtime Data Area(运行时数据区,内存分区)
  3. Execution Engine(执行引擎)
  4. Native Method Library(本地库接口)

它们的运行流程是:

  1. 类加载器(ClassLoader)把Java代码转换为字节码
  2. 运行时数据区(Runtime Data Area)把字节码加载到内存中,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层系统去执行,而是有执行引擎运行
  3. 执行引擎(Execution Engine)将字节码翻译为底层系统指令,再交由CPU执行去执行,此时需要调用其他语言的本地库接口(Native Method Library)来实现整个程序的功能。

25.什么是虚拟机栈

虚拟机栈是描述的是方法执行时的内存模型,是线程私有的,生命周期与线程相同,每个方法被执行的同时会创建栈桢。保存执行方法时的局部变量、动态连接信息、方法返回地址信息等等。方法开始执行的时候会进栈,方法执行完会出栈【相当于清空了数据】,所以这块区域不需要进行 GC

26.JVM为什么采用双亲委派机制

第一、可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。

第二、为了安全,防止核心类被篡改

27.详细聊一下分代回收吗?

在java8时,堆被分为了两份:新生代和老年代,它们默认空间占用比例是1:2

对于新生代,内部又被分为了三个区域。Eden区,From区,To区默认空间占用比例是8:1:1

具体的工作机制是有些情况:

  1. 当创建一个对象的时候,那么这个对象会被分配在新生代的Eden区。当Eden区要满了时候,触发MinorGC。
  2. 当进行MinorGC后,此时在Eden区和From存活的对象被移动到To区,并且当前对象的年龄会加1,清空Eden区和From区,交换From区和To区。
  3. 对象的年龄达到了某一个限定的值(默认15岁 ),那么这个对象就会进入到老年代中。

当然也有特殊情况,如果进入Eden区的是一个大对象,在触发YoungGC的时候,会直接存放到老年代

当老年代满了之后,触发FullGCFullGC同时回收新生代和老年代,当前只会存在一个FullGC的线程进行执行,其他的线程全部会被挂起。 我们在程序中要尽量避免FullGC的出现。

28.讲一下新生代、老年代、永久代的区别?

新生代主要用来存放新生的对象。

老年代主要存放应用中生命周期长的内存对象。

永久代指的是永久保存区域。主要存放Class和Meta(元数据)的信息。在Java8中,永久代已经被移除,取而代之的是一个称之为元空间的区域。元空间和永久代类似,不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存的限制。

29.JVM 调优的参数可以在哪里设置参数值?

我们当时的项目是SpringBoot项目,可以在项目启动的时候,java -jar中加入参数就行了

30.java内存oom(内存泄露)的排查思路

第一,可以通过jmap指定打印他的内存快照 dump文件

第二,可以通过jdk自带工具VisualVM去分析 dump文件

第三,通过查看堆信息的情况,可以大概定位内存溢出是哪行代码出了问题

第四,找到对应的代码,通过阅读上下文的情况,进行修复即可

31.CPU持续飙高的排查思路

第一,可以使用使用top命令查看占用cpu的情况

第二,通过top命令查看后,可以查看是哪一个进程占用cpu较高,记录这个进程id

第三,可以通过ps 查看当前进程中的线程信息,看看哪个线程的cpu占用较高

第四,可以jstack命令打印进行的id,找到这个线程,就可以进一步定位问题代码的行号

四.JUC篇

1.线程与进程的区别

1、进程是资源分配的最小单位,线程是资源调度的最小单位。

2、线程是在进程下运行的。一个进程可以包含多个线程。

3、进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间。而线程是共享进程中的数据的,使用相同的地址空间。

4、同一进程下不同线程间数据容易共享,不同进程间数据很难共享。

5、线程之间没有单独的地址空间,一个线程死掉,整个进程也死掉。而一个进程死掉并不会对另外一个进程造成影响。

五.MySQL篇

1.MyIsAm和InnoDB的区别

InnoDB有三大特性,分别是事务、外键、行级锁,这些都是MyIsAm不支持的,

另外InnoDB是聚簇索引,MyIAm是非聚簇索引,

InnoDB不支持全文索引,MyIAm支持

InnoDB支持自增和MVCC模式的读写,MyIAm不支持

MyIsAM的访问速度一般InnoDB快,差异在于innodb的mvcc、行锁会比较消耗性能,还可能有回表的过程(先去辅助索引中查询数据,找到数据对应的key之后,再通过key回表到聚簇索引树查找数据)

2.MySQL事务特性

原子性:一个事务内的操作统一成功或失败

一致性:事务前后的数据总量不变

隔离性:事务与事务之间相互不影响

持久性:事务一旦提交发生的改变不可逆

3.事务靠什么保证

原子性:由undolog日志保证,他记录了需要回滚的日志信息,回滚时撤销已执行的sql

一致性:由其他三大特性共同保证,是事务的目的

隔离性:由MVCC保证

持久性:由redolog日志和内存保证,MySQL修改数据时内存和redolog会记录操作,宕机时可恢复

4.事务的隔离级别

在高并发情况下,并发事务会产生脏读、不可重复读、幻读问题,这时需要用隔离级别来控制

读未提交: 允许一个事务读取另一个事务已提交的数据,可能出现不可重复读,幻读。

读提交: 只允许事务读取另一个事务没有提交的数据可能出现不可重复读,幻读。

可重复读: 确保同一字段多次读取结果一致,可能出现欢幻读。

可串行化: 所有事务逐次执行,没有并发问日

Inno DB 默认隔离级别为可重复读级别,分为快照度和当前读,并且通过间隙锁解决了幻读问题。

5.什么是快照读和当前读

*快照读读取的是当前数据的可见版本,可能是会过期数据,不加锁的select就是快照都

*当前读读取的是数据的最新版本,并且当前读返回的记录都会上锁,保证其他事务不会并发修改这条记录。如update、insert、delete、select for undate(排他锁)、select lockin share mode(共享锁) 都是当前读

6.MVCC是什么

MVCC是多版本并发控制,为每次事务生成一个新版本数据,每个事务都由自己的版本,从而不加锁就决绝读写冲突,这种读叫做快照读。只在读已提交和可重复读中生效。

实现原理由四个东西保证,他们是

undolog日志:记录了数据历史版本

readView:事务进行快照读时动态生成产生的视图,记录了当前系统中活跃的事务id,控制哪个历史版本对当前事务可见

隐藏字段DB_TRC_ID: 最近修改记录的事务ID

隐藏字段DB_Roll_PTR: 回滚指针,配合undolog指向数据的上一个版本

7.MySQL有哪些索引

主键索引:一张表只能有一个主键索引,主键索引列不能有空值和重复值

唯一索引:唯一索引不能有相同值,但允许为空

普通索引:允许出现重复值

组合索引:对多个字段建立一个联合索引,减少索引开销,遵循最左匹配原则

全文索引:myisam引擎支持,通过建立倒排索引提升检索效率,广泛用于搜索引擎

8.聚簇索引和非聚簇索引的区别

聚簇索引:

  1. 聚簇索引的叶子节点存放的是主键值和数据行
  2. 优点:根据索引可以直接获取值,所以他获取数据更快;对于主键的排序查找和范围查找效率更高;
  3. 缺点:如果主键值很大的话,辅助索引也会变得很大;如果用uuid作为主键,数据存储会很稀疏;修改主键或乱序插入会让数据行移动导致页分裂;所以一般我们定义主键时尽量让主键值小,并且定义为自增和不可修改。

非聚簇索引(辅助索引)

​ 1. 非聚簇索引的叶子节点存放的是数据行地址,先根据索引找到数据地址,再根据地址去找数据

他们都是b+数结构

9.MySQL如何做慢SQL优化

开启慢查询日志( SET GLOBAL long_query_time=阈值;超过阈值的sql就会记录到慢查询日志当中),或查看执行计划(explain+SQL)。慢查询优化如下:

(1)分析sql语句,是否加载了不需要的数据列

(2)分析sql执行计划,字段有没有索引,索引是否失效,是否用对索引

(3) 使用复杂查询时,尽量使用关联查询来代替子查询,并且最好使用内连接

(4)orderby查找时使用索引进行排序,否则的话需要进行回表,然后在排序缓冲区中进行排序。

(5)groupby查询时,同样要建立联合索引,避免使用到临时表

(6)分页查询时,如果偏移量太大,比如要查询一百万条数据后的十条记录,可以使用主键+子查询的方式,避免进行全表扫描

(7)使用count函数时直接使用count的话count(*)的效率最高,也可以额外创建一张表去统计不同表中的数据行数,但维护麻烦

count(*)或count(唯一索引)或count(数字):表中总记录数,count(字段)不会统计null

(8) 在写update语句时,where条件要添加使用索引,否则会锁会从行锁升级为表锁

(9)表中数据是否太大,是不是要分库分表

10.为什么要用内连接而不用外连接?

用外连接的话连接顺序是固定死的,比如left join,他必须先对左表进行全表扫描,然后一条条到右表去匹配;而内连接的话MySQL会自己根据查询优化器去判断用哪个表做驱动。

子查询的话同样也会对驱动表进行全表扫描,所以尽量用小表做驱动表。

11.MySQL整个查询的过程

(1)客户端向 MySQL 服务器发送一条查询请求
(2)服务器首先检查查询缓存,如果命中缓存,则返回存储在缓存中的结果。否则进入下一阶段
(3)服务器进行 SQL 解析、预处理、再由优化器生成对应的执行计划
(4)MySQL 根据执行计划,调用存储引擎的 API 来执行查询
(5)将结果返回给客户端,同时缓存查询结果
注意:只有在8.0之前才有查询缓存,8.0之后查询缓存被去掉了

12.执行计划中有哪些字段?

我们想看一个sql的执行计划使用的语句是explain+SQL,表中的字段包括:

id:select查询的优先级,id越大优先级越高,子查询的id一般会更大

select_type:查询的类型,是普通查询还是联合查询还是子查询,常见类型有simple(不包含子查询),primary(标记复杂查询中最外层的查询),union(标记primart只后子查询)

table:者一行的数据是数哪张表的

type:扫描类型,效率从底到高为

ALL(全表扫描)>index(全索引扫描,我们的需要的数据在索引中可以获取)>range(范围索引扫描)>ref(使用非唯一索引列进行了关联查询)> eq_ref (使用唯一索引进行关联查询)>const(查询结果只有一行,体现在使用唯一索引进行单表查询)>system(表中只有一行数据)

possible_keys(可能的):当前查询语句可能用到的索引,可能为null(如果用了索引但是为null有可能是表数据太少innodb认为全表扫描更快)

key:实际使用到的索引

ref(编号):显示索引的哪一行被使用了

rows:估算大概多少行数据被查找了

excess(额外的):MySQL如何查询额外信息,常见的有:

Using filesort:排序时使用外部排序而不是索引排序,需要回表查询数据,然后在排序缓冲区中进行排序

backward index scan:排序时使用了索引排序,但如果是按照降序排序的话就会使用反向扫描索引

Using index:表示使用覆盖索引(覆盖索引:如果key中显示有索引,select后面查询的字段都可以从通过这个索引获取)

Using temporary:查询时要建立一个临时表存放数据

13.哪些情况索引会失效

(1)where条件中有or,除非所有查询条件都有索引,否则失效

(2)like查询用%开头,索引失效

(3)索引列参与计算,索引失效

(4)违背最左匹配原则,索引失效

(5)索引字段发生类型转换,索引失效

(6)MySQL觉得全表扫描更快时(数据少),索引失效

14.B和B+数的区别,为什么使用B+数

二叉树:索引字段有序,极端情况会变成链表形式

AVL数:树的高度不可控

B数:控制了树的高度,但是索引值和data都分布在每个具体的节点当中,若要进行范围查询,要进行多次回溯,IO开销大

B+树:非叶子节点只存储索引值,叶子节点再存储索引+具体数据,从小到大用链表连接在一起,范围查询可直接遍历不需要回溯3

15.MySQL有哪些锁

基于粒度:

*表级锁:对整张表加锁,粒度大并发小

*行级锁:对行加锁,粒度小并发大

*间隙锁:间隙锁,锁住表的一个区间,间隙锁之间不会冲突只在可重复读下才生效,解决了幻读

基于属性:

*共享锁:又称读锁,一个事务为表加了读锁,其它事务只能加读锁,不能加写锁

*排他锁:又称写锁,一个事务加写锁之后,其他事务不能再加任何锁,避免脏读问题

16.MySQL内连接、左连接、右连接的区别

内连接取量表交集部分,左连接取左表全部右表匹部分,右连接取右表全部坐表匹部分

17.sql执行顺序

from、 on 、join 、where 、group by、having、select、order by、limit

18.如何设计数据库?

(1)抽取实体,如用户信息,商品信息,评论

(2)分析其中属性,如用户信息:姓名、性别…

(3)分析表与表之间的关联关系

然后可以参考三大范式进行设计,设计主键时,主键要尽量小并且定义为自增和不可修改。

19.where和having的区别?

where是约束声明,having是过滤声明,where早于having执行,并且where不可以使用聚合函数,having可以

20.三大范式

第一范式:每个列原子性,都不可再分。

第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。

第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。

21.char和varchar的区别

char是不可变的,最大长度为255,varchar是可变的字符串,最大长度为2^16

22.InnoDB 什么情况下会产生死锁

事务1已经获取数据A的写锁,想要去获取数据B的写锁,然后事务2获取了B的写锁,想要去获取A的写锁,相互等待形成死锁。
MySQL解决死锁的机制有两个:1.等待, 直到超时 2.发起死锁检测,主动回滚一条事务
死锁检测的原理是构建一个以事务为顶点、 锁为边的有向图, 判断有向图是否存在环, 存在即有死锁。
我们平时尽量减少事务操作的资源和隔离级别

23.MySQL 删除自增 id,随后重启 MySQL 服务,再插入数据,自增 id 会从几开始?

innodb 引擎:
MySQL8.0前,下次自增会取表中最大 id + 1。原理是最大id会记录在内存中,重启之后会重新读取表中最大的id
MySQL8.0后,仍从删除数据 id 后算起。原理是它将最大id记录在redolog里了

myisam:
自增的 id 都从删除数据 id 后算起。原理是它将最大id记录到数据文件里了

24.MySQL插入百万级的数据如何优化?

(1)一次sql插入多条数据,可以减少写redolog日志和binlog日志的io次数(sql是有长度限制的,但可以调整)

(2)保证数据按照索引进行有序插入

(3)可以分表后多线程插入

25.MySQL哪些字段适合建立索引?

  1. 表的主键、外键必须有索引;
  2. 数据量500左右的表应该有索引;
  3. 在where从句,group by从句,order by从句,on从句中的列添加索引
  4. 索引应该建在选择性高的字段上;
  5. 索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;
  6. 复合索引的建立需要进行仔细分析;尽量考虑用单字段索引代替:
  7. 不适合的字段
    1. null值过多
    2. 太多重复值的
    3. 不是查询(where, group by, order by)条件字段
    4. 表记录少
    5. 经常插入、删除、修改的表,不要建立太多的索引;
    6. 删除无用的索引,避免对执行计划造成负面影响;

26.MySQL有哪些数据库存储引擎,有什么区别?

  1. 存储引擎是用来把数据存储在文件或内存的技术;
  2. MySQL常用的存储引擎有四种,常用的有MyISAM、InnoDB、MEMORY、ARCHIVE;
  3. InnoDB支持事务,支持外键,支持行锁,写入数据时操作快,MySQL5.6版本以上才支持全文索引。
    MyISAM不支持事务。不支持外键,支持表锁,支持全文索引,读取数据快。

27.什么是存储过程?怎么写?

  1. 存储过程就是具有名字的一段代码,用来完成一个特定的功能
  2. CREATE PROCEDURE 名字(IN p_in int)

28.什么是视图?怎么写?

  1. 视图(VIEW)也被称作虚表,即虚拟的表,是一组数据的逻辑表示,其本质是对应于一条SELECT语句,结果集被赋予一个名字,即视图名字
  2. CREATE VIEW view_name as select语句

29.什么是触发器?怎么写?

  1. 触发器一旦定义,无需用户调用,任何对表的修改操作均由数据库服务器自动激活相应的触发器。
  2. 触发器的主要作用是实现主键和外键不能保证的复杂的参照关系性和数据的一致性,从而保护表中数据;
  3. create trigger 触发器名称 trigger_time 触发事件 on 表名 for each row 触发器主体

30.多列索引的最左原则是什么?

  1. (A、B、C)创建了索引,相当于创建了(A)、(A、B)、(A、B、C)
  2. (A、B)创建了索引,相当于创建了(A)、(A、B)
  3. 创建复合索引时应该将最常用作限制条件的列放在最左边,依次递减

31.说一下常用的聚合函数?

  1. AVG() 返回数值列的平均值。
  2. COUNT()返回匹配指定条件的行数。
  3. SUM()返回数值列的总数。
  4. FIRST()返回指定的列中第一个记录的值。
  5. LAST()返回指定的列中最后一个记录的值。
  6. MAX() 返回指定列的最大值。
  7. MIN()返回指定列的最小值。
  8. ROUND()用于把数值字段舍入为指定的小数位数。
  9. NOW()返回当前系统的日期和时间。
  10. FORMAT()用于对字段的显示进行格式化。

以上的随便列几个

32.说说数据库约束有哪些?

主键约束(PRIMARY KEY,非空和唯一的结合),确保某列(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。

唯一约束(UNIQUE),保证某列的每行必须有唯一的值。

非空约束(NOT NULL),指示某列不能存储 NULL 值。

默认约束(DEFAULT),规定没有给列赋值时的默认值。

检查约束(CHECK),保证列中的值符合指定的条件。

外键约束(FOREIGN KEY),保证一个表中的数据匹配另一个表中的值的参照完整性。

33.说说数据库连接查询有哪些?

自连接,外连接(左外连接、右外连接、全连接),交叉连接。

34.MySQL 如何实现多表查询

多表关系

  • 一对多:在多的一方设置外键,关联一的一方的主键
  • 一对一:用于表结构拆分,在其中任何一方设置外键(给唯一约束UNIQUE),关联另一方的主键
  • 对多对:需要建立中间表,中间表包含两个外键,关联两张表的主键

多表查询

  • 内连接
  • 外连接
  • 自连接
  • 子查询

35.MySQL内连接和外连接的区别 ?

  • 内连接会取出连接表中匹配到的数据,匹配不到的不保留;
  • 外连接会取出连接表中匹配到的数据,匹配不到的也会保留,其值为NULL。以某一个表为主表后,进行关联查询,不管能不能关联的上,主表的数据都会保留,关联不上的以NULL显示

36.CHAR和VARCHAR的区别

区别主要有以下几个方面

1、最大长度:char最大长度是255字符,varchar最大长度是65535个字节。

2、定长:char是定长的,不足的部分用隐藏空格填充,varchar是不定长的。

3、空间使用:char会浪费空间,varchar会更加节省空间。

4、查找效率:char查找效率会很高,varchar查找效率会更低。

​ varchar需要计算内容占用的长度,而char不会,所以char的效率稍高一些

在项目中的使用,这两种方式都会用到,比如像一些枚举值可以选择使用char,像一些描述信息或名字类可以选择使用varchar

37.什么是事务?什么是ACID

事务:由多个操作组成的一个逻辑单元,组成这个逻辑单元的多个操作要么都成功,要么都失败。

事务的四个特性ACID:

  • A=Atomicity原子性:就是上面说的,要么全部成功,要么全部失败,不可能只执行一部分操作。
  • C=Consistency一致性:系统(数据库)总是从一个一致性的状态转移到另一个一致性的状态,不会存在中间状态。
  • I=Isolation隔离性: 通常来说:一个事务在完全提交之前,对其他事务是不可见的
  • D=Durability持久性:一旦事务提交,那么就永远是这样子了,哪怕系统崩溃也不会影响到这个事务的结果。

38.MySQL的默认事务隔离级别?

  1. MySQL默认的事务隔离级别就是:REPEATABLE READ
  2. 可通过命令 select @@tx_isolation 查看默认的隔离级别

39.了解过MySQL的索引吗 ?

索引(index)类似一本书的目录,是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构(B+树),这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

40.聚集索引选取规则?

  • 如果存在主键,主键索引就是聚集索引。
  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
  • 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。

41.什么是回表查询?为什么需要回表查询?

**回表查询:**先到二级索引中查找数据,找到主键值,然后再到聚集索引中根据主键值,获取数据的方式,就称之为回表查询

**回表的原因:**是 select查询要的列在二级索引的列中不存在,需要去主键索引获取,因为主键索引含有整行记录值

42.什么是覆盖索引?

覆盖索引 是指 查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到

43.什么是左前缀原则 ?

如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)。

44.什么是索引下推?

索引下推是MySQL 5.6后出的特性,符合某种条件下,把原来需要在Server端完成的条件判断转交给存储引擎去处理,这种现象称为索引下推

好处:减少回表查询次数,提高查询效率,节约io开销

条件:只适合于二级索引(非主键索引)且为多列索引(复合索引、联合索引)

45.索引是越多越好吗?

索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率

46.什么样的字段需要建索引?

1.哪些数据量大的,且查询频繁的字段
2.where order_by group_by on 后面的字段
3.推荐建立联合索引

47.什么样的字段不需要 ?

1.数据量少的,查询频率小的字段
2.null值多的字段
3.重复值多的字段
4.更改频繁的字段

48.如何定位慢查询?

1.慢查询日志记录了所有执行时间超过指定参数的所有SQL语句的日志。

2.要定位查询,需要先开启慢查询日志,通过SET GLOBAL slow_query_log = 'ON’开启

3.在设置慢查询日志的阈值,通过SET GLOBAL long_query_time=2指定阈值大小

4.通过MySQL.slow_log表查看慢查询日志

49.一个SQL语句执行很慢, 如何分析?

可以采用EXPLAIN查询sql执行计划。

主要可以根据以下字段,判断sql是否需要优化,特别是是否能命中索引或命中索引的情况

  • type 通过sql的连接的类型进行优化
  • possible_key 通过它查看是否可能会命中索引
  • key 当前sql实际命中的索引
  • key_len 索引占用的大小
  • Extra 额外的优化建议

六.Redis篇

1.简单介绍下redis?

  1. Redis是非关系型数据库
  2. 它的Value支持五种数据类型:String,List,Set,Zset,Hash。String和Hash使用最广泛。
  3. 数据存在内存中,用于缓存方向,超过 10万次/s读写操作。
  4. 支持AOF和RDB
  5. 支持主从复制。
  6. 可用来做分布式锁和分布式事务。
  7. 海量数据不适合,容错和恢复能力不太够,容易数据不一致,在线扩容难支持。

2.redis为什么快?

(1)完全基于内存操作

(2)数据结构简单,对数据操作简单

(3)redis执行命令是单线程的,避免了上下文切换带来的性能问题,也不用考虑锁的问题

(4) 采用了非阻塞的io多路复用机制,使用了单线程来处理并发的连接;内部采用的epoll+自己实现的事件分离器

其实Redis不是完全多线程的,在核心的网络模型中是多线程的用来处理并发连接,但是数据的操作都是单线程。Redis坚持单线程是因为Redis是的性能瓶颈是网络延迟而不是CPU,多线程对数据读取不会带来性能提升。

3.Redis数据类型

String 常用命令: set,get,decr,incr,mget等

Hash 常用命令: hget,hset,hgetall 等

List 常用命令: lpush,rpush,lpop,rpop,lrange 等

Set 常用命令: sadd,spop,smembers,sunion 等

SortSet 常用命令: zadd,zrange,zrem,zcard 等

4.redis的使用场景?

  1. 缓存,将热点数据放到内存中。
  2. 计数器,可以对 String 进行自增自减运算,从而实现计数器功能。
  3. 队列,List 是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息。不过最好使用 Kafka、RabbitMQ 等消息中间件。
  4. 分布式锁,在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。
  5. 会话缓存,可以使用 Redis 来统一存储多台应用服务器的会话信息。
  6. 全页缓存(FPC),除基本的会话token之外,Redis还提供很简便的FPC平台。没接触过。
  7. 交集、差集、并集,Set 可以实现交集、差集、并集等操作,从而实现共同好友等功能。没接触过。
  8. 排行榜,ZSet 可以实现有序性操作,从而实现排行榜等功能。
  9. 发布/订阅功能,用的少,没有MQ好。

5.redis存储的数据类型有哪些?如何选择?

  1. STRING,字符串,最简单的k-v存储,短信验证码,配置信息等,就用这种类型来存储。
  2. HASH,包含键值对的无序散列表,一般key为ID或者其他唯一标识,value对应的就是详情了。如商品详情,个人信息详情,新闻详情等
  3. LIST,列表,因为list是有序的,比较适合存储一些有序且数据相对固定的数据。如省市区表、字典表。List还可以做消息队列。
  4. SET,无序集合,可以简单的理解为ID-List的模式,如微博中一个人有哪些好友,set最牛的地方在于,可以对两个set提供交集、并集、差集操作。例如:查找两个人共同的好友等。
  5. ZSET,有序集合,set的增强版本,增加了一个score参数,自动会根据score的值进行排序。比较适合类似于top N等不根据插入的时间来排序的数据。
  6. Bitmaps,用于签到
  7. Geo,存储地理信息
  8. HyperLogLog,用来做基数统计

6.redis持久化机制

(1)快照持久化RDB

redis的默认持久化机制,通过父进程fork一个子进程,子进程将redis的数据快照写入一个临时文件,等待持久化完毕后替换上一次的rdb文件。整个过程主进程不进行任何的io操作。持久化策略可以通过save配置单位时间内执行多少次操作触发持久化。所以RDB的优点是保证redis性能最大化,恢复速度数据较快,缺点是可能会丢失两次持久化之间的数据

(2)追加持久化AOF

以日志形式记录每一次的写入和删除操作,策略有每秒同步、每次操作同步、不同步,优点是数据完整性高,缺点是运行效率低,恢复时间长

7.Redis缓存穿透如何解决?

缓存穿透是指频繁请求客户端和缓存中都不存在的数据,缓存永远不生效,请求都到达了数据库。

解决方案:

(1)在接口上做基础校验,比如id<=0就拦截

(2)缓存空对象:找不到的数据也缓存起来,并设置过期时间,可能会造成短期不一致

(3)布隆过滤器:在客户端和缓存之间添加一个过滤器,拦截掉一定不存在的数据请求

8.Redis如何解决缓存击穿?

缓存击穿是只一个热点key,在某一瞬间失效,导致大量请求到达数据库

解决方案:

(1)设置热点数据永不过期

(2)给缓存重建的业务加上互斥锁,缺点是性能低

8.Redis如何解决缓存雪崩?

缓存雪崩是值某一时间Key同时失效或redis宕机,导致大量请求到达数据库

解决方案:

(1)搭建集群保证高可用

(2)进行数据预热,给不同的key设置随机的过期时间

(3)给缓存业务添加限流降级,通过加锁或队列控制操作redis的线程数量

(4)给业务添加多级缓存

9.Redis分布式锁的实现原理

原理是使用setnx+setex命令来实现,但是会有一系列问题:

(1)任务时常超过缓存时间,锁自动释放。可以使用Redision看门狗解决

(2)加锁和释放锁的不是同一线程。可以在Value中存入uuid,删除时进行验证。但是要注意验证锁和删除锁也不是一个原子性操作,可以用lua脚本使之成为原子性操作

(3)不可重入。可以使用Redision解决(实现机制类似AQS,计数)

(4)redis集群下主节点宕机导致锁丢失。使用红锁解决

10.Redis集群方案

(1)主从模式:个master节点,多个slave节点,master节点宕机slave自动变成主节点

(2)哨兵模式:在主从集群基础上添加哨兵节点或哨兵集群,用于监控master节点健康状态,通过投票机制选择slave成为主节点

(3)分片集群:主从模式和哨兵模式解决了并发读的问题,但没有解决并发写的问题,因此有了分片集群。分片集群有多个master节点并且不同master保存不同的数据,master之间通过ping相互监测健康状态。客户端请求任意一个节点都会转发到正确节点,因为每个master都被映射到0-16384个插槽上,集群的key是根据key的hash值与插槽绑定

11.Redis集群主从同步原理

主从同步第一次是全量同步:slave第一次请求master节点会根据replid判断是否是第一次同步,是的话master会生成RDB发送给slave。

后续为增量同步:在发送RDB期间,会产生一个缓存区间记录发送RDB期间产生的新的命令,slave节点在加载完后,会持续读取缓存区间中的数据

12.Redis缓存一致性解决方案

Redis缓存一致性解决方案主要思考的是删除缓存和更新数据库的先后顺序

先删除缓存后更新数据库存在的问题是可能会数据不一致,一般使用延时双删来解决,即先删除缓存,再更新数据库,休眠X秒后再次淘汰缓存。第二次删除可能导致吞吐率降低,可以考虑进行异步删除。

先更新数据库后删除缓存存在的问题是会可能会更新失败,可以采用延时删除。但由于读比写快,发生这一情况概率较小。

但是无论哪种策略,都可能存在删除失败的问题,解决方案是用中间件canal订阅binlog日志提取需要删除的key,然后另写一段非业务代码去获取key并尝试删除,若删除失败就把删除失败的key发送到消息队列,然后进行删除重试。

13.Redis如何实现key的过期删除?

采用的定期过期+惰性过期

定期删除 :Redis 每隔一段时间从设置过期时间的 key 集合中,随机抽取一些 key ,检查是否过期,如果已经过期做删除处理。
惰性删除 :Redis 在 key 被访问的时候检查 key 是否过期,如果过期则删除。

14.Redis内存淘汰策略

当内存不足时按设定好的策略进行淘汰,策略有(1)淘汰最久没使用的(2)淘汰一段时间内最少使用的(3)淘汰快要过期的

15.redis的如何做事务支持

  1. Redis的事务实质上是命令的集合,在一个事务中要么所有命令都被执行,要么所有事物都不执行。
  2. 事务从开始到执行会经历以下三个阶段,MULTI 开始到 EXEC结束前,中间所有的命令都被加入到一个命令队列中;当执行 EXEC命令后,将QUEUE中所有的命令执行。也就是。
    1. MULTI开始事务。
    2. 命令入队列(QUEUE)。
    3. EXEC触发执行事务。
  3. Redis的事务没有关系数据库事务提供的回滚(rollback),所以开发者必须在事务执行失败后进行后续的处理
    1. 如果在一个事务中的命令出现错误,那么所有的命令都不会执行
    2. 如果在一个事务中出现运行错误,那么正确的命令会被执行
  4. 此外我们可以使用DISCARD取消事务。

16.Redis有哪些优缺点

  1. 快,读的速度是110000次/s,写的速度是81000次/s
  2. 结构丰富,支持5种,分别是string、has、list、set、zset。
  3. 持久化,支持AOF(Append Only File)和RDB(Redis DataBase)
  4. 分布式事务,Redis的事务实质上是命令的集合,在一个事务中要么所有命令都被执行,要么所有事物都不执行。
  5. 分布式是锁,分布式锁是控制分布式系统之间同步访问共享资源的一种方式
  6. 主从复制,主机会自动将数据同步到从机,可以实现读写分离。
  7. 不能存海量数据,受到物理内存的限制
  8. 不具备自动容错和恢复功能,主机从机的 宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
  9. 可能数据不一致,主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
  10. Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂,为避免这一问题,上线时必须确保有足够的空间。

七.基础框架篇

1.什么是Spring?

Spring是个轻量级的框架,通过IOC达到松耦合的目的,通过AOP可以分离应用业务逻辑和系统服务进行内聚性的开发,不过配置各种组件时比较繁琐,所以后面才出选了SpringBoot的框架。

2.IOC是什么?

IOC是控制反转,是一种思想,把对象的创建和调用从程序员手中交由IOC容器管理,降低对象之间的依赖关系。

创建一个bean的方式有xml方式、@Bean注解方式、@Componte方式

我们在对一个bean进行实例化后,要对他的属性进行填充,大多数我们都是使用 @Autowire直接的填充依赖注入的,他是有限按照类型进行匹配。

3.AOP是什么?

AOP是面向切面编程,可以将那些与业务不相关但是很多业务都要调用的代码抽取出来,思想就是不侵入原有代码的情况下对功能进行增强。

SpringAOP是基于动态代理实现的,动态代理是有两种,一种是jdk动态代理,一种是cglib动态代理;

jdk动态代理是原理是利用反射来实现的,需要调用反射包下的Proxy类的newProxyInstance方法来返回代理对象,这个方法中有三个参数,分别是用于加载代理类的类加载器,被代理类实现的接口的class数组和一个用于增强方法的InvocaHandler实现类。

cglib动态代理原理是利用asm开源包来实现的,是把被代理类的class文件加载进来,通过修改它的字节码生成子类来处理

jdk动态代理要求被代理类必须有实现的接口,生成的动态代理类会和代理类实现同样的接口,cglib则,生成的动态代理类会继承被代理类。Spring默认使用jdk动态代理,当被代理的类没有接口时就使用cglib动态代理

4.谈谈Spring的IOC和DI

  1. Ioc,控制反转;DI,依赖注入。
  2. 基本看不到New关键字了,对象的创建,销毁,调用都交给了Spring容器。
  3. Ioc和DI可以理解为同一个概念,都是一种编程思想。

5.如何使用aop自定义日志?

第一步:创建一个切面类,把它添加到ioc容器中并添加@Aspect注解

第二步: 在切面类中写一个通知方法,在方法上添加通知注解并通过切入点表达式来表示要对哪些方法进行日志打印,然后方法参数为JoinPoint

第三步:通过JoinPoint这个参数可以获取当前执行的方法名、方法参数等信息,这样就可以根据需求在方法进入或结束时打印日志

6.循环依赖是什么,怎么解决的?

循环依赖就是在创建 A 实例的时候里面包含着 B 属性实例,所以这个时候就需要去创建 B 实例,而创 建 B 实例过程中也包含着 A 实例。 这样 A 实例还在创建的过程当中,所以就导致 A 和 B 实例都创建不出来。

Spring通过三级缓存来解决循环依赖:

一级缓存:缓存经过完整的生命周期的Bean

二级缓存 :缓存未经过完整的生命周期的Bean

三级缓存:缓存的是ObjectFactory,其中存储了一个生成代理类的拉姆达表达式

我们在创建 A 的过程中,先将 A 放入三级缓存 ,这时要创建B,B要创建A就直接去三级缓存中查找,并且判断需不需要进行 AOP 处理,如果需要就执行拉姆达表达式得到代理对象,不需要就取出原始对象。然后将取出的对象放入二级缓存中,因为这个时候 A 还未经 过完整的生命周期所以不能放入一级缓存。这个时候其他需要依赖 A 对象的直接从二级缓存中去获取即可。当B创建完成,A 继续执行生命周期,当A完成了属性的注入后,就可以放入一级缓存了

7.Bean 的作用域

(1)Singleton:一个IOC容器只有一个

(2)Prototype:每次调用getBean()都会生成一个新的对象

(3)request:每个http请求都会创建一个自己的bean

(4)session:同一个session共享一个实例

(5)application:整个serverContext只有一个bean

(6)webSocket:一个websocket只有一个bean

8.Bean 生命周期

1.当程序加载运行时会根据Spring中配置文件找到bean配置的属性和方法,并通过java反射机制创建实例化bean对象。

Bean实现了BeanNameAware接口,执行了setBeanName方法,实现注入对象。

2.实现了BeanFactoryAware工厂接口,执行了setBeanFactory方法。

3.实现了ApplicationContext接口类,执行了setApplicationContest方法。

4.实现了BeanPostProcessor接口类,执行postProcessBeforeInitialization方法

5.实现了InitiliazingBean 执行afterPropertiesSet方法,并加载配置文件定义了init-method 则执行对应初始化方法BeanPostProcessor 执行postProcessorfterInitilization方法,完成 Bean的初始化,使得bean可以使用。

6.实现了DisposabileBean接口,加载配置文件中的destroy-method方法销毁bean对象实例。

9.Spring 事务原理?

Spring事务有编程式和声明式,我们一般使用声明式,在某个方法上增加@Transactional注解,这个方法中的sql会统一成功或失败。

原理是:

当一个方法加上@Transactional注解,Spring会基于这个类生成一个代理对象并将这个代理对象作为bean,当使用这个bean中的方法时,如果存在@Transactional注解,就会将事务自动提交设为false,然后执行方法,执行过程没有异常则提交,有异常则回滚、

10.Spring事务失效场景

(1)事务方法所在的类没有加载到容器中

(2)事务方法不是public类型

(3)同一类中,一个没有添加事务的方法调用另外以一个添加事务的方法,事务不生效

(4)Spring事务默认只回滚运行时异常,可以用rollbackfor属性设置

(5)业务自己捕获了异常,事务会认为程序正常秩序

11.Spring事务的隔离级别

default:默认级别,使用数据库自定义的隔离级别

其它四种隔离级别与MySQL一样

12.Spring事务的传播行为

(1)支持当前事务,如果不存在,则新启一个事务

(2)支持当前事务,如果不存在,则抛出异常

(3)支持当前事务,如果不存在,则以非事务方式执行

(4)不支持当前事务,创建一个新事物

(5)不支持当前事务,如果已存在事务就抛异常

(6)不支持当前事务,始终以非事务方式执行

13.Spring用了哪些设计模式

BeanFactory用了工厂模式,AOP用了动态代理模式,RestTemplate用来模板方法模式,SpringMVC中handlerAdaper用来适配器模式,Spring里的监听器用了观察者模式

14.Spring的bean是线程安全的吗?

Spring的默认bean作用域是单例的,单例的bean不是线程安全的,但是开发中大部分的bean都是无状态的,不具备存储功能,比如controller、service、dao,他们不需要保证线程安全。

如果要保证线程安全,可以将bean的作用域改为prototype,比如像Model View。

另外还可以采用ThreadLocal来解决线程安全问题。ThreadLocal为每个线程保存一个副本变量,每个线程只操作自己的副本变量。

15.Spring用过哪些重要的注解?

@Component,@Service,@Repository,@Controller 用于服务类

@Autowired 通过类型来实现自动注入bean

@Qualifier 配合@Autowired实现根据name注入bean。

@Scope 用于配置 Spring bean 的范围

@Configuration,@ComponentScan,@Bean 用于基于 java 的配置

@Aspect,@Before,@After,@Around,@Pointcut 用于切面编程

16.Spring自动装配bean有哪些方式?

1、no:这是 Spring 框架的默认设置,在该设置下自动装配是关闭的,开发者需要自行在 bean 定义中用标签明确的设置依赖关系 。

2、byName:该选项可以根据bean名称设置依赖关系 。 当向一个bean中自动装配一个属性时,容器将根据bean的名称自动在在配置文件中查询一个匹配的bean。 如果找到的话,就装配这个属性,如果没找到的话就报错 。

3、byType:该选项可以根据 bean 类型设置依赖关系 。 当向一个 bean 中自动装配一个属性时,容器将根据 bean 的类型自动在在配置文件中查询一个匹配的 bean。 如果找到的话,就装配这个属性,如果没找到的话就报错 。

4、constructor :构造器的自动装配和byType模式类似,但是仅仅适用于与有构造器相同参数的bean,如果在容器中没有找到与构造器参数类型一致的bean ,那么将会抛出异常 。

5、default:该模式自动探测使用构造器自动装配或者byType自动装配 。 首先会尝试找合适的带参数的构造器,如果找到的话就是用构造器自动装配,如果在bean内部没有找到相应的 构造器或者是无参构造器,容器就会自动选择 byTpe 的自动装配方式 。

17.Spring依赖注入有哪些方式?

有4种依赖注入方式:set注入、构造注入、静态工厂、实例工厂

有2 种实现方式:注解(如@Autowired,@Resource,@Required)和配置文件(xml形式)

18.使用Spring框架的好处是什么?

  1. 轻量:Spring 是轻量级,无侵入。
  2. 控制反转(IOC):Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
  3. 面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
  4. 容器:Spring 包含并管理应用中对象的生命周期和配置。
  5. MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。
  6. 事务管理:Spring 提供一个持续的事务管理接口,可以通过配置文件或者注解轻松实现。
  7. 异常处理:Spring提供了全局异常处理功能,只需要声明一个全局异常处理器就可以捕获所有异常信息。

19.Mybatis的一级、二级缓存?

1、一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当Session进行flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存。

2、二级缓存:二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储。

默认情况下二级缓存并没有开启,要想使用二级缓存,可以使用标签开启

3、对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear。

20.如何使用Mybatis实现批量插入?

1、Mybatis的接口方法参数需要定义为集合类型List

2、在映射文件中通过forEach标签遍历集合,获取每一个元素作为insert语句的参数值

21.Mybatis是否支持延迟加载?

Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。

默认情况下延迟加载是关闭的。

22.Mybatis有哪些动态sql?并简述执行原理?

Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态 拼接sql的功能,Mybatis提供了9种动态sql标签 trim|where|set|foreach|if|choose|when|otherwise|bind。

其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此 来完成动态sql的功能。

23.Mybatis如何实现多表查询

第一种是 : 编写多表关联查询的SQL语句 , 使用ResultMap建立结果集映射 , 在ResultMap中建立多表结果集映射的标签有associationcollection

第二种是 : 将多表查询分解为多个单表查询, 使用ResultMap表的子标签associationcollection标签的select属性指定另外一条SQL的定义去执行, 然后执行结果会被自动封装

24.当实体类中的属性名和表中的字段名不一样 ,怎么办

第1种: 通过在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。

第2种: 通过来映射字段名和实体类属性名的一一对应的关系。

第3种,开启Mybatis驼峰命名自动匹配映射

25.Mybatis 如何获取生成的主键

使用insert标签中的useGeneratedKeys和keyProperty 属性。

useGeneratedKeys:是够获取自动增长的主键值。true表示获取。

keyProperty:指定将获取到的主键值封装到哪儿个属性里

26.#{}和${}的区别是什么?

  1. #{}是预编译处理,可以有效的防止SQL注入。预编译的时候会将sql中的#{}替换为?号,使用PreparedStatement的set方法来赋值
  2. ${}是直接字符串替换,所以有sql注入的风险
  3. order by 字段,from 表名称,只能用${}。

27.Mybatis模糊查询like语句该怎么写?

  1. 不用${},有sql注入风险
  2. 用 #{},%号在java中或者sql中都可以。sql中使用%,需要配合concat函数。

28.Spring MVC中的拦截器和Servlet中的filter有什么区别?

过滤器:依赖于servlet容器,在实现上基于函数回调,可以对几乎所有请求进行过滤

拦截器:依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架,属于面向切面编程(AOP)的一种运用。

29.如何定义一个全局异常处理类?

想要定义一个全局异常处理类的话,我们需要在这个类上添加@ControllerAdvice注解,然后定义一些用于捕捉不同异常类型的方法,在这些方法上添加@ExceptionHandler(value = 异常类型.class)和@ResponseBody注解,方法参数是HttpServletRequest和异常类型,然后将异常消息进行处理。

如果我们需要自定义异常的话,就写一个自定义异常类,该类需要继承一个异常接口,类属性包括final类型的连续id、错误码、错误信息,再根据需求写构造方法;

30.SpringMVC工作原理

SpringMVC工作过程围绕着前端控制器DispatchServerlet,几个重要组件有HandleMapping(处理器映射器)、HandleAdapter(处理器适配器)、ViewReslover(试图解析器)

工作流程:

(1)DispatchServerlet接收用户请求将请求发送给HandleMapping

(2)HandleMapping根据请求url找到具体的handle和拦截器,返回给DispatchServerlet

(3)DispatchServerlet调用HandleAdapter,HandleAdapter执行具体的controller,并将controller返回的ModelAndView返回给DispatchServler

(4)DispatchServerlet将ModelAndView传给ViewReslover,ViewReslover解析后返回具体view

(5)DispatchServerlet根据view进行视图渲染,返回给用户

31.SpringMVC常见注解

  1. @Controller:用于定义控制器类
  2. @ResponseBody:表示方法的返回结果直接写入HTTP response body中
  3. @PathVariable:获取路径参数
  4. @RequestParam:用在方法的参数前面
  5. @RequestBody:请求的json转化为bean去接收
  6. @RestController:是@Controller和@ResponseBody的合集
  7. @RequestMapping:提供路由信息,负责URL到Controller中的具体函数的映射
  8. @GetMapping:是@RequestMapping(method = RequestMethod.GET)的缩写。不支持
  9. @RequestMapping的自定义属性。
  10. @PostMapping:是@RequestMapping(method = RequestMethod.POST)的缩写。不支持
  11. @RequestMapping的自定义属性。
  12. @ControllerAdvice:统一处理异常。
  13. @ExceptionHandler:用在方法上表示遇到这个异常就执行以下方法。

八.微服务框架

1.介绍下SpringBoot

  1. SpringBoot 是 Spring 开源组织下的子项目,用于简化配置,提供了各种启动器,方便上手
  2. 独立运行Spring Boot而且内嵌了各种servlet容器,Tomcat、Jetty等,现在不再需要打成war包部署到容器中,Spring Boot只要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内。
  3. 简化配置Spring-boot-starter-web启动器自动依赖其他组件,减少了maven的配置
  4. 约定优于配置,Spring Boot能根据当前类路径下的类、jar包来自动配置bean

2.SpringBoot自动配置原理

启动类@SpringBootApplication注解下,有三个关键注解

(1)@SpringBootConfiguration:表示启动类是一个自动配置类

(2)@CompontScan:扫描启动类所在包外的组件到容器中

(3)@EnableConfigutarion:最关键的一个注解,他拥有两个子注解,其中@AutoConfigurationpackageu会将启动类所在包下的所有组件到容器中,@Import会导入一个自动配置文件选择器,他会去加载META_INF目录下的Spring.factories文件,这个文件中存放很大自动配置类的全类名,这些类会根据元注解的装配条件生效,生效的类就会被实例化,加载到ioc容器中

3.SpringBoot常用注解

@RestController :修饰类,该控制器会返回Json数据

@RequestMapping(“/path”) :修饰类,该控制器的请求路径

@Autowired : 修饰属性,按照类型进行依赖注入

@PathVariable : 修饰参数,将路径值映射到参数上

@ResponseBody :修饰方法,该方法会返回Json数据

@RequestBody(需要使用Post提交方式) :修饰参数,将Json数据封装到对应参数中
@Controller@Service@Compont: 将类注册到ioc容器

@Transaction:开启事务

4.Spring Boot的核心注解是哪个?他由哪几个注解组成的?

Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 :

  • @SpringBootConfiguration: 组合了- @Configuration注解,实现配置文件的功能;
  • @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项
  • @ComponentScan:Spring组件扫描

5.常用的SpringBoot起步依赖有哪些

image.png

6.SpringBoot支持的配置文件有哪些 ? 加载顺序是什么样的

  1. 从源码中可见,SpringBoot项目支持两种配置文件,一种是yml,一种是properties文件。
  2. 先加载application.yml然后加载application.properties文件。如果有相同的配置,先加载的会被后加载的文件覆盖,假如在启动项目的时候给了启动参数,则最后生效,会覆盖前面所有相同的配置

7.运行一个SpringBoot项目有哪些方式

  1. 直接使用jar -jar 运行
  2. 开发过程中运行main方法
  3. 可以配置插件 , 将SpringBoot项目打war包, 部署到Tomcat中运行
  4. 直接用maven插件运行 maven Spring-boot:run

8.SpringBoot如何定义多套不同环境配置?

提供多套配置文件,如:

applcation.properties
application-dev.properties
application-test.properties
application-prod.properties

然后在applcation.properties文件中指定当前的环境spring.profiles.active=test,这时候读取的就是application-test.properties文件。

9.什么是微服务?微服务的优缺点是什么?

微服务

将单体服务拆分成一组小型服务。拆分完成之后,每个小型服务都运行在独立的进程中。服务与服务之间采用轻量级的通信机制来进行沟通(Spring Cloud 中是基于Http请求)

每一个服务都按照具体的业务进行构建,如电商系统中,订单服务,会员服务,支付服务等。这些拆分出来的服务都是独立的应用服务,可以独立的部署到上产环境中。相互之间不会受影响。所以一个微服务项目就可以根据业务场景进行开发。这在单体类项目中是无法实现的。

优点:松耦合,聚焦单一业务功能,无关开发语言,团队规模降低。在开发中,不需要了解多有业务,只专注于当前功能,便利集中,功能小而精。微服务一个功能受损,对其他功能影响并不是太大,可以快速定位问题。微服务只专注于当前业务逻辑代码,不会和 html、css 或其他界面进行混合。可以灵活搭配技术,独立性比较好。

缺点:随着服务数量增加,管理复杂,部署复杂,服务器需要增多,服务通信和调用压力增大,运维工程师压力增大,人力资源增多,系统依赖增强,数据一致性,性能监控难度增大。

10.SpringCloud的主要组件(主要项目)有哪些?

早期我们一般认为的Spring Cloud五大组件是

  • Eureka : 注册中心
  • Ribbon : 负载均衡
  • Feign : 远程调用
  • Hystrix : 服务熔断
  • Zuul/Gateway : 网关

随着SpringCloudAlibba在国内兴起 , 我们项目中使用了一些阿里巴巴的组件

  • 注册中心/配置中心 Nacos
  • 负载均衡 Ribbon
  • 服务调用 Feign
  • 服务保护 sentinel
  • 服务网关 Gateway

11.Springcloud主要解决什么问题?

解决服务之间的通信、容灾、负载平衡、冗余问题,能方便服务集中管理,常用组件有注册中心、配置中心、远程调用。服务熔断、网关

12.SpringBoot和SpringCloud的区别?

  1. SpringBoot专注于快速方便的开发单个个体微服务。
  2. SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,
    为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务
  3. SpringBoot可以离开SpringCloud独立使用开发项目, 但是SpringCloud离不开SpringBoot ,属于依赖的关系
  4. SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。

13.CAP理论

C:一致性,这里指的强一致性,也就是数据更新完,访问任何节点看到的数据完全一致

A:可用性,就是任何没有发生故障的服务必须在规定时间内返回合正确结果

P:容灾性,当网络不稳定时节点之间无法通信,造成分区,这时要保证系统可以继续正常服务。提高容灾性的办法就是把数据分配到每一个节点当中,所以P是分布式系统必须实现的,然后需要在C和A中取舍

14.为什么不能同时保证一致性和可用性呢?

当网络发生故障时,如果要保障数据一致性,那么节点相互间就只能阻塞等待数据真正同步时再返回,就违背可用性了。如果要保证可用性,节点要在有限时间内将结果返回,无法等待其它节点的更新消息,此时返回的数据可能就不是最新数据,就违背了一致性了

15.服务注册和发现是什么意思?SpringCloud如何实现服务注册发现?

各种注册中心组件的原理和流程其实大体上类似,核心的功能就一下几个 :

  1. 服务注册 : 服务启动的时候会将服务的信息注册到注册中心, 比如: 服务名称 , 服务的IP , 端口号等
  2. 服务发现 : 服务调用方调用服务的时候, 根据服务名称从注册中心拉取服务列表 , 然后根据负载均衡策略 , 选择一个服务, 获取服务的IP和端口号, 发起远程调用
  3. 服务状态监控 : 服务提供者会定时向注册中心发送心跳 , 注册中心也会主动向服务提供者发送心跳探测, 如果长时间没有接收到心跳, 就将服务实例从注册中心下线或者移除

使用的话, 首先需要部署注册中心服务 , 然后在我们自己的微服务中引入注册中心依赖, 然后再配置文件中配置注册中心地址 就可以了

16.nacos、eureka的区别?

① Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式

② 临时实例心跳不正常会被剔除,非临时实例则不会被剔除

③ Nacos支持服务列表变更的消息推送模式,服务列表更新更及时

④ Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;

总结:

  • Eureka采用AP方式
  • naocs默认是AP模式,可以采用CP模式

17.项目中微服务之间是如何通讯的?

  1. 同步通信:通过Feign发送http请求调用
  2. 异步:消息队列,如RabbitMq,KfaKa等

18.介绍一下SpringCloud里面Ribbon组件?

  1. ribbon是一个负载均衡客户端。
  2. feign默认集成了ribbon;SpringCloud-GateWay、restTemplate也会选择集成Ribbon。
  3. ribbon,具有多种负载均衡策略;默认是简单轮询服务列表。

19.你们项目负载均衡如何实现的?

服务调用过程中的负载均衡一般使用SpringCloud的Ribbon 组件实现 , Feign的底层已经自动集成了Ribbon , 使用起来非常简单

客户端调用的话一般会通过网关, 通过网关实现请求的路由和负载均衡

20.Ribbon负载均衡策略有哪些?如何自定义负载均衡策略?

这个不太了解,默认负载均衡策略是轮询,要想自定义负载均衡,可以通过定义IRule实现负载均衡规则

21.你们项目中有做过服务降级嘛?

我们项目中涉及到服务调用得地方都会定义降级, 一般降级逻辑就是返回默认值 , 降级的实现也非常简单 , 就是创建一个类实现FallbackFactory接口 , 然后再对应的Feign客户端接口上面 , 通过@FeignClient指定降级类

22.介绍下Spring Cloud Gateway?

  1. Spring Cloud官方推出的第二代网关框架,取代Zuul网关
  2. 基于Filter链提供网关功能:路由、重试、认证、负载、限流、熔断等功能
  3. 重要的概念是路由(route)、断言(Predicate)、过滤器(Filter)
  4. 包含很多的网关过滤器、网关过滤器工厂、全局过滤器

23.什么是Spring Cloud Gateway以及在你们的项目中如何去应用该组件的?

Spring Cloud Gateway:是Spring Cloud中所提供的一个服务网关组件,是整个微服务的统一入口,在服务网关中可以实现请求路由、统一的日志记录,流量监控、权限校验等一系列的相关功能!

项目应用:权限的校验

具体实现思路:使用Spring Cloud Gateway中的全局过滤器拦截请求(GlobalFilter、Order),从请求头中获取token,然后解析token。如果可以进行正常解析,此时进行放行;如果解析不到直接返回。

24.你们项目的配置文件是怎么管理的?

大部分的固定的配置文件都放在服务本地 , 一些根据环境不同可能会变化的部分, 放到Nacos中

Naocs中主要存放的是各个微服务共享的配置,需要随着需求动态变更的配置。

25.常用限流算法

计数器算法:使用redis的setnx和过期机制实现

漏桶算法:一般使用消息队列来实现,系统以恒定速度处理队列中的请求,当队列满的时候开始拒绝请求。

令牌桶算法:计数器算法和漏桶算法都无法解决突然的大并发,令牌桶算法是预先往桶中放入一定数量token,然后用恒定速度放入token直到桶满为止,所有请求都必须拿到token才能访问系统

26.熔断限流的理解?

SprngCloud中用Hystrix组件来进行降级、熔断、限流

熔断是对于消费者来讲,当对提供者请求时间过久时为了不影响性能就对链接进行熔断,

限流是对于提供者来讲,为了防止某个消费者流量太大,导致其它更重要的消费者请求无法及时处理。限流可用通过拒绝服务、服务降级、消息队列延时处理、限流算法来实现

27.断路器/熔断器用过嘛? 断路器的状态有哪些?

我们项目中使用Hystrix实现的断路器 ,默认是关闭的,如果需要开启需要在引导类上添加注解:

@EnableCircuitBreaker

断路器状态机包括三个状态:

  • closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
    • 请求错误率超过 5%(默认)
  • open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后(默认值)会进入half-open状态
  • half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
    • 请求成功:则切换到closed状态
    • 请求失败:则切换到open状态

28.说一下feign的基本使用和高级使用?

  1. 方便进行进行服务间的调用。
  2. 简单的几个注解,就能实现Web Service 客户端,方便了Web Service 客户端开发。
  3. 主要注解有@EnableFeignClients,@FeignClient
  4. 支持 Hystrix 和它的 Fallback。
  5. 支持 Ribbon 的负载均衡
  6. 支持OkHttp,支持Gzip

29.谈谈你对springCloud理解?

  1. 有很多优点

    1. 组件丰富
    2. 有名气,用的人多,社区活跃度高。
    3. 服务拆分粒度低。
    4. 使用互联网时代
  2. 也有缺点

    1. 运维成本高,微服务过多,治理成本高。
    2. 容错,分布式事务等对团队挑战大。

九.RabbitMQ篇

1.RabbitMQ如何保证消息不丢失?

发送端:

(1)创建一个消息状态表,开启RabbitMQ的confirm机制,当收到MQ回传的ACK以更新消息状态

(2)开启定时任务,隔断时间重新发送状态表中超时的任务,多次投递失败进行报警。

消费端:

(1)为了避免消息重复消费,要增加一张消息处理表,消费者拿到消息时判断消息处理表中当前消息是否已经存在,已经存在就抛弃,不存在就进行消费并放入记录表,消费和放入记录表要放在一个事务当中。

(2)开启手动ack模式,在消费者处理完业务后才返回ACK,避免消息还没有处理完就ACK。

2.RabbitMQ如何保证消费顺序

RabbitMQ消费顺序乱了是因为消费者集群拿到消息后对消息处理速度不同导致的,比如可能将增删改变成了增改删。

解决方法:为RabbitMQ创建多个Queue,每个消费者只监听其中一个Queue,同一类型的消息都放在一个queue中,同一个 queue 的消息是一定会保证有序的。

十.Linux/Nginx篇

1.linux常用命令

ifconfig:查看网络接口详情

ping:查看与某主机是否能联通

ps -ef|grep 进程名称:查看进程号

lost -i 端口 :查看端口占用情况

top:查看系统负载情况,包括系统时间、系统所有进程状态、cpu情况

free:查看内存占用情况

kill:正常杀死进程,发出的信号可能会被阻塞

kill -9:强制杀死进程,发送的是exit命令,不会被阻塞

2.Nginx反向代理是什么

反向代理是用来代理服务器接收请求的,然后将请求转发给内部网络的服务器,并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。

3.负载均衡算法有哪些

负载均衡算法有:轮询(默认)、带权轮询、ip_hash(按ip哈希结果分配,能解决session共享问题)、url_hash(按访问的URL的哈希结果分配)、fair(根据服务端响应时间分配,响应时间短优先)

十一.项目篇

1.秒杀项目流程

用户点击下单按钮时,进行三次判断:先判断请求路径是否合法,因为做了动态URL;再判断用户是否已经下单过,就是看redis缓存中有没有用户下单信息;最后判断库存,这里进行了redis库存预减,由于判断库存和预减库存不是原子性操作,所以用lua脚本来执行这一段代码。然后从这里开始使用分布式锁,锁id为用户id+商品id,防止一个用户发送多次请求让redis多次预减。

Redis扣减成功后,进行异步下单,直接将正在处理返回给前端,将用户id和商品Id发送RabbitMQ中,负责下单的业务会从消息队列中拿出消息,去执行以下操作:

1.减库存,减库存时用where 库存>0防止超卖

2.订单表中生成记录,订单表中的用户id和商品id添加了联合唯一索引防止超卖

减库存和增加订单放在一个事务内保证一致性

3.将用户id和订单id缓存到redis中用来最初对用户重复下单的判断

4.释放分布式锁,根据value去判断锁是不是当前线程的,判断和删除锁不是原子性操作,所以封装到了lua脚本中

2.提升qps的操作

(1)页面动静分离,静态页面缓存到redis
(2)分布式锁拦截不同用户的重复
(3)限流算法
(4)验证码限流
(5)rabbitMq流量削峰
(6)接口隐藏

相关内容