Java常见面试题(实习篇)

未完待续 !!!

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

一.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.说一下TCP的三次握手

20200904160558436.png

三次握手:

  • 首先客户端发送带有 SYN 标志的数据包到服务端
  • 然后服务端发送带有 SYN/ACK 标志的数据包到客户端
  • 最后客户端发送带有带有 ACK 标志的数据包到服务端

3.为什么要三次握手?一次,两次不行吗?

三次握手的目的是建立可靠的通信信道,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的,可以从以下两个方面考虑(我们这里假设客户端是首先发起连接请求):

  • 假设建立TCP连接仅需要两次握手,那么如果第二次握手时,服务端返回给客户端的确认报文丢失了,客户端这边认为服务端没有和他建立连接,而服务端却以为已经和客户端建立了连接,并且可能向服务端已经开始向客户端发送数据,但客户端并不会接收这些数据,浪费了资源。如果是三次握手,不会出现双方连接还未完全建立成功就开始发送数据的情况。
  • 如果服务端接收到了一个早已失效的来自客户端的连接请求报文,会向客户端发送确认报文同意建立TCP连接。但因为客户端并不需要向服务端发送数据,所以此次TCP连接没有意义并且浪费了资源。

所以三次握手就能确认双发收发功能都正常,缺一不可

4.第 2 次握手传回了 ACK,为什么还要传回 SYN?

接收端传回发送端所发送的 ACK 是为了告诉客户端,我接收到的信息确实就是你所发送的信号了,这表明从客户端到服务端的通信是正常的。而回传 SYN也就是同步序列编号则是为了建立并确认从服务端到客户端的通信

5.说一下TCP的四次挥手?

20200904160558691.png

四次挥手:

  • 第一次挥手:客户端向服务端发送的数据完成后,向服务端发起释放连接报文,报文包含标志位FIN=1,序列号seq=u。此时客户端只能接收数据,不能向服务端发送数据。
  • 第二次挥手:服务端收到客户端的释放连接报文后,向客户端发送确认报文,报文包含标志位ACK=1,序列号seq=v,确认号ack=u+1。此时客户端到服务端的连接已经释放掉,但服务端到客户端的单向连接还能正常传输数据,即使客户端无法收到。
  • 第三次挥手:服务端发送完数据后,向客户端发出连接释放报文,报文包含标志位FIN=1,标志位ACK=1,序列号seq=w,确认号ack=u+1
  • 第四次挥手:客户端收到服务端发送的释放连接请求,向服务端发送确认报文,报文包含标志位ACK=1,序列号seq=u+1,确认号ack=w+1

6.为什么TCP连接的时候是3次,关闭的时候却是4次?

  • 关闭连接时,客户端向服务端发送FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据
  • 服务器收到客户端的FIN 报文时,先回一个ACK应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送FIN 报文给客户端来表示同意现在关闭连接。

从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的ACKFIN一般都会分开发送,从而比三次握手导致多了一次。

7.为什么客户端发出第四次挥手的确认报文后要等2MSL的时间才能释放TCP连接?

MSL的意思是报文的最长寿命。要确保服务器是否已经收到了我们的ACK报文,过了一个MSL后如果没有收到的话,服务器会重新发FIN报文给客端,此时又花了一个MSL,客户端再次收到FIN报文之后,就知道之前的ACK报文丢失了,然后再次发送ACK报文,一来一去正好2个MSL

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

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

9.TCP中拥塞控制和流量控制有什么区别?

流量控制是TCP 提供的可以让「发送方」根据「接收方」的实际接收能力控制发送数据量的⼀种机制。因为如果一直无脑的发数据给对方,但对方处理不过来,那么就会导致触发重发机制,从而导致网络流量的无端的浪费

拥塞控制和流量控制不同,拥塞控制是一个全局性的过程,而流量控制指点对点通信量的控制。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致于过载。

10.流量控制具体怎么控制的?

  • TCP利用滑动窗口机制实现流量控制。
  • 在通信过程中,接收方根据自己接收缓存的大小,动态地调整发送方的发送窗口大小,发送方的发送窗口(swnd)取接收窗口(rwnd)和拥塞窗口(cwnd)的最小值。

11.TCP 滑动窗口

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

12.拥塞控制具体的算法怎么样的?

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

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

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

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

13.说⼀说TCP与UDP的区别?

