基础语法
1 String 、StringBuilder 、StringBuffer 的区别?
1,String是只读字符串,引用的字符串内容是不能被改变的。
2,StringBuilder和StringBuffer表示的字符串对象可以直接被修改,StringBuilder单线程环境下使用,效率要比StringBuffer要高,StringBuffer加锁了,更加线程安全
2 equals() 与 == 的区别?
对于基本类型来说 一般使用==来比较值是否相等 equals不能用在基本类型 对象引用的话,equals()方法使用 == 来判断两个数据是否相等,如果我们自定义类时直接调用Object类中的equals()方法,判断地址值是否相同
3 是否可以继承String类? 为什么用final修饰?
不能,String是final修饰的类,不能被继承;
为了效率和安全
1.只有字符串是不可变的,字符串池才有可能实现
2.只有字符串是不可变的,所以多线程是安全的,同一个字符串实例可以被多个线程共享
3.因为字符串是不可变得,所以他在被创建的时候就已经被hashcode缓存了,不需要重新计算,使得字符串很适合做map中的键
4.如果字符串是可变的,会引起很严重的安全问题
4 什么是面向对象编程 ?
面向对象编程的基本思想就是利用类 方法 继承封装多态 等概念来进行程序设计,从现实世界中实际存在的事物(对象)出发来构造软件系统,尽可能的运用人类的自然思想进行程序设计
5 面向对象的三大特征
继承:子类继承父类可以实现代码的抽取和复用,单继承,多实现
封装:将事物的属性和行为抽取出来封装到类中
多态:父类的引用指向子类的实现 Animal a = new Dog(); 三个条件:继承 重写 向上转型
6 JDK,JRE,JVM
JDK是开发者工具包,包含jre和JVM
JER是开发环境,包含JVM和核心类库
JVM是虚拟机
7 java基本数据类型
整型 byte short int long
浮点型 float double
布尔型 boolean
字符型 char
8 什么是方法签名
方法名称和参数类型
9 java中的访问修饰符
private default protect public
10 final有什么用?和finally,finalize有什么区别?
final修饰的类不能被继承 final修饰的方法不能被重写 final修饰的变量为常量,不能被重新赋值
finally一般用在try-catch结构中,不管是否出现异常,finally后面的语句都会被执行
finalize 是Object类的方法 用在垃圾回收器中
11 this和super的区别
this指向的是自身的一个对象,代表对象本身,
super指向的是自己的一个超类对象,这个超类对象是最近的一个父类
this()调用的是本类的其他构造方法
super()调用的是父类的构造方法
12 break ,continue ,return 的区别及作用?
break 跳出当前循环 continue结束当前循环进入下一循环 return 结束整个方法
13 抽象类能被final修饰吗?
不能,final修饰的类不能被继承,而抽象类本身就是用来被继承的,相悖所以不能
14 static修饰的方法能调用非static修饰的变量和方法吗 ?
static修饰的方法只能调用静态方法和变量
15 局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?
因为生命周期不一致 局部变量保存在栈中 当方法结束后 非final修饰的变量会被销毁 而局部内部类对局部变量的引用仍然存在 调用局部变量的时候就会出错
16 重载(Overload)和重写(Override)的区别
重载:方法名称相同 参数类型不同 参数类型:参数数量 参数类型 参数顺序
重写:发生在父子类中 方法的名称参数必须相同 抛出的异常小于父类 访问修饰符大于等于父类
17 构造器(constructor)是否可被重写(override)
构造器不能被继承,所以不能被重写,但是可以被重载
18 Java中创建对象的方式有哪些
1 使用new关键字
2 使用Class类的newInstance方法
3 使用Constructor类的newInstance方法
4 使用clone方法
5 使用反序列化
集合
1 ArrayList和LinkList的区别?
ArrayList查询速度快(不准确),尾部增删快,头部增删慢,随机访问速度快;LinkList头尾增删速度快,中间不高,性能远比arraylist差,不适合做查询;真想查询用hashMap
- 是否保证线程安全: ArrayList 和 LinkedList 都不保证线程安全;
- 底层数据结构: Arraylist 底层使用的是数组;LinkedList 底层使用的是双链表;
- 插入和删除是否受元素位置的影响:
ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。
LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响。 - 是否支持快速随机访问 : LinkedList 不支持高效的随机元素访问,ArrayList 实现了RandmoAccess 接口,所以有随机访问功能
- 内存空间占用:
ArrayList的空间浪费主要结尾会预留一定的容量空间
LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间
2 HashMap底层原理
JDK8以前:数组+双链表
JDK8以后:数组+双链表+红黑树
3 HashMap 的resize过程是什么样的?
采用hash表数据加链表的形式,1.8以后引入了红黑树的数据结构 ,初始化数组长度为16 ,当数组长度到0.75时扩容 , 链表长度大于8时转为红黑树 , 红黑树的长度小于6时转为链表结构数组中的元素容量超过了阙值的0.75就会触发扩容长度
4 HashMap你经常用在那个地方?
- 电子商务的应用中使用HashMap作为缓存。
- 金融领域出于性能的考虑非常多的运用HashMap和ConcurrentHashMap。
- controller层向前台返回数据可以用map封装数据
- mybatis中的map可以作为参数或者封装结果集
5 HashMap和Hashtable的区别?
- hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。
- hashTable同步的,而HashMap是非同步的,效率上比hashTable要高。
- hashMap允许空键值,而hashTable不允许。
6 List、Map、Set三个接口,存取元素时,各有什么特点?
List 有序可重复
Set 无序不重复
Map 一般都是键值对key-value值,value可多值
7 HashSet是如何保证元素唯一性的呢?
通过元素的两个方法 hashcode和equals来完成的
8 TreeSet怎么对集合中的元素进行排序?
TreeSet底层数据结构是二叉树。
-
让元素自身具备比较性,需要元素对象实现Comparable接口,覆盖compareTo方法
-
让集合自身具备比较性,需要定义一个实现了Comparator接口的比较器,覆盖compare方法
9 map集合的两种取出方式?
-
通过map.keyset,先得到map集合的键,再根据键得到value值
-
通过map.entrySet直接得到键值对
10 Collection和Collections的区别?
Collection是集合类的上级接口,继承它的接口主要有set和list
Collections是工具类,有很多操作集合的方法
11 ConcurrentHashMap 的工作原理及代码实现
ConcurrentHashMap引入了分割(Segment)的概念,只对Map的一部分(Segment)进行上锁 , 这样 保证同步的时候,锁住的不是整个Map
12 HashCode的方法和作用
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作 用是确定该对象在哈希表中的索引位置
不同的对象可能会有相同的hashcode , 在比较一个对象是否相等时 , 首先需要比较对象的hashcode是否相等, 其次要比较equal是否相等
因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖 hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象 无论如何都不会相等
13 什么是链表
链表是可以将物理地址上不连续的数据连接起来,通过指针来对物理地址进行操作,实现增删改查 等功能。
链表大致分为单链表和双向链表
单链表:每个节点包含两部分,一部分存放数据变量的data,另一部分是指向下一节点的next指 针
双向链表:除了包含单链表的部分,还增加的pre前一个节点的指针
14 链表的优缺点
链表的优点
- 插入删除速度快(因为有next指针指向其下一个节点,通过改变指针的指向可以方便的增加 删除元素)
- 内存利用率高,不会浪费内存(可以使用内存中细小的不连续空间(大于node节点的大 小),并且在需要空间的时候才创建空间)
- 大小没有固定,拓展很灵活。
链表的缺点 : 不能随机查找,必须从第一个开始遍历,查找效率低
15 什么是红黑树
红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须满足下面性质:
- 性质1:每个节点要么是黑色,要么是红色。
- 性质2:根节点是黑色。
- 性质3:每个叶子节点(NIL)是黑色。
- 性质4:每个红色结点的两个子结点一定都是黑色。
- 性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。
16 常用的集合类有哪些?
Map接口和Collection接口是所有集合框架的父接口:
Collection接口的子接口包括:Set接口和List接口
Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及 Properties等
Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
17 哪些集合类是线程安全的?
- Vector:就比Arraylist多了个 synchronized (线程安全),因为效率较低,现在已经不太建议使 用。
- hashTable:就比hashMap多了个synchronized (线程安全),不建议使用。
- ConcurrentHashMap:是Java5中支持高并发、高吞吐量的线程安全HashMap实现。
18 遍历一个 List 有哪些不同的方式?
- for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素, 当读取到最后一个元素后停止。
- 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。
- foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过 程中操作数据集合,例如删除、替换。
19 如何实现数组和 List 之间的转换?
数组转 List:使用 Arrays. asList(array) 进行转换。
List 转数组:使用 List 自带的 toArray() 方法。
数组转set : 使用构造函数 , 直接传入数组
new HashSet(arrays[])
20 如何把一个线程不安全的集合转化为线程安全集合?
向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合 equles 方法比较。
HashSet 中的add ()方法会使用HashMap 的put()方法。HashMap 的 key 是唯一的在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复
21 说一下 HashSet 的实现原理?
HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统 一为present,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。
22 HashSet如何检查重复?HashSet是如何保证数据不可重复的?
向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合 equles 方法比较。
HashSet 中的add ()方法会使用HashMap 的put()方法。
HashMap 的 key 是唯一的在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复
23 说一下HashMap的实现原理?
HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射 操作,并允许使用null值和null键。
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
HashMap 基于 Hash 算法实现的
- 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
- 存储时,如果出现hash值相同的key,此时有两种情况。
- 如果key相同,则覆盖原始值;
- 如果key不同(出现冲突),则将当前的key-value放入链表中
- 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
24 HashMap是如何解决hash冲突
核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。
需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,数据长度低于6之后会退化为链表
25 为什么HashMap的初始长度是16 ?
因为长度太小很容易导致map扩容影响性能,如果分配的太大的话又会浪费资源,所以就使用16作为初始大小
减少hash碰撞
提高map查询效率
分配过小防止频繁扩容
分配过大浪费资源
26 HashMap 和 ConcurrentHashMap 的区别 ?
ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,
相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,
而 HashMap没有锁机制,不是线程安全的。
HashMap的键值对允许有null,但是ConCurrentHashMap都不允许
27 什么是TreeMap ?
TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,
或者根据 创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
28 comparable 和 comparator的区别?
comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序
comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用 来排序
29 如何实现对集合数据排序
使用Stream中的sorted方法
使用Collections中的sort方法
使用有序的集合 , 例如 : TreeSet , TreeMap
io流
1 io流体系介绍
四大家族(InputStream、OutputStream、Reader、Writer)
InputStream与OutputStream是所有字节型输入输出流的祖宗类
Reader与Writer是所有字节型输入输出流的祖宗类
2 Java的序列化
什么是序列化?
序列化是一种处理对象流的机制,序列化就是把对象转化为字节流,被传输的对象必须实现serializable接口。
什么是反序列化?
反序列化是序列化的逆过程,把字节流转化为对象
3 谈谈Java IO里面的常见类,字节流,字符流、接口、实现类、方法阻塞
输入流就是从外部文件输入到内存,输出流主要是从内存输出到文件
字符流中有抽象类InputStream和OutputStream,字节流的子类有
FileInputStream
FileOutputStream
BufferedOutputStream等。
字符流的子类有
BufferedReader
Writer
都实现了Closeable, Flushable, Appendable这些接口。程序中的输入输出都是以流的形式保存的,流中保存的实际上全都是字节文件
java中的阻塞式方法是指在程序调用改方法时,必须等待输入数据可用或者检测到输入结束或者抛出异常,否则程序会一直停留在该语句上,不会执行下面的语句。比如read()和readLine()方法
4 如何将字节流转化为字符流 ?
InputStreamReader 是字节流通向字符流的桥梁
BufferedReader in= new BufferedReader(new InputStreamReader(System.in));
OutputStreamWriter 是字符流通向字节流的桥梁
Writer out = new BufferedWriter(new OutputStreamWriter(System.out);
事务
1 说下事务的ACID
原子性(Atomicity): 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency): 事务前后数据的完整性必须保持一致。
隔离性(Isolation): 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
持久性(Durability): 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
2 事务有哪几种隔离级别呢?
读未提交(read uncommitted)
另一个事务修改了数据,但尚未提交,而本事务中的SELECT会读到这些未被提交的数据脏读
读已提交(read committed)RC
事务能够看到的数据都是其他事务已经提交的修改,也就是保证不会看到任何中间性状态,当然脏读也不会出现。
可重复读(repeatable read)RR
保证同一个事务中多次读取的数据是一致的,这是 MySQL InnoDB 引擎的默认隔离级别。
串行(serializable)
并发事务之间是串行化的,通常意味着读取需要获取共享读锁,
更新需要获取排他写锁,如果 SQL 使用 WHERE 语句,还会获取区间锁,是最高的隔离级别。
3 不考虑事务的隔离级别会有什么问题
并发事务可能造成:脏读、不可重复读和幻读等问题 ,
这些问题其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决
4 事务的并发?什么是脏读? 事务隔离级别,每个级别会引发什么问题,MySQL默认是哪个级别?
事务的并发问题
- 脏读:脏读是指在一个事务处理过程中读取了另一个事务未提交的数据。
- 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果因此本事务先后两次读到的数据结果会不一致。
- 幻读:可重复读的隔离级别解决了不可重复读的问题,保证了同一个事务里,查询的结果都是事务开始时的状态(一致性)。
MySQL默认的事务隔离级别为repeatable-read(可重复读)
MySQL 支持 4 中事务隔离级别.
事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持.
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大
可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读取,而且具有较好的并发性能。
尽管它会导致不可重复读、幻读这些并发问题,
在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
通信协议
1 名词解释 IP协议 TCP协议 UDP
- IP协议 分组交换协议 不可靠传输 一个数据包自动分成若干小的数据包然后通过网络进行传输
- TCP协议 传输控制协议 可靠协议 建立再IP协议之上 先建立连接,然后才能传输数据,传输完成后断开连接 支持双向通信,双方可以同时传输和接收数据
- UDP 数据报文协议 不面向连接,不可靠传输 传输效率高,通常用来传输视频等能容忍丢失部分数据的文件。
2 TCP与UDP比较
TCP:面向连接的协议 数据传输之前必然要建立连接
UDP: 每个数据报中都给出了完整的地址信息,无需要建立发送方和接收方的连接
TCP:传输数据大小限制 双方的socket就可以按统一的格式传输大的数据
UDP : UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。
TCP : 可靠协议,确保完全正确发送数据
UDP: 不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方
3 Socket
网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket。
Socket通常用来实现客户方和服务方的连接。一个Socket由一个IP地址和一个端口号唯一确定。
在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。
异常
1 异常的处理
声明:其实就是程序中遇到异常时,自己不处理,交给其它程序处理 throw (在使用throw抛出异常代码的后面,不能书写任意代码。)
捕获:其实就是在程序中遇到异常时,不会交给其它程序处理,自己处理 try 、catch、finally(使用try…catch…finally结构,catch中抛出异常后面如果有其他语句,先执行finally语句再去执行catch中的其他语句)
2 定义异常处理时,什么时候定义try,什么时候定义throws呢?
功能内部如果出现异常,如果内部可以处理,就用try;
如果功能内部处理不了,就必须声明出来,让调用者处理。
3 请写出你最常见到的5个runtime exception。
NullPointerException(空指针异常)
ArrayIndexOutOfBoundsException(数组索引越界异常)
ClassCastException(类型转换异常)
ArithmeticException(数学计算异常)
NumberFormatException(数字格式化异常)
反射
1 什么是反射机制?反射机制优缺点
JAVA反射机制是在运行过程中,对于任意一个类,都可以知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取信息,动态调用的方法称为java反射机制
反射机制优缺点
缺点 : 性能差,比直接的java代码慢很多
优点 : 灵活性高,运行期类型的判断,动态加载类
2 反射机制的应用场景
我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,
例如模块化的开发,通过反射去调用对应的字节码;
动态代理设计模式也采用了反射机制,
还有我们日常使用的 Spring/SpringMVC / Mybatis 等框架也大 量使用到了反射机制
3 Java获取反射的三种方法
1.通过new对象实现反射机制
2.通过路径实现反射机制
3.通过类名实现反射机制
JDK1.8新特性
1 lambda表达式
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑
是对函数式接口的另外一种重写形式
函数式接口(接口中只有一个抽象方法)
2 Optional
Optional类是Java8为了解决null值判断问题,借鉴google guava类库的Optional类而引入的一个同名Optional类,
使用Optional类可以避免显式的null值判断(null的防御性检查),避免null导致的NPE(NullPointerException)
JVM
1 JVM运行流程
首先通过编译器把 Java 代码转换成字节码文件,然后类加载器(ClassLoader)再把字节码加载到内存中,放在运行时数据区的方法区内,然后需要特定的命令解析器执行引擎 ,将字节码翻译成底层系统指令,再交由 CPU 去执行
2 说一下 JVM 运行时数据区
Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。
这些区域都有各自的用途,以及创建和销毁的时间,
有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。
Java 虚拟机所管理的内存被划分为如下几个区域
方法区 虚拟机栈 本地方法栈 堆 程序计数器
-
程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解 析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳 转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成; 为什么要线程计数器?因为线程是不具备记忆功能
-
Java 虚拟机栈(Java Virtual Machine Stacks):每个方法在执行的同时都会在Java 虚拟机栈中创 建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息; 栈帧就是Java虚拟机栈中的下一个单位
-
本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的; Native 关键字修饰的方法是看不到的,Native 方法的源码大部分都是 C和C++ 的代码
-
Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实 例都在这里分配内存;
-
方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的 代码等数据。
3 说一说你对Java堆的理解 ?
java堆(Java Heap)是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。
在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
java堆是垃圾收集器管理的主要区域,因此也被成为“GC堆”
4 对Java方法区的理解
方法区是所有线程共享的内存区域,它用于存储已被Java虚拟机加载的类信息、常量、静态变量、 即时编译器编译后的代码等数据。
5 知道直接内存吗?
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机中定义的内 存区域。
我的理解就是直接内存是基于物理内存和Java虚拟机内存的中间内存
6 强引用、软引用、弱引用、虚引用的区别?
强引用
我们平时new了一个对象就是强引用,例如 Object obj = new Object(); 即使在内存不足的情况下,JVM 宁愿抛出OutOfMemory错误也不会回收这种对象
软引用
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会 回收这些对象的内存。
SoftReference softRef = new SoftReference(str); // 软引用
弱引用
具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦 发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
String str=new String(“abc”);
WeakReference abcWeakRef = new WeakReference(str);
虚引用
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚 引用主要用来跟踪对象被垃圾回收器回收的活动。
7 有没有了解过GC ?
GC 是垃圾收集的意思
在JVM中, 有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,
只有在虚拟机空闲或者当前 堆内存不足时,才会触发执行,
扫描那些没有被任何引用的对象,并将它们添加到要回收的集合 中,进行回收。
8 垃圾回收器的原理是什么?有什么办法手动进行垃圾回收?
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。
通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。
通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空 间。
程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执 行。
9 讲一下新生代、老年代、永久代的区别
在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )
划分的目的是为了使 JVM 能够更好的进行内存的分配以及回收。
新生代中一般保存新出现的对象,所以每次垃圾收集时都发现大批对象死去,只有少量对象存活, 便采用了 复制算法 ,只需要付出少量存活对象的复制成本就可以完成收集。
老年代中一般保存存活了很久的对象,他们存活率高、没有额外空间对它进行分配担保,就必须采 用 “标记-清理”或者“标记-整理” 算法。
永久代就是JVM的方法区。在这里都是放着一些被虚拟机加载的类信息,静态变量,常量等数据。 这个区中的东西比老年代和新生代更不容易回收。
10 对象什么时候可以被垃圾器回收 ?
当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。
11 什么是类加载器,类加载器有哪些?
主要有以下四种类加载器:
- 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
- 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提 供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
- 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现
12 说一下类装载的执行过程?
类装载分为以下 5 个步骤:
加载:根据查找路径找到相应的 class 文件然后导入;
验证:检查加载的 class 文件的正确性;
准备:给类中的静态变量分配内存空间;
解析:虚拟机将常量池中的符号引用替换成直接引用的过程。
初始化:对静态变量和静态代码块执行初始化工作。
13 双亲委派
当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,
由父类去加 载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。
14 JVM调优
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,
其中最常用的是 jconsole 和 jvisualvm 这 两款视图监控工具。
- jconsole:用于对 JVM 中的内存、线程和类等进行监控;
- jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。
多线程
1 名词解释:同步,异步,阻塞,非阻塞,同步阻塞,异步阻塞,异步非阻塞
2 解释下多线程
线程,线程是程序的执行路径,或者可以说是程序的控制单元
一个进程可能包含一个或多个进程,当一个进程存在多条执行路径时,就可以将该执行方式称为多线程。
线程的执行方式大致可分为就绪(wait),执行(run),阻塞(block)三个状态,多个线程在运行中相互抢夺资源,造成线程在上述的三个状态之间不断的相互转换
3 创建线程的三种方法
- 继承Thread类,重写父类run()方法
- 实现runnable接口
- 使用ExecutorService、Callable、Future实现有返回结果的多线程(JDK5.0以后)
4 多线程同步机制
- 在需要同步的方法的方法签名中加入synchronized关键字。
- 使用synchronized块对需要进行同步的代码段进行同步。
- 使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象
5 线程的几种可用状态(生命周期)
- 就绪(Runnable):线程准备运行,不一定立马就能开始执行。
- 运行中(Running):进程正在执行线程的代码。
- 等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。
- 睡眠中(Sleeping):线程被强制睡眠。
- I/O阻塞(Blocked on I/O):等待I/O操作完成。
- 同步阻塞(Blocked on Synchronization):等待获取锁。
- 死亡(Dead):线程完成了执行。
6 线程锁对象详解
当多个线程同时访问共享数据时,可能导致数据不同步,甚至错误!
适用于密集型操作 需要资源保持同步 需要用到线程锁
优缺点:
- 优点:保证资源同步
- 缺点:有等待肯定会慢
7 同步方法的实现方式
- 同步方法 : 有synchronized关键字修饰的方法。
- 同步代码块 : 有synchronized关键字修饰的语句块。
- 使用特殊域变量(volatile)实现线程同步 : volatile关键字为域变量的访问提供了一种免锁机制
- 使用重入锁实现线程同步
- 使用局部变量threadlocal实现线程同步
- 使用阻塞队列实现线程同步
- 使用原子变量实现线程同步
8 什么是死锁(deadlock)?如何避免死锁?
两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。
避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。
9 sleep方法和wait方法的区别?
来自不同的类:wait()方法是Object类的方法,sleep方法是Thread类的方法。
对于锁的占用情况不同:sleep方法没有释放锁,而 wait 方法释放了锁,
使用范围: wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用)
唤醒方式:sleep()方法的线程通常是睡眠一定时间后,自动醒来。调用wait()方法,必须采用notify()或者notifyAll()方法唤醒
10 线程局部变量ThreadLocal
ThreadLocal 的作用和目的:
用于实现线程内的数据共享,相同程序代码下多个模块在同一个线程中运行时要共享一份数据
ThreadLocal 的应用场景:
订单处理包含一系列操作
银行转账包含一系列操作
同一段代码被不同的线程调用运行时
11 什么是守护线程?
守护线程(即daemon thread),是个服务线程,服务于其他的用户线程
1、守护线程,比如垃圾回收线程,就是最典型的守护线程。
2、用户线程,就是应用程序里的自定义线程
用户自定义线程
1、应用程序里的线程,一般都是用户自定义线程。
2、用户也可以自定义守护线程,只需要调用Thread类的设置方法设置一下即可。
12 synchronized和volatile比较
1)volatile不需要加锁,比synchronized更轻便,不会阻塞线程
2)synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性
13 乐观锁和悲观锁的理解及如何实现?原理和应用场景?
-
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,
这样别人想拿这个数据就会阻塞直到它拿到锁
悲观锁,先获取锁,再进行业务操作,必须在事务中使用。 -
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,
但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制
乐观锁,先进行业务操作,只在最后实际更新数据时进行检查数据是否被更新过。
并不会对数据加锁,而是通过对比数据的时间戳或者版本号,来实现乐观锁需要的版本判断。
14 为什么要用线程池
减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
15 3个方法代表了Servlet的生命周期
1、init方法:负责初始化Servlet对象。
2、service方法:负责响应客户的请求。
3、destroy方法:当Servlet对象退出生命周期时,负责释放占用的资源。
16 线程的 run()和 start()有什么区别?
run() 调用Thread中定义的方法
start() 启动线程
17 如何唤醒一个阻塞的线程?
- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,
并不能确切的唤醒某一 个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关; - notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,
而是让它 们竞争,只有获得锁的线程才能进入就绪状态
18 在 Java 程序中怎么保证多线程的运行安全?
使用安全类,比如 java.util.concurrent 下的类,使用原子类AtomicInteger
使用自动锁 synchronized。
使用手动锁 Lock。
19 线程之间如何通信及线程之间如何同步
通 过 在 线 程 之 间 共 享 对 象 就 可 以 了 , 然 后 通 过 wait/notify/notifyAll 、 await/signal/signalAll 进行唤起和等待
20 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗 ?
synchronized 关键字可以用在如下三个地方 :
-
修饰实例方法
-
修饰静态方法
-
修饰代码块
项目开发过程中很少使用synchronized 关键字修饰 , 因为效率比较低 , 会阻塞, 对于互联网项目来说不太合适 , 一般在单例模式中使用synchronized 实现双重检测锁
21 单例模式了解吗?解释一下双重检验锁方式实现单例模式
饿汉 懒汉 双重检查锁
22 新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?
用 join 方法
23 形成死锁的四个必要条件是什么 ?
- 互斥条件:在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,就只能等 待,直至占有资源的进程用毕释放。
- 占有且等待条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进 程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
- 不可抢占条件:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过 来。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。(比如一个进程集合,A在 等B,B在等C,C在等A)
24 线程 B 怎么知道线程 A 修改了变量
volatile 修饰变量
synchronized 修饰修改变量的方法
wait/notify
while 轮询探测 , 例如CAS
25 创建线程池的参数有哪些
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回 收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列 中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
26 如何创建线程池
使用new的方式
使用Excutors工具类创建
27 线程池的执行流程
提交一个任务到线程池中,线程池的处理流程如下:
- 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没 有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个 流程。
- 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队 列里。如果工作队列满了,则进入下个流程。
- 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任 务。如果已经满了,则交给饱和策略来处理这个任务。
28 如何合理分配线程池大小?
CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在 执行任务
IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数
29 线程池启动线程 submit()和 execute()方法有什么不同?
execute 没有返回值,如果不需要知道线程的结果就使用 execute 方法,性能会好很 多。
submit 返回一个 Future 对象,如果想知道线程结果就使用 submit 提交,
而且它能 在主线程中通过 Future 的 get 方法捕获线程中的异常。
30 如果你提交任务时,线程池队列已满,这时会发生什么
有俩种可能:
- 如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,
没关系,继续添加任务到 阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放 任务 - 如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue 中ArrayBlockingQueue 满了,
会根据maximumPoolSize 的值增加线程数量,如果增加了线程数量 还是处理不过来,
ArrayBlockingQueue 继续满,那么则会使用拒绝策略RejectedExecutionHandler 处理满了的任务,默认是 AbortPolicy
31 多线程中 synchronized 锁升级的原理是什么?
在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级
synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,
在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,
再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,
如果不一致,则升级偏向锁为 轻量级锁,通过自旋循环一定次数来获取锁,
执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
32 什么是 CAS ?
CAS 是一种基于锁的操作,而且是乐观锁。在 java 中锁分为乐观锁和悲观锁
CAS是通过无限循环来获取数据的,如果在第一轮循环中,a 线程获取地址里面的值被b 线程修改了,那么 a 线程需要自旋到下次循环才有可能机会执行
CAS操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止 , 可以理解成一个无阻塞多线程争抢资源的模型
33 CAS 的会产生什么问题?
ABA 问题: 从 Java1.5 开始 JDK 的 atomic包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。
循环时间长开销大: 对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大,从而浪费更多的 CPU 资源, 效率低于 synchronized。
只能保证一个共享变量的原子操作: 当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量 操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁。
34 什么是偏向锁 ?
偏向锁,顾名思义,它会偏向于第一个访问锁的线程,
如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,
减少加锁/解锁的一些CAS操作(比 如等待队列的一些CAS操作),这种情况下,就会给线程加一个偏向锁。
如果在运行过程中,遇 到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
35 什么是轻量级锁 ? 什么是重量级锁?
轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,
当第二个线程加入锁 争用的时候,轻量级锁就会升级为重量级锁;
重量级锁是synchronized ,是 Java 虚拟机中最为基础的锁实现。
在这种状态下,Java 虚拟机会阻 塞加锁失败的线程,并且在目标锁被释放的时候,唤醒这些线程。
36 什么是自旋锁 ? 自旋锁存在什么问题 ?
很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,
此时等待的线程都加锁 可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。
既然 synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在 synchronized 的边界做忙循环,这就是自旋
存在什么问题
如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。
上面Java实现的自旋锁不是公平的,即无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题。
37 synchronized 和 Lock 有什么区别?
- synchronized是Java内置关键字,在JVM层面,Lock是个Java类;
- synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
- synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁; 而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
38 volatile 关键字的作用
Java 提供了 volatile 关键字来保证可见性和禁止指令重排。确保一个线程的修改能对其他线程是可见的。
它会保证修改的值会立即被更新到主内存中,当有其他线程需要读取时,它会去内存中读取新值。
volatile 可以提供线程间的可见性 , 但是不能保证操作的原子性
39 ThreadLocal的底层原理
ThreadLocal是Java中所提供的线程本地存储机制,可以利⽤该机制将数据缓存在某个线程内部, 该线程可以在任意时刻、任意⽅法中获取缓存的数据
ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象中都存在⼀个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
(如果在线程池中使⽤ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使⽤完之后,应该要 把设置的key,value,也就是Entry对象进⾏回收,但线程池中的线程不会回收,⽽线程对象是通过 强引⽤指向ThreadLocalMap,ThreadLocalMap也是通过强引⽤指向Entry对象,线程不被回收, Entry对象也就不会被回收,从⽽出现内存泄漏,解决办法是,在使⽤了ThreadLocal对象之后,⼿ 动调⽤ThreadLocal的remove⽅法,⼿动清楚Entry对象)
40 项目中哪里使用的ThreadLocal
- 我经历的项目中, 一般用ThreadLocal保存登录用户信息 ,
在项目中提供一个过滤器 , 拦截到所有的请求, 获取请求中的UserId头 , 构建User对象保存到ThreadLocal - 在ThreadLocal中保存一些当前请求的响应信息 , 在返回的时候还是通过过滤器把响应信息从ThreadLocal中取出来, 返回给客户端
- 我们锁使用的很多框架内容也使用了ThreadLocal机制 ,
例如Spring的事务控制就会将连接放入到ThreadLocal , Seata的分布式事务控制也会将全局事务XID存入到ThreadLocal
41 使用ThreadLocal可能会产生什么问题?如何解决 ?
可能会产生内存泄露问题 : 因为当ThreadLocal对象使⽤完之后,应该要 把设置的key,value,也就是Entry对象进⾏回收,但线程池中的线程不会回收,⽽线程对象是通过 强引⽤指向ThreadLocalMap,ThreadLocalMap也是通过强引⽤指向Entry对象,线程不被回收, Entry对象也就不会被回收,从⽽出现内存泄漏,解决办法是,在使⽤了ThreadLocal对象之后,⼿ 动调⽤ThreadLocal的remove⽅法,⼿动清除Entry对象
JavaWeb
1 jsp和servlet的区别和联系
- jsp经编译后就变成了Servlet.JSP的本质就是Servlet
- jsp更擅长表现于页面显示,servlet更擅长于逻辑控制.
- Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到.
- Jsp是Servlet的一种简化
联系
JSP是Servlet技术的扩展,本质上就是Servlet的简易方式
2 jdbc操作数据库的步骤是什么?
第一步:Class.forName()加载数据库连接驱动;
第二步:DriverManager.getConnection()获取数据连接对象;
第三步:根据 SQL 获取 sql 会话对象,有 2 种方式 Statement、PreparedStatement ;
第四步:执行 SQL 处理结果集,执行 SQL 前如果有参数值就设置参数值 setXXX();
第五步:关闭结果集、关闭会话、关闭连接。
3 使用PreparedStatement的原因?
1、 PreparedStatement 接口继承 Statement,执行速度要快于 Statement 对象。
2 、 作 为 Statement 的 子 类 , PreparedStatement 继 承 了 Statement 的 所 有 功 能 。
3、在 JDBC 应用中,在任何时候都不要使用 Statement,原因如下:
一、代码的可读性和可维护性.Statement 需要不断地拼接,而 PreparedStatement 不会。
二、PreparedStatement 尽最大可能提高性能.DB 有缓存机制,相同的预编译语句再次被调用不会再次需要编译。
三、PreparedStatement极大地提高了安全性.Statement 容易被 SQL 注入,而 PreparedStatementc 传入的内容不会和 sql 语句发生任何匹配关系。
4 Cookie和session区别
1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用COOKIE。
4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
5 建议:将登陆信息等重要信息存放为SESSION,其他信息如果需要保留,可以放在COOKIE中
5 什么是servlet?
servlet是用来处理客户端请求并产生动态网页内容的java类
5 Servlet生命周期知道嘛 ?
加载Servlet
初始化
请求处理
销毁
6 转发和重定向
- 转发是服务器行为,重定向是客户端行为。
- 转发通过RequestDispatcher对象的forward(HttpServletRequest request,HttpServletResponse response)方法实现的。
重定向(Redirect) 是利用服务器返回的状态吗来实现的。 - 从地址栏显示来说
forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,
然后把这些内容再发给浏览器.所以它的地址栏还是原来的地址.
redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL. - 从数据共享来说
forward:转发页面和转发到的页面可以共享request里面的数据.
redirect:不能共享数据. - 从运用地方来说
forward:一般用于用户登陆的时候,根据角色转发到相应的模块.
redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等 - 从效率来说
forward:高.;redirect:低.
7 什么是cookie?什么是session?有什么区别 ?
Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,
它的过期时间可以任意设置,如果不主动清除,很长一段时间都能保留
Session是在无状态的HTTP协议下,服务端记录用户状态时用于标识具体用户的机制,
它是在服务端保存的用来跟踪用户的状态的数据结构,可以保存在文件、数据库、或者集群中。
Session:
数据存放在服务端,安全(只存放和状态相关的)
session不仅仅是存放字符串,还可以存放对象
session是域对象(session本身是不能跨域的,但可以通过相应技术来解决)
sessionID的传输默认是需要cookie支持的
Cookie
数据存放在客户端,不安全(存放的数据一定是和安全无关紧要的数据)
cookie只能存放字符串,不能存放对象
cookie不是域对象(Cookie是支持跨域的)
8 Tomcat是如何创建servlet类实例?用到了什么原理
当tomcat启动时,会读取在webapps目录下所有的web应用中的web.xml文件。
然后对xml文件进行解析,并读取servlet注册信息。
然后将每个应用中注册的servlet类都进行加载,并通过反射的方式实例化
9 Servlet是单例的还是多例的 ?
servlet是单列模式创建的,只实例化一次,同一个servlet可以处理多个用户请求,
当同时有两个用户访问时,则会启动两个负责处理请求的servlet线程,所以会出现线程安全问题
10 过滤器,拦截器,监听器的区别 ?
过滤器 , 监听器以及Servlet是JAVAWEB的核心三个组件 (Servlet用于处理用户请求 )
监听器用户监听整个应用程序 , 例如 ServletContext容器创建销毁 , Session创建销毁 ,Request创建销毁等
过滤器用于过滤用户请求 , 过滤资源路径可以在web.xml中配置, 也可以使用@WebFilter注解配置
拦截器是SpringMVC中的技术, 对请求进行拦截 , 拦截的是进入到SpringMVC框架内部的请求 , Filter可以拦截所有的web请求
其他
1 生产环境下你们怎么监控服务状态,如何获取数据,如何维护
编写监控docker的脚本
结合linux的crontab工具定时地检测制定docker容器
使用zabbix结合脚本实现对docker容器的批量监控
Redis
1 Redis中有哪些数据结构(类型) ?
Redis 有 5 种基础数据结构,它们分别是:
string(字符串)、list(列表)、hash(字典)、set(集 合) 和 zset(有序集合)
2 Redis 和 Memcached 的区别有哪些?
- Redis 支持复杂的数据结构
Memcached 仅提供简单的字符串。
Redis 提供复杂的数据结构,丰富的数据操作 - Redis原生支持集群模式 , Memcached不支持原生集群
主从复制集群
哨兵集群
Cluster集群 - 网络IO模型不同
Memcached 是多线程,非阻塞 IO 复用的网络模型
Redis 使用单线程的 IO 复用模型 - 持久化存储
Memcached 不支持持久化存储,重启时,数据被清空。
Redis 支持持久化存储,重启时,可以恢复已持久化的数据
3 为什么 Redis 单线程模型也能效率这么高?
- C 语言实现。
- 纯内存操作。
Redis 为了达到最快的读写速度,将数据都读到内存中,并通过异步的方式将数据写入磁盘。
所以 Redis 具有快速和数据持久化的特征。
如果不将数据放在内存中,磁盘 I/O 速度为严重影响 Redis 的性能。 - 基于非阻塞的 IO 多路复用机制。
- 单线程,避免了多线程的频繁上下文切换问题。
Redis 利用队列技术,将并发访问变为串行访问,消除了传统数据库串行控制的开销。 - 丰富的数据结构。
4 Redis有几种持久化方式?如何选择?
Redis 提供了两种方式,实现数据的持久化到硬盘。
- RDB 持久化(全量),是指在指定的时间间隔内将内存中的数据集快照写入磁盘。
- AOF持久化(增量),以日志的形式记录服务器所处理的每一个写、删除操作
RDB和AOF一起使用, 在Redis4.0版本支持混合持久化方式
5 Redis支持事务吗 ?
Redis 作为 NoSQL 数据库也同样提供了事务机制。
在 Redis 中,MULTI / EXEC / DISCARD / WATCH 这四个命令是我们实现事务的基石
6 Redis 有几种数据过期策略?
Redis 提供了 3 种数据过期策略:
惰性删除:当读/写一个已经过期的 key 时,会触发惰性删除策略,直接删除掉这个过期 key 。
定期删除:由于惰性删除策略无法保证冷数据被及时删掉,所以 Redis 会定期主动淘汰一批已过期的 key 。
主动删除:当前已用内存超过 maxmemory 限定时,触发主动清理策略,即 「数据“淘汰”策略」 。
7 Redis 有哪几种数据淘汰策略?
Redis 内存数据集大小上升到一定大小的时候,就会进行数据淘汰策略。
Redis 提供了 6 种数据淘汰策略:
volatile-lru
volatile-ttl
volatile-random
allkeys-lru
allkeys-random
【默认策略】no-enviction
在 Redis 4.0 后,基于 LFU(Least Frequently Used)最近最少使用算法,增加了 2 种淘汰策略:
volatile-lfu
allkeys-lfu
8 如果有大量的 key 需要设置同一时间过期,一般需要注意什么?
如果大量的 key 过期时间设置的过于集中,
到过期的那个时间点,Redis可能会出现短暂的卡顿现象。
1.一般需要在时间上加一个随机值,使得过期时间分散一些。
2.调大 hz 参数,每次过期的 key 更多,从而最终达到避免一次过期过多。
9 Redis集群都有哪些方案?
我所了解的Redis集群方案 :
主从复制集群
哨兵集群
Cluster集群
10 什么是 Redis 主从同步?
Redis 的主从同步(replication)机制,允许 Slave 从 Master 那里,
通过网络传输拷贝到完整的数据备份,从而达到主从机制。
主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,
而从数据库一般是只读的,并接收主数据库同步过来的数据。
一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。
第一次同步时,主节点做一次 bgsave 操作,并同时将后续修改操作记录到内存 buffer ,待完成后将 RDB 文件全量同步到复制节点,复制节点接受完成后将 RDB 镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程
11 Redis Cluster 的主从复制模型是怎样的?
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有 N-1 个复制节点。
所以,Redis Cluster 可以说是 Redis Sentinel 带分片的加强版。也可以说:
Redis Sentinel 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master ,继续提供服务。
Redis Cluster 着眼于扩展性,在单个 Redis 内存不足时,使用 Cluster 进行分片存储。
12 Redis Cluster 会有写操作丢失吗?为什么?
Redis 并不能保证数据的强一致性,而是【异步复制】,这意味这在实际中集群在特定的条件下可能会丢失写操作。
13 Redis 有哪些重要的健康指标?
存活情况
连接数
阻塞客户端数量
使用内存峰值
内存碎片率
缓存命中率
OPS
持久化
失效KEY
慢日志
14 Redis中的大key怎么处理?
分片处理
15 缓存雪崩(缓存失效)了解过吗?
缓存失效 缓存失效指的是大量的缓存在同一时间失效,
到时DB的瞬间压力飙升。造成这种现象的 原因是,key的过期时间都设置成一样了。
解决方案是,key的过期时间引入随机因素, 比如5分钟+随机秒这种方式
16 缓存穿透,缓存击穿
缓存穿透
是指查询一条数据库和缓存都没有的一条数据,就会一直查询数据库,
对数据库的访问压力就会增大,缓存穿透的解决方案,有以下2种:
缓存空对象:代码维护较简单,但是效果不好。 布隆过滤器:代码维护复杂,效果很好
缓存击穿
是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,
同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案 :
- 热点数据提前预热
- 设置热点数据永远不过期。
- 加锁 , 限流
17 缓存预热 缓存更新 缓存降级
缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。
这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
缓存更新
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),
还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
- 定时去清理过期的缓存;
- 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,
第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!
缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,
仍然需要保证服务还是可用的,即使是有损服务。
系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
而有些服务是无法降级的(如加入购物车、结算)。
服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
18 缓存并发
有时候如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况,
如果并发确实很大,这也可能造成DB压力过大,还有缓存频繁更新的 问题。
一般处理方案是在查DB的时候进行加锁,如果KEY不存在,就加锁,然后查DB入缓存, 然后解锁;
其他进程如果发现有锁就等待,然后等解锁后再查缓存或者进入DB查询
19 热点数据和冷数据是什么?
热点数据,缓存才有价值
对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,
不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存
信息修改频率不高,读取通常非常高的场景。
数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,
如果缓存还没有起作用就失效了,那就没有太大价值了。
那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?
比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,
此时就需要将数据同步保存到Redis缓存,减少数据库压力
20 有没有尝试进行多机redis 的部署?如何保证数据一致的?
主从复制,读写分离
一类是主数据库(master)一类是从数据库(slave),
主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,
而从数据库一般是只读的,并接收主数据库同步过来的数据,
一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。
对于大量的请求怎么样处理
- redis是一个单线程程序,也就说同一时刻它只能处理一个客户端请求;
- redis是通过IO多路复用(select,epoll, kqueue,依据不同的平台,采取不同的实现)来处理多个客户端请求的
21 Redis 常见性能问题和解决方案?
1 - Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件
2 - 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
3 - 为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内
4 - 尽量避免在压力很大的主库上增加从库
5 - 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <-Slave3…
22 为什么Redis的操作是原子性的,怎么保证原子性的?
'- 对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。
- Redis的操作之所以是原子性的,是因为Redis是单线程的。
- Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。
23 Redis事务
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
Redis会将一个事务中的所有命令序列化,然后按顺序执行。
- redis 不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
- 如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
- 如果在一个事务中出现运行错误,那么正确的命令会被执行
24 Redis实现分布式锁
Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在,则 SET)的简写。
加锁:使用 setnx key value 命令,如果key不存在,设置value(加锁成功)。如果已经存在lock(也就是有客户端持有锁了),则设置失败(加锁失败)。
解锁:使用 del 命令,通过删除键值释放锁。释放锁之后,其他客户端可以通过 setnx 命令进行加锁。
25 Redis的内存用完了会发生什么?
如果达到设置的上限,Redis 的写命令会返回错误信息( 但是读命令还可以正常返回。)
或者你可 以将 Redis 当缓存来使用配置淘汰机制, 当 Redis 达到内存上限时会冲刷掉旧的内容。
26 Redis 如何做内存优化?
尽可能使用散列表( hashes), 散列表( 是说散列表里面存储的数少) 使用的内存非常小,
所 以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的 web 系统中有一个用户对象,
不 要为这个用户的名称, 姓氏, 邮箱, 密码设置单独的 key,
而是应该把这个用户的所有信息存储到一张散列表里面
27 Redis和Mysql如何保证数据一致?
- 先更新Mysql,再更新Redis,如果更新Redis失败,可能仍然不⼀致
- 先删除Redis缓存数据,再更新Mysql,再次查询的时候在将数据添加到缓存中,
这种⽅案能解决1 ⽅案的问题,但是在⾼并发下性能较低,⽽且仍然会出现数据不⼀致的问题,
⽐如线程1删除了 Redis缓存数据,正在更新Mysql,
此时另外⼀个查询再查询,那么就会把Mysql中⽼数据⼜查到 Redis中 - 使用MQ异步同步, 保证数据的最终一致性
Mybatis/Mybatis-plus
1 Mybatis的工作原理 ?
1 读取 MyBatis 配置文件
2 加载映射文件Mapper.xml
3 构造会话工厂SqlSessionFactory
4 创建会话对象SqlSession
5 通过Executor 执行器动态地生成需要执行的 SQL 语句
6 MappedStatement 对象存储要映射的 SQL 语句的 id、参数等信息。
7 执行输入参数映射
8 根据查询结果执行输出结果映射封装数据
2 在mapper中如何传递多个参数 ?
1 顺序传参法 ,arg0代表第一个参数 arg1代表第二个参数
2 使用@Param注解传参法
3 封装到对象或者Map集合
3 mybatis映射文件中用到哪些标签
select : 查询标签
update : 更新标签
delete : 删除标签
insert : 插入标签
where : 类似于SQL语句的where
if : 动态SQL对条件进行判断
foreach : 遍历循环 , 拼接SQL , 一般用于批量处理
set : 类似于SQL语句中 的set关键词
resultMap : 结果集映射标签
4 mybatis如何实现多表查询 ?
1 编写多表查询SQL语句 , 使用ResultMap建立结果集映射
2 使用延迟加载 , 多个SQL语句组合
主要使用的注解就是 :
collection : 建立一对多映射
association : 简历一对一映射
5 foreach标签哪些属性?
collection : 指定遍历的集合, 如果遍历的是数组就是array , 遍历的是list集合就是list , 遍历的是对象或者Map的集合属性就是对象属性名称或者map的key
item : 遍历的每一项
index : 遍历过程中的索引
open : 字符串拼接前缀
separator : 分隔符
close : : 字符串拼接后
6 Mybatis如何获取生成的主键?
1 insert 标签上使用useGeneratedKeys=“true” keyProperty
2 在insert标签内部使用selectKey标签配置keyProperties , keyColum通过select last_insert_id()查询
7 当实体类中的属性名和表中的字段名不一样 ,怎么办
第1种: 通过在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
第2种: 通过 resultMap来映射字段名和实体类属性名的一一对应的关系。
如果仅仅是驼峰命名 可以配置驼峰映射
8 使用MyBatis的mapper接口调用时有哪些要求?
1 Mapper接口方法名和mapper.xml中定义的每个sql的id相同。
2 Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相 同。
3 Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。
4 Mapper.xml文件中的namespace即是mapper接口的类路径。
9 Dao接口里的方法,参数不同时,方法能重载吗 ?
不能,因为是全限名+方法名的保存和寻找策略。
10 Mybatis是如何进行分页的?分页插件的原理是什么?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,
可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,
在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
11 Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,
association指的就是 一对一,collection指的就是一对多查询。
可以配置lazyLoadingEnabled=true|false来完成
原理是使用CGLIB创建目标对象的代理对象
12 Mybatis的一级、二级缓存 ?
一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,
当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存
二级缓存与一级缓存其机制相同,默认也是采用 HashMap 存储,
不同在于其 存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。
默认不打开二级缓 存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状 态),可在它的映射文件中配置
13 使用Mybatis-Plus的过程中多表查询如何做 ?
使用mybtis的映射配置文件, 自己编写SQL语句, 编写ResultMap映射
14 Mybatis是如何实现实体类和数据库表映射的 ?
通过注解 :
@TableName
@TableFiled
@TableId
@Version
15 Mybatis-Plus自动填充用过吗?
通过@TableFiled中的fill属性可以来指定字段什么时候被自动填充
还需要自定义实现MyMetaObjectHandler
16 #{}和${}的区别
1 #{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理。
(Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用 PreparedStatement的set方法来赋值。)
2 #{} 可以有效的防止SQL注入,提高系统安全性;${} 不能防止SQL 注入
3 #{} 的变量替换是在数据库系统中; ${} 的变量替换是在 数据库系统外
17 Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动 态sql的执行原理吗?
Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,
完成逻辑判断和动态拼接sql的功能,
Mybatis提供了9种动态sql标签 trim|where|set|foreach|if|choose|when|otherwise|bind。
其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此 来完成动态sql的功能。
18 简述Mybatis的插件运行原理,以及如何编写一个插件。
Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、 Executor这4种接口的插件,
Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现 接口方法拦截功能,
每当执行这4种接口对象的方法时,就会进入拦截方法,拦截那些你指定需要拦截的方法。
实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,
指定要拦截哪一 个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
Mysql
1 mysql的优化,mysql的性能优化
1.从设计方面 选择合适的存储引擎 , 合适的字段类型 , 遵循范式(反范式设计)
存储引擎 :
不需要事务, 不需要外键读写较多的的使用MyIsam
需要事务, 需要外键的使用InnoDB
合适的字段类型 :
定长字符串用char , 不定长用varchr
状态, 性别等有限数量值的用tinyint
遵循范式 :
第一范式1NF,原子性
第二范式2NF,消除部分依赖
第三范式3NF,消除传递依赖
2.从功能方面可以对索引优化,采用缓存缓解数据库压力,分库分表。
3.从架构方面可以采用主从复制,读写分离,负载均衡
顺序优化:
- SQL语句及索引的优化
- 数据库表结构的优化
- 系统配置的优化
- 硬件的优化
2 MYSQL超大分页怎么处理 ?
MYSQL 不是跳过offset行, 而是取offset+N行, 然后放弃前offset行 , 返回N行, 所以当offset比较法的情况下分页效率很低
正确的处理方法是 : 先快速定位需要获取的id再关联查询获取数据
3 MYSQL索引
索引是帮助MySQL高效获取数据的数据结构。能加快数据库的查询速度,索引往往是存储在磁盘上的文件中的
普通索引 index:对索引字段没有要求。
唯一索引 unique index:要求索引字段值不能重复。同时增加唯一约束。
主键索引 primary key:要求索引字段值不能重复,也不能为NULL。同时增加主键约束。
复合索引
全文索引 fulltext key:索引字段的来源不是所有字段的数据,而是从字段中提取的特别关键词。
4 mysql索引的优势和劣势
优势:
可以提高数据检索的效率,降低数据库的IO成本,类似于书的目录。
通过索引列对数据进行行排序,降低数据排序的成本,降低了了CPU的消耗
劣势:
索引会占据磁盘空间
索引虽然会提高查询效率,但是会降低更新表的效率。比如每次对表进行增删改操作,MySQL不仅要保存数据,还要保存或者更新对应的索引文件
5 mysql索引是怎么存储的
索引是在存储引擎中实现的,也就是说不同的存储引擎,会使用不同的索引
6 什么情况下索引会失效?
- 范围查询
mysql 会一直从左向右匹配直到遇到范围查询(>、<、between、like)就停止匹配。范围列可以用到索引,但是范围列后面的列无法用到索引。 - like 语句
如果通配符 % 不出现在开头,则可以用到索引,like “value%”可以使用索引,但是 like “%value%”不会使用索引,走的是全表扫描 - 列上使用函数或表达式
如果查询条件中含有函数或表达式,将导致索引失效而进行全表扫描 - 根据null值查询
只要列中包含有 NULL 值都将不会被包含在索引中,复合索引中只要有一列含有 NULL 值,那么这一列对于此复合索引就是无效的。所以在数据库设计时不要让字段的默认值为 NULL
5 如果一条SQL语句执行很慢 , 如何找到慢的原因 ?
1 开启慢查询日志或者使用监控工具监控慢查询 , 例如 : skywalking
2 为常用的查询字段或者条件建立索引
3 使用Explain关键字查看SQL执行计划 , 看索引是否命中 , 以及命中的级别
4 根据情况修改SQL语句
6 mysql有哪几类锁?
MySQL 提供了全局锁、行级锁、表级锁。其中 InnoDB 支持表级锁和行级锁,MyISAM 只支持表级锁。
乐观锁和悲观锁
7 什么是全局锁?它的应用场景有哪些?
全局锁就是对整个数据库实例加锁,它的典型使用场景就是做全库逻辑备份。
这个命令可以使整个库处于只读状态。使用该命令之后,
数据更新语句、数据定义语句、更新类事务的提交语句等操作都会被阻塞。
8 什么是共享锁?
共享锁又称读锁 (read lock),是读取操作创建的锁。
其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
当如果事务对读锁进行修改操作,很可能会造成死锁。
9 什么是排它锁?
排他锁 exclusive lock(也叫 writer lock)又称写锁。
若某个事物对某一行加上了排他锁,只能这个事务对其进行读写,
在此事务结束之前,其他事务不能对其进行加任何锁,其他进程可以读取,不能进行写操作,需等待其释放
10 MYSQL的默认隔离级别是什么?如何调整隔离级别 ?
MYSQL的默认隔离级别是可重复读(RepeatableRead)
Oracle的默认隔离级别是读已提交(ReadCommited)
调整隔离级别的方式 :
-
查看隔离级别: 通过,show variables like '%tx_isolation%'查看当前事务的隔离级别
-
设置会话隔离级别:set session transaction isolation level 隔离级别
-
设置全局隔离级别,通过修改配置文件 /etc/my.cnf , 在文件的文末添加配置:transaction-isolation =隔离级别来设置
-
程序中一般通过Spring控制事务, 可以在事务属性上,通过isolation 属性设置隔离级别
11 MySQL 事务实现原理是什么?
事务的实现是基于数据库的存储引擎,不同的存储引擎对事务的支持程度不一样。
MySQL 中支持事务的存储引擎有InnoDB 和 NDB。
12 说说分库与分表设计(面试过)分片
分库与分表的目的在于,
减小数据库的单库单表负担,提高查询性能,缩短查询时间。
通过分表,可以减少数据库的单表负担,将压力分散到不同的表上,
同时因为不同的表上的数据量少了,起到提高查询性能,缩短查询时间的作用,
此外,可以很大的缓解表锁的问题。
分表策略可以归纳为垂直拆分和水平拆分。
**水平分表
将不常用的字段单独拆分到另外一张扩展表. 将大文本的字段单独拆分到另外一张扩展表,
将不经常修改的字段放在同一张表中,将经常改变的字段放在另一张表中。
对于海量用户场景,可以考虑取模分表,数据相对比较均匀,不容易出现热点和并发访问的瓶颈。
**库内分表
仅仅是解决了单表数据过大的问题,但并没有把单表的数据分散到不同的物理机上,
因此并不能减轻 MySQL 服务器的压力,仍然存在同一个物理机上的资源竞争和瓶颈,
包括 CPU、内存、磁盘 IO、网络带宽等。
分库与分表带来的分布式困境与应对之策
- 数据迁移与扩容问题----
一般做法是通过程序先读出数据,然后按照指定的分表策略再将数据写入到各个分表中。 - 分页与排序问题----
需要在不同的分表中将数据进行排序并返回,并将不同分表返回的结果集进行汇总和再次排序,最后再返回给用户。
13 Mysql常见的存储引擎InnoDB、MyISAM的区别?
1)事务:MyISAM不支持,InnoDB支持
2)锁级别:MyISAM 表级锁,InnoDB 行级锁及外键约束
(MySQL表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。
对MyISAM表进行读操作时,它不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写操作;
而对MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作。
InnoDB行锁是通过给索引项加锁来实现的,即只有通过索引条件检索数据,InnoDB才使用行级锁,否则将使用表锁!
行级锁在每次获取锁和释放锁的操作需要消耗比表锁更多的资源。
在InnoDB两个事务发生死锁的时候,会计算出每个事务影响的行数,然后回滚行数少的那个事务。
当锁定的场景中不涉及Innodb的时候,InnoDB是检测不到的。只能依靠锁定超时来解决。
3)MyISAM存储表的总行数;InnoDB不存储总行数;
4)MyISAM采用非聚集索引,B+树叶子存储指向数据文件的指针。
InnoDB主键索引采用聚集索引,B+树叶子存储数据
5)适用场景:
- MyISAM适合:插入不频繁,查询非常频繁,如果执行大量的SELECT,MyISAM是更好的选择, 没有事务。
- InnoDB适合:可靠性要求比较高,或者要求事务;表更新和查询都相当的频繁, 大量的INSERT或UPDATE
14 数据库三范式,根据某个场景设计数据表?优缺点
1 - 所有字段值都是不可分解的原子值。
2 - 在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。
3 - 数据表中的每一列数据都和主键直接相关,而不能间接相关。
第一范式(确保每列保持原子性)
第二范式(确保表中的每列都和主键相关) 在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中
第三范式(确保每列都和主键列直接相关,而不是间接相关)
15 MySQL 索引使用的注意事项
MySQL 索引通常是被用于提高 WHERE 条件的数据行匹配时的搜索速度,
函数,运算,否定操作符,连接条件,多个单列索引,最左前缀原则,范围查询,不会包含有NULL值的列,like 语句
1 不要在列上使用函数和进行运算
2 不要在列上使用函数,这将导致索引失效而进行全表扫描。
3 尽量避免使用 != 或 not in或 <> 等否定操作符
4 尽量避免使用 or 来连接条件
5 多个单列索引并不是最佳选择,复合索引的最左前缀原则
6 查询中的某个列有范围查询,则其右边所有列都无法使用索引优化查找。
7 索引不会包含有NULL值的列
8 当查询条件左右两侧类型不匹配的时候会发生隐式转换,隐式转换带来的影响就是可能导致索引失效而进行全表扫描。
9 like 语句的索引失效问题
like 的方式进行查询,在 like “value%” 可以使用索引,但是对于 like “%value%” 这样的方式,执行全表查询,
这在数据量小的表,不存在性能问题,但是对于海量数据,全表扫描是非常可怕的事情。
所以,根据业务需求,考虑使用 ElasticSearch 或 Solr 是个不错的方案。
16 sql的优化
- 建索引
- 减少表之间的关联
- 优化 sql,尽量让 sql 很快定位数据,不要让sql 做全表查询,应该走索引,把数据 量大的表排在前面
- 简化查询字段,没用的字段不要,已经对返回结果的控制,尽量返回少量数据
- 尽量用PreparedStatement 来查询,不要用 Statement
17 CHAR和VARCHAR的区别?
1 char的长度是不可变的,用空格填充到指定长度大小,而varchar的长度是可变的。
2 char的存取数度还是要比varchar要快得多
3 char的存储方式是:对英文字符(ASCII)占用1个字节,对一个汉字占用两个字节。
varchar的存储方式是:对每个英文字符占用2个字节,汉字也占用2个字节。
18 MySQL数据库作发布系统的存储怎么优化?
'- 设计良好的数据库结构,允许部分数据冗余,尽量避免join查询,提高效率。
-
选择合适的表字段数据类型和存储引擎,适当的添加索引。
-
mysql库主从读写分离。
-
找规律分表,减少单表中的数据量提高查询速度。
-
添加缓存机制,比如memcached,apc等。
-
不经常改动的页面,生成静态页面。
-
书写高效率的SQL。比如
SELECT * FROM TABEL
改为SELECT field_1, field_2, field_3 FROM TABLE.
19 内连接、自连接、外连接(左、右、全)、交叉连接的区别
'- 内连接:只有两个元素表相匹配的才能在结果集中显示。
- 外连接:左外连接: 左边为驱动表,驱动表的数据全部显示,匹配表的不匹配的不会显示。
- 右外连接:右边为驱动表,驱动表的数据全部显示,匹配表的不匹配的不会显示。
- 全外连接:连接的表中不匹配的数据全部会显示出来。
- 交叉连接:笛卡尔效应,显示的结果是链接表数的乘积。
20 需要创建索引的情况 不需要创建索引情况
需要
- 主键自动建立主键索引
- 频繁作为查询条件的字段应该创建索引
- 多表关联查询中,关联字段应该创建索引 (on 两边都要创建索引)
- 查询中排序的字段,应该创建索引
- 频繁查找字段 , 应该创建索引
- 查询中统计或者分组字段,应该创建索引
不需要
- 表记录太少
- 经常进⾏行增删改操作的表
- 频繁更新的字段
- where条件里使用频率不高的字段
21 Mysql慢查询
慢SQL的系统表现
1,数据库CPU负载高。一般是查询语句中有很多计算逻辑,导致数据库cpu负载。
2,IO负载高导致服务器卡住。这个一般和全表查询没索引有关系。
3,查询语句正常,索引正常但是还是慢。如果表面上索引正常,但是查询慢,需要看看是否索引没有生效。
开启SQL慢查询的日志
1 在 MySQL 的配置文件 my.cnf 的 [mysqld] 项下配置慢查询日志开启/
2 Mysql官方也提供了mysqldumpslow这个工具分析慢查询
SQL调优
1 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配
2 =和in可以乱序
3 尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少
4 索引列不能参与计算,保持列“干净”
5 尽量的扩展索引,不要新建索引。
SQL慢查询的解决步骤
-
打开慢日志查询,确定是否有SQL语句占用了过多资源,
如果是,在不改变业务原意的前提下,对insert、group by、order by、join等语句进行优化。 -
考虑调整MySQL的系统参数: innodb_buffer_pool_size、innodb_log_file_size、table_cache等。
-
确定是否是因为高并发引起行锁的超时问题。
-
如果数据量过大,需要考虑进一步的分库分表
MongoDB
1 MongoDB的概念
MongoDB是由 C++语言编写的,有自己强大的查询语法的一个基于分布式文件存储的开源数据库系统。
1、一个mongodb实例可以创建多个数据库
2、一个数据库可以创建多个集合
3、一个集合可以包括多个文档
应用场景
- 网站数据:mongo 非常适合实时的插入,更新域查询,并具备网站实时的数据存储所需的复制及高度伸缩性。
- 缓存:由于性能很高,mongo 也适用作为信息基础设施的缓存层。在系统重启之后由 mongo 搭建的持久化缓存可以避免下层的数据源过载。
- 大尺寸、低价值的数据:使用传统的关系型数据库存储一些数据时可能会比较贵,在此之前,很多程序员 往往会选择传统的文字进行存储。
- 高性伸缩的场景:mongo 非常适合由数十或者数百台服务器组成的数据库。
- 用于兑现及 JSON 数据的存储:mongo 的 BSON 数据格式非常适合文档格式化的存储及查询。
- 重要数据:mysql,一般数据:mongodb,临时数据:memcache
- 对于关系型数据库而言,mongodb 提供了一个更快速的视图 view;而对于 java 程序而言,mongodb 可以作为一个持久化的数组来使用,并且这个持久化的数组还可以支持排序、条件、限制等功能。
- 将 mongo 代替 mysql 的部分功能,主要一个思考点就是:把 mongodb 当作 mysql 的一个 view(视图) 如 mongodb 经常用于评论系统。针对评论这样数据量大并且价值不高的数据,我们通常采用 MongoDB 来实 现存储。
2 MongoDB的优缺点
优点:
1) 弱一致性(最终一致),更能保证用户的访问速度。
2) 文档结构的存储方式,能够更快捷的获取
3) 内置 GridFS,高效存储二进制大对象(比如照片和视频)
4) 支持复制集、主备、互为主备、自动分片等特性
5) 动态查询
6) 全索引支持,扩展到内部对象和内嵌数组
缺点:
1) 不支持事务
2) MongoDB 占用空间过大,维护工具不够成熟
3 为什么要在 MongoDB 中使用分析器?
mongodb 中包括了一个可以显示数据库中每个操作性能特点的数据库分析器.
通过这个分析器你可以找到比预期慢的查询(或写操作);
利用这一信息,比如,可以确定是否需要添加索引.
4 为什么 MongoDB 的数据文件很大?
MongoDB 采用的预分配空间的方式来防止文件碎片
5 mongodb与mysql的区别?
mongodb 的本质还是一个数据库产品,它与 mysql 的区别在于它 不会遵循一些约束,
比如:sql 标准、ACID 属性,表结构等。
面向集合文档的存储:适合存储 Bson(json 的扩展)形式的数据;
格式自由,数据格式不固定,生产环境下修改结构都可以不影响程序运行;
强大的查询语句,面向对象的查询语言,基本覆盖 sql 语言所有能力;
完整的索引支持,支持查询计划;
支持复制和自动故障转移;
支持二进制数据及大型对象(文件)的高效存储;
使用分片集群提升系统扩展性;
使用内存映射存储引擎,把磁盘的 IO 操作转换成为内存的操作
6 GirdFS 是什么?工作原理是什么?如何使用?
GridFS 是 MongoDB 提供的用于持久化存储文件的模块,它可以作为分布式文件系统使用,
CMS子系统将页面文件、模板文件存储到 GridFS 中,
由于本项目使用 MongoDB,选用 GridFS 可以快速集成开发。
它的工作原理是: 在 GridFS 存储文件是将文件分块存储,文件会按照 256KB 的大小分割成多个块进行存储,
GridFS 使用两个集合(collection)存储文件,一个集合是 chunks, 用于存储文件的二进制数据;
一个集合是files,用于存储文件 的元数据信息(文件名称、块大小、上传时间等信息)。
从 GridFS 中读取文件要对文件的各各块进行组装、 合并
7 数据库表设计过吗,怎么设计
1 每张表都具备id这个属性,而随着系统越做越大,id则会越来越多,
所以他的类型应设为bigint(也可以是Long),长度设为20
2 是否启用(bool类型的东西)、类型(type),状态(state)类型应设为tinyint,长度设为1,一般设个默认值0
3 创建时间(createtime)应设为datetime类型,数据库也是有date类型,
而我们用datetime而不用date的原因是datetime有时分秒,而date
4 普通的varchar类型字段建议设置默认值为null(empty string)
5 手机号可以使用char类型,因为手机号都是固定13位的,用char可以节省空间
6 对于内容字数比较多的时候你就不能使用varchar了,应该改用text类型
7 一对多关系一般都是主从表,一个主表可以对应着多个从表,也就是说他们需要关联字段(id),
分别在主表和从表里加上他们的关联字段就好,没有建关联表的必要。
8 多对多关系的话,则必须要建立关联表,把两张甚至多张表关联起来,
根据aid查询多个bid,再拿这多个bid来用,反过来也是,用bid查询多个aid,
就可以拿到这多个aid来用,用的意思就是增删改查及批量增删改查
Gateway
1 在你们的项目中用到了网关的哪些功能 ?
路由
跨域
鉴权
限流
2 你在开发过程中经常使用的路由断言有哪些 ?
Path : 根据请求路径匹配
Before/After : 根据时间路由 , 一般用于新版本上线
Header : 根据请求头路由, 一般用户灰度发布
3 你们在开发中经常用到的过滤器有哪些 ?
AddRequestHeader : 添加请求头 , 用户sentinel黑白名单
RequestRateLimiter : 限流配置
StripPrefix : 取消路径前缀
4 有没有使用过网关的全局过滤器 ?
使用过, 主要通过自定义全局过滤器实现对请求的统一权限校验
客户端发送请求携带token到网关, 由网关负责统一的token解析,
解析完毕之后获取token中的用户信息, 保存到请求头中, 路由到微服务
Feign
1 什么是Feign?
- Feign 是一个声明web服务客户端,这使得编写web服务客户端更容易
- 他将我们需要调用的服务方法定义成抽象方法保存在本地就可以了,
不需要自己构建Http请求了,直接调用接口就行了,
调用方法要和本地抽象方法的映射路径 , 参数和返回类型一致。
2 Feign的服务调用和Dubbo有什么区别?
feign是基于HTTP协议的远程调用
Dubbo是基于RPC的远程调用
3 使用Feign调用服务过程中超时怎么办?
- 可以调整feign的超时时间
ConnectTimeout
ReadTimeout
4 使用Feign进行远程调用, 如何实现负载均衡 ?
Ribbon
5 如何提高Feign的服务调用效率 ?
Feign底层采用URLConnection发起远程调用, 默认不支持连接池
我们可以修改底层实现使用OkHttp , HttpClient 实现远程调用 , 这样连接可以回收 , 提升了效率
具体做法就是
- 添加feign-httpclient依赖
- 配置使用HttpClient
Nacos
1 Nacos作为配置中心的原理
1 服务提供者在nacos上注册服务信息,然后发送数据到服务端
2 服务消费者主动去服务端上拉取数据
2 Nacos如何实现环境隔离 , namespace知道嘛 ?
Nacos提供了namespace来实现环境隔离功能。
nacos中可以有多个namespace
namespace下可以有group、service等
不同namespace之间相互隔离,例如不同namespace的服务互相不可见
namespace 命名空间
可以自己指定也可以默认产生UUID , 在application.yml配置文件中配置
3 什么是Nacos服务分级存储模型 ?
一个服务可以有多个实例 , 假如这些实例分布于全国各地的不同机房 ,
Nacos就将同一机房内的实例 划分为一个集群 , 一个服务可以包含多个集群,形成分级模型
配置 discovery:
cluster-name: HZ # 集群名称即可实现
配置之后微服务互相访问时,会尽可能访问同集群实例,因为本地访问速度更快。当本集群内不可用时,才访问其它集群
4 Nacos中注册的服务实例类型有哪些 ?
Nacos的服务实例分为两种l类型:
临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。
非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。
5 Nacos和Eureka有什么区别 ?
Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
临时实例心跳不正常会被剔除,非临时实例则不会被剔除
Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;
Eureka采用AP方式
6 项目启动的时候是先加载本地文件还是Nacos中的文件 ?
在本地文件中会配置Nacos配置中心地址, 所以肯定是先加载本地配置文件, 连接上Nacos再加载配置中心中的文件
7 如何实现配置的热更新 , 配置改变的情况下不修改源码即可实现配置的更新 ?
我们使用Nacos作为配置中心, 实现热更新非常简单, 有二种方式 ;
- 在需要热更新的类上添加@RefreshScope注解, 使用@Value注解读取需要更新的配置
- 使用@ConfigurationProperties注解代替@Value注解 , 可以直接实现热更新
当配置方式改变的时候, Nacos会主动向服务推送改变的内容
Ribbon
1 Ribbon是如何实现负载均衡的 ?
1 发送请求,被LoadBalancerInterceptor拦截器拦截,请求被交给ribbon来处理
2 拦截器拦截请求,交给了RibbonLoadBalancerClient的execute方法(下面的逻辑都是包含在这个方法中)
3 在进行负载均衡之前首先得知道有哪些服务实例信息,所以通过DynamicServerListLoadBalancer的updateListOfServers方法从注册中心(Eureka)那里获取到了所有的服务实例信息,并且会定时更新
4 使用负载均衡算法(默认轮询算法)从所有的服务实例信息中选择一台机器出来
5 将请求发送给负载均衡选择出来的服务实例上去
2 Ribbon支持的负载均衡策略有哪些?
- RoundRobinRule 轮询策略 默认采用线性负载轮询负载均衡策略
- AvailabilityFilteringRule 可用性过滤策略 该策略根据服务状态 (宕机和繁忙) 来分配权重,过滤掉那些因为一直连接失败或高并发的服务实例
- WeightedResponseTimeRule 加权响应时间策略 是 RoundRibbonRule 的一个子类,它对 RoundRibbonRule 的功能进行了扩展。它根据每一个服务实例的运行情况先计算出该服务实例的一个权重,然后根据权重进行服务实例的挑选,这样能够调用到更优的服务实例。
- ZoneAvoidanceRule 区域感知轮询策略 该策略以区域、可用的服务器为基础,选择服务实例并对服务实例进行分类
- BestAvailableRule 最空闲策略 该服务是逐个考察各服务实例,然后选择一个最小的并发请求的服务实例来提供实例
- RandomRule 随机策略 随机选择一个可用的服务实例。
- RetryRule 重试策略
3 如何修改Ribbon的负载均衡策略 ?
修改Ribbon负载均衡策略的方式有二种 :
- 在配置类中配置IRule
- 在application.yml配置文件中配置
4 如何自定义负载均衡策略?
Ribbon自定义负载均衡也很简单 , 自己创建类实现IRule接口 ,
实现里面的choose方法即可 , 至于具体的负载均衡规则如何做是什么就要看具体的需求了
Sentinal
1 了解过服务雪崩 ?
微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务 ,
如果其中一个下游服务出现了问题 , 或者服务器负载比较高 , 处理缓慢,
这个时候就会导致上游服务等待 , 线程得不到释放 , 最后导致服务器资源耗尽宕机,
然后他的上游服务同样如此, 这样因为一个服务导致上游的所有服务都出现问题的线下称之为雪崩
2 如何解决雪崩问题
- 超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待
- 线程隔离/信号量隔离
- 熔断降级
3 你们项目中是如何使用Sentinal的 ?
sentinel使用非常简单
启动sentinel服务端
在项目找那个引入sentinel启动器
在application.yml中配置sentinel地址
在sentinel中配置各种控制规则
4 sentinel支撑的流控模式有哪些 ?
直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流
5 sentinel支持的流控效果有哪些 ?
流控效果是指请求达到流控阈值时应该采取的措施,包括三种:
1 快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。
2 warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
3 排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长
6 sentinel是否可以实现对指定热点数据访问限流 ?
可以, sentinel支持热点参数限流 , 具体就是分别统计参数值相同的请求,判断是否超过QPS阈值。
具体就是需要在控制面板配置热点参数和阈值
7 sentinel中如何对一个普通的方法限流 ?
想要使用sentinel限流 , 必须把方法标注为一个sentinel资源,通过@SentinalResource注解来标注方法即可
8 sentinel断路器知道嘛 ? 他是怎么工作的 ?
状态机包括三个状态:
-
closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
-
open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后会进入half-open状态
- half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。请求成功:则切换到closed状态请求失败:则切换到open状态
9 sentinel什么情况下会触发熔断降级 ?
断路器熔断策略有三种:慢调用、异常比例、异常数 , 可以在sentinel控制台中配置
10 sentinel限流底层是通过什么机制实现的 ?
线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果
信号量隔离(默认使用):不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求。
11 sentinel如何实现对访问来源的控制 ?
白名单:来源(origin)在白名单内的调用者允许访问
黑名单:来源(origin)在黑名单内的调用者不允许访问
主要做法就是 :
实现RequestOriginParser接口 , 从request对象中,获取请求者的origin值并返回
12 sentinel中配置的规则是如何保存的 ? 重启之后还生效嘛 ?
现在,sentinel的所有规则都是内存存储,重启后所有规则都会丢失。在生产环境下,我们必须确保这些规则的持久化,避免丢失。
规则是否能持久化,取决于规则管理模式,sentinel支持三种规则管理模式:
原始模式:Sentinel的默认模式,将规则保存在内存,重启服务会丢失。
pull模式 : 控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则
push模式 :控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。
开源的sentinel版本中不支持push模式, 需要修改源码实现
ElasticSearch
1 什么是Elasticsearch
Elasticsearch 是基于 Lucene 的 Restful 的分布式实时全文搜索引擎,
每个字段都被索引并可被搜索,可以快速存储、搜索、分析海量的数据。
2 Elasticsearch 的基本概念
(1)index 索引:索引类似于mysql 中的数据库,Elasticesearch 中的索引是存在数据的地方,包含了一堆有相似结构的文档数据。
(2)type 类型:类型是用来定义数据结构,可以认为是 mysql 中的一张表,type 是 index 中的一个逻辑数据分类
(3)document 文档:类似于 MySQL 中的一行,不同之处在于 ES 中的每个文档可以有不同的字段,但是对于通用字段应该具有相同的数据类型,文档是es中的最小数据单元,可以认为一个文档就是一条记录。
(4)Field 字段:Field是Elasticsearch的最小单位,一个document里面有多个field
(5)shard 分片:单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在多台服务器上存储。有了shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。
(6)replica 副本:任何一个服务器随时可能故障或宕机,此时 shard 可能会丢失,因此可以为每个 shard 创建多个 replica 副本。replica可以在shard故障时提供备用服务,保证数据不丢失,多个replica还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认5个),replica shard(随时修改数量,默认1个),默认每个索引10个 shard,5个primary shard,5个replica shard,最小的高可用配置,是2台服务器。
3 倒排索引和正排索引
'- 正向索引是最传统的,根据id索引的方式。
但根据词条查询时,必须先逐条获取每个文档,
然后判断文档中是否包含所需要的词条,是根据文档找词条的过程。
- 而倒排索引则相反,是先找到用户要搜索的词条,
根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。
正向索引:
- 优点:
- 可以给多个字段创建索引
- 根据索引字段搜索、排序速度非常快
- 缺点:
- 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。
倒排索引:
- 优点:
- 根据词条搜索、模糊搜索时,速度非常快
- 缺点:
- 只能给词条创建索引,而不是字段
- 无法根据字段做排序
4 text和keyword类型的区别
两个的区别主要分词的区别:keyword 类型是不会分词的,
直接根据字符串内容建立倒排索引,keyword类型的字段只能通过精确值搜索到;
Text 类型在存入 Elasticsearch 的时候,会先分词,然后根据分词后的内容建立倒排索引
5 query和filter 的区别?
**(1)query:**查询操作不仅仅会进行查询,还会计算分值,用于确定相关度;
**(2)filter:**查询操作仅判断是否满足查询条件,不会计算任何分值,
也不会关心返回的排序问题,同时,filter 查询的结果可以被缓存,提高性能。
6 es写数据的过程(ES的写入流程:)
(1)客户端选择一个 node 发送请求过去,这个 node 就是 coordinating node (协调节点)
(2)coordinating node 对 document 进行路由,将请求转发给对应的 node(有 primary shard)
(3)实际的 node 上的 primary shard 处理请求,然后将数据同步到 replica node
(4)coordinating node 等到 primary node 和所有 replica node 都执行成功之后,就返回响应结果给客户端。
7 (ES的更新和删除流程)写数据的底层原理:
1)数据
删除和更新都是写操作,但是由于 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变更;所以 ES 利用 .del 文件 标记文档是否被删除,磁盘上的每个段都有一个相应的.del 文件
(1)如果是删除操作,文档其实并没有真的被删除,而是在 .del 文件中被标记为 deleted 状态。该文档依然能匹配查询,但是会在结果中被过滤掉。
(2)如果是更新操作,就是将旧的 doc 标识为 deleted 状态,然后创建一个新的 doc。
8 ES在高并发下如何保证读写一致性?
(1)对于更新操作:可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖
(2)对于写操作,一致性级别支持 quorum/one/all,默认为 quorum,即只有当大多数分片可用时才允许写操作。
(3)对于读操作,可以设置 replication 为 sync(默认),这使得操作在主分片和副本分片都完成后才会返回
9 Elasticsearch的分布式原理(ES如何选举Master节点)
Elasticsearch 会对存储的数据进行切分,将数据划分到不同的分片上,
同时每一个分片会保存多个副本,主要是为了保证分布式环境的高可用。
在 Elasticsearch 中,节点是对等的,节点间会选取集群的 Master,
由 Master 会负责集群状态信息的改变,并同步给其他节点。
10 Elasticsearch是如何避免脑裂现象:
(1)当集群中 master 候选节点数量不小于3个时(node.master: true
),
可以通过设置最少投票通过数量(discovery.zen.minimum_master_nodes
),
设置超过所有候选节点一半以上来解决脑裂问题,即设置为 (N/2)+1
;
(2)当集群 master 候选节点只有两个时,这种情况是不合理的,
最好把另外一个node.master
改成false。
如果我们不改节点设置,还是套上面的(N/2)+1
公式,此时discovery.zen.minimum_master_nodes
应该设置为2。
这就出现一个问题,两个master备选节点,只要有一个挂,就选不出master了
11 ES的深度分页与滚动搜索scroll
(1)深度分页:**
深度分页其实就是搜索的深浅度,(比如第1页,第2页,第10页,第20页,是比较浅的;第10000页,第20000页就是很深了。)
搜索得太深,就会造成性能问题,会耗费内存和占用cpu。
而且es为了性能,他不支持超过一万条数据以上的分页查询。
(2)滚动搜索:**
一次性查询1万+数据,往往会造成性能影响,因为数据量太多了。
这个时候可以使用滚动搜索,也就是 scroll。
滚动搜索可以先查询出一些数据,然后再紧接着依次往下查询。
在第一次查询的时候会有一个滚动id,相当于一个锚标记 ,随后再次滚动搜索会需要上一次搜索滚动id
每次搜索都是基于一个历史的数据快照,查询数据的期间,如果有数据变更,那么和搜索是没有关系的。
Kafka
1 什么是Kafka?Kafka中有哪几个组件?
Kafka是分布式发布-订阅消息系统是一个可划分的,冗余备份的持久性的日志服务,它主要用于处理流式数据。
组件:
主题(Topic):Kafka主题是一堆或一组消息。
生产者(Producer):在Kafka,生产者发布通信以及向Kafka主题发布消息。
消费者(Consumer):Kafka消费者订阅了一个主题,并且还从主题中读取和处理消息。
经纪人(Brokers):在管理主题中的消息存储时,我们使用Kafka Broker
2 Kafka为什么那么快?
-
Kafka的消息是保存或缓存在磁盘上的,一般认为在磁盘上读写数据是会降低性能的,
但是实际上,Kafka的特性之一就是高吞吐率。 -
Kafka也可以轻松支持每秒百万级的写入请求,超过了大部分的消息中间件,
这种特性也使得Kafka在日志处理等海量数据场景广泛应用。 -
写入数据
顺序写入,由于现代的操作系统提供了预读和写技术,磁盘的顺序写大多数情况下比随机写内存还要快。 -
读取数据
基于sendfile实现Zero Copy零拷技术减少拷贝次数,Batching of Messages 批量处理。
合并小的请求,然后以流的方式进行交互,直顶网络上限。
3 Kafka系统工具有哪些类型?
- Kafka迁移工具:它有助于将代理从一个版本迁移到另一个版本。
- Mirror Maker:Mirror Maker工具有助于将一个Kafka集群的镜像提供给另一个。
- 消费者检查:对于指定的主题集和消费者组,它显示主题,分区,所有者。
4 Kafka的message格式是什么?
一个Kafka的Message由一个固定长度的header和一个变长的消息体body组成
header部分
- 由一个字节的magic(文件格式)和四个字节的CRC32(用于判断body消息体是否正常)构成。
body部分
- 由N个字节构成的一个消息体,包含了具体的key/value消息
5 Kafka的优点有那些?
1 高吞吐量:我们在Kafka中不需要任何大型硬件,因为它能够处理高速和大容量数据。
此外,它还可以支持每秒数千条消息的消息吞吐量。
2 低延迟:Kafka可以轻松处理这些消息,具有毫秒级的极低延迟,这是大多数新用例所要求的。
3 容错:Kafka能够抵抗集群中的节点/机器故障。
4 耐久性:由于Kafka支持消息复制,因此消息永远不会丢失。这是耐久性背后的原因之一。
5 可扩展性:Kafka可以扩展,而不需要通过添加额外的节点而在运行中造成任何停机。
6 为什么要使用Kafka?为什么要使用消息队列?
-
缓冲和削峰:
上游数据时有突发流量,下游可能扛不住,或者下游没有足够多的机器来保证冗余,
kafka在中间可以起到一个缓冲的作用,把消息暂存在kafka中,下游服务就可以按照自己的节奏进行慢慢处理。 -
解耦和扩展性:
项目开始的时候,并不能确定具体需求。消息队列可以作为一个接口层,解耦重要的业务流程。
只需要遵守约定,针对数据编程即可获取扩展能力。 -
冗余:
可以采用一对多的方式,一个生产者发布消息,可以被多个订阅topic的服务消费到,供多个毫无关联的业务使用。 -
健壮性:
消息队列可以堆积请求,所以消费端业务即使短时间死掉,也不会影响主要业务的正常进行。 -
异步通信:
很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,
允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
7 Kafka存在那些局限性?
- 没有完整的监控工具集
- 消息调整的问题
- 不支持通配符主题选择
- 速度问题
8 Kafka为什么不支持读写分离?
在 Kafka中,生产者写入消息、消费者读取消息的操作都是与 leader 副本进行交互的,
从而实现的是一种主写主读的生产消费模型。
Kafka 并不支持主写从读,因为主写从读有 2 个很明 显的缺点:
延时问题
数据一致性问题
9 Kafka如何实现延迟队列?
Kafka并没有使用JDK自带的Timer或者DelayQueue来实现延迟的功能,
而是基于时间轮自定义了一个用于实现延迟功能的定时器
底层使用数组实现,数组中的每个元素可以存放一个TimerTaskList对象。
TimerTaskList是一个环形双向链表,在其中的链表项TimerTaskEntry中封装了真正的定时任务TimerTask.
10 Kafka如何保证消息可靠性
导致消费者弄丢数据的情况就是
拉取了这个消息,然后消费者那边自动提交了,Kafka 以为你已经消费好了这个消息,
但其实你才刚准备处理这个消息,你还没处理,你自己就挂了,此时这条消息就丢了。
解决:
关闭自动提交 offset,在处理完之后自己手动提交 offset,就可以保证数据不会丢
broker弄丢数据
解决:
- 给 topic 设置
replication.factor
参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。 - 在 Kafka 服务端设置
min.insync.replicas
参数:这个值必须大于 1,
这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower 吧。 - 在 producer 端设置
acks=all
:这个是要求每条数据,必须是写入所有 replica 之后,才能认为是写成功了。 - 在 producer 端设置
retries=10000000
(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了。
生产者弄丢数据
如果设置了 acks=all
,一定不会丢,要求是,你的 leader 接收到消息,
所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次
11 Kafka中是怎么体现消息顺序性的?
-
可以设置topic 有且只有一个partition
-
根据业务需要,需要顺序的 指定为同一个partition
-
根据业务需要,需要顺序的指定为同一个partition (比如同一个订单,使用同一个key,可以保证分配到同一个partition上)
12 创建topic时如何选择合适的分区数?
kafka集群中,单Topic的partition也并不是越多越好,但通常对于业务方来说
需要根据具体的场景进行分析以确定partition的数量。
通常Kafka集群中的分区越多,吞吐量就越高。但是,必须意识到总分区或每个Broker拥有太多分区
对可用性和延迟等方面的潜在影响,通常这部分需要业务方和基础服务方进行合理规划和调整。
一般情况下,分区数可以配置为Broker节点数的整数倍,
比如:Broker节点是3,那么可以设置分区数为3、6、9。
但是不能无限扩展, 有临界值, 临界值多少, 需要通过压测工具进行测试
13 消费者提交消费位移时提交的是当前消费到的最新消息的offset还是offset+1?
offset+1
14 有哪些情形会造成重复消费?
消费者消费后没有commit offset(程序崩溃/强行kill/消费耗时/自动提交偏移情况下unscrible)
15 哪些情景下会造成消息丢失
消消费者没有处理完消息 提交offset(自动提交偏移 未处理情况下程序异常结束)
RabbitMQ
1 MQ如何保证消息幂等性?
- 乐观锁机制
- 基于Redis的原子操作
- 增加消息状态表。通俗来说就是一个账本,用来记录消息的处理状态,
每次处理消息之前, 都去状态表中查询一次,如果已经有相同的消息存在,那么不处理,可以防止重复发送
2 消息中间件的应用场景
开发中消息队列通常有如下应用场景:
1、任务异步处理。
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
2、应用程序解耦合
MQ 相当于一个中介,生产方通过 MQ 与消费方交互,它将应用程序进行解耦合。
3 什么是消息队列?什么是RabbitMQ?
消息队列
使用队列来通信的组件,把要传输的消息放在队列中。
RabbitMQ
它是实现了高级消息队列协议(AMQP)的消息中间件。
4 为什么要使用消息队列? 消息队列的优点
1 - 系统解耦:解耦消息生产者和消费者之间的关系
2 - 异步调用:用户调用接口时,由于接口之间调用导致用时时间比较久,用户体验不好。
调用接口后将消息放入到MQ后就返回,用户体验好,最终一致性由MQ来保证
3 - 流量削峰:减少高峰时期对服务器压力,先把请求放到MQ中,系统根据实际能处理的并发量来消费请求
5 消息队列的缺点,消息队列带来的问题
1 - 系统可用性降低。如果MQ挂了,整个系统就不能服务了
2 - 系统复杂性提高。消息丢失、消息重复消费、消息重复发送、消息顺序错乱等问题
3 - 一致性问题。将消息放到MQ后就返回给用户成功的信息,但是其他系统消费消息时,
若某个系统失败了,导致数据不一致
6 消息过期时间,队列过期时间
消息过期时间
目前有两种方式设置消息的过期时间,一是通过队列属性设置,二是通过消息单独设置。
如果两种方式同时使用,以最小值为准。
队列过期时间
通过 channel queueDeclare 方法中的 expires 参数可以控制队列被自动删除前处于未使用状态的时间
。未使用的意思是队列上没有任何的消费者,队列也没有被重新声明,并且在过期时间段内也未调用过Basic Get命令
7 死信队列(DLX),变成死信的情况
DLX ,全称为 Dead-Letter-Exchange ,可以称之为死信交换器。
当消息在一个队列中变成死信 (dead message) 之后,它能被重新被发送到另一个交换器中,
这个交换器就是 DLX ,绑定 DLX 的队列就称之为死信队列。
消息变成死信一般由于以下几种情况:
- 消息被拒绝
- 消息过期
- 队列达到最大长度
8 RabbitMQ延时队列,优先级队列
延时队列
在rabbitmq中可通过过期时间(TTL)和死信队列(DLX)实现延时队列
优先级队列
可设置队列支持的最大优先级,然后发送消息时设置消息的优先级。
但是要注意这种情况只在消费者能力小于生产者,有消息堆积时有效。
9 RabbitMQ的持久化
RabbitMQ的持久化分为3个部分:交换器的持久化、队列的持久化和消息的持久化。
但是如果将所有消息持久化,将会严重影响rabbitmq的性能。
即使将交换器、队列和消息都设置了持久化,也不能保证消息100%不丢失。
- 从消费者端,防止消费者收到消息还没来得及处理就宕机的情况,需要将autoAck设置为false
- 从生产者断,防止发送到rabbitmq后还没来的及落盘rabbitmq就宕机的情况,
可以在生产者端引入事务机制或者发送方确认机制来保证消息己经正确地发送并存储RabbitMQ 中
(前提还要保证在调用 channel.basicPublish 方法的时候交换器能够将消息正确路由到相应的队列之中)或者引入镜像队列来保证高可用
10 消息的有序性?
资料上说rabbitmq可以保证消息的有序,其实是不严谨的。
在不使用任何 RabbitM 高级特性 ,也没有消息丢失、网络故障之类异常的情况发生,并且只有一个消费者的情况下
如果有多个生产者同时发送消息,无法确定消息到达 Broker 的前后顺序,也就无法验证消息的顺序性。
需要业务方使用 RabbitMQ 之后做进一步的处理,比如在消息体内添加全局有序标识(类似SequenceID) 来实现。
11 惰性队列
RabbitMQ 3.6.0 版本开始引入了惰性队列 lazy Queue 的概念。
惰性队列会尽可能地将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,
它的一个重要的设计目标是能够支持更长的队列即支持更多的消息存储
。当消费者由于各种各样的原因(比如消费者下线、宕机或者由于维护而关闭等〉
致使长时间内不能消费消息而造成堆积时惰性队列就很有必要了。
12 RabbitMQ 组件
'- Broker:一个Broker可以看做一个RabbitMQ服务器
- Connection:实际的连接(TCP连接)
- Channel:虚拟连接。一个Connection上可以有多个Channel(信道),信道没有数量限制,消息在信道上传输。
- Message
- Virtual host:虚拟broker。其内部含有独立的queue、exchange、binding,以及独立的权限系统
- Exchange:交换机。生产者将消息发送到交换机,由交换机将消息路由到一个或多个队列中。路由不到时,返回给生产者或直接丢弃(mandatory 为 true,返回消息给生产者;为 false 丢弃消息)
- Queue:存储消息的数据结构。可以设置长度,处于ready状态的消息会被计数,不统计处于unack的消息。消息重新入队会处于队头。多个消费者可以订阅同一个队列,队列中的消息会平摊给各个消费者处理
13 RabbitMQ工作模型
- 简单队列(Simple Queue):生产者将消息投递到队列里,消息者从队列里取消息
- 工作队列(Worker Queue):一个生产者,一个队列,多个消费者,用于消息消费耗时的场景
- 发布订阅(fanout)
- Direct:消息根据路由键投递相应的队列中
- Topic:消息根据路由键和绑定键的匹配,投递到相应的队列中。如果多个键匹配成功,且目标队列是同一个队列,队列只会收到一条消息
- Headers:用键值对做匹配,匹配方式有all和any,不常使用
其中3-6是交换机的四种工作模式(四种交换机类型)
14 RabbitMQ消费模式包括哪些?
推送(push)
- 通过
channel.basicConsume
将 channel(信道)设置为推模式,消息可用时自动将消息推送给消费者 - 消费者需要设置缓冲区缓存消息
- 实时性好(长连接)
拉取(pull)
- 使用
channel.basicGet
拉消息 - 轮询模型,消费者发送 get 请求获取消息,如果队列中没有消息,则获得空的回复
- 需要时才去拉取消息,实时性差,耗费资源(短连接)
Qos(质量服务)
消息发送给消费者后,默认是自动确认,如果消费者未能消费成功,则消息丢失。
通过显式确认可以保证只有当消息处理完成并收到Ack后才从队列中删除。
但是存在的问题是:
1)消息太多全部传给消费者,可能造成消费者内存爆满;
2)消息处理慢时,想让别的消费者一起处理,但是这些消息都被原来的消费者接收了,这些消息不会再发送给新添加的消费者
Qos可以解决上述问题,需要开启消息的显式确认,设置每次传输给消费者的消息条数为n,
消费者处理完n条消息后再获取n条消息进行处理;而新增消费者时,消息可以立即发送给新的消费者。
15 多个消费者监听一个队列,消息如何分发?
默认轮训策略
公平分发(QoS):给空闲的消费者发送更多的消息(当消费者有x条消息没有响应时,不再给该消费者发消息)
16 RabbitMQ如何保证消息的可靠性?
生产者
confirm机制:
- 生产者生产的消息带有唯一 ID
- 消息被投递到目标队列后,发送Ack消息(包含消息的唯一ID)给生产者
- 有可能因为网络问题导致Ack消息无法发送到生产者,那么生产者在等待超时后,
会重传消息;或者RabbitMQ内部错误导致消息丢失,则发送nack消息 - 生产者收到Ack消息后,认为消息已经投递成功
队列自身不弄丢消息
队列开启持久化,消息的diliveryMode = 2
队列如何将消息可靠投递到消费者?
手动确认:
- 队列将消息push给消费者(或消费者来pull消息)
- 消费者得到消息并做完业务逻辑
- 消费者发送Ack消息给队列 ,通知队列删除该消息(队列会一直等待直到得到ack消息,队列通过消费者的连接是否中断来确认是否需要重新发送消息,只要连接不中断,消费者有足够长的时间来处理消息,保证数据的最终一致性)
- 队列将已消费的消息删除
17 如何避免消息重复投递或重复消费?
重复投递的原因:等待超时后,需要重试。
避免重复投递:消息生产时,生产者发送的消息携带一个Message ID
(全局唯一ID),
作为去重和幂等的依据,避免重复的消息进入队列
重复消费的原因:消费者接收消息后,在确认之前断开了连接或者取消订阅,
消息会被重新分发给下一个订阅的消费者。
避免重复消费:消息消费时,要求消息体中必须要有一个全局唯一ID,
作为去重和幂等的依据,避免同一条消息被重复消费
18 RabbitMQ消息的状态
'- alpha
:消息内容(包括消息体、属性和headers) 和消息索引都存储在内存中
beta
:消息内容保存在磁盘中,消息索引保存在内存中gamma
:消息内容保存在磁盘中,消息索引在磁盘和内存中都有delta
:消息内容和索引都在磁盘中
19 死信完成延迟队列
存储延迟消息,消息被发送后不想让消费者立即拿到消息,要等待特定时间后才能拿到消息。
实现方式
3.6.x之前,死信队列+TTL过期时间可以实现延迟队列。
3.6.x开始,延迟队列插件
应用场景
x天自动确认收货、自动默认评价。
12306购票支付确认页面,如果30分钟没有付款将自动取消订单。
20 RabbitMQ集群
普通集群
每台机器上启动一个RabbitMQ实例,而创建的queue只放在一个实例上,其他实例同步queue的元数据。
消费时如果连接到了另一个实例上,则实例会从queue所在的实例上拉取数据
多个实例服务一个queue
镜像集群
RabbitMQ的元数据和queue里的消息都会存在于多个实例上,
每次消息进入队列,会自动把消息同步到多个实例的队列中
分为一个master和多个slave。所有的操作最终都会到master上操作
- 生产者可任意选择一个节点连接,如果该节点不是master,则转发给master。
master向slave发送消息,收到半数以上回复后本地提交,再让slave提交 - 消费者可任意选择一个节点连接,如果该节点不是master,则转发给master。
消费者消费后进行 ack 确认,master收到ack后删除,并让slave删除。 - 如果master掉线,自动选出一个节点(slave中消息队列最长的节点)作为新的master
21 RabbitMQ如何保证消息的顺序性?
'- 只有一个消费者,可以保证顺序
- 多个队列,每个队列对应一个消费者,同一个用户的操作hash到同一个队列上
- 每个消息有一个全局ID,同时去关联一个parentMsgId,在前一条消息未消费时不处理下一条消息
22 消息积压在消息队列中会导致什么结果?产生的原因是?如何解决?
原因:消费者消费速度慢,或者出现了问题
导致的结果:1)磁盘空间满了;2)海量消息堆积,消费者需要很长时间消费
解决办法:
- 磁盘空间满的情况:在其他机器上建立临时的消息队列,
再写一个临时的消费者,把积压的消息放到临时队列里去 - 海量消息堆积的情况:修复消费者问题,停掉现有的消费者,
临时建立10倍的消息队列,再用一个临时的消费者将消息分发到临时消息队列中,
临时征用10倍的机器部署消费者。等积压消息消费完成后,再恢复成之前的架构
23 Kafka和RabbitMQ的对比
保证消息可靠性:
- Kafka
1)使用acks=all,Leader收到消息后,等待所有ISR列表的Follower返回后再发送ack给生产者(分区副本数和ISR最少副本数配置成大于1)
2)生产者使用同步发送消息(默认是异步,多个请求先放在缓冲区,再合并发送)
3)消费者使用手动提交offset,从而保证消息至少消费一次 - RabbitMQ:
24 RabbitMQ的优缺点,Kafka的优缺点
RabbitMQ的优缺点:
优点
- 延迟很低(微秒级)
- 可靠性:使用一些机制来保证可靠性, 如持久化、传输确认及发布确认等。
- 灵活的路由:对于典型的路由功能,RabbitMQ己经提供了一些内置的交换器来实现。
针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器。 - 管理界面 : RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。
- 高可用性:队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用。
- 多种协议:RabbitMQ除了原生支持AMQP协议,还支持STOMP、MQTT等多种消息中间件协议。
- 支持多种语言:如Java、Python、Ruby、PHP、C#、JavaScript等。
- 插件机制 : RabbitMQ提供了许多插件,以实现从多方面进行扩展,也可以自定义插件。
缺点
- erlang开发,难以维护
- 相比较于其他消息中间件,吞吐量较低(万级)
Kafka的优点和缺点
优点:
- 吞吐量十万级
- 可用性非常高(一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用)
- 消息可以做到0丢失
缺点:
- topic增多会导致吞吐量大幅下降(几百个topic),如果要支持大规模topic,需要更多的机器资源
- 依赖ZooKeeper进行元数据管理(额外的复杂性)