JAVA面试
2022-08-29 13:19:37 0 举报
AI智能生成
JAVA面试
作者其他创作
大纲/内容
java相关
基础&进阶篇
java的特点
面向对象
平台无关性
解释执行
多线程
分布式
在 Java 的基本应用编程接口中就有一个网络应用编程接口,它提供了网络应用编程的类库包括 URL、URLConnection、Socket 等。Java 的 RIM 机制也是开发分布式应用的重要手段。
JDK和JRE区别
- JDK(Java SE Development Kit)包括了Java编译器、Java运行时环境、以及常用的Java类库等
- JRE(Java Runtime Environment),Java运行时环境,用于解释执行Java的字节码文件。
- JVM(Java Virtual Mechinal),是JRE的一部分。它是整个Java实现跨平台的核心,负责解释执行字节码文件,是可运行Java字节码文件的虚拟计算机。
JDk包含JRE,JDK 和 JRE 中都包含 JVM
Java8种基本数据类型
6种数字类型
byte(8位 -2^7 ~2^7)、short、int、long、float(32位)、double(64位)
1种字符类型
char(16 位 Unicode 字符)
1种布尔类型
boolean(数据类型表示一位的信息)
Java中引用类型和基本数据类型有什么区别
概念方面说
基本数据类型:变量名指向具体的数值
引用数据类型:变量名不是指向具体的数值,而是指向存数据的内存地址,.也及时hash值
内存构建方面
基本数据类型:被创建时,在栈内存中会被划分出一定的内存,并将数值存储在该内存中
引用数据类型:被创建时,首先会在栈内存中分配一块空间,然后在堆内存中也会分配一块具体的空间用来
存储数据的具体信息,即hash值,然后由栈中引用指向堆中的对象地址.
存储数据的具体信息,即hash值,然后由栈中引用指向堆中的对象地址.
Java中Integer 的自动装箱与拆箱
- 装箱调用包装类的valueOf方法
- 在通过valueOf方法创建Integer对象的时候如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
拆箱调用的是Integer.Value方法
a=a+b与a+=b有什么区别
- +=隐式的将加操作的结果类型强制转换为持有结果的类型
- a=a+b则不会自动进行类型转换(b = a + b; // 报编译错误:cannot convert from int to byte)
Java程序是如何执行的
先把 Java 代码编译成字节码,也就是把 .java 类型的文件编译成 .class 类型的文件。
把 class 文件放置到 Java 虚拟机,这个虚拟机通常指的是 Oracle 官方自带的 Hotspot JVM;
Java 虚拟机使用类加载器(Class Loader)装载 class 文件;
类加载完成之后,会进行字节码效验,字节码效验通过之后 JVM 解释器会把字节码翻译成机器码交
由操作系统执行。
由操作系统执行。
final有哪些用法
被final修饰的类不可以被继承
被final修饰的方法不可以被重写
被final修饰的变量不可以被改变.如果修饰引用,那么表示引用不可变,引用指向的内容可变
被final修饰的方法,JVM会尝试将其内联,以提高运行效率
被final修饰的常量,在编译阶段会存入常量池中.
static都有哪些用法
静态变量和静态方法.也就是被static所修饰的变量/方法
都属于类的静态资源,类实例所共享
都属于类的静态资源,类实例所共享
,static也用于静态块,多用于初始化操作
外static也多用于修饰内部类,此时称之为静态内部类
为什么有些java类要实现Serializable接口
什么是序列化
将对象的状态信息转换为可以存储或传输的形式的过程
除了实现Serializable接口还有什么序列化方式
Json序列化
FastJson序列化
ProtoBuff序列化
Serializable接口的作用
将一个java对象变成字节流的形式传出去或者从一个字节流中恢复成一个java对象
Java可抛出(Throwable)的结构
运行时异常(Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明
抛出它",也"没有用try-catch语句捕获它",还是会编译通过。)
抛出它",也"没有用try-catch语句捕获它",还是会编译通过。)
- ClassCastException (类转换异常)
- IndexOutOfBoundsException (数组越界)
- NullPointerException (空指针异常)
- ArrayStoreException (数据存储异常,操作数组是类型不一致)
- BufferOverflowException
被检查异常(Java编译器会检查它。 此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获
处理,否则不能通过编译。)
处理,否则不能通过编译。)
错误(和运行时异常一样,编译器也不会对错误进行检查。当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程)
- OutOfMemoryError
- ThreadDeath
== 和 equals 的区别是什么
== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比
较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下
equals 比较的是值是否相等。
较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下
equals 比较的是值是否相等。
Hashcode的作用
java的集合有两类,一类是List,还有一类是Set。前者有序可重复,后者无序不重复。当我们在set中插
入的时候怎么判断是否已经存在该元素呢,可以通过equals方法。但是如果元素太多,用这样的方法就
会比较慢。
入的时候怎么判断是否已经存在该元素呢,可以通过equals方法。但是如果元素太多,用这样的方法就
会比较慢。
于是有人发明了哈希算法来提高集合中查找元素的效率。
当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。
如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上
已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。
当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。
如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上
已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。
Java多态的理解
- 多态体现为父类引用变量可以指向子类对象。
- Java作为面向对象的语言,同样可以描述一个事物的多种形态。如Student类继承了Person类,一个Student的对象便既是Student,又是Person。
- 在使用多态后的父类引用变量调用方法时,会调用子类重写后的方法。
Java创建对象有几种方式
- new创建新对象
- 通过反射机制
- 采用clone机制
- 通过序列化机制
ConcurrentModificationException异常出现的原因
在List迭代的时候,使用List.remove()会出现这个异常。
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
list.remove(integer);
}
在迭代器中如果要删除元素的话,需要调用 Iterator 类的 remove 方法。
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
list.remove(integer);
}
在迭代器中如果要删除元素的话,需要调用 Iterator 类的 remove 方法。
HashMap和HashTable、ConcurrentHashMap区别
相同点
- HashMap和Hashtable都实现了Map接口
- 都可以存储key-value数据
不同点
- HashMap可以把null作为key或value,HashTable不可以
- HashMap线程不安全,效率高。HashTable线程安全,效率低。
- HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。
Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好
Java 中 IO 流分为几种
输入流、输出流
字节流(8位字节)
InputStream(读取内容)/OutPutStream(写入内容)
InputStream(读取内容)/OutPutStream(写入内容)
字符流(16位字节)
Reader/Writer
Reader/Writer
BIO、NIO、AIO 有什么区别
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并
发处理能力低。
发处理能力低。
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通
讯,实现了多路复用。
讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于
事件和回调机制。
事件和回调机制。
反射的作用及使用场景
反射的作用
- 动态获取对象的信息(对象的属性和方法)
- 动态调用对象的属性和方法
反射使用的场景
- 操作因访问权限限制的属性和方法
- 实现自定义注解
- 动态加载第三方jar包
- 按需加载类,节省编译和初始化时间
字符串&集合面试题汇总
Java 中操作字符串都有哪些类?它们之间有什么区别?
操作字符串的类有: String 、 StringBuffer 、 StringBuilder 。
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成
新的 String 对象,然后将指针指向新的 String 对象。而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作
新的 String 对象,然后将指针指向新的 String 对象。而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是
非线程安全的,但 StringBuilder 的性能却高于 StringBuffer。
非线程安全的,但 StringBuilder 的性能却高于 StringBuffer。
StringBuilder 与 StringBuffer 有公共父类 AbstractStringBuilder。因为StringBuffer append方法增加了Synchronized,所以是线程安全的。
String str="i"与 String str=new String("i")一样吗?
不一样,因为内存的分配方式不一样。String str="i"的方式,Java 虚拟机会将其分配到常量池中;而
String str=new String("i") 则会被分到堆内存中。
String str=new String("i") 则会被分到堆内存中。
String s = new String("xyz");创建了几个StringObject?是否可
以继承String类?
以继承String类?
两个或一个都有可能,”xyz”对应一个对象,这个对象放在字符串常量缓冲区如果以前就用过’xyz’,那么这里就不会创建”xyz”了,直接从缓冲区拿,这时创建了一个StringObject;但如果以前没有用过"xyz",那么此时就会创建一个对象并放入缓冲区,这种情况它创建两个对象。
不可以继承,String 类是final 修饰的
List、Map、Set三个接口,存取元素时,各有什么特点
List以特定次序来持有元素,可有重复元素。Set无法拥有重复元素,内部排序。Map保存key-value值,
value可多值。
value可多值。
ArrayList和LinkedList区别
- ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
- 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
- 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。(可能海还需要扩容)
ArrayList和Vector的区别
同步性
Vector 是线程安全的,也就是说是它的方法之间是线程同步的,而 ArrayList 是线程序不安全的,它
的方法之间是线程不同步的。
的方法之间是线程不同步的。
数据增长
ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需
要增加 ArrayList 与 Vector 的存储空间
要增加 ArrayList 与 Vector 的存储空间
ArrayList,Vector,LinkedList的存储性能和特性
- ArrayList 和 Vector 都是使用数组方式存储数据,它们都允许直接按序号索引元素,
- 但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢
- Vector 由于使用了 synchronized 方法(线程安全),通常性能上较 ArrayList 差。
- 而 LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,索引就变慢了,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快
HashMap和Hashtable的区别
HashMap 是 Hashtable 的轻量级实现(非线程安全的实现),他们都完成了Map接口,
HashMap 允许将null作为一个entry的key或者value,而 Hashtable 不允许。
HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsvalue 和 containsKey 。
poll()方法和remove()方法区别?
poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是
remove() 失败的时候会抛出异常。
remove() 失败的时候会抛出异常。
Comparator和Comparable的区别?
使用Comparable接口来实现对象之间的比较时,可以使这个类型(设为A)实现Comparable接口,并
可以使用Collections.sort()方法来对A类型的List进行排序,之后可以通过a1.comparaTo(a2)来比较两个
对象;
可以使用Collections.sort()方法来对A类型的List进行排序,之后可以通过a1.comparaTo(a2)来比较两个
对象;
当使用Comparator接口来实现对象之间的比较时,只需要创建一个实现Comparator接口的比较器(设
为AComparator),并将其传给Collections.sort()方法即可对A类型的List进行排序,之后也可以通过调
用比较器AComparator.compare(a1, a2)来比较两个对象。
为AComparator),并将其传给Collections.sort()方法即可对A类型的List进行排序,之后也可以通过调
用比较器AComparator.compare(a1, a2)来比较两个对象。
HashMap的实现原理
哈希表结构:结合数组结构和链表结构的优点,从而实现了查询和修改效率高,插入和删除效率也高的一种数据结构
常见的HashMap就是这样的一种数据结构
常见的HashMap就是这样的一种数据结构
map.put(k,v)实现原理
- 首先将k,v封装到Node对象当中(节点)
- 然后它的底层会调用K的hashCode()方法得出hash值。
- 通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
map.get(k)实现原理
- 先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
- 通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。
HashMap红黑树
jdk1.8最重要的就是引入了红黑树的设计,当hash表的单一链表长度超过 8 个的时候,链表结构就会转为红黑树结构。
Iterator是什么
Iterator接口提供遍历任何Collection的接口。我们可以从一个Collection中使用迭代器方法来获取迭代
器实例。迭代器取代了Java集合框架中的Enumeration。迭代器允许调用者在迭代过程中移除元素。
器实例。迭代器取代了Java集合框架中的Enumeration。迭代器允许调用者在迭代过程中移除元素。
通过迭代器fail-fast属性,你明白了什么
每次我们尝试获取下一个元素的时候,Iterator fail-fast属性检查当前集合结构里的任何改动。如果发现
任何改动,它抛出ConcurrentModificationException。Collection中所有Iterator的实现都是按fail-fast
来设计的(ConcurrentHashMap和CopyOnWriteArrayList这类并发集合类除外)。
任何改动,它抛出ConcurrentModificationException。Collection中所有Iterator的实现都是按fail-fast
来设计的(ConcurrentHashMap和CopyOnWriteArrayList这类并发集合类除外)。
在迭代一个集合的时候,如何避免
ConcurrentModificationException
ConcurrentModificationException
在遍历一个集合的时候,我们可以使用并发集合类来避免ConcurrentModificationException,比如使
用CopyOnWriteArrayList,而不是ArrayList。
用CopyOnWriteArrayList,而不是ArrayList。
在Java中,HashMap是如何工作的
HashMap在Map.Entry静态内部类实现中存储key-value对。
HashMap使用哈希算法,在put和get方法中,它使用hashCode()和equals()方法。当我们通过传递key-value对调用put方法的时候,HashMap使 用Key hashCode()和哈希算法来找出存储key-value对的索引。
HashMap默认的初始容量是32,负荷系数是0.75。阀值是为负荷系数乘以容量,无论何时我们尝试添加一个entry,如果map的大小比阀值大的时候,HashMap会对map的内容进行重新哈希,且使用更大的容量。容量总是2的幂
如何决定选用HashMap还是TreeMap
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序
的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元
素会更快,将map换为TreeMap进行有序key的遍历。
的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元
素会更快,将map换为TreeMap进行有序key的遍历。
ArrayList和Vector有何异同点
相同点
两者都是基于索引的,内部由一个数组支持
两者维护插入的顺序,我们可以根据插入顺序来获取元素。
ArrayList和Vector的迭代器实现都是fail-fast的。
ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问
异同点
Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用
CopyOnWriteArrayList。
CopyOnWriteArrayList。
ArrayList比Vector快,它因为有同步,不会过载。
ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。
ArrayList和LinkedList有何区别
ArrayList是基于数组的,LinkedList是基于链表的。
对于ArrayList,它真正的优点是按下标查询元素,相比于LinkedList,LinkedList也可以按下标查询元素,但是LinkedList需要对底层链表进行遍历,才能找到指定下标的元素,而ArrayList不用,所以这是ArrayList的优点。
- ArrayList可以插入到指定下标位置,或者数组末尾,这种插入普通情况下是很快的,但是如果某次插入操作触发了扩容,那么本次插入就增加了额外的扩容成本。
- 对于LinkedList,如果是插在链表的头部或者是尾部都是很快的,因为LinkedList中有单独的属性记录的链表的头结点和尾结点,不过,如果是插在指定下标位置,那么就需要遍历链表找到指定位置,从而降低了效率。
并发集合类是什么
Java1.5并发包(java.util.concurrent)包含线程安全集合类,允许在迭代时修改集合。迭代器被设计为fail-fast的,会抛出ConcurrentModificationException。一部分类为:CopyOnWriteArrayList、ConcurrentHashMap、CopyOnWriteArraySet
BlockingQueue是什么
Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变
为非空;当在添加一个元素时,它会等待队列中的可用空间。
为非空;当在添加一个元素时,它会等待队列中的可用空间。
BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待
生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。
生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。
Collections类是什么
Java.util.Collections是一个工具类仅包含静态方法,它们操作或返回集合。它包含操作集合的多态算
法,返回一个由指定集合支持的新集合和其它一些内容。这个类包含集合框架算法的方法,比如折半搜
索、排序、混编和逆序等。
法,返回一个由指定集合支持的新集合和其它一些内容。这个类包含集合框架算法的方法,比如折半搜
索、排序、混编和逆序等。
我们如何对一组对象进行排序
如果我们需要对一个对象数组进行排序,我们可以使用Arrays.sort()方法。如果我们需要排序一个对象
列表,我们可以使用Collection.sort()方法。
列表,我们可以使用Collection.sort()方法。
当集合作为参数传递给一个函数时,如何才可以确保不能修改它
在作为参数传递之前,我们可以使用Collections.unmodifiableCollection(Collection c)方法创建一个只
读集合,这将确保改变集合的任何操作都会抛出UnsupportedOperationException。
读集合,这将确保改变集合的任何操作都会抛出UnsupportedOperationException。
我们如何从给定集合那里创建一个synchronized的集合
我们可以使用Collections.synchronizedCollection(Collection c)根据指定集合来获取一个
synchronized(线程安全的)集合。
synchronized(线程安全的)集合。
与Java集合框架相关的有哪些最好的实践
- 根据需要选择正确的集合类型。比如,如果指定了大小,我们会选用Array而非ArrayList。
- 如果我们想根据插入顺序遍历一个Map我们需要使用TreeMap。
- 如果我们不想重复,我们应该使用Set。
一些集合类允许指定初始容量,所以如果我们能够估计到存储元素的数量,我们可以使用它,就避免
了重新哈希或大小调整。
了重新哈希或大小调整。
尽可能使用Collections工具类,或者获取只读、同步或空的集合,而非编写自己的实现。它将会提
供代码重用性,它有着更好的稳定性和可维护性。
供代码重用性,它有着更好的稳定性和可维护性。
LinkedHashMap和HashMap的区别
HashMap
- HashMap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。遍历时,取得数据的顺序是完全随机的。
- HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null。
- HashMap不支持线程的同步(即任一时刻可以有多个线程同时写HashMap),可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
- Hashtable与 HashMap类似,它继承自Dictionary类。不同的是:Hashtable不允许记录的键或者值为空;它支持线程的同步(即任一时刻只有一个线程能写Hashtable),因此也导致了 Hashtable在写入时会比较慢。
LinkedHashMap
- 保存插入顺序:LinkedHashMap是HashMap的子类,但是内部还有一个双向链表维护键值对的顺序,每个键值对既位于哈希表中,也位于双向链表中。LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。也可以在构造时带参数,按照应用次数排序。
- 速度慢:在遍历的时候会比HashMap慢,不过有种情况例外:当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢。因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关
Java并发编程
简述线程、程序、进程的基本概念。以及他们之间关系是什么
进程
- 是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。
- 线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。
程序
是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
线程
线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与
进程不同的是同类的多个线程共享同一块内存空间和一组系统资源
进程不同的是同类的多个线程共享同一块内存空间和一组系统资源
线程的创建方式
继承Thread类,作为线程对象存在(继承Thread对象)
实现runnable接口,作为线程任务存在
匿名内部类创建线程对象
创建带返回值的线程
1. 实现Callable 接口,重写call方法
2. FutureTask<Integer> task = new FutureTask<Integer>(实现类);
3. new Thread(实现类).start()
4. task.get()
2. FutureTask<Integer> task = new FutureTask<Integer>(实现类);
3. new Thread(实现类).start()
4. task.get()
线程池创建线程
- ExecutorService threadPool = Executors.newFixedThreadPool(10);
- threadPool.execute(()-> System.out.println(222));
线程有哪些基本状态?
new:初始状态,线程被构建,但是还没有调用start方法
runnable:运行状态,java线程将操作系统中的就绪和运行两种状态笼称位“运行中”
blocked:阻塞状态:表示线程阻塞于锁
waiting:等待状态,表示线程进入等待状态
time_wating:超时等待状态
terminated:终止状态
线程的三种阻塞状态
等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线
程放入锁池中。
程放入锁池中。
其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻
塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状
态。(注意,sleep是不会释放持有的锁)
塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状
态。(注意,sleep是不会释放持有的锁)
Java中堆和栈有什么不同
栈:在函数中定义的基本类型的变量和对象的引用变量都是在函数的栈内存中分配。
堆:堆内存用于存放由new创建的对象和数组。
如何确保线程安全
对非安全的代码进行加锁控制
使用线程安全的类
多线程并发情况下,线程共享的变量改为方法级的局部变量
什么是线程调度器(Thread Scheduler)和时间分片(Time
Slicing)
Slicing)
线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦创建一个线程并启
动它,它的执行便依赖于线程调度器的实现
动它,它的执行便依赖于线程调度器的实现
时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级
或者线程等待的时间。
或者线程等待的时间。
volatile关键字的作用
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他
线程来说是立即可见的。
线程来说是立即可见的。
禁止进行指令重排序。
- volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
- synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可
见性和原子性。
见性和原子性。
从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,
什么是Java内存模型
Java内存模型定义了一种多线程访问Java内存的规范。
Java内存模型将内存分为了主内存和工作内存。
定义了几个原子操作,用于操作主内存和工作内存中的变量
定义了volatile变量的使用规则
sleep方法和wait方法有什么区别
sleep()方法是属于Thread类中的。而wait()方法,则是属于Object类中
的
的
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当
指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁
指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁
当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用
notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。
notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。
线程的sleep()方法和yield()方法有什么区别?
- sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会。
- yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
- 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
- sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
Thread.sleep(0)的作用是什么
java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让
某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配
时间片的操作,这也是平衡CPU控制权的一种操作
某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配
时间片的操作,这也是平衡CPU控制权的一种操作
线程类的构造方法、静态块是被哪个线程调用的
线程类的构造方法、静态块是被new这个线程类所在的线程
所调用的,而run方法里面的代码才是被线程自身所调用的。
所调用的,而run方法里面的代码才是被线程自身所调用的。
在线程中你怎么处理不可控制异常
非运行时异常(Checked Exception):这种异常必须在方法声明的throws语句指定,或者在方法体内
捕获。例如:IOException和ClassNotFoundException。
捕获。例如:IOException和ClassNotFoundException。
运行时异常:这种异常不必在方法声明中指定,也不需要在方法体中捕获。
例如,NumberFormatException
例如,NumberFormatException
线程中异常:实现用来处理运行时异常
的类,这个类实现UncaughtExceptionHandler接口并且实现这个接口的uncaughtException()方法
的类,这个类实现UncaughtExceptionHandler接口并且实现这个接口的uncaughtException()方法
有三个线程T1,T2,T3,如何保证顺序执行
在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线
程,另外一个线程完成该线程继续执行。
程,另外一个线程完成该线程继续执行。
什么是CAS
CAS,全称为Compare and Swap,即比较-替换。
内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会
将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合
将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合
CAS 有什么缺陷,如何解决
ABA 问题:并发环境下,假设初始条件是A,去修改数据时,发现是A就会执行修改。但是看到的虽然是A,中间可能发生了A变B,B又变回A的情况。此时A已经非彼A,数据即使成功修改,也可能有问题。
可以通过AtomicStampedReference「解决ABA问题」,它,一个带有标记的原子引用类,通过控制变量值的版本来保证CAS的正确性。
可以通过AtomicStampedReference「解决ABA问题」,它,一个带有标记的原子引用类,通过控制变量值的版本来保证CAS的正确性。
循环时间长开销
自旋CAS,如果一直循环执行,一直不成功,会给CPU带来非常大的执行开销。
很多时候,CAS思想体现,是有个自旋次数的,就是为了避开这个耗时问题
自旋CAS,如果一直循环执行,一直不成功,会给CPU带来非常大的执行开销。
很多时候,CAS思想体现,是有个自旋次数的,就是为了避开这个耗时问题
只能保证一个变量的原子操作
CAS 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性的。
可以通过这两个方式解决这个问题:
1. 使用互斥锁来保证原子性;
2. 将多个变量封装成对象,通过AtomicReference来保证原子性
CAS 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性的。
可以通过这两个方式解决这个问题:
1. 使用互斥锁来保证原子性;
2. 将多个变量封装成对象,通过AtomicReference来保证原子性
什么是AQS
AQS全称为AbstractQueuedSychronizer,翻译过来应该是抽象队列同步器。是一个双向链表。包括head节点和tail节点。head节点主要用作后续的调度。
如果说java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心了,
ReentrantLock、CountDownLatch、Semaphore等等都用到了它。
ReentrantLock、CountDownLatch、Semaphore等等都用到了它。
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
线程池作用
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系
统的稳定性,使用线程池可以进行统一的分配,调优和监控。
统的稳定性,使用线程池可以进行统一的分配,调优和监控。
ThreadLocal的实现原理
Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,即每个线程都有
一个属于自己的ThreadLocalMap。
一个属于自己的ThreadLocalMap。
ThreadLocalMap内部维护着Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本
身,value是ThreadLocal的泛型值。
身,value是ThreadLocal的泛型值。
每个线程在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个
ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
ThreadLocal 内存泄露问题
ThreadLocalMap中使用的 key 为 ThreadLocal 的弱引用ThreadLocalMap的key没了,value还在,这就会「造成了内存泄漏问题」。
使用完ThreadLocal后,及时调用remove()方法释放内存空间。
使用完ThreadLocal后,及时调用remove()方法释放内存空间。
notify()和notifyAll()有什么区别?
notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。
notify(): 唤醒一个正在等待该对象的线程。
notifyAll(): 唤醒所有正在等待该对象的线程
wait() 应配合while循环使用,不应使用if,务必在wait()调用前后都检查条件,如果不满足,必须调用
notify()唤醒另外的线程来处理,自己继续wait()直至条件满足再往下执行。
notify()唤醒另外的线程来处理,自己继续wait()直至条件满足再往下执行。
wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什
么区别
么区别
- wait()方法立即释放对象监视器
- notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。
锁的状态
无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态
偏向锁适用于只有一个线程访问同步块的场景。
轻量级锁的,竞争的线程不会阻塞,适用于持有锁的时间比较短。没有竞争到的线程会自旋来获取锁
自旋锁的优缺点
自旋锁不会引起调用者休眠,如果自旋锁已经被别的线程保持,调用者就一直循环在那里看是否该自旋
锁的保持者释放了锁。由于自旋锁不会引起调用者休眠,所以自旋锁的效率远高于互斥锁。
锁的保持者释放了锁。由于自旋锁不会引起调用者休眠,所以自旋锁的效率远高于互斥锁。
虽然自旋锁效率比互斥锁高,但它会存在下面两个问题
- 自旋锁一直占用CPU,在未获得锁的情况下,一直运行,如果不能在很短的时间内获得锁,会导致CPU效率降低。
- 试图递归地获得自旋锁会引起死锁。
在Java中Executor、ExecutorService、Executors的区别
ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口
Executor 接口定义了 execute()方法用来接收一个
Runnable接口的对象,而 ExecutorService 接口中的 submit()方法可以接受Runnable和Callable
接口的对象。
Runnable接口的对象,而 ExecutorService 接口中的 submit()方法可以接受Runnable和Callable
接口的对象。
Executor 和 ExecutorService 接口第四个区别是除了允许客户端提交一个任务,ExecutorService
还提供用来控制线程池的方法。比如:调用 shutDown() 方法终止线程池。
还提供用来控制线程池的方法。比如:调用 shutDown() 方法终止线程池。
Executors 类提供工厂方法用来创建不同类型的线程池。
newFixedThreadPool(int nThreads)(创建固定大小的线程池)
newSingleThreadExecutor()(创建只有一个线程的线程池)
newCachedThreadPool()(创建一个不限线程数上限的线程池,任何提交的任务都将立即执行)
不使用stop停止线程
你可以用volatile 布尔
变量来退出run()方法的循环或者是取消任务来中断线程
变量来退出run()方法的循环或者是取消任务来中断线程
高并发、任务执行时间短的业务怎样使用线程池?
并发不高、任务执行时间长的业务怎样使用线程池?
并发不高、任务执行时间长的业务怎样使用线程池?
高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
- 假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务
- 假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,线程池中的线程数设置得少一些,减少线程上下文的切换
synchronized 关键字的了解
synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰
的方法或者代码块在任意时刻只能有一个线程执行。
的方法或者代码块在任意时刻只能有一个线程执行。
synchronized关键字最主要的三种使用方式
修饰静态方法、修饰静态方法、修饰代码块
synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。
synchronized 关键字加到实例方法上是给对象实例上锁。
synchronized 关键字加到实例方法上是给对象实例上锁。
获取到线程dump文件
1. 获取到线程的pid,可以通过使用jps命令,在Linux环境下还可以使用ps -ef | grep java
2. 打印线程堆栈,可以通过使用jstack pid命令,在Linux环境下还可以使用kill -3 pid
2. 打印线程堆栈,可以通过使用jstack pid命令,在Linux环境下还可以使用kill -3 pid
synchronized 关键字的底层原理
synchronized 同步语句块:实现使用的是 monitorenter 和 monitorexit 指令,其中monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
ReadWriteLock是什么
ReentrantLock某些时候有局限读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降
低了程序的性能。因为这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,
ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,
写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。
低了程序的性能。因为这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,
ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,
写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。
什么是乐观锁和悲观锁
乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总
是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,
如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,
如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总
是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十
一,直接上了锁就操作资源了。
是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十
一,直接上了锁就操作资源了。
Linux环境下如何查找哪个线程使用CPU最长
获取项目的pid,jps或者ps -ef | grep java
top -H -p pid,顺序不能改变
JVM面试题
什么是Java虚拟机
Java虚拟机是一个可以执行Java字节码的虚拟机进程。
Java虚拟机让这应用程序可以运行在任意的
平台,而不需要程序员为每一个平台单独重写或者是重新编译。变为可能
平台,而不需要程序员为每一个平台单独重写或者是重新编译。变为可能
Java内存结构
方法区和堆是所有线程共享的内存区域;而java栈、本地方法栈和程序员计数器是运行是线程私有的内存区域。
1. Java堆(Heap):是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区
域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分
配内存。
域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分
配内存。
2. 方法区:与Java堆一样,是各个线程共享的内存区域,它
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
3. java虚拟机栈:与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程
私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时
候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信
息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时
候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信
息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
4. 本地方法栈:,本地方法栈(Native Method Stacks)与虚拟机栈所发挥的
作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法
栈则是为虚拟机使用到的Native方法服务。
作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法
栈则是为虚拟机使用到的Native方法服务。
5. 程序计数器:是一块较小的内存空间,它可以看作当前线程所执行的字节码的行号指示器。
Java 堆的结构是什么样子的
- JVM 的堆是运行时数据区, 所有类的实例和数组都是在堆上分配内存。
- 它在 JVM 启动的时候被创建。 对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。
- 堆内存是由存活和死亡的对象组成的。 存活的对象是应用可以访问的, 不会被垃圾回收。 死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。
jdk1.8之: 堆分为: 新生代,老年代和元空间, 新生代有分为 伊甸园区(Eden),S0区(Survivor)和s1区(Survivor)
堆空间的大小设置
-Xms 堆空间的起始内存。 -Xmx 堆空间的最大内存
为什么要有新生代和老年代
- 分代的目的:优化GC的性能,对象的生命周期不同,GC回收时间不同,甚至有对象一直不会被GC,而有的对象"朝生夕死"
- 若不分代–>GC需要扫描整个堆空间,分代之后–>对具体某一区域进行适合的GC
- 不同代根据其特点进行不同的垃圾回收算法–>提高回收效率(分代收集算法)
- 新生代与老年代空间默认比例1:2 新生代占整个堆的1/3
survivor存在意义:
增加进入老年代的筛选条件,减少送到老年代的对象,减少FullGC的次数。也就减少stop the world 用户线程暂停时间,提高用户线程效率
为什么要设置两个survivor区
只有一个survivor区,在第一次Eden区满进行MinorGC,存活对象放到survivor区;第二次Eden区满MinorGC–>survivor区,会产生不连续的内存,无法存放更多的对象。
堆中的对象分配过程
- new的对象先放Eden区,放得下直接放入(此区有大小限制 参数-Xmn 一般默认)
- 当创建新对象,Eden空间填满,会触发一次Minor GC/YGC,将Eden不再被其他对象引用的对象进行销毁。将Eden中未销毁的对象移到survive0区。survive0区需要销毁的也会被销毁,每个对象都有一个年龄计数器年龄还存活的对象年龄计数加1
- 如果Eden有空间,加载的新对象放到Eden区(超大对象放不下直接入老年代)
- 再次eden区满,触发垃圾回收,回收eden+survive0(from),幸存下来的放在survive1(to)区
- 再垃圾回收,又会将幸存者重新放回survive0区,依次类推,当分代年龄等于15的 对象进老年区(默认值是15 , 可以通过参数 -XX:MaxTenuringThreshold=N进行设置)
- 老年代满或放不下,触发majorGC,再放不下,OOM
- 特别注意: 在Eden区满了的时候,才会触发MinorGC,而幸存者区满了后,不会触发MinorGC操作
MinorGC的触发条件
Eden区满,触发MinorGC,Survivor区满不触发GC。每次MinorGC会清理年轻代(eden+survivor)的内存,因为Java对象大多朝生夕死,所以MinorGC非常频繁,MinorGC会引发STW(stop the world 用户线程暂停)
老年代GC(MajorGC/Full GC)触发条件
老年代空间不足,会触发MinorGC,空间还不足,触发MajorGC/Full GC。还不足,OOM;
FullGC的触发机制
- 调用System.gc()时,系统建议执行FullGC,但是不必然执行
- 老年代空间不足
- 方法区空间不足
- 通过MinorGC后进入老年代的平均内存大小,大于老年代的可用内存
Java 中堆和栈有什么区别
栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。
栈:在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配
堆:堆内存用来存放由 new 创建的对象和数组,在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管
理。在堆中产生了一个数组或者对象之后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取
值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量
理。在堆中产生了一个数组或者对象之后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取
值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量
JVM内存分哪几个区,每个区的作用是什么?
堆:Java堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作 。
方法区
1. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类 型的卸载
2. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。
3. 该区域是被线程共享的。
4. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编 译时确定,运行时生成的常量也会存在这个常量池中。
1. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类 型的卸载
2. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。
3. 该区域是被线程共享的。
4. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编 译时确定,运行时生成的常量也会存在这个常量池中。
虚拟机栈:
1. 虚拟机栈也就是我们平常所称的栈内存,它为java方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操 作数栈、动态链接和方法出口等信息。
2. 虚拟机栈是线程私有的,它的生命周期与线程相同。
3. 局部变量表里存储的是基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指 向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定
4. 操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式
5. 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。
1. 虚拟机栈也就是我们平常所称的栈内存,它为java方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操 作数栈、动态链接和方法出口等信息。
2. 虚拟机栈是线程私有的,它的生命周期与线程相同。
3. 局部变量表里存储的是基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指 向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定
4. 操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式
5. 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。
本地方法栈:本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务。
程序计数器
内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。
内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。
怎么获取 Java 程序使用的内存?
可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。
- Runtime.freeMemory() 方法返回剩余空间的字节数
- Runtime.totalMemory()方法总内存的字节数
- Runtime.maxMemory() 返回最大内存的字节数
Java类加载过程
加载
加载是类加载的第一个过程,在这个阶段,将完成一下三件事情:
加载是类加载的第一个过程,在这个阶段,将完成一下三件事情:
- 通过一个类的全限定名获取该类的二进制流。
- 将该二进制流中的静态存储结构转化为方法区运行时数据结构。
- 在内存中生成代表该类的 Class 对象,作为方法区这个类的各种数据的访问入口
链接
验证
验证是连接阶段的第一步,这一阶段的目的是确保class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束,保证这些信息被当作代码运行后不会危害虚拟机。文件格式、元数据、字节码、符号引用验证;
准备
准备阶段是正式为类中定义的变量(即静态变量、被static修饰的变量)分配内存并设置类变量初始值的阶段。准备
阶段不分配类中的实例变量的 内存,实例变量将会在对象实例化时随着对象一起分配在 Java 堆 中。
阶段不分配类中的实例变量的 内存,实例变量将会在对象实例化时随着对象一起分配在 Java 堆 中。
解析
该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一 定在初始化动作完成之前,也有可能
在初始化之后。
在初始化之后。
初始化
初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载
器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java 程
序代码。
器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java 程
序代码。
使用
卸载
JVM中对象的创建过程
1. 拿到内存创建指令
当javax虚拟机遇到一条字节码new指令时,首先检查这个指令的参数是否能在常量池中(方法区中)定位到一个类的的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化过,如果没有那必须先执行类加载过程。,否则直接准备为新的对象分配内存
当javax虚拟机遇到一条字节码new指令时,首先检查这个指令的参数是否能在常量池中(方法区中)定位到一个类的的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化过,如果没有那必须先执行类加载过程。,否则直接准备为新的对象分配内存
2. 分配内存
虚拟机为对象分配内存(堆)分配内存分为指针碰撞和空闲列表两种方式;分配内存还要要保证并发安全,有两种方式。
虚拟机为对象分配内存(堆)分配内存分为指针碰撞和空闲列表两种方式;分配内存还要要保证并发安全,有两种方式。
3. 初始化
分配完内存后要对对象的头(Object Header)进行初始化,这新信息包括:该对象对应类的元数据、该对象的GC代、对象的哈希码。
抽象数据类型默认初始化为null,基本数据类型为0,布尔为false....
分配完内存后要对对象的头(Object Header)进行初始化,这新信息包括:该对象对应类的元数据、该对象的GC代、对象的哈希码。
抽象数据类型默认初始化为null,基本数据类型为0,布尔为false....
4. 调用对象的初始化方法 也就是执行构造方法。
Java对象结构
Java对象由三个部分组成:对象头、实例数据、对齐填充。
对象头
对象头由两部分组成,第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID,官方称它为"Mark word"
第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象头中还有一部分用来记录数组长度。
对象头由两部分组成,第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID,官方称它为"Mark word"
第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象头中还有一部分用来记录数组长度。
实例数据:实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)
对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)
如何判断对象可以被回收
引用计数法
在JDK1.2之前,使用的是引用计数器算法。在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值就+1,当引用失效的时
候,计数器的值就-1,当引用计数器被减为零的时候,标志着这个对象已经没有引用了。如果两个类相互引用,这种算法就会有问题
在JDK1.2之前,使用的是引用计数器算法。在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值就+1,当引用失效的时
候,计数器的值就-1,当引用计数器被减为零的时候,标志着这个对象已经没有引用了。如果两个类相互引用,这种算法就会有问题
可达性分析法
通过一系列的称为 GC Roots 的对象作为起点, 然后向下搜索; 搜索所走过的路径称为引用链/Reference Chain, 当一个对象到 GC Roots 没有任何引用链相连时, 即该对象不可达, 也就说明此对象是不可用的,
通过一系列的称为 GC Roots 的对象作为起点, 然后向下搜索; 搜索所走过的路径称为引用链/Reference Chain, 当一个对象到 GC Roots 没有任何引用链相连时, 即该对象不可达, 也就说明此对象是不可用的,
Java的四种引用类型
java为引用类型专门定义了一个类叫做Reference。Reference是跟java垃圾回收机制息息相关的类,通过探讨Reference的实现可以更加深入的理解java的垃圾回收是怎么工作的。
强引用
java中的引用默认就是强引用,任何一个对象的赋值操作就产生了对这个对象的强引用。
强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收。
java中的引用默认就是强引用,任何一个对象的赋值操作就产生了对这个对象的强引用。
强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收。
软引用Soft Reference
软引用在java中有个专门的SoftReference类型,软引用的意思是只有在内存不足的情况下,被引用的对象才会被回收。
软引用在java中有个专门的SoftReference类型,软引用的意思是只有在内存不足的情况下,被引用的对象才会被回收。
弱引用weak Reference
weakReference和softReference很类似,不同的是weekReference引用的对象只要垃圾回收执行,就会被回收,而不管是否内存不足。
weakReference和softReference很类似,不同的是weekReference引用的对象只要垃圾回收执行,就会被回收,而不管是否内存不足。
虚引用PhantomReference
PhantomReference的作用是跟踪垃圾回收器收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理,当程序员调用ReferenceQueue.pull()方法,将引用出ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了。
PhantomReference的作用是跟踪垃圾回收器收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理,当程序员调用ReferenceQueue.pull()方法,将引用出ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了。
什么情况下会发生栈溢出
栈的大小可以通过-Xss参数进行设置,当递归层次太深的时候,就会发生栈溢出。比如循环调用,递归
等。
等。
GC是什么?为什么要有GC
GC是垃圾收集的意思,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存
的目的,Java语言没有提供释放已分配内存的显示操作方法。
的目的,Java语言没有提供释放已分配内存的显示操作方法。
简述 Java 垃圾回收机制。
在 Java 中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在 JVM 中,有
一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存
不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收
一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存
不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收
JVM 的永久代中会发生垃圾回收么?
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。
注:Java 8 中已经移除了永久代,新加了一个叫做元数据区的native 内存区。
注:Java 8 中已经移除了永久代,新加了一个叫做元数据区的native 内存区。
什么是分布式垃圾回收(DGC)?它是如何工作的
DGC 叫做分布式垃圾回收。RMI (Remote Method Invocation)使用 DGC 来做自动垃圾回收。因为 RMI 包含了跨虚拟机的远程对象的
引用,垃圾回收是很困难的。DGC 使用引用计数算法来给远程对象提供自动内存管理。
引用,垃圾回收是很困难的。DGC 使用引用计数算法来给远程对象提供自动内存管理。
JVM垃圾处理方法
标记-清除算法
该算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回收的对象(可达性分析), 在标记完成后统一清理掉所有被标记的对象
该算法主要有两个缺点:
1. 执行效率不稳定,当含有大量对象时候,导致标记和清除两个过程的执行效率都随对象数量增长而降低。
2. 标记清除后会产生大量不连续的内存碎片, 空间碎片太多可能会导致在运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集。
该算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回收的对象(可达性分析), 在标记完成后统一清理掉所有被标记的对象
该算法主要有两个缺点:
1. 执行效率不稳定,当含有大量对象时候,导致标记和清除两个过程的执行效率都随对象数量增长而降低。
2. 标记清除后会产生大量不连续的内存碎片, 空间碎片太多可能会导致在运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集。
标记复制算法
该算法的核心是将可用内存按容量划分为大小相等的两块, 每次只用其中一块, 当这一块的内存用完, 就将还存活的对象(非垃圾)复制到另外一块上面, 然后把已使用过的内存空间一次清理掉。
优点:不用考虑碎片问题,方法简单高效。
缺点:内存浪费严重
现代商用VM的新生代均采用复制算法,但由于新生代中的98%的对象都是生存周期极短的,因此并不需完全按照1∶1的比例划分新生代空间,而是将新生代划分为一块较大的Eden区和两块较小的Survivor区
(HotSpot默认Eden和Survivor的大小比例为8∶1), 每次只用Eden和其中一块Survivor。
该算法的核心是将可用内存按容量划分为大小相等的两块, 每次只用其中一块, 当这一块的内存用完, 就将还存活的对象(非垃圾)复制到另外一块上面, 然后把已使用过的内存空间一次清理掉。
优点:不用考虑碎片问题,方法简单高效。
缺点:内存浪费严重
现代商用VM的新生代均采用复制算法,但由于新生代中的98%的对象都是生存周期极短的,因此并不需完全按照1∶1的比例划分新生代空间,而是将新生代划分为一块较大的Eden区和两块较小的Survivor区
(HotSpot默认Eden和Survivor的大小比例为8∶1), 每次只用Eden和其中一块Survivor。
标记-整理算法(老年代)
标记清除算法会产生内存碎片问题, 而复制算法需要有额外的内存担保空间, 于是针对老年代的特点, 又有了标记整理算法. 标记整理算法的标记过程与标记清除算法相同, 但后续步骤不再对可回收对象直接清理,而是让所有存活的对象都向一端移动,然后清理掉端边界以外的内存
标记清除算法会产生内存碎片问题, 而复制算法需要有额外的内存担保空间, 于是针对老年代的特点, 又有了标记整理算法. 标记整理算法的标记过程与标记清除算法相同, 但后续步骤不再对可回收对象直接清理,而是让所有存活的对象都向一端移动,然后清理掉端边界以外的内存
分代收集算法
把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
垃圾收集器
Serial 收集器
Serial收集器是Hotspot运行在Client模式下的默认新生代收集器, 它在进行垃圾收集时,会暂停所有的工作进程,用一个线程去完成GC工作。Serial 收集器对于运行在客户端模式下的虚拟机来说是一个很好的选择
特点:简单高效,适合jvm管理内存不大的情况(十兆到百兆)。
Serial收集器是Hotspot运行在Client模式下的默认新生代收集器, 它在进行垃圾收集时,会暂停所有的工作进程,用一个线程去完成GC工作。Serial 收集器对于运行在客户端模式下的虚拟机来说是一个很好的选择
特点:简单高效,适合jvm管理内存不大的情况(十兆到百兆)。
Parnew 收集器
ParNew收集器其实是Serial的多线程版本,回收策略完全一样。
ParNew收集器其实是Serial的多线程版本,回收策略完全一样。
CMS收集器
又称多并发低暂停的收集器。由他的英文组成可以看出,它是基于标记-清除算法实现的。整个过程分4个步骤:
1. 初始标记(CMS initial mark):仅只标记一下GC Roots能直接关联到的对象, 速度很快(这个过程需要停顿用户线程)
2. 并发标记(CMS concurrent mark: GC Roots Tracing过程)遍历整个对象图的过程
3. 重新标记(CMS remark):修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(这个过程需要停顿用户线程)
4. 并发清除(CMS concurrent sweep: 已死对象将会就地释放)
又称多并发低暂停的收集器。由他的英文组成可以看出,它是基于标记-清除算法实现的。整个过程分4个步骤:
1. 初始标记(CMS initial mark):仅只标记一下GC Roots能直接关联到的对象, 速度很快(这个过程需要停顿用户线程)
2. 并发标记(CMS concurrent mark: GC Roots Tracing过程)遍历整个对象图的过程
3. 重新标记(CMS remark):修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(这个过程需要停顿用户线程)
4. 并发清除(CMS concurrent sweep: 已死对象将会就地释放)
优点:并发收集、低停顿
缺点
1. CMS默认启动的回收线程数=(CPU数目+3)*4当CPU数>4时, GC线程最多占用不超过25%的CPU资源, 但是当CPU数<=4时, GC线程可能就会过多的占用用户CPU资源, 从而导致应用程序变慢, 总吞吐量降低.
2. 无法清除浮动垃圾(GC运行到并发清除阶段时用户线程产生的垃圾),因为用户线程是需要内存的,如果浮动垃圾施放不及时,很可能就造成内存溢出,所以CMS不能像别的垃圾收集器那样等老年代几乎满了才触发,CMS提供了参数 -XX:CMSInitiatingOccupancyFraction 来设置GC触发百分比(1.6后默认92%),当然我们还得设置启用该策略 -XX:+UseCMSInitiatingOccupancyOnly
3. 因为CMS采用标记-清除算法,所以可能会带来很多的碎片,如果碎片太多没有清理,jvm会因为无法分配大对象内存而触发GC,因此CMS提供了 -XX:+UseCMSCompactAtFullCollection 参数,它会在GC执行完后接着进行碎片整理,但是又会有个问题,碎片整理不能并发,所以必须单线程去处理,所以如果每次GC完都整理用户线程stop的时间累积会很长,所以XX:CMSFullGCsBeforeCompaction 参数设置隔几次GC进行一次碎片整理(默认为0)。
1. CMS默认启动的回收线程数=(CPU数目+3)*4当CPU数>4时, GC线程最多占用不超过25%的CPU资源, 但是当CPU数<=4时, GC线程可能就会过多的占用用户CPU资源, 从而导致应用程序变慢, 总吞吐量降低.
2. 无法清除浮动垃圾(GC运行到并发清除阶段时用户线程产生的垃圾),因为用户线程是需要内存的,如果浮动垃圾施放不及时,很可能就造成内存溢出,所以CMS不能像别的垃圾收集器那样等老年代几乎满了才触发,CMS提供了参数 -XX:CMSInitiatingOccupancyFraction 来设置GC触发百分比(1.6后默认92%),当然我们还得设置启用该策略 -XX:+UseCMSInitiatingOccupancyOnly
3. 因为CMS采用标记-清除算法,所以可能会带来很多的碎片,如果碎片太多没有清理,jvm会因为无法分配大对象内存而触发GC,因此CMS提供了 -XX:+UseCMSCompactAtFullCollection 参数,它会在GC执行完后接着进行碎片整理,但是又会有个问题,碎片整理不能并发,所以必须单线程去处理,所以如果每次GC完都整理用户线程stop的时间累积会很长,所以XX:CMSFullGCsBeforeCompaction 参数设置隔几次GC进行一次碎片整理(默认为0)。
G1收集器
Garbage First(简称G1)同优秀的CMS垃圾回收器一样,G1也是关注最小时延的垃圾回收器,也同样适合大尺寸堆内存的垃圾收集,官方也推荐使用G1来代替选择CMS。G1最大的特点是引入分区的思路,弱化分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷。
因为每个区都有E、S、O代,所以在G1中,不需要对整个Eden等代进行回收,而是寻找可回收对象比较多的区,然后进行回收(虽然也需要STW操作,但是花费的时间是很少的),保证高效率
Garbage First(简称G1)同优秀的CMS垃圾回收器一样,G1也是关注最小时延的垃圾回收器,也同样适合大尺寸堆内存的垃圾收集,官方也推荐使用G1来代替选择CMS。G1最大的特点是引入分区的思路,弱化分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷。
因为每个区都有E、S、O代,所以在G1中,不需要对整个Eden等代进行回收,而是寻找可回收对象比较多的区,然后进行回收(虽然也需要STW操作,但是花费的时间是很少的),保证高效率
简单描述一下(分代)垃圾回收的过程
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是2/3。
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:
当年轻代中的Eden区分配满的时候,就会触发年轻代的GC(Minor GC)。具体过程如下
1. 在Eden区执行了第一次GC之后,存活的对象会被移动到其中一个Survivor分区(以下简称from)
2. Eden区再次GC,这时会采用复制算法,将Eden和from区一起清理。存活的对象会被复制到to区。接下来,只需要清空from区就可以了
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:
当年轻代中的Eden区分配满的时候,就会触发年轻代的GC(Minor GC)。具体过程如下
1. 在Eden区执行了第一次GC之后,存活的对象会被移动到其中一个Survivor分区(以下简称from)
2. Eden区再次GC,这时会采用复制算法,将Eden和from区一起清理。存活的对象会被复制到to区。接下来,只需要清空from区就可以了
你都用过G1垃圾回收器的哪几个重要参数
1. 最重要的是MaxGCPauseMillis,可以通过它设定G1的目标停顿时间,它会尽量的去达成这个目标。
2. G1HeapRegionSize可以设置小堆区的大小,一般是2的次幂。
3. InitiatingHeapOccupancyPercent,启动并发GC时的堆内存占用百分比。
2. G1HeapRegionSize可以设置小堆区的大小,一般是2的次幂。
3. InitiatingHeapOccupancyPercent,启动并发GC时的堆内存占用百分比。
MinorGC,MajorGC、FullGC都什么时候发生?
当年轻代满时就会触发Minor GC,这里的年轻代满指的是Eden代满,Survivor满不会引发GC。
Full GC触发机制:
1. 调用System.gc时,系统建议执行Full GC,但是不必然执行
2. 老年代空间不足
3. 方法区空间不足
4. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
5. 堆中分配很大的对象,而老年代没有足够的空间
2. 老年代空间不足
3. 方法区空间不足
4. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
5. 堆中分配很大的对象,而老年代没有足够的空间
新生代、老年代、持久代都存储哪些东西
新生代:方法中new一个对象,就会先进入新生代。
老年代:
1. 新生代中经历了N次垃圾回收仍然存活的对象就会被放到老年代中。
2. 大对象一般直接放入老年代。
3. 当Survivor空间不足。需要老年代担保一些空间,也会将对象放入老年代
1. 新生代中经历了N次垃圾回收仍然存活的对象就会被放到老年代中。
2. 大对象一般直接放入老年代。
3. 当Survivor空间不足。需要老年代担保一些空间,也会将对象放入老年代
永久代:指的是方法区
对象是怎么从年轻代进入老年代的
1. 如果对象够老,会通过提升(Promotion)进入老年代,这一般是根据对象的年龄进行判断的。
2. 动态对象年龄判定。有的垃圾回收算法,比如G1,并不要求age必须达到15才能晋升到老年代,它会使用一些动态的计算方法。
3. 分配担保。当 Survivor 空间不够的时候,就需要依赖其他内存(指老年代)进行分配担保。这个时候,对象也会直接在老年代上分配。
4. 超出某个大小的对象将直接在老年代分配。不过这个值默认为0,意思是全部首选Eden区进行分配。
2. 动态对象年龄判定。有的垃圾回收算法,比如G1,并不要求age必须达到15才能晋升到老年代,它会使用一些动态的计算方法。
3. 分配担保。当 Survivor 空间不够的时候,就需要依赖其他内存(指老年代)进行分配担保。这个时候,对象也会直接在老年代上分配。
4. 超出某个大小的对象将直接在老年代分配。不过这个值默认为0,意思是全部首选Eden区进行分配。
可达性算法中,哪些对象可作为GC Roots对象。
1. 虚拟机栈中引用的对象
2. 方法区静态成员引用的对象
3. 方法区常量引用对象
4. 本地方法栈JNI引用的对象
2. 方法区静态成员引用的对象
3. 方法区常量引用对象
4. 本地方法栈JNI引用的对象
垃圾回收器的基本原理是什么?有什么办法主动通知虚拟机进行垃圾回收?
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。
程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。
调优命令有哪些
Sun JDK监控和故障处理命令有jps 、jstat 、jmap 、jhat、 jstack、 jinfo
jps,JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。
jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
jmap,JVM Memory Map命令用于生成heap dump文件
jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看
jstack,用于生成java虚拟机当前时刻的线程快照。
jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数。
DTrace:性能分析工具
你知道哪些JVM性能调优
1. -Xms4g:初始化堆内存大小为4GB。
2. -Xmx4g:堆内存最大值为4GB。
3. -Xmn1200m:设置年轻代大小为1200MB。增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
4. -Xss512k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1MB,以前每个线程堆栈大小为256K。应根据应用线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成
5. -XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5 默认是2 (年轻代占整个堆栈的1/3)
6. -XX:SurvivorRatio=8 设置年轻代中Eden区与Survivor区的大小比值。设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
7. -XX:MaxTenuringThreshold=15:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
8. -XX:MaxDirectMemorySize=1G:直接内存。报java.lang.OutOfMemoryError: Direct buffer memory异常可以上调这个值。
9. -XX:ConcGCThreads=4:CMS垃圾回收器并行线程线,推荐值为CPU核心数。
10. -XX:ParallelGCThreads=8:新生代并行收集器的线程数。
2. -Xmx4g:堆内存最大值为4GB。
3. -Xmn1200m:设置年轻代大小为1200MB。增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
4. -Xss512k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1MB,以前每个线程堆栈大小为256K。应根据应用线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成
5. -XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5 默认是2 (年轻代占整个堆栈的1/3)
6. -XX:SurvivorRatio=8 设置年轻代中Eden区与Survivor区的大小比值。设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
7. -XX:MaxTenuringThreshold=15:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
8. -XX:MaxDirectMemorySize=1G:直接内存。报java.lang.OutOfMemoryError: Direct buffer memory异常可以上调这个值。
9. -XX:ConcGCThreads=4:CMS垃圾回收器并行线程线,推荐值为CPU核心数。
10. -XX:ParallelGCThreads=8:新生代并行收集器的线程数。
常用的调优工具有哪些
1、jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控
2、jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。
3、MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Javaheap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
4、GChisto,一款专业分析gc日志的工具
2、jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。
3、MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Javaheap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
4、GChisto,一款专业分析gc日志的工具
OOM你遇到过哪些情况,SOF你遇到过哪些情况
OutOfMemoryError异常
除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可
能。
能。
java堆用于存储对象实例,我们只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免
垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。
垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。
出现这种异常,一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
如果是内存泄漏,可进一步通过工具查看泄漏对象到GCRoots的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。
如果不存在泄漏,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。
如果是内存泄漏,可进一步通过工具查看泄漏对象到GCRoots的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。
如果不存在泄漏,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。
运行时常量池溢出(java.lang.OutOfMemoryError:PermGenspace)
由于常量池分配在方法区
内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容
量。
内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容
量。
方法区溢出(java.lang.OutOfMemoryError:PermGenspace)
方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。也有可能是
方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。
方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。
SOF(堆栈溢出StackOverflow)
StackOverflowError 的定义:当应用程序递归太深而发生堆栈溢出时,抛出该错误。
栈溢出的原因:递归调用,大量循环或死循环,全局变量是否过多,数组、List、map数据过大。
finalize() 方法什么时候被调用
它最主要的用途是回收特殊渠道申请的内存。Java 程序有垃圾回收器,所以一般情况下内存问题不用程
序员操心。但有一种 JNI(Java Native Interface)调用non-Java 程序(C 或 C++), finalize() 的工作
就是回收这部分的内存。
序员操心。但有一种 JNI(Java Native Interface)调用non-Java 程序(C 或 C++), finalize() 的工作
就是回收这部分的内存。
你都有哪些手段用来排查内存溢出?
内存溢出包含很多种情况,我在平常工作中遇到最多的就是堆溢出。有一次线上遇到故障,重新启动
后,使用jstat命令,发现Old区在一直增长。我使用jmap命令,导出了一份线上堆栈,然后使用MAT进
行分析。通过对GC Roots的分析,我发现了一个非常大的HashMap对象,这个原本是有位同学做缓存
用的,但是一个无界缓存,造成了堆内存占用一直上升。后来,将这个缓存改成 guava的Cache,并设
置了弱引用,故障就消失了。
后,使用jstat命令,发现Old区在一直增长。我使用jmap命令,导出了一份线上堆栈,然后使用MAT进
行分析。通过对GC Roots的分析,我发现了一个非常大的HashMap对象,这个原本是有位同学做缓存
用的,但是一个无界缓存,造成了堆内存占用一直上升。后来,将这个缓存改成 guava的Cache,并设
置了弱引用,故障就消失了。
生产上如何配置垃圾收集器的
假如生产环境CPU占用过高,请谈谈你的分析思路和定位。
首先,使用top -H命令获取占用CPU最高的线程,并将它转化为16进制。
然后,使用jstack命令获取应用的栈信息,搜索这个16进制。这样能够方便的找到引起CPU占用过高的具体原因。
如果有条件的话,直接使用arthas就行操作就好了,不用再做这些费事费力的操作。
然后,使用jstack命令获取应用的栈信息,搜索这个16进制。这样能够方便的找到引起CPU占用过高的具体原因。
如果有条件的话,直接使用arthas就行操作就好了,不用再做这些费事费力的操作。
对于JDK自带的监控和性能分析工具用过哪些
jps:用来显示Java进程
jstat:用来查看GC;
jmap:用来dump堆;
jstack:用来dump栈;
jhsdb:用来查看执行中的内存信息;
栈帧都有哪些数据
栈帧包含:局部变量表、操作数栈、动态连接、返回地址等。
JIT是什么
为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并
进行各种层次的优化。完成这个任务的编译器,就称为即时编译器(Just In Time Compiler),简称 JIT
编译器。
进行各种层次的优化。完成这个任务的编译器,就称为即时编译器(Just In Time Compiler),简称 JIT
编译器。
Java的双亲委托机制是什么
概念
比如一个HelloWorld.class这样的文件要被加载时。不考虑我们自定义的类加载器,首先会在Application ClassLoader中检查是否加载过,如果有那就无需再加载。如果没有,那么会拿到父级加载器,然后调用父级类加载器的loadClass()方法。父加载器中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达BootstrapClassLoader之前,都是在检查是否加载过,并不会选择自己去加载。知道BootstrapClassLoader,已经没有父级类加载器了,这时候开始考虑自己是否能加载,如果自己无法加载,会下沉到子级类加载器去加载,一直到最底层,如果没有任何类加载器能加载,就会抛出ClassNotFoundException异常
作用
防止重复加载同一个.class:通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全
保证核心的.class不能被篡改:
网络协议面试题
网络协议是什么
在计算机网络要做到井井有条的交换数据,就必须遵守一些事先约定好的规则,比如交换数据的格式、是否需要发送一个应答信息。这些规则被称为网络协议。
TCP/IP网络结构
应用层
应用层最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服
务。我们常见应用层的网络服务协议有:HTTP,HTTPS,FTP,TELNET等。
务。我们常见应用层的网络服务协议有:HTTP,HTTPS,FTP,TELNET等。
传输层
建立了主机端到端的链接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输
服务,包括处理差错控制和流量控制等问题。我们通常说的,TCP UDP就是在这一层。端口号既是这里的“端”。
服务,包括处理差错控制和流量控制等问题。我们通常说的,TCP UDP就是在这一层。端口号既是这里的“端”。
网络层
网络层 本层通过IP寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由
和交换节点,正确无误地按照地址传送给目的端的运输层。就是通常说的IP层。
和交换节点,正确无误地按照地址传送给目的端的运输层。就是通常说的IP层。
数据链路层
通过一些规程或协议来控制这些数据的传输,以保证被传输数据的正确性。实现这些规
程或协议的 硬件 和软件加到物理线路,这样就构成了数据链路
程或协议的 硬件 和软件加到物理线路,这样就构成了数据链路
什么是TCP/IP和UDP
TCP/IP
传输控制/网络协议,是面向连接的协议,发送数据前要先建立连接(发送方和接收方的成对
的两个之间必须建 立连接),TCP提供可靠的服务,也就是说,通过TCP连接传输的数据不会丢失,没有
重复,并且按顺序到达
的两个之间必须建 立连接),TCP提供可靠的服务,也就是说,通过TCP连接传输的数据不会丢失,没有
重复,并且按顺序到达
UDP
UDP它是属于TCP/IP协议族中的一种。是无连接的协议,发送数据前不需要建立连接,是没有可靠
性的协议。因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
性的协议。因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
TCP与UDP区别
TCP是面向连接的协议,发送数据前要先建立连接,TCP提供可靠的服务,也就是说,通过TCP连接
传输的数据不会丢失,没有重复,并且按顺序到达;
传输的数据不会丢失,没有重复,并且按顺序到达;
UDP是无连接的协议,发送数据前不需要建立连接,是没有可靠性;
UDP通信类似于学校广播,靠着广播播报直接进行通信。
UDP通信类似于学校广播,靠着广播播报直接进行通信。
TCP只支持点对点通信,UDP支持一对一、一对多、多对一、多对多;
TCP是面向字节流的,UDP是面向报文的; 面向字节流是指发送数据时以字节为单位,一个数据包
可以拆分成若干组进行发送,而UDP一个报文只能一次发完。
可以拆分成若干组进行发送,而UDP一个报文只能一次发完。
TCP首部开销(20字节)比UDP首部开销(8字节)要大
UDP 的主机不需要维持复杂的连接状态表
TCP和UDP的应用场景
对某些实时性要求比较高的情况使用UDP,比如游戏,媒体通信,实时直播,即使出现传输错误也可以
容忍;其它大部分情况下,HTTP都是用TCP,因为要求传输的内容可靠,不出现丢失的情况
容忍;其它大部分情况下,HTTP都是用TCP,因为要求传输的内容可靠,不出现丢失的情况
什么是Http协议
Http协议是对客户端和服务器端之间数据之间实现可靠性的传输文字、图片、音频、视频等超文本
数据的规范,格式简称为“超文本传输协议”
数据的规范,格式简称为“超文本传输协议”
Http协议属于应用层,及用户访问的第一层就是http
Http和Https的区别
Http协议运行在TCP之上,明文传输,客户端与服务器端都无法验证对方的身份;
Https是身披SSL(Secure Socket Layer)外壳的Http,运行于SSL上,SSL运行于TCP之上,是添加了加密和认证机制的HTTP。
1. 端口不同:Http与Http使用不同的连接方式,用的端口也不一样,前者是80,后者是443;
2. 资源消耗:和HTTP通信相比,Https通信会由于加减密处理消耗更多的CPU和内存资源;
3. 开销:Https通信需要证书,而证书一般需要向认证机构购买;
2. 资源消耗:和HTTP通信相比,Https通信会由于加减密处理消耗更多的CPU和内存资源;
3. 开销:Https通信需要证书,而证书一般需要向认证机构购买;
HTTPS工作流程
1. Client发起一个HTTPS(比如https://juejin.im/user/5a9a9cdcf265da238b7d771c)的请求,根据RFC2818的规定,Client知道需要连接Server的443(默认)端口。
2. Server把事先配置好的公钥证书(public key certificate)返回给客户端。
3. Client验证公钥证书:比如是否在有效期内,证书的用途是不是匹配Client请求的站点,是不是在CRL吊销列表里面,它的上一级证书是否有效,这是一个递归的过程,直到验证到根证书(操作系统内置的Root证书或者Client内置的Root证书)。如果验证通过则继续,不通过则显示警告信息。
4. Client使用伪随机数生成器生成加密所使用的对称密钥,然后用证书的公钥加密这个对称密钥,发给Server。
5. Server使用自己的私钥(private key)解密这个消息,得到对称密钥。至此,Client和Server双方都持有了相同的对称密钥。
6. Server使用对称密钥加密“明文内容A”,发送给Client。
7. Client使用对称密钥解密响应的密文,得到“明文内容A”。
8. Client再次发起HTTPS的请求,使用对称密钥加密请求的“明文内容B”,然后Server使用对称密钥解密密文,得到“明文内容B”。
2. Server把事先配置好的公钥证书(public key certificate)返回给客户端。
3. Client验证公钥证书:比如是否在有效期内,证书的用途是不是匹配Client请求的站点,是不是在CRL吊销列表里面,它的上一级证书是否有效,这是一个递归的过程,直到验证到根证书(操作系统内置的Root证书或者Client内置的Root证书)。如果验证通过则继续,不通过则显示警告信息。
4. Client使用伪随机数生成器生成加密所使用的对称密钥,然后用证书的公钥加密这个对称密钥,发给Server。
5. Server使用自己的私钥(private key)解密这个消息,得到对称密钥。至此,Client和Server双方都持有了相同的对称密钥。
6. Server使用对称密钥加密“明文内容A”,发送给Client。
7. Client使用对称密钥解密响应的密文,得到“明文内容A”。
8. Client再次发起HTTPS的请求,使用对称密钥加密请求的“明文内容B”,然后Server使用对称密钥解密密文,得到“明文内容B”。
三次握手与四次挥手
三次握手(我要和你建立链接,你真的要和我建立链接么,我真的要和你建立链接,成功)
四次挥手(我要和你断开链接;好的,断吧。我也要和你断开链接;好的,断吧):
建立连接可以两次握手吗?为什么
不可以。因为可能会出现已失效的连接请求报文段又传到了服务器端。这样,server 的很多资源就白白浪费掉了。采用 “三次握手” 的办法可以防止上述现象发生。例如刚才那种情况,client 不会向 server 的确认发出确认。server 由于收不到确认,就知道client 并没有要求建立连接。
而且,两次握手无法保证Client正确接收第二次握手的报文(Server无法确认Client是否收到),也无法保证Client和Server之间成功互换初始序列号。
GET 与 POST 的区别
功能
GET一般用来从服务器上获取资源,POST一般用来更新服务器上的资源;
参数形式
GET请求的数据会附在URL之后,即将请求数据放置在HTTP报文的 请求头中,以?分割URL和传输数据,参数之间以&相连。POST请求会把提交的数据则放置在是HTTP请求报文的 请求体中。
安全性
POST的安全性要比GET的安全性高,因为GET请求提交的数据将明文出现在URL上,
而且POST请求参数则被包装到请求体中,相对更安全
而且POST请求参数则被包装到请求体中,相对更安全
请求的大小
GET请求的长度受限于浏览器或服务器对URL长度的限制,允许发送的数据量比较
小,而POST请求则是没有大小限制的。
小,而POST请求则是没有大小限制的。
Socket和http的区别和应用场景
Socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉;
Socket适用场景:网络游戏,银行持续交互,直播,在线视屏等。
http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断开等待下次连接
http适用场景:公司OA服务,互联网服务,电商,办公,网站等等等等
HTTP通信过程中,浏览器与服务器之间将完成7个步骤
建立TCP连接
在HTTP工作开始之前,Web浏览器首先要通过网络与Web服务器建立连接,该连接是通过TCP来完成的,该协议与IP协议共同构建Internet,即著名的TCP/IP协议族
Web浏览器向Web服务器发送请求命令
一旦建立了TCP连接,Web浏览器就会向Web服务器发送请求命令。例如:GET/sample/hello.jsp HTTP/1.1。
Web浏览器发送请求头信息
浏览器发送其请求命令之后,还要以头信息的形式向Web服务器发送一些别的信息,之后浏览器发送了一空白行来通知服务器,它已经结束了该头信息的发送。
Web服务器应答
客户机向服务器发出请求后,服务器会客户机回送应答, HTTP/1.1 200 OK ,应答的第一部分是协议的版本号和应答状态码。
Web服务器发送应答头信息
正如客户端会随同请求发送关于自身的信息一样,服务器也会随同应答向用户发送关于它自己的数据及被请求的文档。
Web服务器向浏览器发送数据
Web服务器向浏览器发送头信息后,它会发送一个空白行来表示头信息的发送到此为结束,接着,它就以Content-Type应答头信息所描述的格式发送用户所请求的实际数据。
Web服务器关闭TCP连接
一般情况下,一旦Web服务器向浏览器发送了请求数据,它就要关闭TCP连接,然后如果浏览器或者服务器在其头信息加入了这行代码:Connection:keep-alive。TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。
浏览器中输入:“ www.xxx.com ” 之后都发生了什么?请详细阐
述。
述。
由域名→IP地址 寻找IP地址的过程依次经过了浏览器缓存、操作系统自身的DNS缓存、hosts文件、路由器缓存、 DNS系统调用递归搜索根域名服务器。
建立TCP/IP连接(三次握手具体过程)
由浏览器发送一个HTTP请求
经过路由器的转发,通过服务器的防火墙,该HTTP请求到达了服务器
服务器处理该HTTP请求,返回一个HTML文件
浏览器解析该HTML文件,并且显示在浏览器端
Session、Cookie 与 Application
介绍
Cookie和Session都是客户端与服务器之间保持状态的解决方案,具体来说,cookie机制采用的是在客
户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。
户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。
Cookie 及其相关 API
Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie,而客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器,服务器检查该Cookie,以此来辨认用户状态。
Session 及其相关 API
服务器默认为客户浏览器的cookie中设置 sessionid,这个sessionid就和cookie对应,浏览器在向服务
器请求过程中传输的cookie 包含 sessionid ,服务器根据传输cookie 中的 sessionid 获取出会话中存储
的信息,然后确定会话的身份信息。
器请求过程中传输的cookie 包含 sessionid ,服务器根据传输cookie 中的 sessionid 获取出会话中存储
的信息,然后确定会话的身份信息。
Session 与 Cookie 的对比
实现机制
Session的实现常常依赖于Cookie机制,通过Cookie机制回传SessionID;
大小限制
Cookie有大小限制并且浏览器对每个站点也有cookie的个数限制,Session没有大小限
制,理论上只与服务器的内存大小有关;
制,理论上只与服务器的内存大小有关;
安全性
Cookie存在安全隐患,通过拦截或本地文件找得到cookie后可以进行攻击,而Session由
于保存在服务器端,相对更加安全;
于保存在服务器端,相对更加安全;
服务器资源消耗
Session是保存在服务器端上会存在一段时间才会消失,如果session过多会增加
服务器的压力。
服务器的压力。
常见HTTP状态码大全
100-199 用于指定客户端应相应的某些动作。
200-299 用于表示请求成功。
300-399 用于已经移动的文件并且常被包含在定位头信息中指定新的地址信息。
301 (SC_MOVED_PERMANENTLY)
状态是指所请求的文档在别的地方;文档新的URL会在定位响应头信息中给出。浏览器会自动连接到新的URL。
400-499 用于指出客户端的错误。
400(错误请求)
服务器不理解请求的语法
401(未授权)
请求要求身份验证。对于登录后请求的网页,服务器可能返回此响应。
403(禁止)
服务器拒绝请求。
404(未找到)
服务器找不到请求的网页。
500-599 用于支持服务器错误。
500(服务器内部错误)
服务器遇到错误,无法完成请求。
501(尚未实施)
服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码。
502(错误网关)
服务器作为网关或代理,从上游服务器收到无效响应。
504(网管超时)
服务器目前无法使用(由于超载或停机维护)。通常,这只是暂时状态。
505(HTTP 版本不受支持)
服务器不支持请求中所用的 HTTP 协议版本。
XSS 攻击
XSS是指恶意攻击者利用网站没有对用户提交数据进行转义处理或者过滤不足的缺点,进而添加一些脚
本代码嵌入到web页面中去,使别的用户访问都会执行相应的嵌入代码,从而盗取用户资料、利用用户
身份进行某种动作或者对访问者进行病毒侵害的一种攻击方式。
本代码嵌入到web页面中去,使别的用户访问都会执行相应的嵌入代码,从而盗取用户资料、利用用户
身份进行某种动作或者对访问者进行病毒侵害的一种攻击方式。
什么是对称加密与非对称加密
对称密钥加密
是指加密和解密使用同一个密钥的方式,这种方式存在的最大问题就是密钥发送问题,即
如何安全地将密钥发给对方;
如何安全地将密钥发给对方;
非对称加密
是指使用一对非对称密钥,即公钥和私钥,公钥可以随意发布,但私钥只有自己知道。发送密文的一方使用对方的公钥进行加密处理,对方接收到加密信息后,使用自己的私钥进行解密。 由于非对称加密的方式不需要发送用来解密的私钥,所以可以保证安全性;但是和对称加密比起来,非常的慢
Https加密
对称加密和非对称加密结合方式
浏览器使用Https的URL访问服务器,建立SSL链接。
服务器收到SSL链接,发送非对称加密的公钥A返回给浏览器
浏览器生成随机数,作为对称加密的密钥B(AES)
浏览器使用公钥A,对自己生成的密钥B进行加密,得到密钥C(RSA)
浏览器将密钥C,发送给服务器。
服务器用私钥D对接受的密钥C进行解密,得到对称加密钥B。
浏览器和服务器之间可以用密钥B作为对称加密密钥进行通信。
Cookie与Session区别
Cookie数据存放在客户端上,安全性较差,Session数据放在服务器上,安全性相对更高
单个cookie保存的数据不能超过4K,session无此限制
session一定时间内保存在服务器上,当访问增多,占用服务器性能,考虑到服务器性能方面,应当
使用cookie。
使用cookie。
数据库
MYSQL面试题
MySQL中的varchar和char有什么区别
空间大小
char是一个定长字段,假如申请了 char(10) 的空间,那么无论实际存储多少内容.该字段都占用10个字符, 而varchar是变长的,也就是说申请的只是最大长度,占用的空间为实际字符长度+1
检索效率
在检索效率上来讲,char > varchar,因此在使用中,如果确定某个字段的值的长度,可以使用char,否则应该尽量使用varchar.例如存储用户MD5加密后的密码,则应该使用char.排序时varchar会消耗更多内存,因为order by col采用fixed_length计算col长度(memory引擎也一样)
varchar(10)和int(10)代表什么含义
- varchar的10代表了申请的空间长度,也是可以存储的数据的最大长度
- 而int的10只是代表了展示的长度,不足10位以0填充.也就是说,int(1)和int(10)所能存储的数字大小以及占用的空间都是相同的,只是在展示时按照长度展示.一般int后面的数字,配合zerofill一起使用才有效。先看个例子:
mysql的4种隔离级别
存在的问题
脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。
read uncommitted(未提交读)
读取尚未提交的数据 :哪个问题都不能解决
read committed(提交读)
读取已经提交的数据 :可以解决脏读 ---- oracle默认的
repeatable read(可重复读)
重读读取:可以解决脏读 和 不可重复读 —mysql默认的
serializable(可串行化)
可以解决 脏读 不可重复读 和 虚读—相当于锁表
innodb的事务与日志的实现方式
redo 日志 redo log叫做重做日志,是用来实现事务的持久性(用于数据库的崩溃恢复),当事务提交之后会把所有修改信息都会存到该日志中。该日志由两部分组成,一个是在内存里的redo log buffer,另一个是在磁盘里的redo log文件。 mysql 为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到Buffer Pool(缓冲池)里头,把这个当作缓存来用。然后使用后台线程去做缓冲池和磁盘之间的同步。 那么问题来了,如果还没来的同步的时候宕机或断电了怎么办?由于buffer pool是在内存里的, 这样会导致丢部分已提交事务的修改信息!所以引入了redo log来记录已成功提交事务的修改信息,之后,系统重启后读取redo log恢复最新数据。虽然redo log也有内存buffer缓冲的部分,如果要严格保证数据不丢失,就要在事务提交前做一次磁盘写入,但是这种IO操作相比于buffer pool这种以页(16kb)为管理单位的随机写入,它做的是几个字节的顺序写入,效率要高得多。
当开始一个事务的时候,会记录该事务的lsn(log sequence number)号; 当事务执行时,会往InnoDB存储引擎的日志
的日志缓存里面插入事务日志;当事务提交时,必须将存储引擎的日志缓冲写入磁盘(通过innodb_flush_log_at_trx_commit来控制),也就是写数据前,需要先写日志。这种方式称为“预写日志方式”,
的日志缓存里面插入事务日志;当事务提交时,必须将存储引擎的日志缓冲写入磁盘(通过innodb_flush_log_at_trx_commit来控制),也就是写数据前,需要先写日志。这种方式称为“预写日志方式”,
undo 日志
- undo log 叫做回滚日志,保证事务的原子性,记录事务修改之前的数据信息,因此假如由于系统错误或者rollback操作而回滚的话可以根据undo log的信息来进行回滚到没被修改前的状态。
- 他正好跟前面所说的重做日志所记录的相反,重做日志记录数据被修改后的信息。undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。
MySQL的binlog有有几种录入格式
STATEMENT
记录SQL语句。日志文件小,节约IO,但是对一些系统函数不能准确复制或不能复制,如now()、uuid()等
ROW
记录表的行更改情况,可以为数据库的恢复、复制带来更好的可靠性,但是二进制文件的大小相较于STATEMENT会有所增加
MIXED
STATEMENT和ROW模式的混合。默认采用STATEMENT格式进行二进制日志文件的记录,但是在一些情况下会使用ROW格式。
超大分页怎么处理?
offect特别大的时候效率就非常低下,我们可以快速定位获取的ID,然后关联,例如 select * from table where id in (select id from table where age > 20 limit 1000000,10) .这样虽由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快. 同时如果ID连续的好,我们还可以 select * from table where id > 1000000 limit 10 ,效率也是不错的,
慢查询怎么优化过
首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多
结果中并不需要的列,对语句进行分析以及重写.
结果中并不需要的列,对语句进行分析以及重写.
分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能
的命中索引
的命中索引
如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵
向的分表
向的分表
什么是存储过程
存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程的名字并给出参数来调用存储过程。
MySQL三大范式
第一范式
每个列都不可以再拆分。
第二范式
在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。
第三范式
在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。
MySQL的复制原理以及流程
主:binlog线程——记录下所有改变了数据库数据的语句,放进master上的binlog中;
从:io线程——在使用start slave 之后,负责从master上拉取 binlog 内容,放进 自己的relay log中;
从:sql执行线程——执行relay log中的语句;
MySQL由哪些部分组成, 分别用来做什么
Server
- 连接器: 管理连接, 权限验证.
- 分析器: 词法分析, 语法分析.
- 优化器: 执行计划生成, 索引的选择.
- 执行器: 操作存储引擎, 返回执行结果.
存储引擎
存储数据, 提供读写接口.
如果一个表有一列定义为 TIMESTAMP
每当行被更改时,时间戳字段将获取当前时间戳。
MySQL 里记录货币用什么字段类型好
NUMERIC 和 DECIMAL 类型被 MySQL 实现为同样的类型, 这在 SQL92 标准允许。他们被用于保存
值, 该值的准确精度是极其重要的值, 例如与金钱有关的数据。
值, 该值的准确精度是极其重要的值, 例如与金钱有关的数据。
MySQL 数据库作发布系统的存储,一天五万条以上的增量, 预
计运维三年,怎么优化?
计运维三年,怎么优化?
1. 选择合适的表字段数据类型和存储引擎, 适当的添加索引。
2. MySQL 库主从读写分离。
3. 找规律分表, 减少单表中的数据量提高查询速度。
4. 添加缓存机制, 比如 memcached
2. MySQL 库主从读写分离。
3. 找规律分表, 减少单表中的数据量提高查询速度。
4. 添加缓存机制, 比如 memcached
优化数据库的方式
选取最适用的字段属性
一般说来,数据库中的表越小,在它上面执行的查询也就会越快。因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽可能小。比如varchar 改为char
尽量把字段设置为NOTNULL,这样在将来执行查询的时候,数据库不用去比较NULL值。
对于某些文本字段,例如“省份”或者“性别”,我们可以将它们定义为ENUM类型。因为在MySQL中,ENUM类型被当作数值型数据来处理,而数值型数据被处理起来的速度要比文本类型快得多。
使用连接(JOIN)来代替子查询(Sub-Queries)
SELECT * FROM customerinfo WHERE CustomerID NOT in (SELECT CustomerID FROM salesinfo)
SELECT * FROM customerinfo LEFT JOIN salesinfo ON customerinfo.CustomerID = salesinfo.CustomerID WHERE salesinfo.CustomerID IS NULL
SELECT * FROM customerinfo LEFT JOIN salesinfo ON customerinfo.CustomerID = salesinfo.CustomerID WHERE salesinfo.CustomerID IS NULL
连接(JOIN)..之所以更有效率一些,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上的需要两个步骤的查询工作。
使用联合(UNION)来代替手动创建的临时表
MySQL从4.0的版本开始支持union查询,它可以把需要使用临时表的两条或更多的select查询合并的一个查询中。在客户端的查询会话结束的时候,临时表会被自动删除,从而保证数据库整齐、高效。使用union来创建查询的时候,我们只需要用UNION作为关键字把多个select语句连接起来就可以了,要注意的是所有select语句中的字段数目要想同。
事务处理
BEGIN;
INSERT INTO salesinfo SET CustomerID=14;
UPDATE inventory SET Quantity=11 WHERE item='book';
COMMIT;
INSERT INTO salesinfo SET CustomerID=14;
UPDATE inventory SET Quantity=11 WHERE item='book';
COMMIT;
锁定表、优化事务处理
事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。
使用外键
锁定表的方法可以维护数据的完整性,但是它却不能保证数据的关联性。这个时候我们就可以使用外键。
使用索引
- 索引是提高数据库性能的常用方法,它可以令数据库服务器以比没有索引快得多的速度检索特定的行,尤其是在查询语句当中包含有MAX(),MIN()和ORDERBY这些命令的时候,性能提高更为明显。
- 一般说来,索引应建立在那些将用于JOIN,WHERE判断和ORDERBY排序的字段上。尽量不要对数据库中某个含有大量重复的值的字段建立索引。对于一个ENUM类型的字段来说,出现大量重复值是很有可能的情况
优化的查询语句
首先,最好是在相同类型的字段间进行比较的操作。
其次,在建有索引的字段上尽量不要使用函数进行操作。
第三,在搜索字符型字段时,我们有时会使用LIKE关键字和通配符,这种做法虽然简单,但却也是以牺牲系统性能为代价的。
简单描述 MySQL 中,索引,主键,唯一索引,联合索引的区
别,对数据库的性能有什么影响
别,对数据库的性能有什么影响
索引介绍
索引是一种特殊的文件(InnoDB 数据表上的索引是表空间的一个组成部分), 它们包含着对数据表里所有记录的引用指针。索引可以极大的提高数据的查询速度, 但是会降低插入、删除、更新表的速度, 因为在执行这些写操作时, 还要操作索引文件。
普通索引
普通索引(由关键字 KEY 或 INDEX 定义的索引)的唯一任务是加快对数据的访问速度。普通索引允许被索引的数据列包含重复的值。如果能确定某个数据列将只包含彼此各不相同的值, 在为这个数据列创建索引的时候就应该用关键字 UNIQUE 把它定义为一个唯一索引。也就是说, 唯一索引可以保证数据记录的唯一性。
主键
主键, 是一种特殊的唯一索引, 在一张表中只能定义一个主键索引, 主键用于唯一标识一条记录, 使用关键字 PRIMARY KEY 来创建。
联合索引
索引可以覆盖多个数据列,如像 INDEX(columnA, columnB)索引
唯一索引
唯一索引可以保证数据记录的唯一性。
InnoDB和MyISAM有什么区别
InnoDB支持事务,MyISAM不支持
nnoDB支持外键,而MyISAM不支持。
InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的。MyISAM是非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。
Innodb不支持全文索引,而MyISAM支持全文索引
InnoDB支持表、行(默认)级锁,而MyISAM支持表级锁
myisamchk 是用来做什么的
它用来压缩 MyISAM 表, 这减少了磁盘或内存使用。
MyISAM Static 和 MyISAM Dynamic 有什么区别?
在 MyISAM Static 上的所有字段有固定宽度。动态 MyISAM 表将具有像 TEXT, BLOB 等字段, 以适应
不同长度的数据类型。
不同长度的数据类型。
主键使用自增ID还是UUID
推荐使用自增ID,不要使用UUID.
因为在InnoDB存储引擎中,主键索引是作为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了主键索引以及全部的数据(按照顺序),如果主键索引是自增ID,那么只需要不断向后排列即可,如果是UUID,由于到来的ID与原来的大小不确定,会造成非常多的数据插入,数据移动,然后导致产生很多的内存碎片,进而造成插入性能的下降.
索引是个什么样的数据结构呢
索引的数据结构和具体存储引擎的实现有关, 在MySQL中使用较多的索引有Hash索引,B+树索引等,而我
们经常使用的InnoDB存储引擎的默认索引实现为:B+树索引
们经常使用的InnoDB存储引擎的默认索引实现为:B+树索引
唯一索引比普通索引快吗, 为什么
唯一索引不一定比普通索引快, 还可能慢
查询时, 在未使用 limit 1 的情况下, 在匹配到一条数据后, 唯一索引即返回, 普通索引会继续匹配下一条数据, 发现不匹配后返回. 如此看来唯一索引少了一次匹配, 但实际上这个消耗微乎其微
- 更新时,普通索引将记录放到 change buffer 中语句就执行完毕了. 而对唯一索引而言, 它必须要校验唯一性, 因此, 必须将数据页读入内存确定没有冲突, 然后才能继续操作. 对于写多读少的情况, 普通索引利用 change buffer 有效减少了对磁盘的访问次数, 因此普通索引性能要高于唯一索引.
- 在MySQL中数据分为内存和磁盘两个部分;在buffer pool中缓存热的数据页和索引页,减少磁盘读;通过change buffer就是为了缓解磁盘写的一种手段。
做过哪些MySQL索引相关优化
尽量使用主键查询: 聚簇索引上存储了全部数据, 相比普通索引查询, 减少了回表的消耗.
MySQL5.6之后引入了索引下推优化, 通过适当的使用联合索引, 减少回表判断的消耗
若频繁查询某一列数据, 可以考虑利用覆盖索引避免回表.
联合索引将高频字段放在最左边
怎么看到为表格定义的所有索引?
SHOW INDEX FROM <tablename>;
B-Tree 和 B+Tree的区别
B-Tree 的关键字和记录是放在一起的B-Tree 的关键字和记录是放在一起的
B+Tree 的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。所有叶子节点之间都有一个链指针。
B+Tree 的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。所有叶子节点之间都有一个链指针。
为什么 B+Tree 比 B-Tree 更适合实际应用中操作系统的文件索引和数据库索引?
B+Tree 的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对 B-Tree 更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说 IO 读写次数也就降低了。
Hash索引和B+树所有有什么区别或者说优劣呢
- hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据.
- B+树底层实现是多路平衡查找树.对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据
- hash索引进行等值查询更快(一般情况下),但是却无法进行范围查询.因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询.而B+树的的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似),天然支持范围.
- hash索引不支持使用索引进行排序,原理同上.
- hash索引不支持模糊查询以及多列索引的最左前缀匹配.原理也是因为hash函数的不可预测
- hash索引任何时候都避免不了回表查询数据,而B+树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询
上面提到了B+树在满足聚簇索引和覆盖索引的时候不需要回表查
询数据,什么是聚簇索引?
询数据,什么是聚簇索引?
在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引. 在InnoDB中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引.如果没有唯一键,则隐式的生成一个键来建立聚簇索引
在建立索引的时候,都有哪些需要考虑的因素呢?
- 建立索引的时候一般要考虑到字段的使用频率,经常作为条件进行查询的字段比较适合.
- 如果需要建立联合索引的话,还需要考虑联合索引中的顺序.
那么在哪些情况下会发生针对该列创建了索引但是在查询的时候
并没有使用呢?
并没有使用呢?
- 使用不等于查询,
- 列参与了数学运算或者函数
- 在字符串like时左边是通配符.类似于'%aaa'.
- 当mysql分析全表扫描比使用索引快的时候不使用索引.
- 当使用联合索引,前面一个条件为范围查询,后面的即使符合最左前缀原则,也无法使用索引.
什么是事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务是一系列的操作,他们要符合ACID特性.最常见的理解就是:事务中的操作要么全部成功,要么全部失败.但是只是这样还不够的.
ACID是什么?可以详细说一下吗?
A=Atomicity
原子性:就是上面说的,要么全部成功,要么全部失败.不可能只执行一部分操作.
原子性:就是上面说的,要么全部成功,要么全部失败.不可能只执行一部分操作.
C=Consistency
一致性:系统(数据库)总是从一个一致性的状态转移到另一个一致性的状态,不会存在中间状态
一致性:系统(数据库)总是从一个一致性的状态转移到另一个一致性的状态,不会存在中间状态
I=Isolation
隔离性: 通常来说:一个事务在完全提交之前,对其他事务是不可见的.注意前面的通常来说加了红色,意味着有例外情况.
隔离性: 通常来说:一个事务在完全提交之前,对其他事务是不可见的.注意前面的通常来说加了红色,意味着有例外情况.
D=Durability
持久性:一旦事务提交,那么就永远是这样子了,哪怕系统崩溃也不会影响到这个事务的结果
持久性:一旦事务提交,那么就永远是这样子了,哪怕系统崩溃也不会影响到这个事务的结果
并发事务带来哪些问题
脏读(Dirty read)
当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。那么另外一个事务读到的这个数据是“脏数据”
丢失修改(Lost to modify)
指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。
不可重复读(Unrepeatableread)
指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
幻读(Phantom read)
读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
怎么解决这些问题呢?MySQL的事务隔离级别了解吗?
未提交读(READ UNCOMMITTED)
这个隔离级别下,其他事务可以看到本事务没有提交的部分修改.因此会造成脏读的问题这个级别的性能没有足够大的优势,但是又有很多的问题,因此很少使用.
已提交读(READ COMMITTED)
其他事务只能读取到本事务已经提交的部分。解决了脏读,但是有不可重复读的问题
REPEATABLE READ(可重复读)
可重复读隔离级别解决了上面不可重复读的问题(看名字也知道),但是仍然有一个新问题,就是幻读。当你读取id> 10 的数据行时,对涉及到的所有行加上了读锁此时例外一个事务新插入了一条id=11的数据,因为是新插入的,所以不会触发上面的锁的排斥,那么进行本事务进行下一次的查询时会发现有一条id=11的数据,
SERIALIZABLE(可串行化)
这是最高的隔离级别,可以解决上面提到的所有问题,因为他强制将所以的操作串行执行,这会导致并发性能极速下降,因此也不是很常用.
对MySQL的锁了解吗
当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,锁机制就
是这样的一个机制.
是这样的一个机制.
表级锁: 开销小, 加锁快; 不会出现死锁; 锁定粒度大, 发生锁冲突的概率最高, 并发度最低。
行级锁: 开销大, 加锁慢; 会出现死锁; 锁定粒度最小, 发生锁冲突的概率最低, 并发度也最高。
页面锁: 开销和加锁时间界于表锁和行锁之间; 会出现死锁; 锁定粒度界于表锁和行锁之间, 并发度一般。
MySQL都有哪些锁呢?
共享锁
又叫做读锁. 当用户要进行数据的读取时,对数据加上共享锁.共享锁可以同时加上多个.
排他锁
又叫做写锁. 当用户要进行数据的写入时,对数据加上排他锁.排他锁只可以加一个,他和其他的排他锁,共享锁都相斥.
Explain 性能分析
查看执行计划:使用 EXPLAIN 关键字可以模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理SQL 语句的。分析查询语句或是表结构的性能瓶颈。
id
查询的序列号,包含一组数字,表示查询中执行 select 子句或操作表的顺序。
- id 相同,执行顺序由上至下
- id 不同,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行
- id 有相同也有不同:id 如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id 值越大,优先级越高,越先执行
select_type
代表查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询,取值范围如下:
- simple:简单的 select 查询,查询中不包含子查询或者 UNION
- primary:查询中若包含任何复杂的子部分,最外层查询则被标记为 primary
- derived:在 FROM 列表中包含的子查询被标记为 DERIVED (衍生),MySQL 会递归执行这些子查询, 把结果放在临时表里。
- depedent subquery:在 SELECT 或 WHERE 列表中包含了子查询,子查询基于外层
- subquery:在 SELECT 或 WHERE 列表中包含了子查询
- union:若第二个 SELECT 出现在 UNION 之后,则被标记为 UNION;若 UNION 包含在FROM 子句的子查询中,外层 SELECT 将被标记为:DERIVED
table
table:这个数据是基于哪张表的。
type
type:是查询的访问类型。是较为重要的一个指标,结果值从最好到最坏依次是:system > const> eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery >range > index > ALL,一般来说,得保证查询至少达到 range 级别,最好能达到 ref。
只需要记住:system > const > eq_ref > ref > range > index > ALL 就行了,其他的不常见。
只需要记住:system > const > eq_ref > ref > range > index > ALL 就行了,其他的不常见。
- system:表只有一行记录(等于系统表),这是 const 类型的特列,平时不会出现,这个也可以忽略不计。
- const:表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。因为只匹配一行数据,所以很快。
- eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。
- ref:非唯一性索引扫描,返回匹配某个单独值的所有行。然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。
- range:只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引一般就是在 where 语句中出现了 between、<、>、in 等的查询这种范围扫描索引扫描比全表扫描要好
- index: 出现 index 是 sql 使用了索引但是没用索引进行过滤,一般是使用了覆盖索引或者是利用索引进行了排序分组。
- all:将遍历全表以找到匹配的行。
possible_keys
possible_keys:显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
key
key:实际使用的索引。如果为 NULL,则没有使用索引。
key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。key_len 显示的值为索引字段的最大可能长度,并非实际使用长度。如何计算 key_len?
- 先看索引上字段的类型 + 长度,比如:int=4; varchar(20)=20; char(20)=20
- 如果是 varchar 或者 char 这种字符串字段,视字符集要乘不同的值,比如 utf-8 要乘 3,GBK 要乘 2
- varchar 这种动态字符串要加 2 个字节
- 允许为空的字段要加 1 个字节
ref
ref:显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
rows
rows:显示 MySQL 认为它执行查询时必须检查的行数。越少越好!
Extra
Extra:其他的额外重要的信息
- Using filesort:说明 mysql 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL 中无法利用索引完成的排序操作称为“文件排序”。查询中排序的字段, 排序字段若通过索引去访问将大大提高排序速度。
- Using temporary:使用临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。排序分组字段若通过索引去访问将大大提高速度。
- Using index:表示相应的 select 操作中使用了覆盖索引 (Covering Index),避免访问了表的数据行,效率不错!如果同时出现 using where,表明索引被用来执行索引键值的查找;如果没有同时出现 using where,表明索引只是用来读取数据而非利用索引执行查找。
- Using where:表明使用了 where 过滤。
如何优化SQL
SQL语句中IN包含的值不应过多
MySQL对于IN做了相应的优化,即将IN中的常量全部存储在一个数组里面,而且这个数组是排好序的。但是如果数值较多,产生的消耗也是比较大的。再例如: select id from table_name where num in(1,2,3) 对于连续的数值,能用 between 就不要用 in 了;再或者使用连接来替换。
SELECT语句务必指明字段名称
SELECT *增加很多不必要的消耗(cpu、io、内存、网络带宽);增加了使用覆盖索引的可能性;当表结构发生改变时,前断也需要更新。所以要求直接在select后面接上字段名。
当只需要一条数据的时候,使用limit 1
这是为了使EXPLAIN中type列达到const类型
如果排序字段没有用到索引,就尽量少排序
如果限制条件中其他字段没有索引,尽量少用or
or两边的字段中,如果有一个不是索引字段,而其他条件也不是索引字段,会造成该查询不走索引的情况。很多时候使用 union all 或者是union(必要的时候)的方式来代替“or”会得到更好的效果
尽量用union all代替union
union和union all的差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟。当然,union all的前提条件是两个结果集没有重复数据。
不使用ORDER BY RAND()
select id from `table_name` order by rand() limit 1000;
可优化为:select id from `table_name` t1 join (select ROUND(rand() * (select max(id) from `table_name`)) as nid) t2 ont1.id > t2.nid limit 1000;
区分in和exists, not in和not exists
in适合子查询小,exists适合子查询比较大的情况
- 如果sql语句中包含了exists关键字,它优先执行exists左边的语句(即主查询语句)。然后把它作为条件,去跟右边的语句匹配。如果匹配上,则可以查询出数据。如果匹配不上,数据就被过滤掉了。
- 如果sql语句中包含了in关键字,则它会优先执行in里面的子查询语句,然后再执行in外面的语句。如果in里面的数据量很少,作为条件查询速度更快。
关于not in和not exists,推荐使用not exists,不仅仅是效率问题,not in可能存在逻辑问题。
select colname … from A表 where a.id not in (select b.id from B表)
select colname … from A表 Left join B表 on where a.id = b.id where b.id is null
使用合理的分页方式以提高分页的效率
select id,name from table_name limit 866613, 20
使用上述sql语句做分页的时候,可能有人会发现,随着表数据量的增加,直接使用limit分页查询会越来越慢。
使用上述sql语句做分页的时候,可能有人会发现,随着表数据量的增加,直接使用limit分页查询会越来越慢。
优化的方法如下:可以取前一页的最大行数的id,然后根据这个最大的id来限制下一页的起点。比如此列中,上一页最大的id是866612。sql可以采用如下的写法:
select id,name from table_name where id> 866612 limit 20
select id,name from table_name where id> 866612 limit 20
避免在 where 子句中对字段进行 null 值判断
对于null的判断会导致引擎放弃使用索引而进行全表扫描。
不建议使用%前缀模糊查询
例如LIKE “%name”或者LIKE “%name%”,这种查询会导致索引失效而进行全表扫描。但是可以使用LIKE
“name%
“name%
那么如何解决这个问题呢,答案:使用全文索引
ALTER TABLE `table_name` ADD FULLTEXT INDEX `idx_user_name` (`user_name`);
避免在where子句中对字段进行表达式操作
比如
select user_id,user_project from table_name where age*2=36;
select user_id,user_project from table_name where age*2=36;
对字段就行了算术运算,这会造成引擎放弃使用索引,建议改成
select user_id,user_project from table_name where age=36/2;
select user_id,user_project from table_name where age=36/2;
避免隐式类型转换
where 子句中出现 column 字段的类型和传入的参数类型不一致的时候发生的类型转换,建议先确定
where中的参数类型
where中的参数类型
对于联合索引来说,要遵守最左前缀法则
必要时可以使用force index来强制查询走某个索引
注意范围查询语句
对于联合索引来说,如果存在范围查询,比如between,>,<等条件时,会造成后面的索引字段失效。
巧用STRAIGHT_JOIN
inner join是由mysql选择驱动表,但是有些特殊情况需要选择另个表作为驱动表,比如有group by、order by等「Using filesort」、「Using temporary」时。STRAIGHT_JOIN来强制连接顺序,在STRAIGHT_JOIN左边的表名就是驱动表,右边则是被驱动表。在使用STRAIGHT_JOIN有个前提条件是该查询是内连接,也就是inner join。
选择表合适存储引擎
myisam:应用时以读和插入操作为主,只有少量的更新和删除,并且对事务的完整性,并发性要求不是很高的。
InnoDB:事务处理,以及并发条件下要求数据的一致性。除了插入和查询外,包括很多的更新和删除。(InnoDB有效地降低删除和更新导致的锁定)。
对于支持事务的InnoDB类型的表来说,影响速度的主要原因是AUTOCOMMIT默认设置是打开的,而且程序没有显式调用BEGIN 开始事务,导致每插入一条都自动提交,严重影响了速度。可以在执行SQL前调用begin,多条SQL形成一个事物(即使autocommit打开也可以),将大大提高性能。
优化表的数据类型,选择合适的数据类型:
原则
更小通常更好,简单就好,所有字段都得有默认值,尽量避免null。
例如
数据库表设计时候更小的占磁盘空间尽可能使用更小的整数类型。(mediumint就比int更合适)
比如时间字段:datetime和timestamp,datetime占用8个字节,而timestamp占用4个字节,只用了一
半,而timestamp表示的范围是1970—2037适合做更新时间
半,而timestamp表示的范围是1970—2037适合做更新时间
应该尽量把字段设置为NOT NULL,这样在将来执行查询的时候,数据库不用去比较NULL值。
对于某些文本字段,例如“省份”或者“性别”,我们可以将它们定义为ENUM类型。因为在MySQL中,ENUM类型被当作数值型数据来处理,而数值型数据被处理起来的速度要比文本类型快得多。
SQL性能优化策略
- 对查询进行优化,应尽量避免全表扫描,首先应考虑在where及order by涉及的列上建立索引。
- 应尽量避免在where子句中对字段进行null值判断,创建表时NULL是默认值,但大多数时候应该使用NOT NULL,或者使用一个特殊的值,如0,-1作为默认值。
- 应尽量避免在where子句中使用!=或<>操作符,MySQL只有对以下操作符才使用索引:<,<=,=, >,>=,BETWEEN,IN,以及某些时候的LIKE。
- 应尽量避免在where子句中使用or来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,可以使用UNION合并查询:select id from t where num=10 union all select id from t where num=20。\
- in和not in也要慎用,否则会导致全表扫描,对于连续的数值,能用between就不要用in了:Selectid from t where num between 1 and 3。
- 应尽量避免在where子句中对字段进行表达式操作,应尽量避免在where子句中对字段进行函数操作。
- 很多时候用exists代替in是一个好的选择: select num from a where num in(select num from b) 用 select num from a where exists(select 1 from b where num=a.num) 替换
- 存储的字符长度是可变化的,建议使用 varchar 类型,它可以节省存储空间。存储的字符串的长度是固定不变的,建议使用 char 类型,这种场景下选用它的不会浪费存储空间,效率还比较高。
- 使用“临时表”暂存中间结果 :简化SQL语句的重要方法就是采用临时表暂存中间结果,但是临时表的好处远远不止这些,将临时结果暂存在临时表,后面的查询就在tempdb中了,这可以避免程序中多次扫描主表,也大大减少了程序执行中“共享锁”阻塞“更新锁”,减少了阻塞,提高了并发性能。
- 多个OR的字句没有用到索引,改写成UNION的形式再试图与索引匹配。一个关键的问题是否用到索引。
Redis面试题
什么是 Redis?简述它的优缺点?
介绍
Redis 的全称是:Remote Dictionary.Server,是一个 Key-Value 类型的内存数据库,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush 到硬盘上进行保存。
优缺点
- Redis 最大的魅力是支持保存多种数据结构,此外单个 value 的最大限制是 1GB,不像 memcached 只能保存 1MB 的数据,因此 Redis 可以用来实现很多有用的功能。
- 他的 List 来做 FIFO 双向链表,实现一个轻量级的高性能消息队列服务
- Redis 也可以对存入的 Key-Value 设置 expire 时间
缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。
为什么要用 redis/为什么要用缓存
高性能
将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。
高并发
直接操作缓存能够承受的请求是远远大于直接访问数据库的,可以提高并发
Redis 与 memcached 相比有哪些优势
redis支持更丰富的数据类型(支持更复杂的应用场景):Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。
Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。
集群模式:memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是redis 目前是原生支持 cluster 模式的
Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。
Redis 支持哪几种数据类型?
String
List
Hash
Set
Zset(Sorted Set)
Redis 有哪几种数据淘汰策略
当Redis内存超出物理内存限制时,内存的数据开始和磁盘产生频繁的交换,这样Redis性能会急剧下降,Redis提供了配置参数maxmemory来设置几种淘汰策略
1. noeviction:不会继续服务写请求 ,读请求可以继续,这是默认的淘汰策略
2. volatile-lru: 尝试淘汰设置了过期时间的key,最少使用的key优先淘汰,没有设置过期时间不会被淘汰。
3. volatile-ttl: 淘汰设置了过期时间的key,不过比较的是key的剩余寿命ttl,越小的优先被淘汰。
4. volatile-random: 淘汰设置了过期时间的key,随机key
5. allkeys-lru: 尝试回收最少使用的键(LRU)
6. allkeys-random: 回收随机的键
2. volatile-lru: 尝试淘汰设置了过期时间的key,最少使用的key优先淘汰,没有设置过期时间不会被淘汰。
3. volatile-ttl: 淘汰设置了过期时间的key,不过比较的是key的剩余寿命ttl,越小的优先被淘汰。
4. volatile-random: 淘汰设置了过期时间的key,随机key
5. allkeys-lru: 尝试回收最少使用的键(LRU)
6. allkeys-random: 回收随机的键
一个字符串类型的值能存储最大容量是多少
512M
Redis中数据库默认是多少个db即作用
Redis默认支持16个数据库,可以通过配置databases来修改这一数字。客户端与Redis建立连接后会自动选择0号数据库,不过可以随时使用select命令更换数据库.集群不支持多个db,b不能使用select集群默认db0
Redis 集群方案什么情况下会导致整个集群不可用?
有 A,B,C 三个节点的集群,在没有复制模型的情况下,如果节点 B 失败了,那么整个集群就会以为缺少5501-11000 这个范围的槽而不可用。
MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证redis 中的数据都是热点数据?
redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
Redis 有哪些适合的场景
会话缓存(Session Cache)
最常用的一种使用 Redis 的情景是会话缓存(sessioncache),用 Redis 缓存会话比其他存储(如Memcached)的优势在于:Redis 提供持久化。
全页缓存(FPC)
除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台。回到一致性问题,即使重启了 Redis 实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似 PHP 本地FPC。
队列
Reids 在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得 Redis 能作为一个很好的消息队列平台来使用。
排行榜/计数器
Redis 在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(SortedSet)也使得我们在执行这些操作的时候变的非常简单,Redis 只是正好提供了这两种数据结构。
发布/订阅
说说 Redis 哈希槽的概念
Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。
以下情况可能导致写操作丢失
- 过期 key 被清理
- 最大内存不足,导致 Redis 自动清理部分 key 以节省空间
- 主库故障后自动重启,从库自动同步
- 单独的主备方案,网络不稳定触发哨兵的自动切换主从节点,切换期间会有数据丢失
Redis 中的管道有什么用?
一次请求/响应,应服务器能实现处理新的请求即使旧的请求还未被响应,这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。
怎么理解 Redis 事务
概念
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
Redis 事务相关的命令
watch key1 key2 ...
监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断
watch指令类似于乐观锁,在事务提交时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端更改,则使用EXEC执行事务时,事务队列将不会被执行
multi
标记一个事务块的开始( queued )
exec
执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )
discard
取消事务,放弃事务块中的所有命令
unwatch
取消watch对所有key的监控
Redis key 的过期时间
expire key time 指令可以设置 key 的超时时间,单位秒。
ttl key 查看剩余时间
persist key 清除过期时间
使用过 Redis 做异步队列么,你是怎么用的
般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当 sleep一会再重试。
缺点:
在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 rabbitmq 等
能不能生产一次消费多次呢?
使用 pub/sub 主题订阅者模式,可以实现 1:N 的消息队列。
什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免?
缓存穿透
概念
按照 key 去缓存查询,如果不存在对应的 value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的 key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
如何避免
- 查询返回的数据为空,仍把这个空结果进行缓存,但过期时间会比较短
- 布隆过滤器:将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对 DB 的查询。
缓存击穿
概念
对于设置了过期时间的 key,缓存在某个时间点过期的时候,恰好这时间点对这个 Key 有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。
如何避免
- 使用互斥锁:当缓存失效时,不立即去 load db,先使用如 Redis 的 setnx 去设置一个互斥锁,当操作成功返回时再进行 load db 的操作并回设缓存,否则重试 get 缓存的方法。
- 永远不过期:物理不过期,但逻辑过期(后台异步线程去刷新)。
缓存雪崩
概念
设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB,DB 瞬时压力过重雪崩。与缓存击穿的区别:雪崩是很多 key,击穿是某一个key 缓存。
如何避免
将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机
redis 和 memcached 什么区别?
为什么高并发下有时单线程的redis 比多线程的memcached 效率要高?
为什么高并发下有时单线程的redis 比多线程的memcached 效率要高?
区别
1.memcached 可缓存图片和视频。redis 支持除 k/v 更多的数据结构;
2.redis 可以使用虚拟内存,redis 可持久化和 aof 灾难恢复,redis 通过主从支持数据备份;
3.redis 可以做消息队列。
2.redis 可以使用虚拟内存,redis 可持久化和 aof 灾难恢复,redis 通过主从支持数据备份;
3.redis 可以做消息队列。
原因
memcached多线程模型引入了缓存一致性和锁,加锁带来了性能损耗。redis使用的多路服用网络模型
redis 主从复制如何实现的?redis 的集群模式如何实现?redis 的 key 是如何寻址的?
主从复制实现
主节点将自己内存中的数据做一份快照,将快照发给从节点,从节点将数据恢复到内存中。之后再每次增加新数据的时候,主节点以类似于 mysql 的二进制日志方式将语句发送给从节点,从节点拿到主节点发送过来的语句进行重放。
实现方式
codis-路由查询分片
Redis-cluster
redis-cluster 分片原理
每个 Master 节点都会负责一部分的槽,当有某个 key 被映射到某个 Master 负责的槽,那么这个Master 负责为这个 key 提供服务,至于哪个 Master 节点负责哪个槽,可以由用户指定,也可以在初始化的时候自动生成,只有 Master 才拥有槽的所有权。
redis 如何设计分布式锁
线程A使用SETNX key value,获得这个锁,在这期间其他线程通过相同命令获取锁,返回值标识不同,获取锁失败,当线程A执行万以后,del name 删除锁,其他线程才可以执行。通过设置过期时间避免出现其他原因出现死锁情况
zookeeper 如何设计分布式锁
- 客户端对某个方法加锁时,在 zk 上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点node1。
- 客户端获取该路径下所有已经创建的子节点,如果发现自己创建的 node1 的序号是最小的,就认为这个客户端获得了锁
- 如果发现 node1 不是最小的,则监听比自己创建节点序号小的最大的节点,进入等待。
- 获取锁后,处理完逻辑,删除自己创建的 node1 即可。
edis 的持久化
RDB
概述
根据指定的规则“定时”将内存中的数据存储在硬盘上,生成的快照,它是Redis默认的持久化方式,
人工干预
除了让Redis自动进行快照以外,当我们对服务进行重启或者服务器迁移我们需要人工去干预备份。redis提供了两条命令来完成这个任务
save命令
save命令,当执行save命令时,Redis同步做快照操作,在快照执行过程中会阻塞所有来自客户端的请求。当redis内存中的数据较多时,通过该命令将导致Redis较长时间的不响应。所以不建议在生产环境上使用这个命令,而是推荐使用bgsave命令
bgsave命令
- bgsave命令,bgsave命令可以在后台异步地进行快照操作,快照的同时服务器还可以继续响应来自客户端的请求。
- bgsave是异步执行快照的,在调用fork函数创建子进程时,只对这个时间之前的数据集进行备份(fork函数创建的子进程,是复制该时间点的父进程的,该时间点之后的数据是没有复制的),该时间点之后客户端对redis服务端发送的命令对数据的修改,是没有持久化的没有保存到快照中,
AOF
概念
Redis 默认不开启。AOF采用日志的形式来记录每个写操作,并追加到文件中。开启后,执行更改Redis数据的命令时,就会把命令写入到AOF文件中。Redis 重启时会根据日志文件的内容把写指令从前到后执行一次以完成数据的恢复工作。
数据都是实时持久化到磁盘吗
虽然每次执行更改Redis数据库内容的操作时,AOF都会将命令记录在AOF文件中,但是事实上,由于操作系统的缓存机制,数据并没有真正地写入硬盘,而是进入了aof_buf缓存中。在默认情况下系统每30秒会执行一次同步操作。以便将aof_buf缓存中的内容真正地写入磁盘中。
文件越来越大,怎么办?
AOF持久化是Redis不断将写命令记录到 AOF 文件中,随着Redis不断的运行,AOF 的文件会越来越大,文件越大,为了解决这个问题,Redis新增了重写机制,
AOF 文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的 AOF 文件。
AOF 文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的 AOF 文件。
Redis允许同时开启AOF和RDB,既保证了数据安全又使得进行备份等操作十分容易。如果同时开启后,Redis重启会使用AOF文件来恢复数据,因为AOF方式的持久化可能丢失的数据更少。
Redis 4.0 对于持久化机制的优化
Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。
redis 过期策略都有哪些?LRU 算法知道吗
过期策略
定时过期(一 key 一定时器),惰性过期:只有使用 key 时才判断 key 是否已过期,过期则清除。定期过期:前两者折中
LRU 算法实现
1.通过双向链表来实现,新数据插入到链表头部;new LinkedHashMap<K, V>(capacity, DEFAULT_LOAD_FACTORY, true);
2.每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
3.当链表满的时候,将链表尾部的数据丢弃。
在选择缓存时,什么时候选择 redis,什么时候选择
选择 redis 的情况
1、复杂数据结构,value 的数据是哈希,列表,集合,有序集合等这种情况下,会选择redis
2、需要进行数据的持久化功能
3、高可用,redis 支持集群,可以实现主动复制,读写分离
存储的内容比较大,memcache 存储的 value 最大为 1M。
选择 memcache 的场景:
纯 KV,数据量非常大的业务,使用 memcache 更合适,原因是
memcache 的内存分配采用的是预分配内存池的管理方式,能够省去内存分配的时间,redis 是临时申请空间,可能导致碎片化
虚拟内存使用,memcache 将所有的数据存储在物理内存里,redis 有自己的 vm 机制,理论上能够存储比物理内存更多的数据,当数据超量时,引发 swap,把冷数据刷新到磁盘上,从这点上,数据量大时,memcache 更快
网络模型,memcache 使用非阻塞的 IO 复用模型,redis 也是使用非阻塞的 IO 复用模型,但是 redis还提供了一些非 KV 存储之外的排序,聚合功能,复杂的 CPU 计算,会阻塞整个 IO 调度,从这点上由于 redis 提供的功能较多,memcache 更快些
线程模型,memcache 使用多线程,主线程监听,worker 子线程接受请求,执行读写,这个过程可能存在锁冲突。redis 使用的单线程,虽然无锁冲突,但是难以利用多核的特性提升吞吐量。
缓存与数据库不一致怎么办
发生上述不一致的原因在于,主从库数据不一致问题,加入了缓存之后,主从不一致的时间被拉长了
强制读主库,使用一个高可用的主库,数据库读写都在主库,添加一个缓存,提升数据读取的性能。
选择性读主库,添加一个缓存,用来记录必须读主库的数据,将哪个库,哪个表,哪个主键,作为缓存的 key,设置缓存失效的时间为主从库同步的时间,如果缓存当中有这个数据,直接读取主库,如果缓存当中没有这个主键,就到对应的从库中读取。
Redis 常见的性能问题和解决方案
1、master 最好不要做持久化工作,如 RDB 内存快照和 AOF 日志文件
2、如果数据比较重要,某个 slave 开启 AOF 备份,策略设置成每秒同步一次
3、为了主从复制的速度和连接的稳定性,master 和 Slave 最好在一个局域网内
redis 常见数据结构以及使用场景分析
String
常用命令
set,get,decr,incr,mget 等。
场景分析
String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。
常规key-value缓存应用; <br**> 常规计数:微博数,粉丝数等。
Hash
常用命令
hget,hset,hgetall 等
场景分析
hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。
比如我们可以 hash 数据结构来存储用户信息,商品
信息等等
信息等等
List
常用命令
lpush,rpush,lpop,rpop,lrange等
场景分析
- list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,
- Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销
- 另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。
比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的 list 结构来实现。
Set
常用命令
sadd,spop,smembers,sunion 等 set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。
场景分析
当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。
比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程
Sorted Set
常用命令
zadd,zrange,zrem,zcard等
场景分析
和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。
举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 Sorted Set 结构进行存储。
使用 Redis 做过异步队列吗,是如何实现的
- 使用 list 类型保存数据信息,rpush 生产消息,lpop 消费消息
- 当 lpop 没有消息时,可以 sleep 一段时间,然后再检查有没有信息,如果不想 sleep 的话,可以使用 blpop, 在没有信息的时候,会一直阻塞,直到信息的到来。
- redis 可以通过 pub/sub 主题订阅模式实现一个生产者,多个消费者,当然也存在一定的缺点,当消费者下线时,生产的消息会丢失。
Redis 如何实现延时队列
使用 sortedset,使用时间戳做 score, 消息内容作为 key,调用 zadd 来生产消息,消费者使用zrangbyscore 获取 n 秒之前的数据做轮询处理。
为什么redis需要把所有数据放到内存中?
Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。
MongoDB面试题
mongodb是什么
MongoDB 是由 C++语言编写的,是一个基于分布式文件存储数据库系统。在高负载的情况下,添加更多的节点,可以保证服务器性能。MongoDB 旨在给 WEB 应用提供可扩展的高性能数据存储解决方案。
MongoDB 将数据存储给一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON
对象。字段值可以包含其他文档,数组及文档数组。
对象。字段值可以包含其他文档,数组及文档数组。
mongodb有哪些特点?
MongoDB 是一个面向文档存储的数据库,操作起来比较简单和容易。
如果负载的增加(需要更多的存储空间和更强的处理能力) ,它可以分布在计算机网络中的其他节点上这就是所谓的分片。
Mongo 支持丰富的查询表达式。查询指令使用 JSON 形式的标记,可轻易查询文档中内嵌的对象及数组。
MongoDb 使用 update()命令可以实现替换完成的文档(数据)或者一些指定的数据字段 。
Mongodb 中的 Map/reduce 主要是用来对数据进行批量处理和聚合操作。
GridFS 是 MongoDB 中的一个内置功能,可以用于存放大量小文件。
MongoDB 允许在服务端执行脚本, 可以用 Javascript 编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。
mongodb的数据模型
- MongoDB的最小存储单位就是文档(document)对象。文档(document)对象对应于关系型数据库的行。数据在MongoDB中以 BSON(Binary-JSON)文档的格式存储在磁盘上。
- BSON(Binary Serialized Document Format)是一种类json的一种二进制形式的存储格式,简称Binary JSON。BSON有JSON没有的一些数据类型,如Date和BinData类型。
MongoDB的优势有哪些
面向文档的存储:以 JSON 格式的文档保存数据。
任何属性都可以建立索引。
复制以及高可扩展性。
MongoDB的复制工具称为副本集(replica set),它可提供自动故障转移和数据冗余。
MongoDB提供了水平可扩展性作为其核心功能的一部分。分片将数据分布在一组集群的机器上。
自动分片。
丰富的查询功能
mongodb的体系结构
集合
集合就是一组 MongoDB 文档。它相当于关系型数据库(RDBMS)中的表这种概念。一个集合内的多个文档可以有多个不同的字段。
文档
文档由一组key value组成。文档是动态模式,这意味着同一集合里的文档不需要有相同的字段和结构。在关系型数据库中table中的每一条记录相当于MongoDB中的一个文档。
NoSQL数据库有哪些
MongoDB, Cassandra, CouchDB, Hypertable, Redis, Riak, HBASE, Memcache
分析器在MongoDB中的作用是什么?
MongoDB中包括了一个可以显示数据库中每个操作性能特点的数据库分析器。通过这个分析器你可以找到比预期慢的查询(或写操作);
利用这一信息,比如,可以确定是否需要添加索引。
利用这一信息,比如,可以确定是否需要添加索引。
例如profile
如果用户移除对象的属性,该属性是否从存储层中删除?
是的,用户移除属性然后对象会重新保存(re-save())。
更新操作立刻fsync到磁盘?
不会,磁盘写操作默认是延迟执行的。写操作可能在两三秒(默认在60秒内)后到达磁盘。
如何执行事务/加锁
MongoDB没有使用传统的锁或者复杂的带回滚的事务,因为它设计的宗旨是轻量,快速以及可预计的
高性能。
高性能。
为什么我的数据文件如此庞大
MongoDB会积极的预分配预留空间来防止文件系统碎片。
什么是master或者主节点
是当前备份集群(replica set)中负责处理所有写入操作的主要节点/成员。
什么是secondary或slave?
Seconday从当前的primary上复制相应的操作。它是通过跟踪复制oplog(local.oplog.rs)做到的
我可以把moveChunk目录里的旧文件删除吗?
没问题,这些文件是在分片(shard)进行均衡操作(balancing)的时候产生的临时文件。一旦这些操作已经完成,相关的临时文件也应该被删除掉。但目前清理工作是需要手动的,所以请小心地考虑再释放这些文件的空间。
怎么查看 Mongo 正在使用的链接
db._adminCommand("connPoolStats");
如果块移动操作(moveChunk)失败了,我需要手动清除部分转移的文档吗?
不需要,移动操作是一致(consistent)并且是确定性的(deterministic);一次失败后,移动操作会不断重试;当完成后,数据只会出现在新的分片里(shard)。
MongoDB在A:{B,C}上建立索引,查询A:{B,C}和A:{C,B}都会使用索引吗?
不会,只会在A:{B,C}上使用索引。
MongoDB支持存储过程吗?如果支持的话,怎么用?
MongoDB支持存储过程,它是javascript写的,保存在db.system.js表中。
如何理解MongoDB中的GridFS机制,MongoDB为何使用GridFS来存储文件?
按照设计,MongoDB文档(BSON对象)不能超过16M,这是为了使性能保持在最高水平。如果文档超过16M,当查询时将占用大量的内存。GridFS指定了将一个大文件分割成多个文档的机制。
GridFS存储在两个独立的集合中:文件和块。GridFS将文件分成大块,将每个大块存储为单独的文件.GridFS中限制chunk最大为256k。GridFS使用两个文档来存储文件,一个用来存储文件本身的块,另外一个用来存储分块的信息和文件的元数据,默认对应的集合分别为fs.chunks和fs.files.
为什么MongoDB的数据文件很大?
MongoDB采用的预分配空间的方式来防止文件碎片
什么是”mongod“
mongod是处理MongoDB系统的主要进程。它处理数据请求,管理数据存储,和执行后台管理操作。当我们运行mongod命令意味着正在启动MongoDB进程,并且在后台运行。
什么是"mongo"
它是一个命令行工具用于连接一个特定的mongod实例。当我们没有带参数运行mongo命令它将使用默认的端口号和localhost连接
MongoDB哪个命令可以切换数据库
MongoDB 用 use +数据库名称的方式来创建数据库。use 会创建一个新的数据库,如果该数据库存在,则返回这个数据库。
在MongoDB中如何查看数据库列表
使用命令"show dbs"
MongoDB中的分片是什么意思
分片是将数据水平切分到不同的物理节点。
当应用数据越来越大的时候,数据量也会越来越大。当数据量增长时,单台机器有可能无法存储数据或可接受的读取写入吞吐量。利用分片技术可以添加更多的机器来应对数据量增加以及读写操作的要求
什么是复制
复制是将数据同步到多个服务器的过程,通过多个数据副本存储到多个服务器上增加数据可用性。
在MongoDB中如何在集合中插入一个文档
db.collectionName.insert({"key":"value"})
db.collectionName.save({"key":"value"})
在MongoDB中如何查看一个已经创建的集合
show collections
在MongoDB中如何删除一个集合
db.CollectionName.drop
"ObjectID"由哪些部分组成
id是一个 12 字节长的十六进制数,它保证了每一个文档的唯一性。在插入文档时,需要提供 _id 。
如果你不提供,那么 MongoDB 就会为每一文档提供一个唯一的 id。id 的头 4 个字节代表的是当前的时间戳,接着的后 3 个字节表示的是机器 id 号,接着的 2 个字节表示MongoDB 服务器进程 id,最后的 3个字节代表递增值。
在MongoDb中什么是索引
索引用于高效的执行查询.没有索引MongoDB将扫描查询整个集合中的所有文档这种扫描效率很低
索引是一种特殊的数据结构,将一小块数据集保存为容易遍历的形式。索引能够存储某种特殊字段或字段集的值,并按照索引指定的方式将字段值进行排序。
如何添加索引
db.collectionName.createIndex({columnName:1})
用什么方法可以格式化输出结果
db.collectionName.find().pretty()
如何使用"AND"或"OR"条件循环查询集合中的文档
在 find() 方法中,如果传入多个键,并用逗号( , )分隔它们,那么 MongoDB 会把它看成是AND条件。
db.mycol.find({key1:value1, key2:value2}).pretty()
db.mycol.find({key1:value1, key2:value2}).pretty()
>db.mycol.find( {$or: [ {key1: value1}, {key2:value2} ]}).pretty()
在MongoDB中如何更新数据
update() 与 save() 方法都能用于更新集合中的文档。
update() 方法更新已有文档中的值,而 save() 方法则是用传入该方法的文档来替换已有文档。
在MongoDB中如何排序
MongoDB 中的文档排序是通过 sort() 方法来实现的。sort() 方法可以通过一些参数来指定要进行排序的字段,并使用 1 和 -1 来指定排序方式,其中 1 表示升序,而 -1 表示降序。
db.connectionName.find({key:value}).sort({columnName:1})
什么是聚合
聚合操作能够处理数据记录并返回计算结果。聚合操作能将多个文档中的值组合起来,对成组数据执行各种操作,返回单一的结果。它相当于 SQL 中的 count(*) 组合 group by。对于 MongoDB 中的聚合操作,应该使用 aggregate() 方法。
db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
在MongoDB中什么是副本集
在MongoDB中副本集由一组MongoDB实例组成,包括一个主节点多个次节点,MongoDB客户端的所有数据都写入主节点(Primary),副节点从主节点同步写入数据,以保持所有复制集内存储相同的数据,提高数据可用性。
框架相关面试题
Spring
说说你对Spring的理解
介绍
Spring是JAVA企业级应用开源框架,可以实现EJB可以实现的功能,Spring是一个IOC和AOP容器框架。
控制反转(IOC)
Spring容器使用了工厂模式为我们创建了所需要的对象并创建各个实例之间的依赖关系,我们使用时不需要自己去创建,直接调用Spring为我们提供的对象即可,这就是控制反转的思想。
依赖注入(DI)
Spring使用Java Bean对象的Set方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程就是依赖注入的基本思想。
面向切面编程(AOP)
在面向切面编程思想,我们将一个个对象某些类似的方面横向抽象成一个切面,从而达到对一个模块的扩充。我们可以对这个切面进行一些如权限验证、事务管理、记录日志等公用操作处理的过程
使用Spring框架的好处是什么
轻量
Spring 是轻量的,基本的版本大约2MB。
控制反转
Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
面向切面的编程(AOP)
Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
容器
Spring 包含并管理应用中对象的生命周期和配置。
MVC框架
Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品
事务管理
Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。
异常处理
Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。
Spring的核心模块
核心容器
核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。S核心容器的主要主件是BeanFactory,它是工厂模式的实现。BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开
应用上下文(SpringContext)
是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能
面向切面编程(Spring Aop)
是面向对象编程的有效补充和完善,Spring的AOP是基于动态代理实现的,实现的方式有两种分别是Schema和AspectJ这两种方式
JDBC和Dao模块(SpringDao)
JDBC、DAO的抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息。
对象实体映射(SpringORM)
Spring框架插入了若干个ORM框架,从而提供了ORM对象的关系工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有这些都遵从Spring的通用
事物和DAO异常层次结构。
事物和DAO异常层次结构。
Web模块(SpringWeb)
Web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供了上下文。所以Spring框架支持与Struts集成,web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作
MVC模块(Spring MVC)
MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。MVC容纳了大量视图技术,其中包括JSP、POI等,模型由JavaBean构成,存放于m当中,而视图是一个接口,负责实现模型,控制器表示逻辑代码
Spring框架使用了哪些设计模式
单例模式
工厂模式
原型模式
适配器模式
代理模式
观察者模式
Spring支持的ORM
JPA (Java Persistence API)
Hibernate
OJB
TopLink
Spring Framework 有哪些不同的功能?
轻量级 - Spring 在代码量和透明度方面都很轻便。
IOC - 控制反转
AOP - 面向切面编程可以将应用业务逻辑和系统服务分离,以实现高内聚。
MVC - 对 web 应用提供了高度可配置性,其他框架的集成也十分方便。
事务管理 - 提供了用于事务管理的通用抽象层。Spring 的事务支持也可用于容器较少的环境。
JDBC 异常 - Spring 的 JDBC 抽象层提供了一个异常层次结构,简化了错误处理策略
什么是Spring的MVC框架
Spring 配备构建Web 应用的全功能MVC框架。Spring可以很便捷地和其他MVC框架集成,如Struts,Spring 的MVC框架用控制反转把业务对象和控制逻辑清晰地隔离。
springmvc常用到的注解,作用是什么
@Controller注解
- @Controller注解注解的控制器可以同时支持处理多个请求动作,使程序开发变的更加灵活。 @Controller用户标记一个类,使用它标记的类就是一个Spring MVC Controller对象,即:一个控制器类。
- Spring使用扫描机制查找应用程序中所有基于注解的控制器类,分发处理器会扫描使用了该注解的方法,并检测该方法是否使用了@RequestMapping注解,而使用@RequestMapping注解的方法才是真正处理请求的处理器。
@RequestParam注解
该注解类型用于将指定的请求参数赋值给方法中的形参。
@PathVaribale注解
非常方便的获得请求url中的动态参数。@PathVaribale注解只支持一个属性value,类型String,表示绑定的名称,如果省略则默认绑定同名参数。
在 Spring 中,有几种配置 Bean 的方式?
基于XML的配置
基于注解的配置
基于Java的配置
BeanFactory和ApplicationContext区别
描述
BeanFactory:
是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;无法支持spring的许多插件,如AOP功能、Web应用等。
ApplicationContext:
应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能;
国际化(MessageSource)
访问资源,如URL和文件(ResourceLoader)
载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
消息发送、响应机制(ApplicationEventPublisher)
AOP(拦截器)
两者装载bean的区别
BeanFactory:
BeanFactory采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化
ApplicationContext:
- ApplicationContext,他是在容器启动时,一次性创建了所有的Bean。这样在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean,确保当你需要的时候,不需要等待,因为他们已经创建好了。
- 它还可以为Bean配置lazy-init=true来让Bean延迟实例化;
BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式被创建,如使用ContextLoader.
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFatory需要手动注册,而ApplicationContext则是自动注册。
Spring Bean 的生命周期
实例化Bean
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,容器就会调用createBean进行实例化。
对于ApplicationContext,他是在容器启动时,一次性创建了所有的Bean。可以为Bean配置lazy-init=true来让Bean延迟实例化;
设置对象属性(依赖注入)
初始化
Aware
接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(StringbeanId)方法,此处传递的就是Spring配置文件中Bean的id值;
如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
BeanPostProcessor
如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;
InstantiationAwareBeanPostProcessor
InstantiationAwareBeanPostProcessor接口继承BeanPostProcessor接口,它内部提供了3个方法,再加上BeanPostProcessor接口内部的2个方法,所以实现这个接口需要实现5个方法。InstantiationAwareBeanPostProcessor接口的主要作用在于目标对象的实例化过程中需要处理的事情,包括实例化对象的前后过程以及实例的属性设置
SmartInstantiationAwareBeanPostProcessor
智能实例化Bean后置处理器(继承InstantiationAwareBeanPostProcessor)
MergedBeanDefinitionPostProcessor
SmartInitializingSingleton
智能初始化Singleton,该接口只有一个方法,是在所有的非懒加载单实例bean都成功创建并且放到Spring IOC容器之后进行执行的
InitializingBean 与 init-method
如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法
销毁
DisposableBean
当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;
destroy-method
最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
Spring框架中的单例bean是线程安全的吗
肯定不是线程安全的,当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求多对应的业务逻辑(成员方法),此时就要注意了,如果该处理逻辑中有对该单列状态的修改(体现为该单列的成员属性),则必须考虑线程同步问题.
Spring框架并没有对单例bean进行任何多线程的封装处理。如果你的bean有多种状态的话。最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。
Spring如何处理线程并发问题
在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
. 什么是Spring的内部bean
当一个bean仅被用作另一个bean的属性时,能被声明为一个内部bean,为了定义inner bean。内部bean通常是匿名的,它们的Scope一般是prototype。
Spring Bean 有哪些作用域,它们之间有什么区别?
singleton
这种 bean 范围是默认的,这种范围确保不管接受到多少个请求,每个容器中只有一个bean 的实例,单例的模式由 bean factory 自身来维护 。
prototype
原形范围与单例范围相反,为每一个 bean 请求提供一个实例 。
request
在请求 bean 范围内会每一个来自客户端的网络请求创建一个实例,在请求完成以后, bean会失效并被垃圾回收器回收 。
Session
与请求范围类似,确保每个 session 中有一个 bean 的实例,在 session 过期后, bean 会随之失效
Springmvc controller方法中为什么不能定义局部变量?
因为controller是默认单例模式,高并发下全局变量会出现线程安全问题
如何解决呢
都改为局部变量
ThreadLocal,它为多线程并发提供了新思路。
使用@Scope("session"),会话级别
将控制器的作用域从单例改为原型,即在spring配置文件Controller中声明 scope="prototype",每次都创建新的controller
什么是Spring MVC框架的控制器
控制器提供一个访问应用程序的行为,此行为通常通过服务接口实现。控制器解析用户输入并将其转换为一个由视图呈现给用户的模型。
用于接收请求,调用相应的 Model 进行处理,处理器完成业务处理后返回处理结果。
用于接收请求,调用相应的 Model 进行处理,处理器完成业务处理后返回处理结果。
Spring DAO 有什么用
- DAO不但屏蔽了数据存储的最终介质的不同,也屏蔽了具体的实现技术的不同。使得 JDBC,Hibernate 或 JDO 这样的数据访问技术更容易以一种统一的方式工作。
- Spring面向DAO制定了一个通用的异常体系,屏蔽具体持久化技术的异常,使业务层和具体的持久化技术实现解耦。
Spring JDBC API 中存在哪些类
JdbcTemplate
SimpleJdbcTemplate
Spring框架中有哪些不同类型的事件
Spring 的 ApplicationContext 提供了支持事件和代码中监听器的功能。 我们可以创建 bean 用来监听在 ApplicationContext 中发布的事件。ApplicationEvent 类和在 ApplicationContext 接口中处理的事件,如果一个 bean 实现了 ApplicationListener 接口,当一个 ApplicationEvent 被发布以后,bean 会自动被通知。
1. 上下文更新事件(ContextRefreshedEvent):该事件会在 ApplicationContext 被初始化或者 更新时发布。也可以在调用 ConfigurableApplicationContext 接口中的 refresh()方法时被触 发。
2. 上下文开始事件(ContextStartedEvent):当容器调用 ConfigurableApplicationContext 的 Start()方法开始/重新开始容器时触发该事件。
3. 上下文停止事件(ContextStoppedEvent):当容器调用 ConfigurableApplicationContext 的 Stop()方法停止容器时触发该事件。
4. 上下文关闭事件(ContextClosedEvent):当 ApplicationContext 被关闭时触发该事件。容器 被关闭时,其管理的所有单例 Bean 都被销毁。
5. 请求处理事件(RequestHandledEvent):在 Web 应用中,当一个 http 请求(request)结束 触发该事件。
6. 除了上面介绍的事件以外,还可以通过扩展 ApplicationEvent 类来开发自定义的事件
Spring 框架有哪些自动装配模式,它们之间有何区
别?
别?
no
这是 Spring 框架的默认设置,在该设置下自动装配是关闭的,开发者需要自行在 bean 定义中用标签明确的设置依赖关系
byName
该选项可以根据 bean 名称设置依赖关系 。 当向一个 bean 中自动装配一个属性时,容器
将根据 bean 的名称自动在在配置文件中查询一个匹配的 bean。
将根据 bean 的名称自动在在配置文件中查询一个匹配的 bean。
byType
该选项可以根据 bean 类型设置依赖关系 。 当向一个 bean 中自动装配一个属性时,容器将
根据 bean 的类型自动在在配置文件中查询一个匹配的 bean。
根据 bean 的类型自动在在配置文件中查询一个匹配的 bean。
constructor
构造器的自动装配和 byType 模式类似,但是仅仅适用于与有构造器相同参数的 bean,如果在容器中没有找到与构造器参数类型一致的 bean ,那么将会抛出异常 。
autodetect
该模式自动探测使用构造器自动装配或者 byType 自动装配 。 首先,首先会尝试找合适的带参数的构造器,如果找到的话就是用构造器自动装配,如果在 bean 内部没有找到相应的构造器或者是无参构造器,容器就会自动选择 byTpe 的自动装配方式
SpringMVC执行流程和原理 SpringMVC流程
六个组件
DisPatcherServlet 前端控制器
核心。用户在浏览器输入url,发起请求,首先会到达DisPatcherServlet,由它来调用其他组件来配合工作的完成,DisPatcherServlet的存在大大降低了组件之间的耦合性
HandlerMapping 处理器映射器
记录url与处理器的映射,方式有注解、XML配置等
HandLer 处理器
后端控制器(通俗一点:Controller层所写的业务代码)。对用户的请求进行处理
HandlerAdapter 处理器适配器
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
ViewResolver 视图解析器
ViewResolver负责解析view视图,并进行渲染(数据填充),将处理结果通过页面展示给用户看
View 视图
View是一个接口,实现类支持不同的View类型(jsp、freemarker、velocity)
1、用户发送出请求到前端控制器DispatcherServlet。
2、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。
3、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
4、DispatcherServlet调用HandlerAdapter(处理器适配器)
5、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。
6、Controller执行完成返回ModelAndView对象。
7、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。
8、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。
9、ViewReslover解析后返回具体View(视图)。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)
11、DispatcherServlet响应用户
WebApplicationContext
WebApplicationContext 继承了ApplicationContext 并增加了一些WEB应用必备的特有功能,它不同于一般的ApplicationContext ,因为它能处理主题,并找到被关联的servlet。
你用过哪些重要的 Spring 注解
@Controller - 用于 Spring MVC 项目中的控制器类。
@Service - 用于服务类。
@RequestMapping - 用于在控制器处理程序方法中配置 URI 映射。
@ResponseBody - 用于发送 Object 作为响应,通常用于发送 XML 或 JSON 数据作为响应。
@PathVariable - 用于将动态值从 URI 映射到处理程序方法参数。
@Autowired - 用于在 spring bean 中自动装配依赖项。
@Qualifier - 使用 @Autowired 注解,以避免在存在多个 bean 类型实例时出现混淆。
@Scope - 用于配置 spring bean 的范围。
@Configuration,@ComponentScan 和 @Bean - 用于基于 java 的配置。
@Aspect,@Before,@After,@Around,@Pointcut - 用于切面编程(AOP)
@Service - 用于服务类。
@RequestMapping - 用于在控制器处理程序方法中配置 URI 映射。
@ResponseBody - 用于发送 Object 作为响应,通常用于发送 XML 或 JSON 数据作为响应。
@PathVariable - 用于将动态值从 URI 映射到处理程序方法参数。
@Autowired - 用于在 spring bean 中自动装配依赖项。
@Qualifier - 使用 @Autowired 注解,以避免在存在多个 bean 类型实例时出现混淆。
@Scope - 用于配置 spring bean 的范围。
@Configuration,@ComponentScan 和 @Bean - 用于基于 java 的配置。
@Aspect,@Before,@After,@Around,@Pointcut - 用于切面编程(AOP)
DispatcherServlet
Spring的MVC框架是围绕DispatcherServlet来设计的,它用来处理所有的HTTP请求和响应。
@Component, @Controller, @Repository, @Service 有何区别?
@Component:这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。
@Controller:这将一个类标记为 Spring Web MVC 控制器。标有它的 Bean 会自动导入到 IoC 容器中。
@Service:此注解是组件注解的特化。层类中使用 @Service 而不是 @Component,因为它以更好的方式指定了意图。
@Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。
@Autowired 注解解释
通过类型来实现自动注入bean。和@Qualifier注解配合使用可以实现根据name注入bean。
@Qualifier 注解解释
和@Autowired一块使用,在同一类型的bean有多个的情况下可以实现根据name注入的需求
@Autowire和@Resource区别
注解来源
@Autowire Spring注解
@Resource JDK注解(JSR-250标准注解,属于J2EE)
装配方式
@Autowire 优先按类型
@Resource 优先按名称
Spring的IOC理解
IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。
列举 IoC 的一些好处
它将最小化应用程序中的代码量。
它以最小的影响和最少的侵入机制促进松耦合。
它将使您的应用程序易于测试
Spring通知有哪些类型
前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知
后通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。 环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。
解释一下Spring AOP里面的几个名词
切面(Aspect)
被抽取的公共模块,可能会横切多个对象。
连接点(Join point)
指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。
通知(Advice)
在切面的某个特定的连接点(Join point)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。
切入点(Pointcut):
切入点是指 我们要对哪些Join point进行拦截的定义。通过切入点表达式,指定拦截的方法
引入(Introduction)
(也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
目标对象(Target Object)
被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
织入(Weaving)
指把增强应用到目标对象来创建新的代理对象的过程。Spring是在运行时完成织入。
Spring中AOP的底层是怎么实现的
Spring中AOP底层的实现其实是基于JDK的动态代理和cglib动态创建类进行动态代理来实现的:
基于JDK的动态代理的原理
需要用到的几个关键成员 InvocationHandler (你想要通过动态代理生成的对象都必须实现这个接口)真实的需要代理的对象(帮你代理的对象) Proxy对象(是JDK中java.lang.reflect包下的)
首先你的真是要代理的对象必须要实现InvocationHandler 这个接口,并且覆盖这个接口的invoke(Object proxyObject, Method method, Object[] args)方法,这个Invoker中方法的参数的proxyObject就是你要代理的真实目标对象,方法调用会被转发到该类的invoke()方法, method是真实对象中调用方法的Method类,Object[] args是真实对象中调用方法的参数
然后通过Proxy类去调用newProxyInstance(classLoader, interfaces, handler)方法,classLoader是指真实代理对象的类加载器,interfaces是指真实代理对象需要实现的接口,还可以同时指定多个接口,handler方法调用的实际处理者(其实就是帮你代理的那个对象),代理对象的方法调用都会转发到这里,然后直接就能生成你想要的对象类了。
Spring事务的实现方式和实现原理
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
Spring事务的种类
spring支持编程式事务管理和声明式事务管理两种方式:
①编程式事务管理使用TransactionTemplate。
②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务
spring的事务传播行为
① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务
⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
Spring中的隔离级别:
① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
② ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务可以看到这个事务未提交的数据。
③ ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。
④ ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。
⑤ ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。
Springbean的生命周期
1. 首先会先进行实例化bean对象
2. 然后是进行对bean的一个属性进行设置
3. 接着是对BeanNameAware(其实就是为了让Spring容器来获取bean的名称)、BeanFactoryAware(让bean的BeanFactory调用容器的服务)、ApplicationContextAware(让bean当前的applicationContext可以来取调用Spring容器的服务)
4. 然后是实现BeanPostProcessor 这个接口中的两个方法,主要是对调用接口的前置初始化postProcessBeforeInitialization 这里是主要是对xml中自己定义的初始化方法 init-method = “xxxx”进行调用 然后是继续对BeanPostProcessor 这个接口中的后置初始化方法进行一个调用postProcessAfterInitialization() 其实到这一步,这个bean的初始化基本已经完成,就处于就绪状态
5. 然后就是当Spring容器中如果使用完毕的话,就会调用destory()方法 最后会去执行我们自己定义的销毁方法来进行销毁,然后结束生命周期
2. 然后是进行对bean的一个属性进行设置
3. 接着是对BeanNameAware(其实就是为了让Spring容器来获取bean的名称)、BeanFactoryAware(让bean的BeanFactory调用容器的服务)、ApplicationContextAware(让bean当前的applicationContext可以来取调用Spring容器的服务)
4. 然后是实现BeanPostProcessor 这个接口中的两个方法,主要是对调用接口的前置初始化postProcessBeforeInitialization 这里是主要是对xml中自己定义的初始化方法 init-method = “xxxx”进行调用 然后是继续对BeanPostProcessor 这个接口中的后置初始化方法进行一个调用postProcessAfterInitialization() 其实到这一步,这个bean的初始化基本已经完成,就处于就绪状态
5. 然后就是当Spring容器中如果使用完毕的话,就会调用destory()方法 最后会去执行我们自己定义的销毁方法来进行销毁,然后结束生命周期
BeanFactory – BeanFactory 实现举例
Bean 工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从正真的应用代码
中分离。最常用的BeanFactory 实现是XmlBeanFactory 类。
中分离。最常用的BeanFactory 实现是XmlBeanFactory 类。
Mybatis
什么是Mybatis
Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。
MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
ORM是什么
ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。
为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
- Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
- 而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
传统JDBC开发存在什么问题
频繁创建数据库连接对象、释放,容易造成系统资源浪费,影响系统性能。
sql语句定义、参数设置、结果集处理存在硬编码。
使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
JDBC编程有哪些不足之处,MyBatis是如何解决的?
数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
解决:在mybatis-config.xml中配置数据链接池,使用连接池管理数据库连接。
Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。-
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应
解决: Mybatis自动将java对象映射至sql语句
MyBatis的工作原理
读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在MyBatis 配置文件 mybatis-config.xml 中加载。
构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
创建会话对象:由会话工厂创建 SqlSession 对象 sqlSession = sqlSessionFactory.openSession();
Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
为什么需要预编译
- DBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。预编译阶段可以优化SQL 的执行。预编译之后的 SQL 多数情况下可以直接执行。越复杂的SQL,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。
- 同时预编译语句对象可以重复利用。把一个 SQL 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个SQL,可以直接使用这个缓存的 PreparedState 对象。
#{}和${}的区别是什么
- #{} 是预编译处理
- ${} 是字符串替换。
Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
Mybatis在处理${}时,就是把${}替换成变量的值。
Mybatis都有哪些Executor执行器?它们之间的区别是什么?
SimpleExecutor
每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor
执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
BatchExecutor
BatchExecutor:执行update(DBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同
通常一个Xml映射文件,都会写一个Dao接口与之对应,这个Dao接口的工作原理是什么?
Dao接口里的方法,参数不同时,方法能重载吗?
Dao接口里的方法,参数不同时,方法能重载吗?
MappedStatement 类是 Mybatis 框架的核心类之一。Mybatis 通过解析 XML 和 mapper 接口上的注解,生成 sql 对应的 MappedStatement 实例,并放入 SqlSessionTemplate 中 configuration 类属性中正真执行 mapper 接口中的方法时,会从 configuration 中找到对应的 mappedStatement,然后进行后续的操作
接口全限名+方法名拼接字符串作为 key 值对应一个mappedStatement ,接口可以有多个重载方法,但是多个接口对应的映射必须只有一个
Mybatis是如何进行分页的?分页插件的原理是什么?
Mybatis 使用 RowBounds 对象进行分页(逻辑分页,结果集全查出来进行分页),也可以直接编写 sql 实现分页,也可以使用Mybatis 的分页插件。
如何获取自动生成的(主)键值
usegeneratedkeys=”true” keyproperty=”id”
Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
- Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。
- 在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false
实现原理
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法。
比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
Mybatis动态sql有什么用?执行原理?有哪些动态sql
Mybatis提供了9种动态sql标签: trim|where|set|foreach|if|choose|when|otherwise|bind 。
其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
MyBatis实现一对一有几种方式?具体怎么操作的?
有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成;
嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置
MyBatis实现一对多有几种方式,怎么操作的
有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的collection节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的 结果的外键id,去再另外一个表里面查询数据,也是通过配置collection,但另外一个表的查询通过select节点配置。
Mybatis的一级、二级缓存
一级缓存
一级缓存是BaseExecutor中的一个成员变量localCache(对HashMap的一个简单封装),因此一级缓存的生命周期与SqlSession相同。其存储作用域为 Session,当 Sessionflush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
二级缓存
- 二级缓存是Configuration对象的成员变量,因此二级缓存的生命周期是整个应用级别的。并且是基于namespace构建的,一个namesapce构建一个缓存。二级缓存不像一级缓存那样查询完直接放入一级缓存,而是要等事务提交时才会将查询出来的数据放到二级缓存中。
- 可能会出现多个namespace操作同一个表,读取到脏数据。可以通过mapper 文件加入<cache-ref namespace="mapper.StudentMapper"/>
缓存数据更新机制
当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D操作后,默认该作用域下所有 select 中的缓存将被 clear。
接口绑定有几种实现方式,分别是怎么实现的
- 一种是通过注解绑定,就是在接口的方法上面加上@Select@Update 等注解里面包含 Sql 语句来绑定
- 另外一种就是通过 xml 里面写 SQL 来绑定,在这种情况下,要指定 xml 映射文件里面的 namespace 必须为接口的全路径名.
简述Mybatis的插件运行原理,以及如何编写一个插件。
Mybatis 仅可以编写针对 StatementHandler、ParameterHandler、ResultSetHandler、Executor 这 4 种接口的插件,Mybatis 通过动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler 的 invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
SpringBooot
什么是springboot
简化spring应用的初始搭建以及开发过程 使用特定的方式来进行配置
创建独立的spring引用程序 main方法运行
嵌入的Tomcat 无需部署war文件
简化maven配置
自动配置spring添加对应功能starter自动化配置
怎么理解 Spring Boot 中 “约定优于配置“
Spring Boot Starter、Spring Boot Jpa 都是“约定优于配置“的一种体现。都是通过“约定优于配置“的设计思路来设计的
Spring Boot Starter 在启动的过程中会根据约定的信息对资源进行初始化;
SpringBoot Jpa 通过约定的方式来自动生成 Sql ,避免大量无效代码编写
Spring Boot Starter 在启动的过程中会根据约定的信息对资源进行初始化;
SpringBoot Jpa 通过约定的方式来自动生成 Sql ,避免大量无效代码编写
Spring Boot扫描流程
调用run方法中的 refreshContext 方法
用AbstractApplicationContext中的 refresh 方法
委托给 invokeBeanFactoryPostProcessors 去处理调用链
这个方法是实例化并调用所有注册的BeanFactoryPostProcessor bean
其中一个方法 postProcessBeanDefinitionRegistry会 去调用 processConfigBeanDefinitions解析 beandefinitions
在 processConfigBeanDefinitions 中有一个 parse 方法,其中有 componentScanParser.parse的方法,这个方法会扫描当前路径下所有 Component 组件
Spring Boot 如何定义多套不同环境配置
applcation.properties
application-dev.properties
application-test.properties
application-prod.properties
application-dev.properties
application-test.properties
application-prod.properties
在applcation.properties文件中指定当前的环境spring.profiles.active=test
Spring Boot 有哪几种读取配置的方式
@Environment
@Autowired
private Environment environment;
environment.getProperty("server.port")
private Environment environment;
environment.getProperty("server.port")
@Value
@Value("${server.port}")
private Integer port;
private Integer port;
@ConfigurationProperties
@ConfigurationProperties(prefix = "ksd.alipay")//这个注解是用找到类
Spring Boot 支持哪些日志框架?
Spring Boot 支持 Java Util Logging, Log4j2, Lockback 作为日志框架
如何重新加载Spring Boot上的更改
Spring Boot有一个开发工具(DevTools)模块,它有助于提高开发人员的生产力。Java开发人员面临的一个主要挑战是将文件更改自动部署到服务器并自动重启服务器。
你如何理解 Spring Boot 中的 Starters
Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成 Spring及其他技术,而不需要到处找示例代码和依赖包。如你想使用 Spring JPA 访问数据库,只要加入spring-boot-starter-data-jpa 启动器依赖就能使用了。
spring-boot-starter-parent 有什么用 ?
定义了 Java 编译版本为 1.8 。
使用 UTF-8 格式编码。
继承自 spring-boot-dependencies,这个里边定义了依赖的版本,也正是因为继承了这个依赖,所以我们在写依赖时才不需要写版本号。
执行打包操作的配置。
自动化的资源过滤。
自动化的插件配置。
SpringBoot常用的starter有哪些?
spring-boot-starter-web (嵌入tomcat和web开发需要servlet与jsp支持)
spring-boot-starter-data-jpa (数据库支持)
spring-boot-starter-data-redis (redis数据库支持)
spring-boot-starter-data-solr (solr搜索应用框架支持)
mybatis-spring-boot-starter (第三方的mybatis集成starter)
pring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下3 个注解
- @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
- @ComponentScan:Spring组件扫描
Spring Boot 中如何实现定时任务
使用 Spring 中的 @Scheduled 的方式主要通过 @Scheduled 注解来实现。
使用 Quartz ,则按照 Quartz 的方式,定义 Job 和 Trigger 即可。
Spring Boot 还提供了其它的哪些 Starter Project Options
spring-boot-starter-security - 使用 SpringSecurity 进行身份验证和授
spring-boot-starter-web-services - SOAP Web Services
spring-boot-starter-web - Web 和 RESTful 应用程序
spring-boot-starter-test - 单元测试和集成测试
spring-boot-starter-jdbc - 传统的 JDBC
spring-boot-starter-security - 使用 SpringSecurity 进行身份验证和授
spring-boot-starter-data-jpa - 带有 Hibeernate 的 Spring Data JPA
如何禁用一个特定自动配置类
使用 @EnableAutoConfiguration 的 exclude 属性。
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
如果类不在类路径上,可以使用 @EnableAutoConfiguration 的 excludeName 属性
@EnableAutoConfiguration(excludeName={Foo.class})
可以使用配置spring.autoconfigure.exclude属性来控制要排除的自动配置类列表。
Spring Boot中的监视器是什么
- Spring boot actuator是spring启动框架中的重要功能之一。Spring boot监视器可帮助您访问生产环境中正在运行的应用程序的当前状态。
- 有几个指标必须在生产环境中进行检查和监控。即使一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器模块公开了一组可直接作为HTTP URL访问的REST端点来检查状态。
什么是 Spring Batch
spring batch 是一个 方便使用的 较健全的 批处理 框架
- Spring Boot Batch 提供可重用的函数,这些函数在处理大量记录时非常重要;包括日志/跟踪,事务管理,作业处理统计信息,作业重新启动,跳过和资源管理。
- 它还提供了更先进的技术服务和功能,通过优化和分区技术,可以实现极高批量和高性能批处理作业。简单以及复杂的大批量批处理作业可以高度可扩展的方式利用框架处理重要大量的信息。
Spring Boot 中如何解决跨域问题 ?
推荐在后端通过 (CORS,Cross-origin resourcesharing) 来解决跨域问题。这种解决方案并非 Spring Boot 特有的,在传统的 SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.maxAge(3600);
}
}
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.maxAge(3600);
}
}
Spring & SpringBoot 常用注解
参数校验
- JSR(Java Specification Requests) 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们 JavaBean 的属性上面,这样就可以在需要校验的时候进行校验了,非常方便!
- 校验的时候我们实际用的是 Hibernate Validator 框架。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@NotEmpty 被注释的字符串的不能为 null 也不能为空
@NotBlank 被注释的字符串非 null,并且必须包含一个非空白字符
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
@Email 被注释的元素必须是 Email 格式。
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@NotBlank 被注释的字符串非 null,并且必须包含一个非空白字符
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
@Email 被注释的元素必须是 Email 格式。
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
全局处理 Controller 层异常
相关注解
@ControllerAdvice :注解定义全局异常处理类
@ExceptionHandler :注解声明异常处理方法
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
/*** 请求参数异常处理 */
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) { ......}
}
@ResponseBody
public class GlobalExceptionHandler {
/*** 请求参数异常处理 */
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) { ......}
}
测试相关
@ActiveProfiles 一般作用于测试类上, 用于声明生效的 Spring 配置文件。
@Test 声明一个方法为测试方法
@Transactional 被声明的测试方法的数据会回滚,避免污染测试数据
@SpringBootTest(webEnvironment = RANDOM_PORT)
@ActiveProfiles("test")
@Slf4j
public abstract class TestBase { ......
}
@ActiveProfiles("test")
@Slf4j
public abstract class TestBase { ......
}
微服务
SpringCloud面试题
什么是微服务
- 单个轻量级服务一般为一个单独微服务,微服务讲究的是 专注某个功能的实现,比如登录系统只专注于用户登录方面功能的实现,讲究的是职责单一,开箱即用,可以独立运行。
- 微服务架构系统是一个分布式的系统,按照业务进行划分服务单元模块,解决单个系统的不足,满足越来越复杂的业务需求。
Spring Cloud的版本关系
Spring Cloud是一个由许多子项目组成的综合项目,各子项目有不同的发布节奏。为了避免Spring Cloud版本号与子项目版本号混淆,Spring Cloud版本采用了名称而非版本号的命名,这些版本的名字采用了伦敦地铁站的名字,当Spring Cloud的发布内容积累到临界点或者一个重大BUG被解决后,会发布一个"service releases"版本,简称SRX版本,
Spring Cloud的子项目(主要项目)
Spring Cloud Config
集中配置管理工具,分布式系统中统一的外部配置管理,默认使用Git来存储配置,可以支持客户端配置的刷新及加密、解密操作。
Spring Cloud Netflix
Netflix OSS 开源组件集成,包括Eureka、Hystrix、Ribbon、Feign、Zuul等核心组件。
- Eureka:服务治理组件,包括服务端的注册中心和客户端的服务发现机制;Eureka:服务治理组件,包括服务端的注册中心和客户端的服务发现机制;
- Ribbon:负载均衡的服务调用组件,具有多种负载均衡调用策略;
- Hystrix:服务容错组件,实现了断路器模式,为依赖服务的出错和延迟提供了容错能力
- Feign:基于Ribbon和Hystrix的声明式服务调用组件;
- Zuul:API网关组件,对请求提供路由及过滤功能。
Spring Cloud Bus
用于将服务和服务实例与分布式消息系统链接在一起的事件总线。在集群中传播状态更改很有用(例如配置更改事件)。
Spring Cloud Consul
基于Hashicorp Consul的服务治理组件。
Spring Cloud Security
安全工具包,对Zuul代理中的负载均衡OAuth2客户端及登录认证进行支持
Spring Cloud Sleuth
Spring Cloud应用程序的分布式请求链路跟踪,支持使用Zipkin、HTrace和基于日志(例如ELK)的跟踪。
Spring Cloud Stream
轻量级事件驱动微服务框架,可以使用简单的声明式模型来发送及接收消息,主要实现为Apache Kafka及RabbitMQ。
Spring Cloud Task
用于快速构建短暂、有限数据处理任务的微服务框架,用于向应用中添加功能性和非功能性的特性。
Spring Cloud Zookeeper
基于Apache Zookeeper的服务治理组件。
Spring Cloud Gateway
API网关组件,对请求提供路由及过滤功能。
Spring Cloud OpenFeign
基于Ribbon和Hystrix的声明式服务调用组件,可以动态创建基于Spring MVC注解的接口实现用于服务调用,在Spring Cloud 2.0中已经取代Feign成为了一等公民。
spring cloud 的核心组件有哪些?
- Eureka:服务注册于发现。
- Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。
- Ribbon:实现负载均衡,从一个服务的多台机器中选择一台。
- Hystrix:提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。
- Zuul:网关管理,由 Zuul 网关转发请求给对应的服务。
springcloud如何实现服务的注册?
服务发布时,指定对应的服务名,将服务注册到 注册中心(eureka zookeeper)
注册中心加@EnableEurekaServer,服务用@EnableDiscoveryClient,然后用ribbon或feign进行服务直接的调用发现。
什么是Eureka
eureka是Netflix开发的服务发现组件,本身是一个基于REST的服务。Spring Cloud将它集成在其子项目spring-cloud-netflix中, 以实现Spring Cloud的服务发现功能。
Eureka包括两个端:
- Eureka Server:注册中心服务端,用于维护和管理注册服务列表。
- Eureka Client:注册中心客户端,向注册中心注册服务的应用都可以叫做Eureka Client(包括Eureka Server本身)。
Eureka和zookeeper都可以提供服务注册与发现的功能,请说说两个的区别?
zookeeper 是CP原则,强一致性和分区容错性
eureka 是AP 原则 可用性和分区容错性
zookeeper当主节点故障时,zk会在剩余节点重新选择主节点,耗时过长,虽然最终能够恢复,但是选取主节点期间会导致服务不可用,这是不能容忍的。
eureka各个节点是平等的,一个节点挂掉,其他节点仍会正常保证服务。
eureka各个节点是平等的,一个节点挂掉,其他节点仍会正常保证服务。
SpringBoot 和 SpringCloud 之间关系?
SpringBoot:专注于快速方便的开发单个个体微服务(关注微观);
SpringCloud:关注全局的微服务协调治理框架,将SpringBoot开发的一个个单体微服务组合并管理起来(关注宏观);
SpringCloud 和 Dubbo 有哪些区别?
首先,他们都是分布式管理框架。
dubbo 是二进制传输,占用带宽会少一点。SpringCloud是http 传输,带宽会多一点,同时使用http协议一般会使用JSON报文,消耗会更大。
dubbo 开发难度较大,所依赖的 jar 包有很多问题大型工程无法解决。SpringCloud 对第三方的继承可以一键式生成,天然集成。
虽然在一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生RPC带来的问题。而且REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,
dubbo 是二进制传输,占用带宽会少一点。SpringCloud是http 传输,带宽会多一点,同时使用http协议一般会使用JSON报文,消耗会更大。
dubbo 开发难度较大,所依赖的 jar 包有很多问题大型工程无法解决。SpringCloud 对第三方的继承可以一键式生成,天然集成。
虽然在一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生RPC带来的问题。而且REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,
什么是Spring Cloud Config?
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。
在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。
在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。
什么是Ribbon?
ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。feign默认集成了ribbon。
什么是feigin?它的优点是什么?
feign采用的是基于接口的注解
feign整合了ribbon,具有负载均衡的能力
整合了Hystrix,具有熔断的能力
使用
添加pom依赖
启动类添加@EnableFeignClients
定义一个接口@FeignClient(name=“xxx”)指定调用哪个服务
Ribbon和Feign的区别
调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。Feign需要将调用的方法定义成抽象方法即可。
什么是Spring Cloud Gateway
Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,取代Zuul网关。网关作为流量的,在微服务系统中有着非常作用,网关常见的功能有路由转发、权限校验、限流控制等作用。
什么是熔断?什么是服务降级?
熔断
服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。
服务降级
服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的fallback(退路)错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。
Dubbo面试题
Dubbo是什么
Dubbo是阿里巴巴开源的基于 Java 的高性能 RPC 分布式服务框架,现已成为 Apache 基金会孵化项目。
为什么要用Dubbo
内部使用了 Netty、Zookeeper,保证了高性能高可用性。
使用 Dubbo 可以将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,可用于提高业务复用灵活扩展,使前端应用能更快速的响应多变的市场需求。
Dubbo里面有哪几种节点角色
Provider
暴露服务的服务提供者
Consumer
调用远程服务的服务消费方
Registry
服务注册与发现的注册中心
Monitor
统计服务的调用次数和调用时间的监控中心
Container
服务运行容器
画一画服务注册与发现的流程图
子主题
Dubbo默认使用什么注册中心,还有别的选择吗?
推荐使用 Zookeeper 作为注册中心,还有 Redis、Multicast、Simple 注册中心,但不推荐。
Dubbo有哪几种配置方式?
Spring 配置方式
Java API 配置方式
在 Provider 上可以配置的 Consumer 端的属性有哪些?
timeout:方法调用超时
retries:失败重试次数,默认重试 2 次
loadbalance:负载均衡算法,默认随机
actives 消费者端,最大并发调用限制
Dubbo启动时如果依赖的服务不可用会怎样
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,默认 check="true",可以通过 check="false" 关闭检查。
Dubbo默认使用的是什么通信框架
Dubbo 默认使用 Netty 框架
Dubbo有哪几种集群容错方案,默认是哪种?
Failover Cluster
失败自动切换,自动重试其他服务器(默认)
Failfast Cluster
快速失败,立即失败,只调用一次
Failsafe Cluster
失败安全,出现异常时,直接忽略
Failback Cluster
失败自动恢复,记录失败请求,定时重发
Forking Cluster
并行调用多个服务器,只要一个成功即返回
Broadcast Cluster
广播逐个调用所有提供者,任意一个报错则报错
Dubbo有哪几种负载均衡策略,默认是哪种?
随机,按权重设置随机概率(默认)
轮询,按公约后的权重设置轮询比率
最少活跃调用数
一致性hash,相同参数的请求总是发到同一个提供者
当一个服务接口有多种实现时怎么做?
当一个接口有多种实现时,可以用 group 属性来分组,服务提供方和消费方都指定同一个 group 即可。
服务调用超时问题怎么解决
dubbo在调用服务不成功时,默认是会重试两次的。这样在服务端的处理时间超过了设定的超时时间时,就会有重复请求,比如在发邮件时,可能就会发出多份重复邮件,执行注册请求时,就会插入多条重复的注册数据,那么怎么解决超时问题呢?如下对于核心的服务中心,去除dubbo超时重试机制,并重新评估设置超时时间
<dubbo:provider delay="-1" timeout="6000" retries="0"/>
Dubbo可以对结果进行缓存吗?
可以,Dubbo 提供了声明式缓存,用于加速热门数据的访问速度,以减少用户加缓存的工作量
Dubbo服务之间的调用是阻塞的吗?
默认是同步等待结果阻塞的,支持异步调用。
Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。
服务读写推荐的容错策略是怎样的
读操作建议使用 Failover 失败自动切换,默认重试两次其他服务器。
写操作建议使用 Failfast 快速失败,发一次调用失败就立即报错
Dubbo的管理控制台能做什么
管理控制台主要包含:路由规则,动态配置,服务降级,访问控制,权重调整,负载均衡,等管理功能。
说说 Dubbo 服务暴露的过程。
Dubbo 会在 Spring 实例化完 bean 之后,通知实现了 ApplicationListener 的 ServiceBean 类进行回调 onApplicationEvent 事件方法,Dubbo 会在这个方法中调用 ServiceBean 父类 ServiceConfig 的 export 方法,而该方法真正实现了服务的(异步或者非异步)发布
Dubbo 停止维护了吗?
2014 年开始停止维护过几年,17 年开始重新维护,并进入了 Apache 项目。
Dubbo 和 Dubbox 有什么区别?
Dubbox 是继 Dubbo 停止维护后,当当网基于 Dubbo 做的一个扩展项目,如加了服务可 Restful 调用,更新了开源组件等。
在使用过程中都遇到了些什么问题?如何解决的?
dubbo 缺省会在启动时检查依赖是否可用,不可用就抛出异常,阻止 spring 初始化完成,check 属性默认为 true。测试时有些服务不关心或者出现了循环依赖,将 check 设置为 false
spring 2.x 初始化死锁问题。在 spring 解析到 dubbo:service 时,就已经向外暴露了服务,而spring 还在接着初始化其他 bean,如果这时有请求进来,并且服务的实现类里有调用applicationContext.getBean() 的用法。getBean 线程和 spring 初始化线程的锁的顺序不一样,导致了线程死锁,不能提供服务,启动不了。
解决:不要在服务的实现类中使用 applicationContext.getBean(); 如果不想依赖配置顺序,可以将
dubbo:provider 的 deplay 属性设置为 - 1,使 dubbo 在容器初始化完成后再暴露服务。
dubbo:provider 的 deplay 属性设置为 - 1,使 dubbo 在容器初始化完成后再暴露服务。
出现 RpcException: No provider available for remote service 异常
- 检查连接的注册中心是否正确
- 到注册中心查看相应的服务提供者是否存在
- 检查服务提供者是否正常运行
出现” 消息发送失败” 异常
通常是接口方法的传入传出参数未实现 Serializable 接口。
Nginx
什么是Nginx ?
Nginx是一个web服务器和反向代理服务器,用于 HTTP 、 HTTPS 、 SMTP 、 POP3 和 IMAP 协议。
什么是正向代理和反向代理?
正向代理就是一个人发送一个请求直接就到达了目标的服务
反方代理就是请求统一被Nginx接收,nginx反向代理服务器接收到之后,按照一定的规则分发给了后端的业务处理服务器进行处理了
使用“反向代理服务器的优点是什么?
反向代理服务器可以隐藏源服务器的存在和特征
请列举Nginx 的一些特性。
反向代理/L7负载均衡器
嵌入式Perl解释器
动态二进制升级
可用于重新编写URL,具有非常好的PCRE支持
Nginx的优缺点?
优点
占内存小,可实现高并发连接,处理响应快
可实现http服务器、虚拟主机、方向代理、负载均衡
Nginx配置简单
可以不暴露正式的服务器IP地址
缺点
动态处理差:nginx处理静态文件好,耗费内存少,但是处理动态页面则很鸡肋,现在一般前端用nginx作为反向代理抗住压力,
为什么Nginx性能这么高?
因为他的事件处理机制:异步非阻塞事件处理机制:运用了epoll模型,提供了一个队列,排队解决
Nginx应用场景
http服务器。Nginx是一个http服务可以独立提供http服务。可以做网页静态服务器。
虚拟主机。可以实现在一台服务器虚拟出多个网站,例如个人网站使用的虚拟机。
反向代理,负载均衡。当网站的访问量达到一定程度后,单台服务器不能满足用户的请求时,需要用多台服务器集群可以使用nginx做反向代理。并且多台服务器可以平均分担负载,不会应为某台服务器负载高宕机而某台服务器闲置的情况。
nginx 中也可以配置安全管理、比如可以使用Nginx搭建API接口网关,对每个接口服务进行拦截。
Nginx服务器上的Master和Worker进程分别是什么
Master进程:master进程负责管理worker进程,并负责读取配置文件和判断文件语法的工作;是主进程,且只有一个。
Worker进程:worker进程有多个,它负责处理请求;worker的进程数量由管理员自己定义;
Nginx负载均衡的算法怎么实现的?策略有哪些?
1、 轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端某个服务器宕机,能自动剔除故障系统。
upstream backserver {
server 192.168.0.12;
server 192.168.0.13;
}
server 192.168.0.12;
server 192.168.0.13;
}
权重 weight
weight的值越大分配到的访问概率越高
upstream backserver {
server 192.168.0.12 weight=2;
server 192.168.0.13 weight=8;
}
server 192.168.0.12 weight=2;
server 192.168.0.13 weight=8;
}
ip_hash( IP绑定)
每个请求按访问IP的哈希结果分配,使来自同一个IP的访客固定访问一台后端服务器,并且可以有效解决动态网页存在的session共享问题
upstream backserver {
ip_hash;
server 192.168.0.12:88;
server 192.168.0.13:80;
}
ip_hash;
server 192.168.0.12:88;
server 192.168.0.13:80;
}
fair(第三方插件)
必须安装upstream_fair模块。
对比 weight、ip_hash更加智能的负载均衡算法,fair算法可以根据页面大小和加载时间长短智能地进行负载均衡,响应时间短的优先分配。
对比 weight、ip_hash更加智能的负载均衡算法,fair算法可以根据页面大小和加载时间长短智能地进行负载均衡,响应时间短的优先分配。
upstream backserver {
server server1;
server server2;
fair;
}
server server1;
server server2;
fair;
}
url_hash(第三方插件)
必须安装Nginx的hash软件包
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率。
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率。
upstream backserver {
server squid1:3128;
server squid2:3128;
hash $request_uri;
hash_method crc32;
}
server squid1:3128;
server squid2:3128;
hash $request_uri;
hash_method crc32;
}
Nginx配置高可用性怎么配置?
在两台服务器安装keepalived
Nginx怎么判断IP不可访问?
在nginx.conf中的server限制段中.deny IP.表示需要限制该IP不可访问.allow IP表示权该IP可以访问.
Nginx配置文件nginx.conf有哪些属性模块
... #全局块 配置影响nginx全局的指令。一般有运行nginx服务器的用户组,nginx进程pid存放路径,日志存放路径,配置文件引入,允许生成worker process数等。
events { #events块 配置影响nginx服务器或与用户的网络连接。有每个进程的最大连接数,选取哪种事件驱动模型处理连接请求,是否允许同时接受多个网路连接,开启多个网络连接序列化等。
...
}
http #http块 可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置。如文件引入,mime-type定义,日志自定义,是否使用sendfile传输文件,连接超时时间,单连接请求数等。
{
... #http全局块 配置虚拟主机的相关参数,一个http中可以有多个server。
server #server块
{
... #server全局块
location [PATTERN] #location块 配置请求的路由,以及各种页面的处理情况。
{
...
}
location [PATTERN]
{
...
}
}
server
{
...
}
... #http全局块
}
events { #events块 配置影响nginx服务器或与用户的网络连接。有每个进程的最大连接数,选取哪种事件驱动模型处理连接请求,是否允许同时接受多个网路连接,开启多个网络连接序列化等。
...
}
http #http块 可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置。如文件引入,mime-type定义,日志自定义,是否使用sendfile传输文件,连接超时时间,单连接请求数等。
{
... #http全局块 配置虚拟主机的相关参数,一个http中可以有多个server。
server #server块
{
... #server全局块
location [PATTERN] #location块 配置请求的路由,以及各种页面的处理情况。
{
...
}
location [PATTERN]
{
...
}
}
server
{
...
}
... #http全局块
}
Nginx静态资源
静态资源访问,就是存放在nginx的html页面,我们可以自己编写
如何用Nginx解决前端跨域问题
使用Nginx转发请求。把跨域的接口写成调本域的接口,然后将这些接口转发到真正的请求地址。
Nginx怎么处理请求的?
nginx接收一个请求后,首先由listen和server_name指令匹配server模块,再匹配server模块里的location,location就是实际地址
Nginx虚拟主机怎么配置
基于域名的虚拟主机,通过域名来区分虚拟主机——应用:外部网站
基于端口的虚拟主机,通过端口来区分虚拟主机——应用:公司内部网站,外部网站的管理后台
基于ip的虚拟主机
location的作用是什么
location指令的作用是根据用户请求的URI来执行不同的应用,也就是根据用户请求的网站URL进行匹配,匹配成功即进行相关的操作
location的语法
Nginx常用变量
$host: 请求的主机头
$remote_addr: 客户端IP地址
$remote_port: 客户端端口号
$remote_user: 已经经过Auth Basic Module验证的用户名
$http_referer: 请求引用地址
$http_user_agent: 客户端代理信息(UA)
$http_x_forwarded_for: 相当于网络访问路径
$body_bytes_sent: 页面传送的字节数
$time_local: 服务器时间
$request: 客户端请求
$request_uri: 请求的URI,带参数, 不包含主机名
$request_filename: 请求的文件路径
$request_method: 请求的方法,如GET、POST
$args: 客户端请求中的参数
$query_string: 等同于$args, 客户端请求的参数
$nginx_version: 当前nginx版本
$status: 服务器响应状态码
$server_addr: 服务器地址
$server_port: 请求到达的服务器端口号
$server_protocol: 请求的协议版本
$content_type: HTTP请求信息里的Content-Type字段
$content_length: HTTP请求信息里的Content-Length字段
$uri: 请求中的当前URI(不带请求参数,参数位于$args)
$document_root: 当前请求在root指令中指定的值
$document_uri: 与$uri相同
$remote_addr: 客户端IP地址
$remote_port: 客户端端口号
$remote_user: 已经经过Auth Basic Module验证的用户名
$http_referer: 请求引用地址
$http_user_agent: 客户端代理信息(UA)
$http_x_forwarded_for: 相当于网络访问路径
$body_bytes_sent: 页面传送的字节数
$time_local: 服务器时间
$request: 客户端请求
$request_uri: 请求的URI,带参数, 不包含主机名
$request_filename: 请求的文件路径
$request_method: 请求的方法,如GET、POST
$args: 客户端请求中的参数
$query_string: 等同于$args, 客户端请求的参数
$nginx_version: 当前nginx版本
$status: 服务器响应状态码
$server_addr: 服务器地址
$server_port: 请求到达的服务器端口号
$server_protocol: 请求的协议版本
$content_type: HTTP请求信息里的Content-Type字段
$content_length: HTTP请求信息里的Content-Length字段
$uri: 请求中的当前URI(不带请求参数,参数位于$args)
$document_root: 当前请求在root指令中指定的值
$document_uri: 与$uri相同
Nginx设置重定向
return形式
rewrite形式
限流怎么做的?
Nginx的限流都是基于漏桶流算法,底下会说道什么是桶铜流
正常限制访问频率(正常流量)
限制一个用户发送的请求,我Nginx多久接收一个请求。
Nginx中使用ngx_http_limit_req_module模块来限制的访问频率,限制的原理实质是基于漏桶算法原理来实现的。在nginx.conf配置文件中可以使用limit_req_zone命令及limit_req命令限制单个IP的请求处理频率
Nginx中使用ngx_http_limit_req_module模块来限制的访问频率,限制的原理实质是基于漏桶算法原理来实现的。在nginx.conf配置文件中可以使用limit_req_zone命令及limit_req命令限制单个IP的请求处理频率
突发限制访问频率(突发流量)
Nginx提供burst参数结合nodelay参数可以解决流量突发的问题,可以设置能处理的超过设置的请求数外能额外处理的请求数。我们可以将之前的例子添加burst参数以及nodelay参数:
burst: burst=2,设置一个大小为2的缓存区域,当大量请求到来,请求数量超过限流频率时,将其放入缓冲区域
nodelay: 缓冲区满了后直接返回503异常
nodelay: 缓冲区满了后直接返回503异常
限制并发连接数
Nginx中的ngx_http_limit_conn_module模块提供了限制并发连接数的功能,可以使用limit_conn_zone指令以及limit_conn执行进行配置。接下来我们可以通过一个简单的例子来看下
上面配置了单个IP同时并发连接数最多只能10个连接,并且设置了整个虚拟服务器同时最大并发数最多
只能100个链接。
只能100个链接。
为什么要做动静分离?
动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们则根据静态资源的特点将其做缓存操作。
让静态的资源只走静态资源服务器,动态的走动态的服务器
Nginx的静态处理能力很强,但是动态处理能力不足,因此,在企业中常用动静分离技术。
对于静态资源比如图片,js,css等文件,我们则在反向代理服务器nginx中进行缓存。这样浏览器在请求一个静态资源时,代理服务器nginx就可以直接处理,无需将请求转发给后端服务器tomcat。
Zookeeper面试题
ZooKeeper 是什么
分布式应用程序可以基于 Zookeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
Zookeeper 保证了如下分布式一致性特性:
Zookeeper 保证了如下分布式一致性特性:
- 顺序一致性
- 原子性
- 单一视图
- 可靠性实时性(最终一致性)
Zookeeper 都有哪些功能?
集群管理:监控节点存活状态、运行请求等;
主节点选举:主节点挂掉了之后可以从备用的节点开始新一轮选主,主节点选举说的就是这个选举的过程,使用 Zookeeper 可以协助完成这个过程;
分布式锁:Zookeeper 提供两种锁:独占锁、共享锁。独占锁即一次只能有一个线程使用资源,共享锁是读锁共享,读写互斥,即可以有多线线程同时读同一个资源,如果要使用写锁也只能有一个线程使用。Zookeeper 可以对分布式锁进行控制
命名服务:在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。
Zookeeper 文件系统
Zookeeper 提供一个多层级的节点命名空间(节点称为 znode)。
Zookeeper 为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得 Zookeeper不能用于存放大量的数据,每个节点的存放数据上限为1M。
有哪些著名的开源项目用到了 ZooKeeper?
Kafka : ZooKeeper 主要为 Kafka 提供 Broker 和 Topic 的注册以及多个 Partition 的负载均衡等功能。
Hbase : ZooKeeper 为 Hbase 提供确保整个集群只有一个 Master 以及保存和提供 regionserver 状态信息(是否在线)等功能。
Hadoop : ZooKeeper 为 Namenode 提供高可用支持。
说一下 Zookeeper 的通知机制?
client 端会对某个 znode 建立一个 watcher 事件,当该 znode 发生变化时,这些 client 会收到 zk 的通知,然后 client 可以根据 znode 变化来做出业务上的改变等。
Zookeeper 的典型应用场景
Zookeeper 是一个典型的发布/订阅模式的分布式数据管理与协调框架,开发人员可以使用它来进行分布式数据的发布和订阅。
通过对 Zookeeper 中丰富的数据节点进行交叉使用,配合 Watcher 事件通知机制,可以非常方便的构建一系列分布式应用中年都会涉及的核心功能,如:
数据发布/订阅
负载均衡
命名服务
分布式协调/通知
集群管理
Master 选举
分布式锁
分布式队列
通过对 Zookeeper 中丰富的数据节点进行交叉使用,配合 Watcher 事件通知机制,可以非常方便的构建一系列分布式应用中年都会涉及的核心功能,如:
数据发布/订阅
负载均衡
命名服务
分布式协调/通知
集群管理
Master 选举
分布式锁
分布式队列
数据发布/订阅
数据发布/订阅系统,即所谓的配置中心,顾名思义就是发布者发布数据供订阅者进行数据订阅。
目的
动态获取数据(配置信息)
实现数据(配置信息)的集中式管理和数据的动态更新
例子
机器列表信息、运行时开关配置、数据库配置信息等
数据存储:将数据(配置信息)存储到 Zookeeper 上的一个数据节点
数据获取:应用在启动初始化节点从 Zookeeper 数据节点读取数据,并在该节点上注册一个数据变更Watcher
数据变更:当变更数据时,更新 Zookeeper 对应节点数据,Zookeeper会将数据变更通知发到各客户端,客户端接到通知后重新读取变更后的数据即可。
负载均衡
zk 的命名服务
命名服务是指通过指定的名字来获取资源或者服务的地址,利用 zk 创建一个全局的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。
分布式通知和协调
对于系统调度来说:操作人员发送通知实际是通过控制台改变某个节点的状态,然后 zk 将这些变化发送给注册了这个节点的 watcher 的所有客户端。
对于执行情况汇报:每个工作进程都在某个目录下创建一个临时节点。并携带工作的进度数据,这样汇总的进程可以监控目录子节点的变化获得工作进度的实时的全局情况。
zk 的命名服务(文件系统)
命名服务是指通过指定的名字来获取资源或者服务的地址,利用 zk 创建一个全局的路径,即是唯一的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。
zk 的配置管理(文件系统、通知机制)
程序分布式的部署在不同的机器上,将程序的配置信息放在 zk 的 znode 下,当有配置发生改变时,也就是 znode 发生变化时,可以通过改变 zk 中某个目录节点的内容,利用 watcher 通知给各个客户端,从而更改配置。
Zookeeper 集群管理(文件系统、通知机制)
所谓集群管理无在乎两点:是否有机器退出和加入、选举 master。
所有机器约定在父目录下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper 的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个兄弟目录被删除,于是,所有人都知道:它上船了。
Zookeeper 分布式锁(文件系统、通知机制)
有了 zookeeper 的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是保持独占,另一个是控制时序。
保持独占
我们将 zookeeper 上的一个 znode 看作是一把锁,通过 createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。用完删除掉自己创建的 distribute_lock 节点就释放出锁。
控制时序
/distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master 一样,编号最小的获得锁,用完删除,依次方便。
Zookeeper 队列管理(文件系统、通知机制)
同步队列,当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。
在约定目录下创建临时目录节点,监听节点数目是否是我们要求的数目
队列按照 FIFO 方式进行入队和出队操作
和分布式锁服务中的控制时序场景基本原理一致,入列有编号,出列按编号。在特定的目录下创建 PERSISTENT_SEQUENTIAL 节点,创建成功时Watcher 通知等待的队列,队列删除序列号最小的节点用以消费。此场景下Zookeeper 的 znode 用于消息存储,znode 存储的数据就是消息队列中的消息内容,SEQUENTIAL 序列号就是消息的编号,按序取出即可。由于创建的节点是持久化的,所以不必担心队列消息的丢失问题。
Zookeeper 怎么保证主从节点的状态同步?
Zookeeper 的核心是原子广播机制,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 Zab 协议。Zab 协议有两种模式,它们分别是恢复模式和广播模式。
恢复模式
当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数 server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 server 具有相同的系统状态
广播模式
一旦 leader 已经和多数的 follower 进行了状态同步后,它就可以开始广播消息了,即进入广播状态。这时候当一个 server 加入 ZooKeeper 服务中,它会在恢复模式下启动,发现 leader,并和 leader 进行状态同步。待到同步结束,它也参与消息广播。
四种类型的数据节点 Znode
PERSISTENT-持久节点
除非手动删除,否则节点一直存在于 Zookeeper 上
EPHEMERAL-临时节点
临时节点的生命周期与客户端会话绑定,一旦客户端会话失效(客户端与zookeeper 连接断开不一定会话失效),那么这个客户端创建的所有临时节点都会被移除。
PERSISTENT_SEQUENTIAL-持久顺序节点
基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。
EPHEMERAL_SEQUENTIAL-临时顺序节点
基本特性同临时节点,增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。
说几个 zookeeper 常用的命令。
常用命令:ls get set create delete 等。
Zookeeper Watcher 机制 -- 数据变更通知
Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据Watcher 通知状态和事件类型做出业务上的改变。
zookeeper 是如何保证事务的顺序一致性的?
zookeeper 采用了全局递增的事务 Id 来标识,所有的 proposal(提议)都在被提出的时候加上了zxid,zxid 实际上是一个 64 位的数字,高 32 位是 epoch( 时期; 纪元; 世; 新时代)用来标识 leader周期,如果有新的 leader 产生出来,epoch会自增,低 32 位用来递增计数。当新产生 proposal 的时候,会依据数据库的两阶段过程,首先会向其他的 server 发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行。
zk 节点宕机如何处理?
Zookeeper 本身也是集群,推荐配置不少于 3 个服务器。Zookeeper 自身也要保证当一个节点宕机时,其他节点会继续提供服务。
如果是一个 Follower 宕机,还有 2 台服务器提供访问,因为 Zookeeper 上的数据是有多个副本的,数据并不会丢失;
如果是一个 Leader 宕机,Zookeeper 会选举出新的 Leader。
ZK 集群的机制是只要超过半数的节点正常,集群就能正常提供服务。只有在 ZK节点挂得太多,只剩一半或不到一半节点能工作,集群才失效。
如果是一个 Follower 宕机,还有 2 台服务器提供访问,因为 Zookeeper 上的数据是有多个副本的,数据并不会丢失;
如果是一个 Leader 宕机,Zookeeper 会选举出新的 Leader。
ZK 集群的机制是只要超过半数的节点正常,集群就能正常提供服务。只有在 ZK节点挂得太多,只剩一半或不到一半节点能工作,集群才失效。
zookeeper 负载均衡和 nginx 负载均衡区别
zk 的负载均衡是可以调控,nginx 只是能调权重,其他需要可控的都需要自己写插件;但是 nginx 的吞吐量比 zk 大很多,应该说按业务选择用哪种方式。
客户端注册 Watcher 实现
调用 getData()/getChildren()/exist()三个 API,传入 Watcher 对象
标记请求 request,封装 Watcher 到 WatchRegistration
封装成 Packet 对象,发服务端发送 request
收到服务端响应后,将 Watcher 注册到 ZKWatcherManager 中进行管理
请求返回,完成注册。
服务端处理 Watcher 实现
1、服务端接收 Watcher 并存储
接收到客户端请求,处理请求判断是否需要注册 Watcher,需要的话将数据节点的节点路径和ServerCnxn(ServerCnxn 代表一个客户端和服务端的连接,实现了 Watcher 的 process 接口,此时可以看成一个 Watcher 对象)存储在WatcherManager 的 WatchTable 和 watch2Paths 中去。
Watcher 触发
以服务端接收到 setData() 事务请求触发 NodeDataChanged 事件为例:
- 封装 WatchedEvent 将通知状态(SyncConnected)、事件类型(NodeDataChanged)以及节点路径封装成一个WatchedEvent 对象
- 查询 Watcher 从 WatchTable 中根据节点路径查找 Watcher
- 没找到;说明没有客户端在该数据节点上注册过 Watcher
- 找到;提取并从 WatchTable 和 Watch2Paths 中删除对应 Watcher(从这里可以看出 Watcher 在服务端是一次性的,触发一次就失效了)
调用 process 方法来触发 Watcher
这里 process 主要就是通过 ServerCnxn 对应的 TCP 连接发送 Watcher 事件通知。
Zookeeper 对节点的 watch 监听通知是永久的吗?为什么不是永久的?
不是。官方声明:一个 Watch 事件是一个一次性的触发器,当被设置了 Watch的数据发生了改变的时候,则服务器将这个改变发送给设置了 Watch 的客户端,以便通知它们。
服务器角色
Leader
事务请求的唯一调度和处理者,保证集群事务处理的顺序性
集群内部各服务的调度者
Follower
处理客户端的非事务请求,转发事务请求给 Leader 服务器
参与事务请求 Proposal 的投票
参与 Leader 选举投票
Observer
3.0 版本以后引入的一个服务器角色,在不影响集群事务处理能力的基础上提升集群的非事务处理能力
处理客户端的非事务请求,转发事务请求给 Leader 服务器
不参与任何形式的投票
Zookeeper 下 Server 工作状态
LOOKING
寻 找 Leader 状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。
FOLLOWING
跟随者状态。表明当前服务器角色是 Follower。
LEADING
领导者状态。表明当前服务器角色是 Leader。
OBSERVING
观察者状态。表明当前服务器角色是 Observer
ZAB 和 Paxos 算法的联系与区别?
相同点:
两者都存在一个类似于 Leader 进程的角色,由其负责协调多个 Follower 进程的运行
Leader 进程都会等待超过半数的 Follower 做出正确的反馈后,才会将一个提案进行提交
ZAB 协议中,每个 Proposal 中都包含一个 epoch 值来代表当前的 Leader周期,Paxos 中名字为Ballot
不同点
ZAB 用来构建高可用的分布式数据主备系统(Zookeeper),Paxos 是用来构建分布式一致性状态机系统
ZAB 协议
AB协议,全称 Zookeeper Atomic Broadcast(Zookeeper 原子广播协议)。它是专门为分布式协调服务——Zookeeper,设计的一种支持崩溃恢复和原广播的协议。
整个ZAB协议一共定义了三个阶段:
发现:要求zookeeper集群必须选举出一个 Leader 进程,同时 Leader 会维护一个 Follower 可用客户端列表。将来客户端可以和这些 Follower节点进行通信。
同步:Leader 要负责将本身的数据与 Follower 完成同步,做到多副本存储。这样也是提现了CAP中的高可用和分区容错。Follower将队列中未处理完的请求消费完成后,写入本地事务日志中
广播:Leader 可以接受客户端新的事务Proposal请求,将新的Proposal请求广播给所有的 Follower。
Zab协议核心
所有的事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被叫做 Leader服务器。其他剩余的服务器则是 Follower服务器。
Leader服务器 负责将一个客户端事务请求,转换成一个 事务Proposal,并将该 Proposal 分发给集群中所有的 Follower 服务器,也就是向所有 Follower 节点发送数据广播请求(或数据复制)
分发之后Leader服务器需要等待所有Follower服务器的反馈(Ack请求),在Zab协议中,只要超过半数的Follower服务器进行了正确的反馈后(也就是收到半数以上的Follower的Ack请求),那么 Leader 就会再次向所有的 Follower服务器发送 Commit 消息,要求其将上一个 事务proposal 进行提交。
MQ面试题
RabbitMQ是什么?
MQ(Message Queue)消息队列,是 "先进先出" 的一种数据结构。
MQ 一般用来解决应用解耦,异步处理,流量削峰等问题,实现高性能,高可用,可伸缩和最终一致性架构
应用解耦
当 A 系统生产关键数据,发送数据给多个其他系统消费,此时 A 系统和其他系统产生了严重的耦合,如果将 A 系统产生的数据放到 MQ 当中,其他系统去 MQ 获取消费数据,此时各系统独立运行只与 MQ 交互,添加新系统消费 A 系统的数据也不需要去修改 A 系统的代码,达到了解耦的效果。
异步处理
互联网类企业对用户的直接操作,一般要求每个请求在 200ms 以内完成。对于一个系统调用多个系统,不使用 MQ 的情况下,它执行完返回的耗时是调用完所有系统所需时间的总和;
流量削峰
MySQL 每秒最高并发请求在 2000 左右,用户访问量高峰期的时候涌入的大量请求,会将MySQL 打死,然后系统就挂掉,但过了高峰期,请求量可能远低于 2000,这种情况去增加服务器就不值得,如果使用 MQ 的情况,将用户的请求全部放到 MQ 中,让系统去消费用户的请求,不要超过系统
所能承受的最大请求数量,保证系统不会再高峰期挂掉
所能承受的最大请求数量,保证系统不会再高峰期挂掉
为什么要用RocketMq?
吞吐量高:单机吞吐量可达十万级
可用性高:分布式架构
消息可靠性高:经过参数优化配置,消息可以做到0丢失
功能支持完善:MQ功能较为完善,还是分布式的,扩展性好
支持10亿级别的消息堆积:不会因为堆积导致性能下降
源码是java:方便我们查看源码了解它的每个环节的实现逻辑,并针对不同的业务场景进行扩展
稳定性高:RoketMQ在上可能更值得信赖,这些业务场景在阿里双11已经经历了多次考验
使用 MQ 的缺陷有哪些?
系统可用性降低
以前只要担心系统的问题,现在还要考虑 MQ 挂掉的问题,MQ 挂掉,所关联的系统都会无法提供服务。
系统复杂性变高
要考虑消息丢失、消息重复消费等问题
一致性问题
多个 MQ 消费系统,部分成功,部分失败,要考虑事务问题。
你了解哪些常用的 MQ?
ActiveMQ
支持万级的吞吐量,较成熟完善;官方更新迭代较少,社区的活跃度不是很高,有消息丢失的情况。
RabbitMQ
延时低,微秒级延时,社区活跃度高,bug 修复及时,而且提供了很友善的后台界面;用Erlang 语言开发,只熟悉 Java 的无法阅读源码和自行修复 bug。
RocketMQ
阿里维护的消息中间件,可以达到十万级的吞吐量,支持分布式事务
Kafka
分布式的中间件,最大优点是其吞吐量高,一般运用于大数据系统的实时运算和日志采集的场景,功能简单,可靠性高,扩展性高;缺点是可能导致重复消费。
MQ 有哪些使用场景?
异步处理
用户注册后,发送注册邮件和注册短信。用户注册完成后,提交任务到 MQ,发送模块并行获取 MQ 中的任务。
系统解耦
比如用注册完成,再加一个发送微信通知。只需要新增发送微信消息模块,从 MQ 中读取任务,发送消息即可。无需改动注册模块的代码,这样注册模块与发送模块通过 MQ 解耦。
流量削峰
秒杀和抢购等场景经常使用 MQ 进行流量削峰。活动开始时流量暴增,用户的请求写入MQ,超过 MQ 最大长度丢弃请求,业务系统接收 MQ 中的消息进行处理,达到流量削峰、保证系统可用性的目的。
日志处理
日志采集方收集日志写入 kafka 的消息队列中,处理方订阅并消费 kafka 队列中的日志数据。
消息通讯
点对点或者订阅发布模式,通过消息进行通讯。如微信的消息发送与接收、聊天室等。
RabbitMQ特点?
可靠性: RabbitMQ使用一些机制来保证可靠性, 如持久化、传输确认及发布确认等
灵活的路由 : 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能, RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个 交换器绑定在一起, 也可以通过插件机制来实现自己的交换器
扩展性: 多个RabbitMQ节点可以组成一个集群,也可以根据实际业务情况动态地扩展 集群中节点。
高可用性 : 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队 列仍然可用
多种协议: RabbitMQ除了原生支持AMQP协议,还支持STOMP, MQTT等多种消息 中间件协议。
多语言客户端 :RabbitMQ 几乎支持所有常用语言,比如 Java、 Python、 Ruby、 PHP、 C#、JavaScript 等。
管理界面 : RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息、集 群中的节点等。
令插件机制: RabbitMQ 提供了许多插件 , 以实现从多方面进行扩展,当然也可以编写自 己的插件。
AMQP是什么?
RabbitMQ就是 AMQP 协议的 Erlang 的实现
AMQP 的模型架构 和 RabbitMQ 的模型架构是一样的,生产者将消息发送给交换器,交换器和队列绑定 。
RabbitMQ 中的交换器、交换器类型、队列、绑定、路由键等都是遵循的 AMQP 协议中相 应的概念。
Rocketmq的工作流程是怎样的?
介绍
- RocketMQ作为一款纯java、分布式、队列模型的开源消息中间件,支持事务消息、顺序消息、批量消息、定时消息、消息回溯等。
- RocketMQ主要有四大核心组成部分:NameServer、Broker、Producer以及Consumer四部分。
启动流程
首先启动NameServer。NameServer启动后监听端口,等待Broker、Producer以及Consumer连上来
启动Broker。启动之后,会跟所有的NameServer建立并保持一个长连接,定时发送心跳包。心跳包中包含当前Broker信息(ip、port等)、Topic信息以及Borker与Topic的映射关系
创建Topic。创建时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic
Producer发送消息。启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic所在的Broker;然后从队列列表中轮询选择一个队列,与队列所在的Broker建立长连接,进行消息的发送
Consumer消费消息。跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,进行消息的消费
Rocketmq如何保证高可用性?
集群化部署NameServer。Broker集群会将所有的broker基本信息、topic信息以及两者之间的映射关系,轮询存储在每个NameServer中(也就是说每个NameServer存储的信息完全一样)。因此,NameServer集群化,不会因为其中的一两台服务器挂掉,而影响整个架构的消息发送与接收;
集群化部署多broker。producer发送消息到broker的master,若当前的master挂掉,则会自动切
换到其他的master
换到其他的master
消息发送到broker的master节点上,master需要将消息复制到slave节点上,rocketmq提供两种复制方式:同步复制和异步复制
异步复制
异步复制,就是消息发送到master节点,只要master写成功,就直接向客户端返回成功,后续再异步写入slave节点
同步复制
同步复制,就是等master和slave都成功写入内存之后,才会向客户端返回成功
RocketMq的存储机制了解吗
RocketMq采用文件系统进行消息的存储,相对于ActiveMq采用关系型数据库进行存储的方式就更直接,性能更高了
RocketMq采用文件系统存储消息,并采用顺序写写入消息,使用零拷贝发送消息,极大得保证了RocketMq的性能
AMQP模型的几大组件
交换器 (Exchange):消息代理服务器中用于把消息路由到队列的组件。
队列 (Queue):用来存储消息的数据结构,位于硬盘或内存中。
绑定 (Binding):一套规则,告知交换器消息应该将消息投递给哪个队列。
生产者Producer?
消息生产者,就是投递消息的一方。
消息一般包含两个部分:消息体(payload)和标签(Label)。
消费者Consumer?
消费消息,也就是接收消息的一方。
消费者连接到RabbitMQ服务器,并订阅到队列上。消费消息时只消费消息体,丢弃标签。
Broker服务节点?
Broker可以看做RabbitMQ的服务节点。一般情况下Broker可以看做一个RabbitMQ服务器。
Queue队列?
Queue:RabbitMQ的内部对象,用于存储消息。多个消费者可以订阅同一队列,这时队列中的消息会被平摊(轮询)给多个消费者进行处理。
Exchange交换器?
生产者将消息发送到交换器,有交换器将消息路由到一个或者多个队列中。当路由不到时,或返回给生产者或直接丢弃。
RoutingKey路由键?
生产者将消息发送给交换器的时候,会指定一个RoutingKey,用来指定这个消息的路由规则,这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。
Binding绑定?
通过绑定将交换器和队列关联起来,一般会指定一个BindingKey,这样RabbitMq就知道如何正确路由消息到队列了。
交换器4种类型?
fanout
把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中。
direct
把消息路由到BindingKey和RoutingKey完全匹配的队列中。
topic
RoutingKey 为一个 点号'.': 分隔的字符串。
BindingKey和RoutingKey一样也是点号“.“分隔的字符串。
BindingKey可使用 * 和 # 用于做模糊匹配,*匹配一个单词,#匹配多个或者0个
如何保证消息不丢失?
生产者丢失消息:
主流的MQ 都有确认或事务机制,可以保证生产者将消息送达到 MQ。如 RabbitMQ 就有事务模式和 confirm模式
MQ 丢失消息:MQ 成功接收消息内部处理出错、宕机等情况。解决办法:开启 MQ 的持久化配置。
消费者丢失消息:
采用消息自动确认模式,消费者取到消息未处理挂掉了。解决办法:改为手动确认模式,消费者成功消费消息再确认
消息大量积压怎么解决?
检查消费实例,日志中是否有大量消费错误、消费线程是否死锁、是否卡在某些资源上。
通过扩容消费端的实例数来提升总体的消费能力。严重影响 QM 甚至整个系统时,可以考虑临时启用多个消费者,并发接受消息,持久化之后再单独处理,或者直接丢弃消息,回头让生产者重新生产。
如果短时间内没有服务器资源扩容,没办法的办法是将系统降级,通过关闭某些不重要的业务,减少发送的数据量,最低限度让系统还能正常运转,服务重要业务。
如何保证MQ的高可用?
ActiveMQ
Master-Slave 部署方式主从热备,方式包括通过共享存储目录来实现(shared filesystem MasterSlave)、通过共享数据库来实现(shared database Master-Slave)、5.9版本后新特性使用 ZooKeeper 协调选择 master(Replicated LevelDB Store)。
RabbitMQ
单机模式与普通集群模式无法满足高可用,镜像集群模式指定多个节点复制 queue 中的消息做到高可用,但消息之间的同步网络性能开销较大。
RocketMQ
有多 master 多 slave 异步复制模式和多 master 多 slave 同步双写模式支持集群部署模式。
生产者消息运转?
Producer先连接到Broker,建立连接Connection,开启一个信道(Channel)。
Producer声明一个交换器并设置好相关属性
Producer声明一个队列并设置好相关属性。
Producer通过路由键将交换器和队列绑定起来。
Producer发送消息到Broker,其中包含路由键、交换器等信息
相应的交换器根据接收到的路由键查找匹配的队列。
如果找到,将消息存入对应的队列,如果没有找到,会根据生产者的配置丢弃或者退回给生产者。
消费者接收消息过程?
Producer先连接到Broker,建立连接Connection,开启一个信道(Channel)
向Broker请求消费响应的队列中消息,可能会设置响应的回调函数。
等待Broker回应并投递相应队列中的消息,接收消息。
消费者确认收到的消息,ack。
RabbitMq从队列中删除已经确定的消息。
导致的死信的几种原因?
消息被拒(Basic.Reject /Basic.Nack) 且 requeue = false。
消息TTL过期。
队列满了,无法再添加。
死信队列?
DLX,全称为 Dead-Letter-Exchange,死信交换器,死信邮箱。当消息在一个队列中变成死信 (deadmessage) 之后,它能被重新被发送到另一个交换器中,这个交换器就是 DLX,绑定 DLX 的队列就称之为死信队列。
KafKa面试题
什么是kafka
Kafka是分布式发布-订阅消息系统,它最初是由LinkedIn公司开发的,之后成为Apache项目的一部分,Kafka是一个分布式,可划分的,冗余备份的持久性的日志服务,它主要用于处理流式数据
为什么要使用 kafka,为什么要使用消息队列
缓冲和削峰
上游数据时有突发流量,下游可能扛不住,或者下游没有足够多的机器来保证冗余,kafka在中间可以起到一个缓冲的作用,把消息暂存在kafka中,下游服务就可以按照自己的节奏进行慢慢处理。
解耦和扩展性
项目开始的时候,并不能确定具体需求。消息队列可以作为一个接口层,解耦重要的业务流程。只需要遵守约定,针对数据编程即可获取扩展能力
冗余
可以采用一对多的方式,一个生产者发布消息,可以被多个订阅topic的服务消费到,供多个毫无关联的业务使用。
健壮性
消息队列可以堆积请求,所以消费端业务即使短时间死掉,也不会影响主要业务的正常进行。
异步通信
很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
Kafka中的ISR、AR又代表什么?ISR的伸缩又指什么
ISR
Leader维护了⼀个动态的 in-sync replica set(ISR):和 Leader 保持同步的 Follower 集合。当 ISR 集合中的 Follower 完成数据的同步之后,Leader 会给 Follower 发送 ACK。
AR
AR:Assigned Replicas 所有副本
Kafka中的broker 是干什么的
broker 是消息的代理,Producers往Brokers里面的指定Topic中写消息,Consumers从Brokers里面拉取指定Topic的消息,然后进行业务处理,broker在中间起到一个代理保存消息的中转站。
什么是消费者组?
作为队列,消费者组允许你分割数据处理到一组进程集合上(即一个消费者组中可以包含多个消费者进程,他们共同消费该topic的数据),这
有助于你的消费能力的动态调整
有助于你的消费能力的动态调整
作为发布-订阅模型(publish-subscribe),Kafka允许你将同一份消息广播到多个消费者组里,以此来丰富多种数据使用场景。
Kafka中的 zookeeper 起到什么作用,可以不用zookeeper么
早期版本的kafka用zk做meta信息存储,consumer的消费状态,group的管理以及 offset的值。
但是broker依然依赖于ZK,zookeeper 在kafka中还用来选举controller 和 检测broker是否存活等等。
Kafka 为什么那么快
顺序写 由于现代的操作系统提供了预读和写技术,磁盘的顺序写大多数情况下比随机写内存还要快。
Zero-copy 零拷技术减少拷贝次数
Batching of Messages 批量量处理。合并小的请求,然后以流的方式进行交互,直顶网络上限。
如何设置Kafka能接收的最大消息的大小?
Broker端参数:message.max.bytes,max.message.bytes(topic级别),replica.fetch.max.bytes(否则follow会同步失败)
Consumer端参数:fetch.message.max.bytes
__consumer_offsets是做什么用的?
主要用于存储消费者的偏移量,以及消费者的元数据信息(消费者实例,消费者id等等)
ElasticSearch面试题
简要介绍一下Elasticsearch
Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。
ElasticSearch 是基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。
核心特点如下:
分布式的实时文件存储,每个字段都被索引且可用于搜索。
分布式的实时分析搜索引擎,海量数据下近实时秒级响应。
简单的restful api,天生的兼容多语言开发。
易扩展,处理PB级结构化或非结构化数据。
安装 Elasticsearch 需要依赖什么组件吗?
ES 早期版本需要JDK,在7.X版本后已经集成了 JDK,已无需第三方依赖。
ElasticSearch中的集群、节点、索引、文档、类型是什么?
集群
是一个或多个节点(服务器)的集合,它们共同保存您的整个数据,并提供跨所有节点的联合索引和搜索功能。群集由唯一名称标识,默认情况下为“elasticsearch”。此名称很重要,因为如果节点设置为按名称加入群集,则该节点只能是群集的一部分
节点
是属于集群一部分的单个服务器。它存储数据并参与群集索引和搜索功能。
索引
就像关系数据库中的“数据库”。它有一个定义多种类型的映射。索引是逻辑名称空间,映射到一个或多个主分片,并且可以有零个或多个副本分片。
文档
类似于关系数据库中的一行。不同之处在于索引中的每个文档可以具有不同的结构(字段),但是对于通用字段应该具有相同的数据类型。
类型
是索引的逻辑类别/分区,其语义完全取决于用户
Elasticsearch 支持哪些类型的查询?
精确匹配
term、exists、term set、 range、prefix、 ids、 wildcard、regexp、 fuzzy等
全文检索匹配
match、match_phrase、multi_match、match_phrase_prefix、query_string 等
精准匹配检索和全文检索匹配检索的不同?
精确匹配用于:是否完全一致?
举例:邮编、身份证号的匹配往往是精准匹配。
全文检索用于:是否相关?
举例:类似B站搜索特定关键词如“马保国 视频”往往是模糊匹配,相关的都返回就可以。
ElasticSearch中的分片是什么?
为了解决一个索引可以存储TB级数据、响应速度太慢问题,Elasticsearch 提供了将索引划分成多份的能力,每一份就称之为分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。
请解释一下 Elasticsearch 中聚合?
分桶 Bucket 聚合
根据字段值,范围或其他条件将文档分组为桶(也称为箱)。
指标 Metric 聚合
从字段值计算指标(例如总和或平均值)的指标聚合。
管道 Pipeline 聚合
子聚合,从其他聚合(而不是文档或字段)获取输入。
解释一下Elasticsearch Cluster?
Elasticsearch 集群是一组连接在一起的一个或多个 Elasticsearch 节点实例。
Elasticsearch 集群的功能在于在集群中的所有节点之间分配任务,进行搜索和建立索引。
解释一下 Elasticsearch Node?
这里节点实际就是:一个独立的 Elasticsearch 进程,一般将一个节点部署到一台独立的服务器或者虚拟机、容器中
主节点
帮助配置和管理在整个集群中添加和删除节点。
数据节点
存储数据并执行诸如CRUD(创建/读取/更新/删除)操作,对数据进行搜索和聚合的操作。
Coordinate(协调节点)
将集群请求转发到主节点,将与数据相关的请求转发到数据节点
解释一下 Elasticsearch 的 分片?
当文档数量增加,硬盘容量和处理能力不足时,对客户端请求的响应将延迟。在这种情况下,将索引数据分成小块的过程称为分片,可改善数据搜索结果的获取。
定义副本、创建副本的好处是什么?
副本是 分片的对应副本,用在极端负载条件下提高查询吞吐量或实现高可用性。
所谓高可用主要指:如果某主分片1出了问题,对应的副本分片1会提升为主分片,保证集群的高可用。
Elasticsearch 中常用的 cat命令有哪些?
GET _cat/indices?v
查看索引信息
GET _cat/nodes?v
节点信息
GET _cat/count?v
计数
GET _cat/master?v
主节点相关
GET _cat/shards?v
分片
你能否列出与 Elasticsearch 有关的主要可用字段数据类型?
字符串数据类型,包括支持全文检索的 text 类型 和 精准匹配的 keyword 类型。
数值数据类型,例如字节,短整数,长整数,浮点数,双精度数,half_float,scaled_float。
日期类型,日期纳秒Date nanoseconds,布尔值,二进制(Base64编码的字符串)等。
包含对象的复杂数据类型,nested 、Object。
GEO 地理位置相关类型
特定类型如:数组(数组中的值应具有相同的数据类型)
Elasticsearch了解多少,说说你们公司es的集群架构,索引数据大小,分片有多少,以及一些调优手段 。
比如:ES集群架构13个节点,索引根据通道不同共20+索引,根据日期,每日递增20+,索引:10分片,每日递增1亿+数据,
设计阶段调优
根据业务增量需求,采取基于日期模板创建索引,通过roll over API滚动索引;
使用别名进行索引管理;
每天凌晨定时对索引做force_merge操作,以释放空间;
采取冷热分离机制,热数据存储到SSD,提高检索效率;冷数据定期进行shrink操作,以缩减存储;
采取curator进行索引的生命周期管理;
仅针对需要分词的字段,合理的设置分词器;
写入调优
写入前副本数设置为0;
写入前关闭refresh_interval设置为-1,禁用刷新机制;
写入过程中:采取bulk批量写入;
写入后恢复副本数和刷新间隔;
尽量使用自动生成的id。
查询调优
禁用wildcard;
禁用批量terms(成百上千的场景);
充分利用倒排索引机制,能keyword类型尽量keyword;
数据量大时候,可以先基于时间敲定索引再检索;
设置合理的路由机制。
解释一下 Elasticsearch集群中的 索引的概念 ?
Elasticsearch 集群可以包含多个索引,与关系数据库相比,它们相当于数据库表
Elasticsearch 索引数据多了怎么办,如何调优,部署
索引数据的规划,应在前期做好规划,正所谓“设计先行,编码在后”,
动态索引层面
基于 模板+时间+rollover api滚动 创建索引,举例:设计阶段定义:blog索引的模板格式为:
blog_index_时间戳的形式,每天递增数据。
blog_index_时间戳的形式,每天递增数据。
存储层面
冷热数据分离存储 ,热数据(比如最近3天或者一周的数据),其余为冷数据。
部署层面
结合ES自身的支持动态扩展的特点,动态新增机器的方式可以缓解集群压力,注意:如果之前主节点等规划合理 ,不需要重启集群也能完成动态新增的。
我们可以在 Elasticsearch 中执行搜索的各种可能方式有哪些?
基于 DSL 检索(最常用) Elasticsearch提供基于JSON的完整查询DSL来定义查询。
基于 URL 检索
GET /my_index/_search?q=user:seina
类SQL 检索
在 Elasticsearch 中删除索引的语法是什么
DELETE <index_name>
支持通配符删除:
DELETE my_*
在 Elasticsearch 中列出集群的所有索引的语法是什么?
GET _cat/indices
在索引中更新 Mapping 的语法?
ES 写数据过程
客户端选择一个 node 发送请求过去,这个 node 就是 coordinating node (协调节点)。
coordinating node 对 document 进行路由,将请求转发给对应的 node(有 primaryshard)。[路由的算法是?]
实际的 node 上的 primary shard 处理请求,然后将数据同步到 replica node 。
coordinating node 如果发现 primary node 和所有 replica node 都搞定之后,就返回响应结果给客户端。
ES 读数据过程
可以通过 doc id 来查询,会根据 doc id 进行 hash,判断出来当时把 doc id 分配到了哪个shard 上面去,从那个 shard 去查询。
1. 客户端发送请求到任意一个 node,成为 coordinate node 。
2. coordinate node 对 doc id 进行哈希路由,将请求转发到对应的 node,此时会使用 round- robin 随机轮询算法,在 primary shard 以及其所有 replica 中随机选择一个,让读请求负载均衡。
3. 接收请求的 node 返回 document 给 coordinate node 。
4. coordinate node 返回 document 给客户端。
2. coordinate node 对 doc id 进行哈希路由,将请求转发到对应的 node,此时会使用 round- robin 随机轮询算法,在 primary shard 以及其所有 replica 中随机选择一个,让读请求负载均衡。
3. 接收请求的 node 返回 document 给 coordinate node 。
4. coordinate node 返回 document 给客户端。
写请求是写入 primary shard,然后同步给所有的 replica shard;读请求可以从 primary shard 或replica shard 读取,采用的是随机轮询算法。
底层 lucene
简单来说,lucene 就是一个 jar 包,里面包含了封装好的各种建立倒排索引的算法代码。
ES中的倒排索引是什么?
传统的检索方式是通过文章,逐个遍历找到对应关键词的位置。
倒排索引,是通过分词策略,形成了词和文章的映射关系表,也称倒排表,这种词典 + 映射表即为倒排索引。
倒排索引的底层实现是基于:FST(Finite State Transducer)数据结构
空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间;
查询速度快。O(len(str)) 的查询时间复杂度。
空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间;
查询速度快。O(len(str)) 的查询时间复杂度。
请解释在 Elasticsearch 集群中添加或创建索引的过程?
要添加新索引,应使用创建索引 API 选项。创建索引所需的参数是索引的配置Settings,索引中的字段Mapping 以及索引别名 Alias。
详细描述一下Elasticsearch索引文档的过程。
协调节点默认使用文档ID参与计算(也支持通过routing),以便为路由提供合适的分片
shard = hash(document_id) % (num_of_primary_shards)
shard = hash(document_id) % (num_of_primary_shards)
当分片所在的节点接收到来自协调节点的请求后,会将请求写入到Memory Buffer,然后定时(默认是每隔1秒)写入到Filesystem Cache,这个从Momery Buffer到Filesystem Cache的过程就叫做refresh;
在flush过程中,内存中的缓冲将被清除,内容被写入一个新段,段的fsync将创建一个新的提交点,并将内容刷新到磁盘,旧的translog将被删除并开始一个新的translog。
flush触发的时机是定时触发(默认30分钟)或者translog变得太大(默认为512M)时;
详细描述一下Elasticsearch更新和删除文档的过程
删除和更新也都是写操作,但是Elasticsearch中的文档是不可变的,因此不能被删除或者改动以展示其变更;
磁盘上的每个段都有一个相应的.del文件。当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del文件中被标记为删除的文档将不会被写入新段。
在新的文档被创建时,Elasticsearch会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。
详细描述一下Elasticsearch搜索的过程
搜索被执行成一个两阶段过程,我们称之为 Query Then Fetch;
查询阶段
查询会广播到索引中每一个分片拷贝(主分片或者副本分片)。 每个分片在本地执行搜索并构建一个匹配文档的大小为 from + size 的优先队列。PS:在搜索的时候是会查询FilesystemCache的,但是有部分数据还在Memory Buffer,所以搜索是近实时的。
每个分片返回各自优先队列中 所有文档的 ID 和排序值 给协调节点,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
取回阶段
协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。每个分片加载并 丰富 文档,如果有需要的话,接着返回文档给协调节点。一旦所有的文档都被取回了,协调节点返回结果给客户端。
你可以列出 Elasticsearch 各种类型的分析器吗?
Standard Analyzer
标准分析器是默认分词器,如果未指定,则使用该分词器。 它基于Unicode文本分割算法,适用于大多数语言。
Whitespace Analyzer
基于空格字符切词。
Stop Analyzer
在simple Analyzer的基础上,移除停用词。
Keyword Analyzer
不切词,将输入的整个串一起返回。
ES是如何实现master选举的?
前置条件
只有是候选主节点(master:true)的节点才能成为主节点。
选举流程大致描述如下
确认候选主节点数达标,elasticsearch.yml 设置的值discovery.zen.minimum_master_nodes;
对所有候选主节点根据nodeId字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master节点。
如果对某个节点的投票数达到一定的值(候选主节点数n/2+1)并且该节点自己也选举自己,那这个节点就是master。否则重新选举一直到满足上述条件。
Elasticsearch中的属性 enabled, index 和 store 的功能是什么?
enabled:false
仅存储、不做搜索和聚合分析
index:false,
index:是否构建倒排索引(即是否分词,设置false,字段将不会被索引)
store
是否单独设置此字段的是否存储而从_source字段中分离,只能搜索,不能获取值
Elasticsearch中的节点(比如共20个),其中的10个选了一个master,另外10个选了另一个master,怎么办?
当集群master候选数量不小于3个时,可以通过设置最少投票通过数量(discovery.zen.minimum_master_nodes)超过所有候选节点一半以上来解决脑裂问题;
详细描述一下ES索引文档的过程?
这里的索引文档应该理解为文档写入 ES,创建索引的过程。
第一步
客户端向集群某节点写入数据,发送请求。(如果没有指定路由/协调节点,请求的节点扮演协调节点的角色。)
第二步
协调节点接受到请求后,默认使用文档 ID 参与计算(也支持通过 routing),得到该文档属于哪个分片。随后请求会被转到另外的节点。
第三步
当分片所在的节点接收到来自协调节点的请求后,会将请求写入到 Memory Buffer,然后定时(默认是每隔 1 秒)写入到F ilesystem Cache,这个从 Momery Buffer 到 Filesystem Cache 的过程就叫做 refresh;
第四步
当然在某些情况下,存在 Memery Buffer 和 Filesystem Cache 的数据可能会丢失,ES 是通过translog 的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到 translog 中,当 Filesystem cache 中的数据写入到磁盘中时,才会清除掉,这个过程叫做 flush;
第五步
在 flush 过程中,内存中的缓冲将被清除,内容被写入一个新段,段的 fsync 将创建一个新的提交点,并将内容刷新到磁盘,旧的 translog 将被删除并开始一个新的 translog。
第六步
flush 触发的时机是定时触发(默认 30 分钟)或者 translog 变得太大(默认为 512 M)时
在并发情况下,ES如果保证读写一致
可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,
另外对于写操作,一致性级别支持quorum/one/all,默认为quorum,即只有当大多数分片可用时才允许写操作。
对于读操作,可以设置replication为sync(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置replication为async时,也可以通过设置搜索请求参数_preference为primary来查询主分片,确保文档是最新版本。
对于GC方面,在使用ES时要注意什么?
倒排词典的索引需要常驻内存,无法GC,需要监控data node上segment memory增长趋势。
避免返回大量结果集的搜索与聚合。确实需要大量拉取数据的场景,可以采用scan & scroll api来实现。
收藏
0 条评论
下一页