UDP

  • 在传送数据之前不需要先建立连接,远地主机在收到 UDP 报文后,不需要给出任何确认。
  • 虽然 UDP 不提供可靠交付,但在某些情况下 UDP 却是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等。

TCP

  • 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。
  • TCP 不提供广播或多播服务
  • 由于 TCP 要提供可靠的,面向连接的传输服务,这难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。
  • TCP一般用于文件传输、发送和接收邮件、远程登录等场景。

14.TCP 协议如何保证可靠传输?

  1. 校验和:TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
  2. 序列号:TCP 传输时将每个字节的数据都设置序列号。接收端根据序列号排序,并去掉重复序列号的数据
  3. 确认重传:接收方收到发送方发送的数据会给发送方发送确认报文
  4. 超时重传:发送方在发送完数据后等待一个时间,时间到达没有接收到确认报文,那么对刚才发送的数据进行重新发送
  5. 流量控制: 当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。
  6. 拥塞控制: 当网络拥塞时,减少数据的发送。

15.说⼀说GET与POST有哪些区别?

作用:GET方法的含义是请求从服务器获取资源。而POST方法则是相反操作,它向URI指定的资源提交数据,数据就放在报文的 body里。

参数位置:GET的参数放在URL中,POST的参数存储在实体主体中,并且GET方法提交的请求的URL中的数据最多是2048字节,POST请求没有大小限制

安全性:在 HTTP 协议里,所谓的「安全」是指请求方法不会「破坏」服务器上的资源。GET方法是安全的,因为它是「只读」操作,无论操作多少次,服务器上的数据都是安全的。POST因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的

幂等性:所谓的「幂等」,意思是多次执行相同的操作,结果都是「相同」的。结合刚才说的,GET方法是具有幂等性的。而POST方法不具有幂等性

16.说一下在浏览器中输入URL 地址到显示主页的过程?

  1. DNS 解析:浏览器查询 DNS,获取域名对应的 IP 地址
  2. TCP 连接:浏览器获得域名对应的 IP 地址以后,浏览器向服务器请求建立链接,发起三次握手
  3. 发送 HTTP 请求:TCP 连接建立起来后,浏览器向服务器发送 HTTP 请求
  4. 服务器处理请求并返回 HTTP 报文:服务器接收到这个请求,并根据路径参数映射到特定的请求处理器进行处 理,并将处理结果及相应的视图返回给浏览器
  5. 浏览器解析渲染页面
  6. 连接结束

17.输入URL到显示主页过程会使用哪些协议?

  1. 首先浏览器查找域名的IP地址的过程会使用DNS协议
  2. 与服务器建立TCP连接使用到了TCP协议
  3. 建立TCP协议时,需要发送数据,发送数据在网络层使用IP协议
  4. IP数据包在路由器之间,路由选择使用OPSF协议
  5. 路由器在与服务器通信时,需要将ip地址转换为MAC地址,需要使用ARP协议
  6. 在TCP建立完成后,使用HTTP协议访问网页

18.HTTP 1.0 和 HTTP 1.1 的主要区别是什么?

  1. 长连接: 在 HTTP 1.0 中,默认使用的是短连接,也就是说每次请求都要重新建立一次连接。HTTP 1.1 起,默认使用长连接 ,默认开启 Connection: keep-alive。
  2. 错误状态响应码:在 HTTP 1.1 中新增了 24 个错误状态响应码。
  3. 缓存处理HTTP 1.1引入了更多的缓存控制策略
  4. 带宽优化及网络连接的使用:在HTTP 1.0中会存在浪费带宽的现象,主要是因为不支持断点续传功能,客户端只是需要某个对象的一部分,服务端却将整个对象都传了过来。在HTTP 1.1中请求头引入了range头域,它支持只请求资源的某个部分,返回的状态码为206。

19.谈下你对 HTTP 长连接和短连接的理解?

HTTP/1.0 中默认使用短连接。也就是说,客户端和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个 HTML 或其他类型的 Web 页中包含有其他的 Web 资源(如 JavaScript 文件、图像文件、CSS 文件等),每遇到这样一个 Web 资源,浏览器就会重新建立一个 HTTP 会话。

而从 HTTP/1.1 起,默认使用长连接,用以保持连接特性。使用长连接的 HTTP 协议,会在响应头加入:Connection:keep-alive

在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。

Keep-Alive 不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如 Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。

HTTP 协议的长连接和短连接,实质上是 TCP 协议的长连接和短连接。

