Java全栈工程师面试宝典
2022-03-11 09:01:18 3 举报
AI智能生成
本文整理了java全栈工程师常见面试题,并持续更新,如果不对之处,忘大家提出来一起完善,一起学习,一起进步
作者其他创作
大纲/内容
一、Java知识面试点全集
第一题:请说说HashMap和HashTable的区别?
问题:
请说说HashMap和HashTable的区别?
参考答案:
1、HashMap 不是线程安全的、HashTable 是线程安全 Collection。<br>
2、HashMap 是 map 接口的实现类,是将键映射到值的对象,<br>其中键和值都是对象,并且不能包含重复键,但可以包含重复值。<br>
3、HashMap 允许 null key 和 null value,而 HashTable 不允许。
4、HashMap 是 HashTable 的轻量级实现
5、HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsValue <br>和 containsKey。因为 contains 方法容易让人引起误解。<br>
6、HashTable 继承自 Dictionary 类,而 HashMap 是 Java1.2 引进的 Map <br>interface 的一个实现。<br>
7、HashTable 的方法是 Synchronize 的,而 HashMap 不是,在多个线程访<br>问 Hashtable 时,不需要自己为它的方法实现同步,而 HashMap 就必须为之<br>提供同步方法。<br>
第二题:请说说ArrayList 和 LinkedList 的区别?
问题:
请说说ArrayList 和 LinkedList 的区别是什么?
参考答案:
1、ArrayList是基于动态数组的数据结构实现,LinkedList是基于链表的数据结构实<br>现。<br>
2、随机访问数据get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
3、新增(add)和删除(remove)操作,LinedList比较占优势,因为ArrayList要<br>移动数据。<br>
第三题:请说说Array 和 ArrayList 有何区别?
问题:
请说说Array 和 ArrayList 有何区别?
参考答案:
1、Array 可以包含基本数据类型和引用类型,ArrayList只能包含引用类型。<br>ArrayList是一个可变的数组。
2、ArrayList是基于数组实现的,Array不可以调整大小,但ArrayList<br>可以通过内部方法自动调整容量。<br>
3、ArrayList是List接口的实现类,相比Array支持更多的方法和特性。
第四题:请说出JDK7和JDK8中HashMap的大致变化有那些?
问题:
请说出JDK7和JDK8中HashMap的大致变化有那些?
参考答案:
1、1.7中采用数组+链表,1.8采用的是数组+链表/红黑树,即在1.8中链表长<br>度超过一定长度后就改成红黑树存储。<br>
2、1.7扩容时需要重新计算哈希值和索引位置,1.8并不重新计算哈希值,巧妙<br>地采用和扩容后容量进行&操作来计算新的索引位置。<br>
3、1.7是采用表头插入法插入链表,1.8采用的是尾部插入法。
4、在1.7中采用表头插入法,在扩容时会改变链表中元素原本的顺序,以至于<br>在并发场景下导致链表成环的问题;在1.8中采用尾部插入法,在扩容时会保<br>持链表元素原本的顺序,就不会出现链表成环的问题了。<br>
第五题:什么是哈希表?
问题:
什么是哈希表?
参考答案:
1、了解一下其他数据结构
1、数组:采用一段连续的存储单元来存储数据。对于指定下标的查找,<br>时间复杂度为O(1);通过给定值进行查找,需要遍历数组,逐一比对<br>给定关键字和数组元素,时间复杂度为O(n),当然,对于有序数组,<br>则可采用二分查找,插值查找,斐波那契查找等方式,可将查找复杂<br>度提高为O(logn);对于一般的插入删除操作,涉及到数组元素的移<br>动,其平均复杂度也为O(n)<br>
2、线性链表:对于链表的新增,删除等操作(在找到指定操作位置后),<br>仅需处理结点间的引用即可,时间复杂度为O(1),而查找操作需要遍历<br>链表逐一进行比对,复杂度为O(n)<br>
3、二叉树:对一棵相对平衡的有序二叉树,对其进行插入,查找,删除<br>等操作,平均复杂度均为O(logn)。<br>
4、哈希表:相比上述几种数据结构,在哈希表中进行添加,删除,查找等操作,性能<br>十分之高,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1),<br>接下来我们就来看看哈希表是如何实现达到惊艳的常数阶O(1)的。<br>
2、hash表的理解
1、我们知道,数据结构的物理存储结构只有两种:顺序存储结构和链式存储结构(像栈,<br>队列,树,图等是从逻辑结构去抽象的,映射到内存中,也这两种物理组织形式),<br>而在上面我们提到过,在数组中根据下标查找某个元素,一次定位就可以达到,哈希<br>表利用了这种特性,哈希表的主干就是数组。<br>
2、比如我们要新增或查找某个元素,我们通过把当前元素的关键字 通过某个函数映射到数<br>组中的某个位置,通过数组下标一次定位就可完成操作<br>
3、存储位置 = f(关键字)
4、其中,这个函数f一般称为哈希函数,这个函数的设计好坏会直接影响到哈希表的优劣。<br>举个例子,比如我们要在哈希表中执行插入操作:<br>查找操作同理,先通过哈希函数计算出实际存储地址,然后从数组中对应地址取出即可。<br>
5、哈希冲突:然而万事无完美,如果两个不同的元素,通过哈希函数得出的实际存储地址<br>相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进<br>行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。<br><br>前面我们提到过,哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单和散<br>列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好<br>的哈希函数也不能保证得到的存储地址绝对不发生冲突。<br><br>那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找<br>下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址<br>法,也就是数组+链表的方式<br>
3、
第六题:说说Object类下面有几种方法呢?
问题:
说说Object类下面有几种方法呢?
参考答案:
1、总体结构图
2、Object():这个没什么可说的,Object类的构造方法。
3、registerNatives():通过使用registerNatives(或者更确切地说,<br>JNI函数RegisterNatives),可以命名任何你想要你的C函数<br>
4、clone():clone()函数的用途是用来另存一个当前存在的对象。只有实现了<br>Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常<br>
5、getClass():final方法,用于获得运行时的类型。该方法返回的是此Object对象<br>的类对象/运行时类对象Class。效果与Object.class相同。<br>
6、equals():equals用来比较两个对象的内容是否相等。默认情况下(继承自Object<br>类),equals和==是一样的,除非被覆写(override)了。<br>
7、hashCode():该方法用来返回其所在对象的物理地址(哈希码值),常会和<br>equals方法同时重写,确保相等的两个对象拥有相等的hashCode。<br>
8、toString():toString()方法返回该对象的字符串表示,这个方法没什么可说的。
9、wait():导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 <br>notifyAll() 方法。<br>
10、wait(long timeout):导致当前的线程等待,直到其他线程调用此对象的 <br>notify() 方法或 notifyAll() 方法,或者超过指定的时间量<br>
11、wait(long timeout, int nanos):导致当前的线程等待,直到其他线程调用<br>此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或<br>者已超过某个实际时间量。<br>
12、notify():唤醒在此对象监视器上等待的单个线程。
13、notifyAll():唤醒在此对象监视器上等待的所有线程。
14、finalize():当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾<br>回收器调用此方法。<br>
第七题:请说说Java引用类型原理?
问题:
请说说Java引用类型原理?
参考答案:
1、Java中一共有4种引用类型(其实还有一些其他的引用类型比如FinalReference):<br>强引用、软引用、弱引用、虚引用。<br>
2、强引用就是我们经常使用的Object a = new Object(); 这样的形式,在Java中并<br>没有对应的Reference类。<br>
第八题:HashMap的原理与理解?
问题:
HashMap的原理与理解?
参考答案:
1、数据put过程原理
逻辑图
子主题
①判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
②根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直<br>接新建节点添加,转向⑥,如果table[i]不为空,转向③;<br>
③判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则<br>转向④,这里的相同指的是hashCode以及equals;<br>
④判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑<br>树,则直接在树中插入键值对,否则转向⑤;<br>
⑤遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,<br>在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现<br>key已经存在直接覆盖value即可;<br>
⑥插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,<br>如果超过,进行扩容<br>
2、数据get过程原理
1、指定key 通过hash函数得到key的hash值 int hash=key.hashCode();
2、调用内部方法 getNode(),得到桶号(一般都为hash值对桶数求模)<br>int index =hash%Entry[].length;<br>
3、比较桶的内部元素是否与key相等,若都不相等,则没有找到。相等,<br>则取出相等记录的value。<br>
4、如果得到 key 所在的桶的头结点恰好是红黑树节点,就调用红黑树节点<br>的 getTreeNode() 方法,否则就遍历链表节点。getTreeNode 方法使通过<br>调用树形节点的 find()方法进行查找。由于之前添加时已经保证这个树是有<br>序的,因此查找时基本就是折半查找,效率很高。<br>
5、如果对比节点的哈希值和要查找的哈希值相等,就会判断 key 是否相等,相<br>等就直接返回;不相等就从子树中递归查找。<br>
6、如果没有找到就返回null
3、hashMap的hash算法
static final int hash(Object key) {<br> int h;<br> return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);<br> }<br>
4、hashMap我们知道默认初始容量是16,<br>也就是有16个桶,那hashmap是通过什么<br>来计算出put对象的时候该放到哪个桶呢<br>
final Node<K,V> getNode(int hash, Object key) {<br> Node<K,V>[] tab; Node<K,V> first, e; int n; K k;<br> if ((tab = table) != null && (n = tab.length) > 0 &&<br> (first = tab[(n - 1) & hash]) != null) {<br> if (first.hash == hash && // always check first node<br> ((k = first.key) == key || (key != null && key.equals(k))))<br> return first;<br> if ((e = first.next) != null) {<br> if (first instanceof TreeNode)<br> return ((TreeNode<K,V>)first).getTreeNode(hash, key);<br> do {<br> if (e.hash == hash &&<br> ((k = e.key) == key || (key != null && key.equals(k))))<br> return e;<br> } while ((e = e.next) != null);<br> }<br> }<br> return null;<br> }<br>
5、数组长度-1、^运算、>>>16,这三个操作都是为了让key在hashmap的桶中尽可能分散<br>用&而不用%是为了提高计算性能
参考理解地址:
https://blog.csdn.net/qq_33709582/article/details/113337405
第九题:hashCode和equals的关系?
问题
hashCode和equals的关系?
参考答案:
1、重写equals()方法时候一定要重写hashCode()方法
2、如果两个对象的hashCode()相等,那么他们的equals()不一定相等。<br>如果两个对象的equals()相等,那么他们的hashCode()必定相等。
第十题:String类能不能被继承?为什么?
问题:
String类能不能被继承?为什么?
参考答案:
不能<br>因为string类是被final修饰的类,final修饰过的类不能被继承、final修饰过的变量不能被修改
第十一题;简述Java的反射机制和使用场景?
问题:
简述Java的反射机制和使用场景?
参考答案:
反射是Java的一种机制,可以让我们在运行时获取类的信息<br>通过反射我们可以获取到类的所有信息,比如它的属性、构造器、方法、注解等<br>适用于需要动态创建对象的场景
第十二题:String str = new String(“abc“)到底new了几个对象?
问题:
String str = new String(“abc“)到底new了几个对象?
参考答案:
1、两个:如果常量池里面没有“abc”这个字符串,那虚拟机就会在堆内存<br>中new出一个String对象,还会在常量池中new一个abc字符串对象;<br>
2、一个:如果常量池中已经有"abc"这个字符串,也就是说你在前面已经new过<br>一个值为“abc”的字符串,那虚拟机就只会在堆内存中new一个String对象,并<br>将常量池中“abc”的地址指向你刚刚new的String对象<br>
第十三题:Java中接口和抽象类的异同?
问题:
Java中接口和抽象类的异同?
参考答案:
1、都能包含抽象的方法,这些抽象的方法用于描述类具备的功能,不提供具体的实现<br>(jdk1.8允许接口有一个default的实现方法)<br>
2、接口是对事物行为的抽象,而抽象类是对事务本质的抽象
3、接口中的变量必须给出初始值,抽象类可以不给;
4、一个类只能继承一个抽象类,但可以实现多个接口
5、抽象类中可以写非抽象的方法,从而避免在子类中重复书写它们,这样可以提高代码<br>的复用性,这是抽象类的优势;接口中只能有抽象的方法<br>
ps:接口也能被继承,只不过是被接口继承
第十四题:Java中sleep和wait的区别?
问题:
Java中sleep和wait的区别?
参考答案:
1、sleep是Thread的方法,wait是Object的方法
2、sleep方法没有释放锁,而wait方法释放了锁
3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,<br>而sleep可以在任何地方使用<br>
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
第十五题:Java如何进行高效的数组拷贝?
问题:
Java如何进行高效的数组拷贝?
参考答案:
使用java提供方法Arrays.copyOf或 System.arraycopy,是自己new数组, 然后for循环<br>复制效率的两倍左右。为什么快,因为它们是native方法;<br>
第十六题:Java编译后的.class文件包含了哪些内容?
问题:
Java编译后的.class文件包含了哪些内容?
参考答案:
编译后的.class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑<br>地排列在.class文件之中,中间没有添加任何分隔符;<br>
根据Java虚拟机规范的规定,.class文件格式采用一种类似于C语言的伪结构来存储数据,包含无<br>符号数和表:<br>
无符号数:以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,<br>无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值;<br>
表:由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性的以”_info“结尾。
Class文件的结构没有分隔符,无论你是数量还是顺序,都是严格规定的,哪个字节代表什么含义、<br>长度多少、先后顺序如何,都不允许改变<br>
第十七题:解决hash冲突的方式?
问题:
解决hash冲突的方式?
参考答案:
1、拉链法<br>代表作:hashMap<br>原理:把所有hash值相同的元素放到链表中
2、再哈希法<br>原理:产生冲突时,对结果再进行一次hash,直到没有hash冲突,一般没人用,<br>太鲁莽了,而且治标不治本,理论上永远不能彻底解决hash冲突<br>
2、开放地址法<br> a、线性探测法<br> b、线性补偿探测法<br> c、伪随机探测
第十八题:什么是重写和重载?
问题:
什么是重写和重载?
参考答案:
1、重写是在子类存在方法与父类的方法的名字相同,而且参数的个数<br>与类型一样,返回值也一样的方法,就称为重写(Override)<br>
2、重载是一个类中定义了多个方法名相同,而他们的参数的数量不同<br>或数量相同而类型和次序不同,则称为方法的重载(Overload)<br>
3、重载是一个类的多态性表现,而重写是子类与父类的一种多态性表现
第十九题:volatile关键字解决了什么问题?实现原理是什么?
问题:
volatile关键字解决了什么问题?实现原理是什么?
参考答案:
1、解决了什么问题:
1、保证了变量的可见性
2、禁止指令重排
例如:
比如i=i+1,单线程操作没问题,如果使用多线程,比如两个线程,执行这段<br>代码后(i初始值为0),i应该等于2,但是如果不用volatile修饰变量i,结果<br>会等于1,初始时,两个线程分别读取i的值存入各自所在的CPU的高速缓存当<br>中,然后线程1进行加1操作,然后把i的最新值1写入到内存。此时线程2的高速<br>缓存当中i的值还是0,进行加1操作之后,i的值为1,然后线程2把i的值写入内存。<br>
2、实现原理
基于内存屏障,关于内存屏障,搞java开发的同学在开发中不可能接触到,<br>所以不用关心太多,知道内存屏障有什么作用,面试官问到你能唬住他就<br>行了,因为面试官自己也不懂<br>
(1)它确保指令重排序时不会把其后面的指令排到内存屏障前面,也不会把前面<br>的指令排到内存屏障后面,总之一句话,他能保证指令按照我们希望的顺序执行;<br>
(2)它会强制将对缓存的修改操作立即写入主存,使得其它线程能立马发现;
第二十题:什么是内存泄漏,怎么确定内存泄漏?
问题:
什么是内存泄漏,怎么确定内存泄漏?
参考答案:
概念:内存泄漏就是指jvm内存没有及时释放,用人话说就是使用完的对象没有被及时回收<br>怎么确认:linux有个工具叫valgrind。
第二十一题:int和Integer有什么区别?
问题:
int和Integer有什么区别?
参考答案
int是基本数据类型,默认值为0,integer是其包装类型,默认值为null
原始类型: boolean,char,byte,short,int,long,float,double<br>包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
第二十二题:String和StringBuilder、StringBuffer的区别?
问题:
String和StringBuilder、StringBuffer的区别?
参考答案:
Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,<br>它们可以储存和操作字符串。其中String是只读字符串,也就意味着String<br>引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示<br>的字符串对象可以直接进行修改。StringBuilder是Java 5中引入的,它和<br>StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为<br>它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer<br>要高。<br>
第二十三题:常见的运行时异常?
问题:
常见的运行时异常?
参考答案:
1、ArithmeticException(算术异常)
2、ClassCastException (类转换异常)
3、IllegalArgumentException (非法参数异常)
4、IndexOutOfBoundsException (下标越界异常)
5、NullPointerException (空指针异常)
6、SecurityException (安全异常)
第二十四题:悲观锁和乐观锁的原理以及应用场景?
问题:
悲观锁和乐观锁的原理以及应用场景?
参考答案:
悲观锁:
顾名思义,比较悲观,每次去拿数据都认为别人会修改,所以每次在操作前都会加锁,<br>如:读写锁、行锁、表锁等,synchronized的原理也是悲观锁;适用于多写操作<br>
乐观锁:
每次拿数据都认为别人不会修改,所以不会加锁,但是在更新的时候,会先判断在此期<br>间有没有人更新该数据,如果有,返回冲突报错信息,让用户决定怎么操作;适用于多<br>读操作<br>
第二十五题:什么是泛型擦除?
问题:
什么是泛型擦除?
参考答案:
泛型只是为了在编码过程中,我的理解是泛型存在的意义有两个:一是为了让我们更<br>快地发现错误,比如你把User放进了ArrayList< Dog >中,编译器立马会报错;二是<br>避免类型检查,从而避免在运行时抛出 classCastException;泛型擦除就是指泛型会<br>在编译时被消除<br>
代码示例:
ArrayList<User> users = new ArrayList<>();<br>ArrayList<Dog> dogs = new ArrayList<>();<br>System.out.println(users.getClass() == dogs.getClass());<br> //true<br>
如上,运行结果表面两个list是相等的,因为经过编译后,泛型被擦除,两个list当然也就相等;
第二十六题:什么是可重入锁?实现原理?
问题:
什么是可重入锁?实现原理?
参考答案:
1、可重入锁:不是一种锁,它是锁的一种性质,代表该锁是否可重入,可重入意思<br>就是**任意线程在获取到锁后能够再次获取该锁,不会被锁阻塞**,这个锁知道它属<br>于谁,其它线程来了就会阻塞等待;<br>
2、原理:锁的内部维护了线程计数器state和当前持有锁的线程对象,当线程A获取<br>锁资源后,锁会记录下A线程,并且state+1,此时如果有其它线程来获取锁,会被<br>封装成node节点插到队列尾部并且阻塞;而线程A再来获取锁资源时,会成功拿到锁,<br>并且state+1;当线程退出同步代码块时,state-1,如果计数器为0,则释放锁;<br>
第二十七题:synchronized可重入吗?ReentrantLock呢?
问题:
synchronized可重入吗?ReentrantLock呢?
参考答案:
都是可重入锁,java的锁都可重入
第二十八题:公平锁和非公平锁的区别?
问题:
公平锁和非公平锁的区别?
参考答案:
拿ReentrantLock来说,众所周知,没抢到锁的线程会被放入同步队列,当持有锁的线程释放锁后,此时
公平锁:唤醒第一个没被取消且不为null的节点去拿锁;
非公平锁:所有等待线程都会一起争夺锁,包括队列内和队列外的所有线程;
注意:AQS源码注释中有一句话,大概意思是下个头节点不一定能拿到锁,但那是节点已取消或为null时才会<br>发生,aqs解锁逻辑是依次唤醒后继节点,直到找到第一个没被取消且不为null的节点<br>
一句话总结原理:<br>AQS维护了一个CLH队列,队列由node节点来实现,试图加锁失败的线程都会进入队列尾部并自旋,当前持有锁<br>的线程释放锁后,AQS会根据公平锁与否唤醒等待线程去抢夺锁<br>
第二十九题:String有没有长度限制?
问题:
String有没有长度限制?
参考答案:
既然都这么问了那肯定是有的,分编译器和运行期,长度限制不一样<br>编译器2 ^ 16 - 1(65535)<br>运行期2 ^ 32 - 1(2147483647)
不管是编译器还是运行期,如果超出长度都会抛异常:常量字符串过长
第三十题:为什么finally里的代码一定会执行?
问题:
为什么finally里的代码一定会执行?
参考 答案:
编译器在编译的时候,会把finally里面的代码复制多份,分别放在try和catch内所有能够正常执行<br>以及异常执行逻辑的出口处,最直观的就是我们可以在字节码文件里看到很多份finally内部代码;<br>
第三十一题:分别说下ConcurrentHashMap1.7和1.8的实现?
问题:
分别说下ConcurrentHashMap1.7和1.8的实现?
参考答案:
1.7:<br>基于Segment数组和HashEntry,Segment继承自ReentrantLock,懂了吧,它自然就<br>有了锁的基本功能;每个Segment数组中都有多个HashEntry,我们的数据都存在HashEntry<br>里面,每次需要修改数据时,先对HashEntry所在的Segment加锁,其它Segment不受影响,<br>分段锁就是这么来的;<br>
1.8<br>整体实现很像HashMap,在它基础上引入了synchronized,和大量的CAS操作,以及大量的<br>volatile关键字,所以1.8的ConcurrentHashMap中锁的粒度更小;<br>
第三十二题:java有哪些类加载器?
问题:
java有哪些类加载器?
参考答案:
1、启动类(Bootstrap)加载器 BootClassPathHolder: 加载<JAVA_HOME>/lib下的jar包
2、扩展类(Extension)加载器ExtClassLoader:加载<JAVA_HOME>/lib/ext下的jar包
3、系统类(System)加载器AppClassLoader:加载我们自己项目中写的java文件编译而成的<br>class文件,位于target/classes下<br>
第三十三题:判断一块内存空间是否会被垃圾回收器回收的标准有哪些?
问题:
判断一块内存空间是否会被垃圾回收器回收的标准有哪些?
参考答案:
1、对象的引用被赋值为null,并且后面不再调用
2、对象的引用被重新分配了内存空间
3、对象的引用被赋予了新值
第三十四题:
二、Java多线程面试全集
第一题:ReentrantLock公平锁在持有锁线程释放锁后,哪些线程会参与锁竞争?
问题:
ReentrantLock公平锁在持有锁线程释放锁后,哪些线程会参与锁竞争?
参考答案:
五个线程ABCDE一起抢夺锁资源,假设A抢到了锁,BC没抢到但是先后进<br>入了clh队列,DE没抢到也没进入clh队列,当A释放锁后,哪些线程会去<br>抢夺锁资源?<br>
第二题:ThreadLocal实现原理?
问题:
ThreadLocal实现原理?
参考答案:
ThreadLocal中文名叫线程变量,它底层维护了一个map,key就是当前的ThreadLocal对象<br>(可以理解为当前执行该段代码的线程),value就是你set的值,这个map保证了各个线程<br>的数据互不干扰;<br>
第三题:java是如何实现线程安全的?哪些数据结构是线程安全的?
问题:
java是如何实现线程安全的?哪些数据结构是线程安全的?
参考答案:
1、锁机制:用synchronize、lock给共享资源加锁;
2、使用java提供的安全类:java.util.concurrent包下的类自身就是线程安全的,<br>在保证安全的同时还能保证性能;<br>
第四题:什么是CAS操作?什么ABA问题?如何解决?
问题:
什么是CAS操作?什么ABA问题?如何解决?
参考答案:
1、CAS全称compare and swap(比较并交换),作用是保证原子性
CAS操作包含三个操作数 —— 内存位置、预期原值、新值。 如果内存位置的<br>值和预期原值相等,就把该值更新为新值,如果不相等,则什么都不做;<br>
2、ABA问题
CAS操作存在的一个并发问题,打个比方,有两个线程A、B同时操作变量x,<br>A读取到的预期原值是1,此时线程B先将x设置为2,再设置为1,等线程A<br>再来操作的时候,x变量的预期原值和当前值相等,但是x在整个过程中的值<br>是发生过变化的,这在某些业务场景下是不允许的;<br>
3、如何解决
利用版本号,给变量x增加版本号,每次操作增加对本版好的判断和修改;
第五题:说出Java创建线程的三种方式及对比?
问题
说出Java创建线程的三种方式及对比?
参考答案:
1、创建方式
1、继承Thread类创建线程类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法<br>体就代表了线程要完成的任务。因此把run()方法称为执行体。<br>
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
示例
package com.thread; <br><br>public class FirstThreadTest extends Thread{ <br> int i = 0; <br> //重写run方法,run方法的方法体就是现场执行体 <br> public void run() <br> { <br> for(;i<100;i++){ <br> System.out.println(getName()+" "+i); <br> } <br> } <br> public static void main(String[] args) <br> { <br> for(int i = 0;i< 100;i++) <br> { <br> System.out.println(Thread.currentThread().getName()+" : "+i); <br> if(i==20) <br> { <br> new FirstThreadTest().start(); <br> new FirstThreadTest().start(); <br> } <br> } <br> } <br>}
2、通过Runnable接口创建线程类
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法<br>的方法体同样是该线程的线程执行体。<br>
(2)创建 Runnable实现类的实例,并以此实例作为Thread的target来创建<br>Thread对象,该Thread对象才是真正的线程对象<br>
(3)调用线程对象的start()方法来启动该线程
示例
package com.thread; <br><br>public class RunnableThreadTest implements Runnable <br>{ <br><br> private int i; <br> public void run() <br> { <br> for(i = 0;i <100;i++) <br> { <br> System.out.println(Thread.currentThread().getName()+" "+i); <br> } <br> } <br> public static void main(String[] args) <br> { <br> for(int i = 0;i < 100;i++) <br> { <br> System.out.println(Thread.currentThread().getName()+" "+i); <br> if(i==20) <br> { <br> RunnableThreadTest rtt = new RunnableThreadTest(); <br> new Thread(rtt,"新线程1").start(); <br> new Thread(rtt,"新线程2").start(); <br> } <br> } <br> } <br>}
3、通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作<br>为线程执行体,并且有返回值。<br>public interface Callable<br>{<br> V call() throws Exception;<br>}<br>
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,<br>该FutureTask对象封装了该Callable对象的call()方法的返回值。<br>(FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了<br>Future和Runnable接口。)<br>
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
示例:
package com.thread; <br><br>import java.util.concurrent.Callable; <br>import java.util.concurrent.ExecutionException; <br>import java.util.concurrent.FutureTask; <br><br>public class CallableThreadTest implements Callable<Integer> <br>{ <br><br> public static void main(String[] args) <br> { <br> CallableThreadTest ctt = new CallableThreadTest(); <br> FutureTask<Integer> ft = new FutureTask<>(ctt); <br> for(int i = 0;i < 100;i++) <br> { <br> System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i); <br> if(i==20) <br> { <br> new Thread(ft,"有返回值的线程").start(); <br> } <br> } <br> try <br> { <br> System.out.println("子线程的返回值:"+ft.get()); <br> } catch (InterruptedException e) <br> { <br> e.printStackTrace(); <br> } catch (ExecutionException e) <br> { <br> e.printStackTrace(); <br> } <br><br> } <br><br> @Override <br> public Integer call() throws Exception <br> { <br> int i = 0; <br> for(;i<100;i++) <br> { <br> System.out.println(Thread.currentThread().getName()+" "+i); <br> } <br> return i; <br> } <br><br>}
2、对比
1、采用实现Runnable、Callable接口的方式创建多线程时,
优势是:<br>线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。<br><br>在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同<br>一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向<br>对象的思想。<br><br>劣势是:<br>编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。<br>
2、使用继承Thread类的方式创建多线程时
优势是:<br>编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,<br>直接使用this即可获得当前线程。<br><br>劣势是:<br>线程类已经继承了Thread类,所以不能再继承其他父类。<br>
3、Runnable和Callable的区别
(1) Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。<br><br>(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。<br><br>(3) call方法可以抛出异常,run方法不可以。<br><br>(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是<br>否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行<br>情况,可取消任务的执行,还可获取执行结果。<br>
三、Spring相关面试全集
第一题:说说你对spring IOC和AOP的理解?
问题:
说说你对spring IOC和AOP的理解?
参考答案:
1、IOC(控制反转)
也叫DI(依赖注入),是一种思想,不是一种技术,IOC主张把对象的控制权<br>交由spring,底层实现是反射+工厂方法模式,IOC容器实际上就是个Map,<br>用于存放各种对象;<br>
2、AOP(面向切面)
面向切面编程,把一些能共用、冗余、繁琐的功能提取出来,AOP能在不改<br>变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻<br>辑代码重复;常见使用场景有事务管理、日志、全局异常处理、用户鉴权;<br>
第二题:@Autowire和@Resource区别?
问题:
@Autowire和@Resource区别?
参考答案:
1、都是用来装配java bean
@Autowired:按类型注入,这是spring的注解,可以搭配@Qualifie实现按名称注入;
@Resource:默认情况下是按照名称进行匹配,如果没有找到相同名称的Bean,则会按照<br>类型进行匹配,这是java自己的注解;<br>
2、注意事项:
@Autowired有个弊端,打个比方,有个userService,然后它有两个实现类userServiceA<br>和userServiceB,这时候用@Autowired就行不通了,因为它不知道找谁,但是你也不能因<br>为这个一上来就直接用@Resource,这玩意儿性能没@Autowired好,因为@Resource要<br>匹配两次<br>
第三题:springboot自动装配原理?
问题:
springboot自动装配原理?
参考答案:
在springboot的启动类上有个SpringBootApplication注解,这是个组合注解,这个注解里<br>面有个注解叫EnableAutoConfiguration注解,@EnableAutoConfigration 注解会导入一<br>个自动配置选择器去扫描每个jar包的META-INF/xxxx.factories 这个文件,这个文件是一个<br>key-value形式的配置文件,里面存放了这个jar包依赖的具体依赖的自动配置类。这些自动配<br>置类又通过@EnableConfigurationProperties 注解支持通过xxxxProperties 读取application.<br>properties/application.yml属性文件中我们配置的值。如果我们没有配置值,就使用默认值,<br>这就是所谓约定大于配置的具体落地点。<br><br>
四、Mybaties相关面试全集
第一题:mybatis插入一条数据,怎么获得这条数据的id?
问题:
mybatis插入一条数据,怎么获得这条数据的id?
参考答案:
比如你插入一个user对象
userMapper.insert(User user);
在insert标签上加个配置
<insert id="insert" parameterType="User" keyProperty="id" useGeneratedKeys="true"><br>
然后 新插入user的id会赋到你传入的user对象,通过user.getId()就能拿到了
当然 你先insert进去再select出来拿id也不是不
第二题:#{}和${}的区别是什么?
问题:
#{}和${}的区别是什么?
参考答案:
1、${}是Properties文件中的变量占位符,用于标签属性值<br>和sql内部,属于静态文本替换,比如${driver}会被静态替换为<br>com.mysql.jdbc.Driver。<br>
2、#{}是sql的参数占位符,Mybatis会将sql中的#{}替换为?号,在<br>sql执行前会使用PreparedStatement的参数设置方法,按序给sql<br>的?号占位符设置参数值,比如ps.setInt(0, parameterValue),<br>#{item.name}的取值方式为使用反射从参数对象中获取item对象的<br>name属性值,相当于param.getItem().getName()。<br>
第三题:Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
问题:
Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
参考答案:
还有很多其他的标签,<resultMap>、<parameterMap>、<sql>、<include>、<br><selectKey>。<br>动态sql的标签:trim、where、set、foreach、if、choose、when、<br>otherwise、bind等,其中<sql>为sql片段标签,通过<include>标签引入sql片段,<br><selectKey>为不支持自增的主键生成策略标签。<br>
第四题:Mybatis是如何进行分页的?分页插件的原理是什么?
问题:
Mybatis是如何进行分页的?分页插件的原理是什么?
参考答案:
1、Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的<br>内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物<br>理分页功能,也可以使用分页插件来完成物理分页。<br>
2、分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插<br>件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的<br>物理分页语句和物理分页参数。<br>
3、举例:select * from student,拦截sql后重写为:<br>select t.* from (select * from student)t limit 0,10<br>
第五题:简述Mybatis的插件运行原理,以及如何编写一个插件。
问题
简述Mybatis的插件运行原理,以及如何编写一个插件。
参考答案:
1、Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、<br>Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理<br>对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,<br>具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。<br>
2、实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定<br>要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。<br>
第六题:Mybatis执行批量插入,能返回数据库主键列表吗?
问题:
Mybatis执行批量插入,能返回数据库主键列表吗?
参考答案:
能,JDBC都能,Mybatis当然也能。
第八题:Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?
问题:
Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?
参考答案:
1、Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成<br>逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签trim|where|set|<br>foreach|if|choose|when|otherwise|bind。<br>
2、其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态<br>拼接sql,以此来完成动态sql的功能。<br>
第九题:Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
问题:
Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
参考答案:
1、第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。<br>第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS <br>NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽<br>略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe,<br>Mybatis一样可以正常工作。<br>
2、有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象<br>的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。<br>
第十题:Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
问题:
Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
参考答案:
1、Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指<br>的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用<br>延迟加载lazyLoadingEnabled=true|false。<br>
2、它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方<br>法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会<br>单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的<br>对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。<br>
3、当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的
第十一题:Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
问题;
Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
参考答案:
1、不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置<br>namespace,那么id不能重复;毕竟namespace不是必须的,只是最佳实践而已。<br>
2、原因就是namespace+id是作为Map<String, MappedStatement>的key使用的,<br>如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,<br>自然id就可以重复,namespace不同,namespace+id自然也就不同。<br>
第十二题:Mybatis中如何执行批处理?
问题:
Mybatis中如何执行批处理?
参考答案:
使用BatchExecutor完成批处理。
第十三题:Mybatis都有哪些Executor执行器?它们之间的区别是什么?
问题:
Mybatis都有哪些Executor执行器?它们之间的区别是什么?
参考答案:
1、Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、<br>BatchExecutor。<br>
2、SimpleExecutor:每执行一次update或select,就开启一个Statement对象,<br>用完立刻关闭Statement对象。<br>
3、ReuseExecutor:执行update或select,以sql作为key查找Statement对象,<br>存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于<br>Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。<br>
4、BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所<br>有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓<br>存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执<br>行executeBatch()批处理。与JDBC批处理相同。<br>
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
第十四题:Mybatis中如何指定使用哪一种Executor执行器?
问题;
Mybatis中如何指定使用哪一种Executor执行器?
参考答案:
在Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给<br>DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。<br>
第十五题:Mybatis是否可以映射Enum枚举类?
问题:
Mybatis是否可以映射Enum枚举类?
参考答案:
1、Mybatis可以映射枚举类,不单可以映射枚举类,Mybatis可以映射任何对象<br>到表的一列上。映射方式为自定义一个TypeHandler,实现TypeHandler的<br>setParameter()和getResult()接口方法。<br>
2、TypeHandler有两个作用,一是完成从javaType至jdbcType的转换,二是完成<br>jdbcType至javaType的转换,体现为setParameter()和getResult()两个方法,分<br>别代表设置sql问号占位符参数和获取列查询结果。<br>
第十六题:Mybatis映射文件中,如果A标签通过include引用了B标签的内容,<br>请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?<br>
问题:
Mybatis映射文件中,如果A标签通过include引用了B标签的内容,<br>请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?
参考答案:
1、虽然Mybatis解析Xml映射文件是按照顺序解析的,但是,被引用的<br>B标签依然可以定义在任何地方,Mybatis都可以正确识别。<br>
2、原理是,Mybatis解析A标签,发现A标签引用了B标签,但是B标签<br>尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,<br>然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis<br>会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已<br>经存在,A标签也就可以正常解析完成了。<br>
第十七题:简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?
问题:
简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?
参考答案:
1、Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration<br>内部。在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap<br>对象,其每个子元素会被解析为ParameterMapping对象。<br>
2、<resultMap>标签会被解析为ResultMap对象,其每个子元素会被解析为<br>ResultMapping对象。每一个<select>、<insert>、<update>、<delete><br>标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql对象。<br>
第十八题:为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
问题:
为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
参考答案:
Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联<br>集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis<br>在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半<br>自动ORM映射工具。<br>
四、Redis数据库相关面试全集
第一题:你在项目中如何保证缓存和数据库的一致性?
问题:
你在项目中如何保证缓存和数据库的一致性?
参考答案:
记住一句话,只要有引入缓存的地方,都不可能保证强一致性,所以这里的一致性是指最终一致性<br><br>方法很多,最常用的就是延时双删,先删除缓存,再操作数据库,完事儿再删除一次缓存<br><br>第二次删除缓存是为了避免 在第一次删除缓存之后,到操作数据库完成之前,这期间有新的查询过来,导致再次把旧数据生成缓存<br>
第二题:redis的持久化机制?
问题:
redis的持久化机制?
参考答案:
1、所谓持久化机制就是保证 redis 挂掉再重启后,可以恢复数据
2、快照RDB(默认)
默认开启,无需设置,有个参数 save m n,这表示m秒内进行了n次写操作就进行备份,<br>而且可以设置多组,满足不同场景;这里备份有两种,一个是save(阻塞),一个是<br>bgsave(异步),还有一种是自动化,redis的快照是采用bgsave<br>
3、AOF(AppendOnlyFile:只追加文件)
a、需手动开启,在redis.conf中开启appendonly,默认是no,改为yes,生成的日志文件名<br>默认为appendonly.aof,可以修改,然后配置appendfsync,有三个选项,always、<br>everysec和no:<br>b、默认是everysec,表示每秒同步一次,性能和数据可靠性都能兼顾,最坏情况会丢失不到2秒的数据;<br>c、no表示平时不进行同步,只会在redis关闭或者aof被关闭时同步,性能最佳,但是丢数据风险高;<br>d、always表示每次写操作都会同步,性能差,但是丢数据风险低;<br>
第三题:redis如何管理过期的key?
问题:
redis如何管理过期的key?
参考答案:
redis采用 定期 + 惰性 删除的方式来管理key
redis会周期性地扫描当前所有key,发现过期的立即清除,这招叫 定期删除;<br>但是这样有个问题,扫描间隔太长的话,可能导致某些key多存活一个周期;<br>太短的话,又会很影响性能,所以除了定期扫描,redis在使用某个key时会<br>先校验key是否过期,如果过期直接删除,这招叫 惰性删除<br>
第四题:使用Redis有哪些好处?
问题:
使用Redis有哪些好处?
参考答案:
(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找<br>和操作的时间复杂度都是O(1)<br>
(2) 支持丰富数据类型,支持string,list,set,Sorted Set(有序集合),hash
(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要<br>么全部不执行<br>
(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
第五题:redis相比memcached有哪些优势?
问题:
redis相比memcached有哪些优势?
参考答案:
(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
(2) redis的速度比memcached快很多
(3) redis可以持久化其数据
第六题:Memcache与Redis的区别都有哪些?
问题:
Memcache与Redis的区别都有哪些?
参考答案:
1)、存储方式
Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。<br><br>Redis有部份存在硬盘上,这样能保证数据的持久性。
2)、数据支持类型
Memcache对数据类型支持相对简单。<br><br>Redis有复杂的数据类型。
3)、使用底层模型不同
它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。<br><br>Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,<br>会浪费一定的时间去移动和请求。<br>
4)、value大小
redis最大可以达到512M,而memcache只有1MB
第七题:缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题?
问题:
缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题?
参考答案:
1、缓存雪崩
缓存雪崩:由于原有缓存失效,新缓存未到期,访问直达db<br><br>(造成原因:由于设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),<br>所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压<br>力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。<br>
解决办法:<br>1、访问加锁<br>2、使缓存分散<br>最终的目的就是防止大量请求落到db上<br>
2、缓存穿透
缓存穿透是指用户查询数据时数据库没有,自然在缓存中也不会有。这样就导致用户查<br>询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了<br>两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。<br>
解决办法:<br>最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个<br>一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。<br><br>另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是<br>系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。<br>通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问<br>数据库,这种办法最简单粗暴。<br>
3、缓存穿透与缓存击穿的区别
缓存击穿:是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行<br>访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据。<br>
解决方案:在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁<br>住当前key的访问,访问结束再删除该短期key。<br>
4、缓存预热
缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,<br>缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免<br>在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被<br>预热的缓存数据!<br>
解决思路:<br>1、直接写个缓存刷新页面,上线时手工操作下;<br>2、数据量不大,可以在项目启动的时候自动进行加载;<br>3、定时刷新缓存;
5、缓存更新
第八题:Redis 常见性能问题和解决方案?
第九题:说说Redis的过期键删除策略?
问题:
说说Redis的过期键删除策略?
参考答案:
1. 常见的删除策略
定时删除:<br>在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,<br>立即执行对键的删除操作。<br>优点:对内存非常友好<br>缺点:对CPU时间非常不友好<br>
惰性删除:<br>放任过期键不管,每次从键空间中获取键时,检查该键是否过期,如果过期,就删<br>除该键,如果没有过期,就返回该键。<br>优点:对CPU时间非常友好<br>缺点:对内存非常不友好<br>
定期删除:<br>每隔一段时间,程序对数据库进行一次检查,删除里面的过期键,至于要删除哪些<br>数据库的哪些过期键,则由算法决定。<br><br>定期删除策略是定时删除策略和惰性删除策略的一种整合折中方案。<br>
2、Redis使用的过期键删除策略
Redis服务器使用的是惰性删除策略和定期删除策略。
惰性删除策略的实现
过期键的惰性删除策略由expireIfNeeded函数实现,所有读写数据库的<br>Redis命令在执行之前都会调用expireIfNeeded函数对输入键进行检查:<br><br>如果输入键已经过期,那么将输入键从数据库中删除<br><br>如果输入键未过期,那么不做任何处理<br>
子主题
定期删除策略的实现
过期键的定期删除策略由activeExpireCycle函数实现,每当Redis服务器的周期性操作serverCron<br>函数执行时,activeExpireCycle函数就会被调用,它在规定的时间内,分多次遍历服务器中的各个<br>数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键。<br><br>activeExpireCycle函数的大体流程为:<br><br>函数每次运行时,都从一定数量的数据库中随机取出一定数量的键进行检查,并删除其中的过期键,<br>比如先从0号数据库开始检查,下次函数运行时,可能就是从1号数据库开始检查,直到15号数据库<br>检查完毕,又重新从0号数据库开始检查,这样可以保证每个数据库都被检查到。<br><br>划重点:<br><br>关于定期删除的大体流程,最近面试时有被问到,我就是按上述描述回答的。<br><br>可能有的面试官还会问,每次随机删除哪些key呢?可以提下LRU算法(Least Recently Used 最近最<br>少使用),一般不会再细问,不过有兴趣的同学可以深入研究下。更多面试题,欢迎关注公众号 Java<br>面试题精选<br>
3. RDB对过期键的处理
生成RDB文件<br>
在执行SAVE命令或者BGSAVE命令创建一个新的RDB文件时,程序会对<br>数据库中的键进行检查,已过期的键不会被保存到新创建的RDB文件中。<br>
举个例子,如果数据库中包含3个键k1、k2、k3,并且k2已经过期,那么<br>创建新的RDB文件时,程序只会将k1和k3保存到RDB文件中,k2则会被忽略<br>
载入RDB文件<br>
在启动Redis服务器时,如果服务器只开启了RDB持久化,那么服务器将会载入RDB文件:<br><br>如果服务器以主服务器模式运行,在载入RDB文件时,程序会对文件中保存的键进行检查,<br>未过期的键会被载入到数据库中,过期键会被忽略。<br><br>如果服务器以从服务器模式运行,在载入RDB文件时,文件中保存的所有键,不论是否过<br>期,都会被载入到数据库中。<br><br>因为主从服务器在进行数据同步(完整重同步)的时候,从服务器的数据库会被清空,所<br>以一般情况下,过期键对载入RDB文件的从服务器不会造成影响。<br>
4. AOF对过期键的处理
AOF文件写入
如果数据库中的某个键已经过期,并且服务器开启了AOF持久化功能,当过期键被惰性删除<br>或者定期删除后,程序会向AOF文件追加一条DEL命令,显式记录该键已被删除。<br>
举个例子,如果客户端执行命令GET message访问已经过期的message键,那么服务器将执行<br>以下3个动作:<br><br>从数据库中删除message键<br><br>追加一条DEL message命令到AOF文件<br>-向执行GET message命令的客户端返回空回复<br>
AOF文件重写
在执行AOF文件重写时,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的<br>AOF文件中。<br>
复制功能对过期键的处理
在主从复制模式下,从服务器的过期键删除动作由主服务器控制:<br><br>主服务器在删除一个过期键后,会显式地向所有从服务器发送一个DEL命令,告知从服务器删除这个过期键。<br><br>从服务器在执行客户端发送的读命令时,即使发现该键已过期也不会删除该键,照常返回该键的值。<br><br>从服务器只有接收到主服务器发送的DEL命令后,才会删除过期键。
五、Mysql相关面试全集
第一题:MySQL中myisam与innodb的区别?
问题:
MySQL中myisam与innodb的区别?
参考答案:
1、MyISAM:<br><br>不支持事务,但是每次查询都是原子的;<br>支持表级锁,即每次操作对整个表加锁;<br>存储表的总行数;<br>一个MYISAM表有三个文件:索引文件、表结构文件、数据文件;<br>采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅<br>索引不用保证唯一性<br>
2、InnoDb:<br><br>支持ACID的事务,支持事务的四种隔离级别;<br>支持行级锁及外键约束:因此可以支持写并发;<br>不存储总行数;<br><br>一个InnoDb引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能<br>分布在多个文件里),也有可能为多个(设置为独立表空,表大小受操作系统文件大小限制,<br>一般为2G),受操作系统文件大小的限制;<br><br>主键索引采用聚集索引(索引的数据域存储数据文件本身),辅索引的数据域存储主键的值;<br>因此从辅索引查找数据,需要先通过辅索引找到主键值,再访问辅索引;最好使用自增主键,<br>防止插入数据时,为维持B+树结构,文件的大调整。<br>
3、两者的适用场景:<br><br>因为MyISAM相对简单所以在效率上要优于InnoDB.如果系统读多,写少。对原子性要求低。<br>那么MyISAM最好的选择。且MyISAM恢复速度快。可直接用备份覆盖恢复。<br><br>如果系统读少,写多的时候,尤其是并发写入高的时候。InnoDB就是首选了。<br>
第二题:说说你知道的MySQL的索引类型,并分别简述一下各自的场景?
问题:
说说你知道的MySQL的索引类型,并分别简述一下各自的场景。
参考答案:
1、普通索引:没有任何限制条件的索引,该索引可以在任何数据类型中创建。
2、唯一索引:使用UNIQUE参数可以设置唯一索引。创建该索引时,索引列的值<br>必须唯一,但允许有空值。通过唯一索引,用户可以快速地定位某条记录,主键索<br>引是一种特殊的唯一索引。<br>
3、全文索引:仅可用于 MyISAM 表,针对较大的数据,生成全文索引耗时耗空间。
4、空间索引:只能建立在空间数据类型上。这样可以提高系统获取空间数据类型的效<br>率。仅可用于 MyISAM 表,索引的字段不能为空值。使用SPATIAL参数可以设置索引<br>为空间索引。<br>
5、单列索引:只对应一个字段的索引。
6、多列索引:在表的多个字段上创建一个索引。该索引指向创建时对应的多个字段,用<br>户可以通过这几个字段进行查询,想使用该索引,用户必须使用这些字段中的一个字段。<br>
第三题:MySQL建索引需要遵循哪些原则呢?
问题:
MySQL建索引需要遵循哪些原则呢?
参考答案:
1.选择唯一性索引
唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。<br>例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引<br>可以很快的确定某个学生的信息。如果使用姓名的话,可能存在同名<br>现象,从而降低查询速度<br>
2.为经常需要排序、分组和联合操作的字段建立索引
经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,<br>排序操作会浪费很多时间。如果为其建立索引,可以有效地避免排序操作。<br>
3.为常作为查询条件的字段建立索引
如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的<br>查询速度。因此,为这样的字段建立索引,可以提高整个表的查询速度。<br>
4.限制索引的数目
索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘<br>空间就越大。修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变<br>得很浪费时间。<br>
5.尽量使用数据量少的索引
如果索引的值很长,那么查询的速度会受到影响。例如,对一个CHAR(100)类型的字段<br>进行全文检索需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多。<br>
6.尽量使用前缀来索引
如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文<br>检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度<br>
7.删除不再使用或者很少使用的索引
表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。<br>数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。<br>
8.最左前缀匹配原则,非常重要的原则
mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如<br>a 1=”” and=”” b=”2” c=”“> 3 and d = 4 如果建立(a,b,c,d)顺序的索引,<br>d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。<br>
9.=和in可以乱序
比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,<br>mysql的查询优化器会帮你优化成索引可以识别的形式<br>
10.尽量选择区分度高的列作为索引。
区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们<br>扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区<br>分度就 是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很<br>难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条 记录<br>
11.索引列不能参与计算,保持列“干净”
比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很<br>简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用<br>函数才能比较,显然成本 太大。所以语句应该写成<br>create_time = unix_timestamp(’2014-05-29’);<br>
12.尽量的扩展索引,不要新建索引
比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可<br>
注意:选择索引的最终目的是为了使查询的速度变快。上面给出的原则是最基本的准则,但不能拘泥于上面的准则。读者要在以后的学习和工作中进行不断的实践。根据应用的实际情况进行分析和判断,选择最合适的索引方式。
六、nginx相关面试全集
第一题:什么是Nginx?
问题;
什么是Nginx?
参考答案:
Nginx是一个 轻量级/高性能的反向代理Web服务器,他实现非常高效的<br>反向代理、负载平衡,他可以处理2-3万并发连接数,官方监测能支持5万<br>并发,现在中国使用nginx网站用户有很多,例如:新浪、网易、 腾讯等<br>
第二题:为什么要用Nginx?
问题:
为什么要用Nginx?
参考答案:
1、跨平台、配置简单、方向代理、高并发连接:处理2-3万并发连接数,官<br>方监测能支持5万并发,内存消耗小:开启10个nginx才占150M内存 ,nginx <br>处理静态文件好,耗费内存少。<br>
2、而且Nginx内置的健康检查功能:如果有一个服务器宕机,会做一个健康检<br>查,再发送的请求就不会发送到宕机的服务器了。重新将请求提交到其他的节点上。<br>
3、使用Nginx的话还能:<br> 节省宽带:支持GZIP压缩,可以添加浏览器本地缓存<br> 稳定性高:宕机的概率非常小<br> 接收用户请求是异步的
第三题:为什么Nginx性能这么高?
问题:
为什么Nginx性能这么高?
参考答案:
因为他的事件处理机制:异步非阻塞事件处理机制:运用了epoll模型,提供了一<br>个队列,排队解决<br>
第四题:Nginx怎么处理请求的?
问题:
Nginx怎么处理请求的?
参考答案:
nginx接收一个请求后,首先由listen和server_name指令匹配server模块,<br>再匹配server模块里的location,location就是实际地址<br>
server { # 第一个Server区块开始,表示一个独立的虚拟主机站点<br> listen 80; # 提供服务的端口,默认80<br> server_name localhost; # 提供服务的域名主机名<br> location / { # 第一个location区块开始<br> root html; # 站点的根目录,相当于Nginx的安装目录<br> index index.html index.htm; # 默认的首页文件,多个用空格分开<br> } # 第一个location区块结果<br> }
第五题:什么是正向代理和反向代理?
问题:
什么是正向代理和反向代理?
参考答案:
1、正向代理就是一个人发送一个请求直接就到达了目标的服务器。
2、反向代理就是请求统一被Nginx接收,nginx反向代理服务器接收<br>到之后,按照一定的规则分发给了后端的业务处理服务器进行处理了。<br>
第六题:使用“反向代理服务器的优点是什么?
问题:
使用“反向代理服务器的优点是什么?
参考答案:
反向代理服务器可以隐藏源服务器的存在和特征。它充当互联网云和web<br>服务器之间的中间层。这对于安全方面来说是很好的,特别是当您使用<br>web托管服务时。<br>
第七题:Nginx的优缺点?
问题:
Nginx的优缺点?
参考答案:
优点:<br><br>占内存小,可实现高并发连接,处理响应快<br>可实现http服务器、虚拟主机、方向代理、负载均衡<br>Nginx配置简单<br>可以不暴露正式的服务器IP地址
缺点:<br><br>动态处理差:nginx处理静态文件好,耗费内存少,但是处<br>理动态页面则很鸡肋,现在一般前端用nginx作为反向代<br>理抗住压力,<br>
第八题:Nginx应用场景?
问题:
Nginx应用场景?
参考答案:
1、http服务器。Nginx是一个http服务可以独立提供http服务。可以做网页静态服务器。
2、虚拟主机。可以实现在一台服务器虚拟出多个网站,例如个人网站使用的虚拟机。
3、反向代理,负载均衡。当网站的访问量达到一定程度后,单台服务器不能满足用户的请<br>求时,需要用多台服务器集群可以使用nginx做反向代理。并且多台服务器可以平均分担负<br>载,不会应为某台服务器负载高宕机而某台服务器闲置的情况。<br>
4、nginz 中也可以配置安全管理、比如可以使用Nginx搭建API接口网关,对每个接口服务<br>进行拦截。<br>
第九题:Nginx目录结构有哪些?
问题:
Nginx目录结构有哪些?
参考答案:
[root@localhost ~]# tree /usr/local/nginx<br>/usr/local/nginx<br>├── client_body_temp<br>├── conf # Nginx所有配置文件的目录<br>│ ├── fastcgi.conf # fastcgi相关参数的配置文件<br>│ ├── fastcgi.conf.default # fastcgi.conf的原始备份文件<br>│ ├── fastcgi_params # fastcgi的参数文件<br>│ ├── fastcgi_params.default <br>│ ├── koi-utf<br>│ ├── koi-win<br>│ ├── mime.types # 媒体类型<br>│ ├── mime.types.default<br>│ ├── nginx.conf # Nginx主配置文件<br>│ ├── nginx.conf.default<br>│ ├── scgi_params # scgi相关参数文件<br>│ ├── scgi_params.default <br>│ ├── uwsgi_params # uwsgi相关参数文件<br>│ ├── uwsgi_params.default<br>│ └── win-utf<br>├── fastcgi_temp # fastcgi临时数据目录<br>├── html # Nginx默认站点目录<br>│ ├── 50x.html # 错误页面优雅替代显示文件,例如当出现502错误时会调用此页面<br>│ └── index.html # 默认的首页文件<br>├── logs # Nginx日志目录<br>│ ├── access.log # 访问日志文件<br>│ ├── error.log # 错误日志文件<br>│ └── nginx.pid # pid文件,Nginx进程启动后,会把所有进程的ID号写到此文件<br>├── proxy_temp # 临时目录<br>├── sbin # Nginx命令目录<br>│ └── nginx # Nginx的启动命令<br>├── scgi_temp # 临时目录<br>└── uwsgi_temp # 临时目录
第十题:Nginx配置文件nginx.conf有哪些属性模块?
问题:
Nginx配置文件nginx.conf有哪些属性模块?
参考答案:
worker_processes 1; # worker进程的数量<br>events { # 事件区块开始<br> worker_connections 1024; # 每个worker进程支持的最大连接数<br>} # 事件区块结束<br>http { # HTTP区块开始<br> include mime.types; # Nginx支持的媒体类型库文件<br> default_type application/octet-stream; # 默认的媒体类型<br> sendfile on; # 开启高效传输模式<br> keepalive_timeout 65; # 连接超时<br> server { # 第一个Server区块开始,表示一个独立的虚拟主机站点<br> listen 80; # 提供服务的端口,默认80<br> server_name localhost; # 提供服务的域名主机名<br> location / { # 第一个location区块开始<br> root html; # 站点的根目录,相当于Nginx的安装目录<br> index index.html index.htm; # 默认的首页文件,多个用空格分开<br> } # 第一个location区块结果<br> error_page 500502503504 /50x.html; # 出现对应的http状态码时,使用50x.html回应客户<br> location = /50x.html { # location区块开始,访问50x.html<br> root html; # 指定对应的站点目录为html<br> }<br> } <br> ......
第十一题:Nginx静态资源?
问题:
Nginx静态资源?
参考答案:
静态资源访问,就是存放在nginx的html页面,我们可以自己编写
第十二题:如何用Nginx解决前端跨域问题?
问题:
如何用Nginx解决前端跨域问题?
参考答案:
使用Nginx转发请求。把跨域的接口写成调本域的接口,然后将这些接口转发到真正的请求地址。
第十三题:Nginx虚拟主机怎么配置?
问题:
Nginx虚拟主机怎么配置?
参考答案:
1、基于域名的虚拟主机,通过域名来区分虚拟主机——应用:外部网站
2、基于端口的虚拟主机,通过端口来区分虚拟主机——应用:公司内部网站,外部网站的管理后台
3、基于ip的虚拟主机。
示例
基于虚拟主机配置域名
需要建立/data/www /data/bbs目录,windows本地hosts添加虚拟机ip地址对应的域名解析;对应域名网站目录下新增index.html文件;
#当客户端访问www.lijie.com,监听端口号为80,直接跳转到data/www目录下文件<br> server {<br> listen 80;<br> server_name www.lijie.com;<br> location / {<br> root data/www;<br> index index.html index.htm;<br> }<br> }<br><br> #当客户端访问www.lijie.com,监听端口号为80,直接跳转到data/bbs目录下文件<br> server {<br> listen 80;<br> server_name bbs.lijie.com;<br> location / {<br> root data/bbs;<br> index index.html index.htm;<br> }<br> }
基于端口的虚拟主机
使用端口来区分,浏览器使用域名或ip地址:端口号 访问
#当客户端访问www.lijie.com,监听端口号为8080,直接跳转到data/www目录下文件<br> server {<br> listen 8080;<br> server_name 8080.lijie.com;<br> location / {<br> root data/www;<br> index index.html index.htm;<br> }<br> }<br><br> #当客户端访问www.lijie.com,监听端口号为80直接跳转到真实ip服务器地址 127.0.0.1:8080<br> server {<br> listen 80;<br> server_name www.lijie.com;<br> location / {<br> proxy_pass http://127.0.0.1:8080;<br> index index.html index.htm;<br> }<br> }
第十四题:location的作用是什么?
问题:
location的作用是什么?
参考答案:
location指令的作用是根据用户请求的URI来执行不同的应用,<br>也就是根据用户请求的网站URL进行匹配,匹配成功即进行相<br>关的操作。更多面试题,欢迎关注公众号 Java面试题精选<br>
location的语法能说出来吗?
注意:~ 代表自己输入的英文字母
Location正则案例
#优先级1,精确匹配,根路径<br> location =/ {<br> return 400;<br> }<br><br> #优先级2,以某个字符串开头,以av开头的,优先匹配这里,区分大小写<br> location ^~ /av {<br> root /data/av/;<br> }<br><br> #优先级3,区分大小写的正则匹配,匹配/media*****路径<br> location ~ /media {<br> alias /data/static/;<br> }<br><br> #优先级4 ,不区分大小写的正则匹配,所有的****.jpg|gif|png 都走这里<br> location ~* .*\.(jpg|gif|png|js|css)$ {<br> root /data/av/;<br> }<br><br> #优先7,通用匹配<br> location / {<br> return 403;<br> }
第十五题:限流怎么做的?
问题:
限流怎么做的?
参考答案:
Nginx限流就是限制用户请求速度,防止服务器受不了
限流有3种<br><br>1、正常限制访问频率(正常流量)<br>2、突发限制访问频率(突发流量)<br>3、限制并发连接数
Nginx的限流都是基于漏桶流算法
实现三种限流算法
1、正常限制访问频率(正常流量):
限制一个用户发送的请求,我Nginx多久接收一个请求。
Nginx中使用ngx_http_limit_req_module模块来限制的访问频率,<br>限制的原理实质是基于漏桶算法原理来实现的。在nginx.conf配置<br>文件中可以使用limit_req_zone命令及limit_req命令限制单个IP的<br>请求处理频率。<br>
#定义限流维度,一个用户一分钟一个请求进来,多余的全部漏掉<br> limit_req_zone $binary_remote_addr zone=one:10m rate=1r/m;<br><br> #绑定限流维度<br> server{<br><br> location/seckill.html{<br> limit_req zone=zone; <br> proxy_pass http://lj_seckill;<br> }<br><br> }
1r/s代表1秒一个请求,1r/m一分钟接收一个请求, 如果Nginx这时还有别人的<br>请求没有处理完,Nginx就会拒绝处理该用户请求。<br>
2、突发限制访问频率(突发流量):
限制一个用户发送的请求,我Nginx多久接收一个。
上面的配置一定程度可以限制访问频率,但是也存在着一个问题:如果突发流量超出<br>请求被拒绝处理,无法处理活动时候的突发流量,这时候应该如何进一步处理呢?<br>
Nginx提供burst参数结合nodelay参数可以解决流量突发的问题,可以设置能处理的超<br>过设置的请求数外能额外处理的请求数。我们可以将之前的例子添加burst参数以及nodelay参数:<br>
#定义限流维度,一个用户一分钟一个请求进来,多余的全部漏掉<br> limit_req_zone $binary_remote_addr zone=one:10m rate=1r/m;<br><br> #绑定限流维度<br> server{<br><br> location/seckill.html{<br> limit_req zone=zone burst=5 nodelay;<br> proxy_pass http://lj_seckill;<br> }<br><br> }
为什么就多了一个 burst=5 nodelay; 呢,多了这个可以代表Nginx对于一个用户的请求会立即处理<br>前五个,多余的就慢慢来落,没有其他用户的请求我就处理你的,有其他的请求的话我Nginx就漏掉<br>不接受你的请求<br>
3、 限制并发连接数
Nginx中的ngx_http_limit_conn_module模块提供了限制并发连接数的功能,可以<br>使用limit_conn_zone指令以及limit_conn执行进行配置。接下来我们可以通过一个<br>简单的例子来看下:<br>
http {<br> limit_conn_zone $binary_remote_addr zone=myip:10m;<br> limit_conn_zone $server_name zone=myServerName:10m;<br> }<br><br> server {<br> location / {<br> limit_conn myip 10;<br> limit_conn myServerName 100;<br> rewrite / http://www.lijie.net permanent;<br> }<br> }
上面配置了单个IP同时并发连接数最多只能10个连接,并且设置了整个虚拟服务器同时<br>最大并发数最多只能100个链接。当然,只有当请求的header被服务器处理后,虚拟服<br>务器的连接数才会计数。<br>
刚才有提到过Nginx是基于漏桶算法原理实现的,实际上限流一般都是基于漏桶算法和令牌桶算法实现的。接下来我们来看看两个算法的介绍:
第十七题:漏桶流算法和令牌桶算法知道?
问题:
漏桶流算法和令牌桶算法知道?
参考答案:
1、漏桶算法
漏桶算法是网络世界中流量整形或速率限制时经常使用的一种算法,它的主要<br>目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了<br>一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。<br>
也就是我们刚才所讲的情况。漏桶算法提供的机制实际上就是刚才的案例:突发<br>流量会进入到一个漏桶,漏桶会按照我们定义的速率依次处理请求,如果水流过<br>大也就是突发流量过大就会直接溢出,则多余的请求会被拒绝。所以漏桶算法能<br>控制数据的传输速率。<br>
图
子主题
2、令牌桶算法
令牌桶算法是网络流量整形和速率限制中最常使用的一种算法。典型情况下,<br>令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。<br>Google开源项目Guava中的RateLimiter使用的就是令牌桶控制算法。<br>
令牌桶算法的机制如下:存在一个大小固定的令牌桶,会以恒定的速率源源不<br>断产生令牌。如果令牌消耗速率小于生产令牌的速度,令牌就会一直产生直至<br>装满整个令牌桶。更多面试题,欢迎关注公众号 Java面试题精选<br>
图:
第十八题:为什么要做动静分离?
问题:
为什么要做动静分离?
参考答案:
1、Nginx是当下最热的Web容器,网站优化的重要点在于静态化网站,<br>网站静态化的关键点则是是动静分离,动静分离是让动态网站里的<br>动态网页根据一定规则把不变的资源和经常变的资源区分开来,动<br>静资源做好了拆分以后,我们则根据静态资源的特点将其做缓存操作。<br>
2、让静态的资源只走静态资源服务器,动态的走动态的服务器
3、Nginx的静态处理能力很强,但是动态处理能力不足,因此,在企业中常<br>用动静分离技术。<br>
4、对于静态资源比如图片,js,css等文件,我们则在反向代理服务器nginx中<br>进行缓存。这样浏览器在请求一个静态资源时,代理服务器nginx就可以直<br>接处理,无需将请求转发给后端服务器tomcat。<br>
5、若用户请求的动态文件,比如servlet,jsp则转发给Tomcat服务器处理,<br>从而实现动静分离。这也是反向代理服务器的一个重要的作用。<br>
第十九题:Nginx怎么做的动静分离?
问题:
Nginx怎么做的动静分离?
参考答案:
只需要指定路径对应的目录。location/可以使用正则表达式匹配。并指定对<br>应的硬盘中的目录。如下:(操作都是在Linux上)<br>
location /image/ {<br> root /usr/local/static/;<br> autoindex on;<br> }
1.创建目录
mkdir /usr/local/static/image
2.进入目录
cd /usr/local/static/image
3.放一张照片上去
1.jpg
4.重启 nginx
sudo nginx -s reload
打开浏览器 输入 server_name/image/1.jpg 就可以访问该静态图片了
第二十题:Nginx负载均衡的算法怎么实现的?策略有哪些?
问题:
Nginx负载均衡的算法怎么实现的?策略有哪些?
参考答案:
为了避免服务器崩溃,大家会通过负载均衡的方式来分担服务器压力。<br>将对台服务器组成一个集群,当用户访问时,先访问到一个转发服务<br>器,再由转发服务器将访问分发到压力更小的服务器。<br>
Nginx负载均衡实现的策略有以下五种:
1 轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端某个服务器宕机,能自动剔除故障系统
upstream backserver { <br> server 192.168.0.12; <br> server 192.168.0.13; <br>}
2 权重 weight
weight的值越大分配<br>
到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下。其次是为在主从的情况下<br>设置不同的权值,达到合理有效的地利用主机资源。<br>
upstream backserver { <br> server 192.168.0.12 weight=2; <br> server 192.168.0.13 weight=8; <br>}
权重越高,在被访问的概率越大,如上例,分别是20%,80%。
五、网络相关面试全集
第一题:RPC协议与HTTP协议的区别?
问题:
RPC协议与HTTP协议的区别?
参考答案:
1、RPC:远程过程调用,一般用于一台计算机调用另一个计算机上的服务,<br>rpc能让我们像调用本地方法一样调用远程方法;<br>
2、HTTP:超文本传输协议,一般用于浏览器和服务器之间的通讯;<br>
3、RPC效率更高,但是实现起来较复杂;
4、HTTP一般使用json传输数据,RPC一般采用二进制;
第二题:什么websocket?它有什么特点?
问题:
什么websocket?它有什么特点?
参考答案:
作用:<br>用来和服务端保持长连接,使服务端能主动推送消息给客户端<br>
对比http:<br>websocket是一种类似http的协议,可以理解成http协议的加强版;<br>http每次请求都需要建立tcp连接,而且一个request只能对应一个<br>response,每次请求必须由客户端发起,由服务端响应;而websocket<br>能使客户端在和服务端建立好tcp连接后,与服务端保持会话连接,<br>服务端可以自由向客户端推送消息<br>
注意:如果服务端同时维护了很多websocket连接,会对服务端造成很大<br>压力,需要我们对websocket做一些优化(有个互联网独角兽公司面试官<br>这么问我的)<br>我目前能想到的优化就是:<br>1、合并推送:将一些能合并的消息整合到一次推送,以此减少websocket连接<br>2、横向扩展:增加服务器 0.0<br>
六、问题解决思路面试全集
第一题:如果你发现某个接口响应很慢,该怎么排查?
问题:
如果你发现某个接口响应很慢,该怎么排查?
参考答案:
导致接口响应慢的原因太多了:网络、应用层、数据库事务、服务器自身、慢sql等
网络:对于单个请求来讲,网络因素影响其实很小,除非网络挂了导致请求超时才能意识到;<br>而对于大批量请求,每个请求慢10ms,请求多了,时间也就长了,这种情况可以检查下你的<br>应用部署机和数据库机地理位置是不是隔得很远,比如一个在华东一个在西南,地理距离也<br>会对请求响应时间产生影响,请求量越大越明显;<br>
应用层:就是我们敲的controller、service那些代码,这一层出问题很好解决,因为代码毕竟都<br>是我们敲的嘛,一看日志就大概知道什么原因,最多的就是出现死循环(当然一旦出现死循环也<br>不只是响应慢那么简单了);代码逻辑写的差点其实不会太影响性能,现在的cpu执行效率你尽<br>管放心,再怎么优化也顶不了少一次io;<br>
数据库事务:检查下你的数据库是不是卡事务了,导致锁了很多表;
服务器自身:服务器是不是卡了,cpu是不是炸了,内存是不是满了;
慢sql:这一层出问题的几率很大,同一组查询结果,由于sql不同,耗时能相差几百上千倍,可以<br>通过查看sql执行计划来排查问题,<br>
第二题:导致线程阻塞的原因有哪些?
问题:
导致线程阻塞的原因有哪些?
参考答案:
1、主动调用Thread.sleep(1000)方法:暂时放弃对cpu的使用,不会释放锁,睡眠<br>时间到了后直接进入就绪态,拿到cpu时间片立即执行;<br>
2、主动调用Thread.yield()方法:向调度系统表明当前线程愿意放弃其对处理器的使用;
3、遇到Object类的wait()方法:放弃当前持有的锁,进入等待状态,直到有其他<br>线程将其唤醒;<br>
4、遇到Thread类的join()方法:当线程t调用在当前线程内部调用join时,当前<br>线程会陷入阻塞,直到线程t执行完;<br>
5、cpu时间片用完:线程调度是由操作系统控制,同一个系统里面线程那么多,<br>cpu不可能只执行你这一个线程,所以每个线程在执行前都需要先拿到cpu时间<br>片,用完后进入就绪态,再次拿到时间片即可开始执行;<br>
分支主题
子主题
子主题
https://huangjie.blog.csdn.net/article/details/113549520?spm=1001.2014.3001.5502
https://mp.weixin.qq.com/s/uE2G1t6XNuCxkrOYPn4HPw
CENTOS替代品
https://rockylinux.org/download
0 条评论
下一页