20.说一下HTTP 和 HTTPS 的区别?

  • 开销:HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费
  • 资源消耗:HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的SSL加密传输协议,需要消耗更多的CPU和内存资源
  • 端口不同:HTTP和 HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443
  • 安全性:HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。而HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输

21.说说HTTP的优缺点?

HTTP最凸出的优点是「简单、灵活和易于扩展、应用广泛和跨平台」。

  1. 简单:HTTP基本的报文格式就是header + body ,头部信息也是key-value 简单文本的形式,易于理解,降低了学习和使用的门槛。
  2. 灵活和易于扩展:
    • HTTP协议里的各类请求方法、URI/URL、状态码、头字段等每个组成要求都没有被固定死,都允许开发人员自定义和扩充。
    • 同时HTTP由于是工作在应用层,则它下层可以随意变化。
    • HTTPS也就是在HTTP与TCP层之间增加了SSL/TLS安全传输层
  3. 应用广泛和跨平台:互联网发展至今,HTTP的应用范围非常的广泛,从台式机的浏览器到手机上的各种APP。

缺点:

  • 通信使用明文(不加密),内容可能会被窃听
  • 不验证通信方的身份,因此有可能遭遇伪装
  • 无法证明报文的完整性,所以有可能已遭篡改

22.说说URI 和 URL 的区别是什么?

URI统一资源标志符,可以唯一标识一个资源
URL统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。

23.状态码

1xx:请求正在处理

2xx:请求成功处理

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

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

5xx:服务端错误

24.什么是cookie和session,区别是什么?

cookie和session都是用来跟踪浏览器用户身份的会话方式

区别:

  • cookie数据存放在客户的浏览器上,session数据放在服务器缓存中

  • session比cookie更加安全,因为攻击者可以分析存放在本地的cookie进行cookie欺骗

  • session会比较占用服务器性能,当访问增多时应用cookie

  • cookie有大小限制,很多浏览器都限制一个站点最多保存20个cookie

25. 禁用cookie怎么办?

可以通过对URL进行重写,把sessionID附加在URL路径的后面

三.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有什么区别?

拦截器是基于Java的反射机制的,而过滤器是基于函数回调。

拦截器不依赖于servlet容器,过滤器依赖于servlet容器。

拦截器只能对部分请求起作用,而过滤器则可以对几乎所有的请求起作用。

拦截器可以访问spring容器上下文里的对象,而过滤器不能访问。

拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。

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自动配置原理

在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

其中@EnableAutoConfiguration是实现自动化配置的核心注解。

该注解通过@Import注解导入对应的配置选择器。关键的是内部就是读取了该项目和该项目引用的Jar包的的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。

在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。

一般条件判断会有像@ConditionalOnClass这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。

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起步依赖有哪些

Java常见面试题(实习篇)_第1张图片

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. 组件丰富
  2. 有名气,用的人多,社区活跃度高。
  3. 服务拆分粒度低。
  4. 使用互联网时代

也有缺点:

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

30.spring cloud 和dubbo区别?

  1. Dubbo 定位服务治理、Spirng Cloud 是一个生态。
  2. Dubbo基于Tcp协议。SpringCloud基于Http协议+rest接口,更灵活。
  3. Dubbo主要服务注册中心,服务提供者,服务消费者,还有管控中心,SringCloud则有各个组件。
  4. 注册中心,dubbo 是zookeeper,springcloud是eureka,也可以是zookeeper。
  5. 一些政府金融项目还用着dubbo;也有很多项目往springCloud项目转型;新开的项目优先就是用springCloud了。

31.OAuth2.0授权码认证流程

1、客户端请求第三方授权

2、用户(资源拥有者)同意给客户端授权

3、客户端获取到授权码,请求认证服务器申请 令牌

4、认证服务器向客户端响应令牌

5、客户端请求资源服务器的资源,资源服务校验令牌合法性,完成授权

6、资源服务器返回受保护资源

32.微服务与微服务之间如何实现认证?

微服务与微服务之间实现认证,只需要将用户传递的令牌传递给其他微服务即可。如果微服务之间相互调用采用的是Feign模式,可以创建一个拦截器,每次执行请求之间,将令牌添加到头文件中即可传递给其他微服务。

33.如何防止Jwt Token令牌被盗用?

  1. 加密JWT:使用加密算法将JWT中的敏感数据加密,以保护其不被篡改和窃取。

  2. 使用HTTPS:使用HTTPS协议传输JWT,以保证传输过程中的数据不被中间人窃取和篡改。

  3. 设置短有效期:设置JWT的有效期较短,例如几分钟或几小时,这样即使JWT被盗用,也能在较短时间内失效,从而减小风险。

  4. 撤销JWT:当用户注销或修改密码时,应该撤销JWT,这样即使有人盗用了JWT,也无法继续使用。

  5. 限制JWT的使用范围:在JWT中添加一些限制条件,例如限制JWT只能在特定的IP地址或设备上使用,以避免被盗用。

  6. 限制JWT的访问范围:限制JWT只能访问需要访问的资源,以避免恶意用户使用JWT访问其他不应该访问的资源。

九.RabbitMQ篇

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

  1. 保证生产者不丢消息
  • 每次写的消息都会分配一个唯一的id,如果写入了rabbitmq中,rabbitmq会给你回传一个ack消息,告诉你说这个消息ok了。

  • 如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,我们可以重试。

  1. 保证rabbitmq不丢消息

    开启rabbitmq的持久化(持久化queue和message)

  2. 保证消费端不丢消息

  3. 依靠ack机制,简单来说,就是你关闭rabbitmq自动ack,通过一个api来手动ack就行,就是每次你自己代码里确保处理完的时候,在程序里ack一下。

2.RabbitMQ如何保证消费顺序

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

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

3.RabbitMQ使用步骤怎样?

  1. 服务器安装(windows、linux、docker),开始可以先不考虑集群

  2. 引入spring-boot-starter-amqp

  3. application.yml里面完成配置

  4. 决定好的exchange、queue、routingkey

  5. 合适的地方发消息

  6. 合适的地方编写监听器接收消息

  7. 搭建集群

    1. 普通集群
    2. 镜像集群

4.RabbitMQ使用过程中有遇到问题吗?

高版本的rabbitmq配合低版本的erlang,会出现资源占用后不被正常释放的问题

5.RabbitMQ如何保证消息的顺序性?

  1. 哪些情况会导致消息错乱?

    1. 一个queue,有多个consumer去消费,这样就会造成顺序的错误
    2. 一个queue对应一个consumer,但是consumer里面进行了多线程消费,这样也会造成消息消费顺序错误。
  2. 如何保证消息的顺序性呢?两种方式

    1. 拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点;这样也会造成吞吐量下降,可以在消费者内部采用多线程的方式取消费。
    2. 就一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理

6.RabbitMQ如何保证消费的幂等性?

  1. 什么是幂等性?

    1. 就一个数据,或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的,不能出错。
    2. 比如你有个系统,消费一条往,数据库里插入一条,如果消息重复两次,就插入了两条数据,这就不对了。但是你要是消费到第二次的时候,自己判断一下已经消费过了,直接扔了,就保留一条数据,这就是保证了系统的幂等性。
  2. 如何保证消费的幂等性?需要根据具体业务

    1. 比如拿到数据要写库,可以先根据主键查一下,如果数据有,就不插入,update一下
    2. 比如你是写redis,反正每次都是set,天然幂等性
    3. 如果是其他场景,需要让生产者发送每条数据的时候,里面加一个全局唯一的id(message可以设置id,比如用uuid就是可以的),类似订单id之类的东西,然后你这里消费到了之后,先根据这个id去比如redis里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个id写redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可

7.RabbitMQ如何避免消息堆积?

  1. 为什么出现消息堆积?

    1. 消息堆积主要是生产者和消费者不平衡导致的
  2. 核心处理思路还是提高消费者的消费速率,保证消费者不出现消费故障。

  3. 哪些方式可以避免消息堆积呢?

    1. 足够的cpu和内存资源(硬件)
    2. 消费者数量足够多
    3. 避免消费者故障
  4. 如果发现了大量消息堆积如何解决呢?

    1. 如果消息不是很多,修复consumer的问题,重先上线consumer就好了,
    2. 如果消息很多,有100w,1000w条,那就临时紧急扩容处理

8.RabbitMQ常见的配置有哪些?

  1. spring.rabbitmq.host 指定RabbitMQ host.默认为: localhost
  2. spring.rabbitmq.port 指定RabbitMQ 的端口,默认: 5672)
  3. spring.rabbitmq.username 指定登陆broker的用户名
  4. spring.rabbitmq.password 指定broker的密码

9.介绍下RabbitMQ?

  1. RabbitMQ是一个由erlang语言编写的、开源的、在AMQP基础上完整的、可复用的企业消息系统。

  2. RabbitMQ包括五种队列模式,简单队列、工作队列、发布/订阅、路由、主题、rpc等。项目中用路由和主题模式比较多

  3. 有消息确认机制

    1. 模式1:自动确认
    2. 模式2:手动确认
  4. 有持久化机制,提供交换机和队列的持久化

    1. 持久化:将交换机或队列数据保存到磁盘,服务器宕机或者重启之后数据依然存在。
    2. 非持久化:将交换机或队列数据保存到磁盘到内存,服务器宕机或者重启之后数据会丢失

10.介绍下消息队列?

  1. 消息队列(Message Queue),简称为MQ,是分布式系统中重要的组件
  2. 消息队列主要解决了应用耦合、异步处理、流量削锋等问题
  3. 目前使用比较多的Mq是RabbitMQ、RocketMQ、ActiveMQ、Kafka。
  4. Kafka主要在大数据领域用的多,用来处理日志之类的,JavaEE用RabbitMQ、RocketMQ、ActiveMQ居多,用来处理业务,其中RabbitMQ使用广泛。

11.怎么理解了应用耦合、异步处理、流量削锋?

应用解耦

通过消息中间件,让系统和系统之间耦合度降低,系统B出现问题,不会导致依赖它的系统A出现问题。如,支付服务和订单服务。

异步处理

通过消息中间件,让两个操作由串行变为并行,提高吞吐量。如,发送短信和发送邮箱

流量削锋

通过消息中间件,让需要处理的请求,先进入消息队列缓冲,然后在进行消费。比如,秒杀业务中,由于瞬时访问量过大,可以用消息队列做一次缓存

12.谈谈rabbitmq中的延迟队列?

  1. 延迟队列存储的是延迟消息,延迟消息是指消息被发送以后,并不想让消费者立刻拿到消息,而是等待待定时间后,消费者拿到消息进行消息

  2. 在AMQP协议和RabbitMQ中并没有直接支持延迟队列,但是可以通过DLX和TTL模拟延迟队列。

  3. 有什么业务场景?

    1. 订单业务:在电商中,用户下单后30分钟后未付款则取消订单。
    2. 短信通知:用户下单并付款后,1分钟后发短信给用户。

13.谈谈rabbitmq中的死信队列?

  1. 那什么消息是死信(dead message)呢?

    1. 被拒绝(basic.reject或basic.nack)并且requeue=false的消息
    2. TTL过期消息的消息
    3. 队列达到最大长度(队列满了,无法再添加数据到mq中)的消息
  2. DLX(Dead Letter Exchanges)死信队列,本身也是一个普通的消息队列,在创建队列的时候,通过设置一些关键参数x-dead-letter-exchange,可以将一个普通的消息队列设置为死信队列

  3. 当这个队列中有死信时, RabbitMQ就会自动将这个消息重新发布到设置的Exchange上去, 进而被路由到另一个队列

14.谈谈rabbitmq中重试机制?

  1. 默认情况下,如果消费者程序出现异常情况, Rabbitmq 会自动实现补偿机制 也就是重试机制,会一直重试到不抛出异常为准

  2. 默认是5s重试一次,重试策略可以修改

    1. 最大重试次数(默认无数次)
    2. 重试间隔次数
  3. 可以设置应答模式,默认是自动应答,可以采取手动应答

15.谈谈你对AMQP协议的理解

AMQP是什么?

  1. AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是一个进程间传递异步消息的网络协议。
  2. RabbitMQ是基于AMQP的实现

AMQP模型怎样的?

Java常见面试题(实习篇)_第2张图片

AMQP工作过程

  1. 发布者(Publisher)发布消息(Message),经由交换机(Exchange)。
  2. 交换机根据路由规则将收到的消息分发给与该交换机绑定的队列(Queue)。
  3. 最后 AMQP 代理会将消息投递给订阅了此队列的消费者,或者消费者按照需求自行获取。

关于AMQP交换机

  1. 交换机有5种类型

    1. 默认交换机(default exchange)
    2. 直连型交换机(direct exchange),需要指定路由键,项目中用到了
    3. 扇型交换机(funout exchange),1对多
    4. 主题交换机,通过表达式匹配发送
    5. 头交换机,用的少

16.谈谈你对rabbitmq消息确认机制的理解?

  1. 为什么需要消息确认机制?

    1. 生产者 —>队列—>消费者,中间多了一些环节,这也就造成了消息能否最终发送并被消费成功的不确定性,正是这种不确定性使得我们在使用的时候会关注消息到每一步的时候的状态,也就产生了消息的确认机制
  2. 哪些情况可能导致消息失败?

    1. 投递失败,producer投递到exchange的时候失败
    2. 路由失败,exchange没有找到对应的队列
    3. 消费失败,消费者没有成功消费消息

17.项目哪些地方用到了RabbitMQ?

  1. 做微信支付的时候用到了,支付微服务发送消息,订单微服务处理消息

  2. 具体使用逻辑

    1. 用户调用支付微服务(changgou-service-pay)生成微信支付二维码的时候,传递exchange、routingkey、queue信息给微信服务器
    2. 用户支付完成后,微信服务器通过回调地址通知支付微服务(changgou-service-pay),拿到微信服务器透传过来exchange、routingkey、queue信息进行消息的发送
    3. 订单微服务(changgou-service-order)收到消息,更新订单表里面对应字段的状态
  3. 这里就实现了支付微服务和订单微服务的解耦

  4. 这里也实现了支付功能和订单状态修改的异步处理

18.RabbitMQ的工作模式及应用场景

1.简单模式

Java常见面试题(实习篇)_第3张图片

  • 生产者将消息发送到队列,消费者从队列获取消息。
  • 一个队列对应一个消费者

应用场景:短信验证码,聊天

具体场景:有一个oa系统,用户通过接收手机验证码进行注册,页面上点击获取验证码后,将验证码放到消息队列,然后短信服务从队列中获取到验证码,并发送给用户


2.工作模式

Java常见面试题(实习篇)_第4张图片

  • 一个生产者,多个消费者。
  • 一条消息只会被一个消费者接收。
  • rabbitmq采用轮询的方式将消息是平均发送给消费者的。
  • 消费者在处理完某条消息后,才会收到下一条消息。

应用场景:抢红包,大型系统的资源调度

具体场景:有一个电商平台,有两个订单服务,用户下单的时候,任意一个订单服务消费用户的下单请求生成订单即可。不用两个订单服务同时消费用户的下单请求


3.发布/订阅模式

Java常见面试题(实习篇)_第5张图片

  • 将消息发送到交换机,队列从交换机获取消息,队列需要绑定到交换机。
  • 每一个消费者都有自己的一个队列。
  • 生产者没有将消息直接发送到队列,而是发送到交换机(简单队列和工作队列就是发到队列的)。
  • 每一个队列都要绑定到交换机。
  • 生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者获取的目的。
  • 交换机类型为“fanout”。
  • 注意:交换机本身没有存储消息的能力,消息只能存储到队列中。

应用场景:邮件群发,广告

具体场景:有一个商城,我们新添加一个商品后,可能同时需要去更新缓存和数据库


4.路由模式

Java常见面试题(实习篇)_第6张图片

  • 路由模式是发布/订阅模式的一种特殊情况。
  • 路由模式的交换机类型为“direct”。
  • 绑定队列到交换机时指定 key,即路由键,一个队列可以指定多个路由键。
  • 生产者发送消息时需要指定路由键,这时,消息只会发送到绑定的key的对应队列中。

应用场景:根据生产者的要求发送给特定的一个或者一批队列

具体场景:有一个商城,新添加了一个商品,实时性不是很高,只需要添加到数据库即可,不用刷新缓存


5.主题模式

Java常见面试题(实习篇)_第7张图片

  • 将路由键和某模式进行匹配。此时,队列需要绑定到一个模式上。符号“#”匹配一个或多个词,“*”匹配一个词。
  • 绑定队列到交换机指定key时,进行通配符模式匹配。

应用场景:根据生产者的要求发送给通配符匹配的队列

具体场景:有一个商城,新添加了一个商品,实时性不是很高,只需要添加到数据库即可,数据库包含了主数据库mysql1和从数据库mysql2的内容,不用刷新缓存


19.topic主题模式和路由模式区别

主题模式的路由匹配支持通配符模糊匹配,而路由模式仅支持完全匹配

十.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)接口隐藏

你可能感兴趣的:(java,面试,实习)