面试知识点——(金三银四你需要的是她)
目录《Java》《框架类》一、spring二、Spring MVC三、springboot四、SpringCloud五、Dubbo六、MyBatis七、Hibernate八、Storm九、Flume十、Zookeeper《数据库类》一、MySQL二、Oracle三、Redis《前端》一、VUE《Java》1、JVM...
目录
5、ConcurrentHashMap和Hashtable的区别
5、在Provider上可以配置的Consumer 端的属性有哪些
4、如何确保消息正确地发送至 RabbitMQ? 如何确保消息接 收方消费了消息?
3、什么是集合Collection、文档Document,以及与关系型数据库术语类比
11、MongoDB在A:{B,C}上建立索引,查询A:{B,C}和A:{C,B}都会使用索引吗
14、分片(Shard)和复制(replication)是怎样工作的
15、如果块移动操作(moveChunk)失败了,我需要手动清除部分转移的文档吗
17、更新一个正在被迁移的块(Chunk)上的文档时会发生什么
18、如果一个分片(Shard)停止或很慢的时候,发起一个查询会怎样
15、for...in for...of 和 object.keys
《Java》
1、JVM
Jvm体系总体分四大块:类的加载机制、Jvm内存结构、GC算法 垃圾回收、GC分析 命令调优。
1.1、类的加载机制
1)、什么是类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
2)、类的生命周期:加载、连接、初始化、使用、卸载,其中前三部是类的加载的过程。
- 加载:查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象;
- 连接:连接又包含三块内容:验证、准备、解析;
- 验证:文件格式、元数据、字节码、符号引用验证;
- 准备,为类的静态变量分配内存,并将其初始化为默认值;
- 解析,把类中的符号引用转换为直接引用;
- 初始化:为类的静态变量赋予正确的初始值;
- 使用:new出对象程序中使用;
- 卸载,执行垃圾回收。
3)、类加载器
- 启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库;
- 扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器;
- 应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器。
4)、类加载机制
- 全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入;
- 父类委托:先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类;
- 缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效。
1.2、Jvm内存结构
1)、内存结构
注:方法区和堆是所有线程共享的内存区域;而java栈、本地方法栈、程序计数器是线程私有的内存区域。
- 堆:是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存;
- 方法区:与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;
- 程序计数器:是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器;
- JVM栈:与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程;
- 本地方法栈:与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
2)、对象分配规则
- 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC;
- 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存);
- 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区;
- 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代;
- 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。
1.3、GC算法、垃圾回收
1)、对象存活判断
- 引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题;
- 可达性分析:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。
2)、GC算法
GC最基础的算法有三种:标记 -清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法。
- 标记 -清除算法:“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象;
- 复制算法:“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉;
- 标记-压缩算法:标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存;
- 分代收集算法:“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
3)、垃圾回收器
- Serial收集器:串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收;
- ParNew收集器:ParNew收集器其实就是Serial收集器的多线程版本;
- Parallel收集器:Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量;
- Parallel Old 收集器:Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法;
- CMS收集器:CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器;
- G1收集器:G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。
4)、四种引用
- 强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象;
- 软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收;
- 弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象;
- 虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。
1.4、GC分析、命令调优
1)、GC日志分析
摘录GC日志一部分(前部分为年轻代gc回收;后部分为full gc回收):
2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs]
2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]
通过上面日志分析得出,PSYoungGen、ParOldGen、PSPermGen属于Parallel收集器。
- PSYoungGen表示gc回收前后年轻代的内存变化;
- ParOldGen表示gc回收前后老年代的内存变化;
- PSPermGen表示gc回收前后永久区的内存变化;
- young gc 主要是针对年轻代进行内存回收比较频繁,耗时短;
- full gc 会对整个堆内存进行回城,耗时长,因此一般尽量减少full gc的次数。
2)、调优命令
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 这个命令作用是实时查看和调整虚拟机运行参数。
3)、调优工具
常用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。
- jconsole:Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控;
- jvisualvm:jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等;
- MAT:Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗;
- GChisto:一款专业分析gc日志的工具。
2、volatile关键字
volatile是一个类型修饰符(type specifier).volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。Volatile变量具有 synchronized 的可见性特性,但是不具备原子特性。
在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。
当一个变量定义为 volatile 之后,将具备两种特性:
- 保证此变量对所有的线程的可见性,这里的“可见性”,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。
- 禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
性能:volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
作用:
- 内存可见性;
- 有序性、禁止指令重排相关的内容。
3、synchronized关键字
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
4、HashMap的原理
- 在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。
- 在JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
原理:HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。HashMap在每个链表节点中储存键值对对象。当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。
4.1、当两个对象的hashcode相同会发生什么?
因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。
4.2、如果两个键的hashcode相同,你如何获取值对象?
当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。
注:key的选取要使用不可变的、声明作final的对象,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生,提高效率。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择。
4.3、如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。
4.4、你了解重新调整HashMap大小存在什么问题吗?
当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。
5、ConcurrentHashMap和Hashtable的区别
Hashtable和ConcurrentHashMap都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。
6、深拷贝和浅拷贝
- 浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象;
- 深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
7、单例模式
单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。
单例的实现主要是通过以下两个步骤:
- 将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
- 在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。
优点:系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
缺点:当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。
适用场合:
- 需要频繁的进行创建和销毁的对象;
- 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
- 工具类对象;
- 频繁访问数据库或文件的对象。
7.1、饿汉式(静态常量)[可用]
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
7.2、饿汉式(静态代码块)[可用]
public class Singleton {
private static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
7.3、懒汉式(线程不安全)[不可用]
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
7.4、懒汉式(线程安全,同步方法)[不推荐用]
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。
缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。
7.5、懒汉式(线程安全,同步代码块)[不可用]
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
由于第四种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块。但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
7.6、双重检查[推荐用]
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。
优点:线程安全;延迟加载;效率较高。
7.7、静态内部类[推荐用]
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
7.8、枚举[推荐用]
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。
8、工厂模式
工厂模式分为三种:简单工厂模式、工厂方法模式、抽象工厂模式。
8.1、简单工厂模式
简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
实例:
抽象产品类
public interface ICar {
void getCar();
}
具体产品类
public class SportCar implements ICar{
public void getCar(){
System.out.println("跑车路过......");
}
}
public class JeepCar implements ICar{
public void getCar(){
System.out.println("越野车路过.....");
}
}
简单工厂核心类
public class SimpleFactory {
public static final int TYPE_SPORT = 1;
public static final int TYPE_JEEP = 2;
public static ICar createCar(int type) throws Exception{
switch (type) {
case TYPE_SPORT:
return new SportCar();
case TYPE_JEEP:
return new JeepCar();
default:
throw new Exception("算了吧,都买不起啊!!!");
}
}
}
测试类
public class TestFactory{
public static void main(String[] args) {
try{
ICar car= SimpleFactory.createCar(SimpleFactory.TYPE_JEEP);
car.getCar();
}catch (Exception e){
e.printStackTrace();
}
}
}
优点:简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个软件体系结构的优化;
缺点:很明显工厂类集中了所有实例的创建逻辑,容易违反GRASPR的高内聚的责任分配原则。
8.2、工厂方法模式
工厂方法模式Factory Method,又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
工厂方法模式是简单工厂模式的衍生,解决了许多简单工厂模式的问题。首先完全实现‘开-闭 原则’,实现了可扩展。其次更复杂的层次结构,可以应用于产品结果复杂的场合。
实例:
抽象工厂
public interface IFactory{
ICar createCar();
}
抽象产品
public interface ICar{
void getCar();
}
具体工厂
public class SportFactory implements IFactory {
@Override
public ICar createCar(){
return new SportCar();
}
}
public class JeepFactory implements IFactory{
@Override
public ICar createCar(){
return new JeepCar();
}
}
具体产品
public class SportCar implements ICar{
@Override
public void getCar(){
System.out.println("跑车路过......");
}
}
public class JeepCar implements ICar{
@Override
public void getCar(){
System.out.println("越野车路过.....");
}
}
测试类
public class TestFactory {
public static void main(String[] args) {
try {
IFactory factory = null;
//跑车
factory = new SportFactory();
ICar sport = factory.createCar();
sport.getCar();
//越野
factory = new JeepFactory();
ICar jeep = factory.createCar();
jeep.getCar();
} catch (Exception e) {
e.printStackTrace();
}
}
}
优点:
- 子类提供挂钩。基类为工厂方法提供缺省实现,子类可以重写新的实现,也可以继承父类的实现。-- 加一层间接性,增加了灵活性
- 屏蔽产品类。产品类的实现如何变化,调用者都不需要关心,只需关心产品的接口,只要接口保持不变,系统中的上层模块就不会发生变化。
- 典型的解耦框架。高层模块只需要知道产品的抽象类,其他的实现类都不需要关心,符合迪米特法则,符合依赖倒置原则,符合里氏替换原则。
- 多态性:客户代码可以做到与特定应用无关,适用于任何实体类。
缺点:需要Creator和相应的子类作为factory method的载体,如果应用模型确实需要creator和子类存在,则很好;否则的话,需要增加一个类层次。
8.3、抽象工厂模式
抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。根据里氏替换原则,任何接受父类型的地方,都应当能够接受子类型。因此,实际上系统所需要的,仅仅是类型与这些抽象产品角色相同的一些实例,而不是这些抽象产品的实例。换言之,也就是这些抽象产品的具体子类的实例。工厂类负责创建抽象产品的具体子类的实例。
优点:
- 抽象工厂模式隔离了具体类的生产,使得客户并不需要知道什么被创建。
- 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
- 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点:增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。
9、线程
9.1、进程与线程
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间)。进程不依赖于线程而独立存在,一个进程中可以启动多个线程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,线程没有自己的虚拟地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。
9.2、Java中的线程
在 Java程序中,有两种方法创建线程:一是对 Thread 类进行派生并覆盖 run方法; 二是通过实现Runnable接口创建。 一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。 线程总体分两类:用户线程和守候线程。当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的。
9.3、线程的状态
- 新建状态(New) : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
- 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
- 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
- 阻塞状态(Blocked) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- (01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
- (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
- (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
9.4、相关API
- notify():唤醒在此对象监视器上等待的单个线程。
- notifyAll():唤醒在此对象监视器上等待的所有线程。
- wait():让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”),是会线程释放它所持有对象的同步锁。
- wait(long timeout):让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
- wait(long timeout, int nanos):让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
- yield():作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行;是不会释放锁。
- sleep() :作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。sleep也是不会释放锁的。
- join():是让主线程会等待子线程结束之后才能继续运行。
9.5、并行与并发
- 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
- 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
9.6、ThreadLocal类
- 作用:保存线程的独立变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。
- 实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。
- 主要方法是get()和set(T a),set之后在map里维护一个threadLocal -> a,get时将a返回。
9.7、原子类
使用atomic wrapper class如AtomicInteger、AtomicBoolean、AtomicReference等,或者使用自己保证原子的操作,则等同于synchronized。
9.8、Lock类
lock在java.util.concurrent包内,主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。共有三个实现:
ReentrantLock
ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock.WriteLock
与synchronized的区别:
- Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
- Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
10、Java8的特性
- Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
- 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
- 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
- Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- Date Time API − 加强对日期与时间的处理。
- Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
11、Java9的特性
- 模块系统:模块是一个包的容器,Java 9 最大的变化之一是引入了模块系统(Jigsaw 项目)。
- REPL (JShell):交互式编程环境。
- HTTP 2 客户端:HTTP/2标准是HTTP协议的最新版本,新的 HTTPClient API 支持 WebSocket 和 HTTP2 流以及服务器推送特性。
- 改进的 Javadoc:Javadoc 现在支持在 API 文档中的进行搜索。另外,Javadoc 的输出现在符合兼容 HTML5 标准。
- 多版本兼容 JAR 包:多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。
- 集合工厂方法:List,Set 和 Map 接口中,新的静态工厂方法可以创建这些集合的不可变实例。
- 私有接口方法:在接口中使用private私有方法。我们可以使用 private 访问修饰符在接口中编写私有方法。
- 进程 API: 改进的 API 来控制和管理操作系统进程。引进 java.lang.ProcessHandle 及其嵌套接口 Info 来让开发者逃离时常因为要获取一个本地进程的 PID 而不得不使用本地代码的窘境。
- 改进的 Stream API:改进的 Stream API 添加了一些便利的方法,使流处理更容易,并使用收集器编写复杂的查询。
- 改进 try-with-resources:如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。
- 改进的弃用注解 @Deprecated:注解 @Deprecated 可以标记 Java API 状态,可以表示被标记的 API 将会被移除,或者已经破坏。
- 改进钻石操作符(Diamond Operator) :匿名类可以使用钻石操作符(Diamond Operator)。
- 改进 Optional 类:java.util.Optional 添加了很多新的有用方法,Optional 可以直接转为 stream。
- 多分辨率图像 API:定义多分辨率图像API,开发者可以很容易的操作和展示不同分辨率的图像了。
- 改进的 CompletableFuture API : CompletableFuture 类的异步机制可以在 ProcessHandle.onExit 方法退出时执行操作。
- 轻量级的 JSON API:内置了一个轻量级的JSON API
- 响应式流(Reactive Streams) API: Java 9中引入了新的响应式流 API 来支持 Java 9 中的响应式编程。
12、http协议
HTTP(Hyper Text Transfer Protocol)超文本传输协议,是一种建立在TCP上的无状态连接,整个基本的工作流程是客户端发送一个HTTP请求,说明客户端想要访问的资源和请求的动作,服务端收到请求之后,服务端开始处理请求,并根据请求做出相应的动作访问服务器资源,最后通过发送HTTP响应把结果返回给客户端。其中一个请求的开始到一个响应的结束称为事务,当一个事物结束后还会在服务端添加一条日志条目。
12.1、HTTP特点
- 无状态:协议对客户端没有状态存储,对事物处理没有“记忆”能力,比如访问一个网站需要反复进行登录操作;
- 无连接:HTTP/1.1之前,由于无状态特点,每次请求需要通过TCP三次握手四次挥手,和服务器重新建立连接。比如某个客户机在短时间多次请求同一个资源,服务器并不能区别是否已经响应过用户的请求,所以每次需要重新响应请求,需要耗费不必要的时间和流量;
- 基于请求和响应:基本的特性,由客户端发起请求,服务端响应;
- 简单快速、灵活;
- 通信使用明文、请求和响应不会对通信方进行确认、无法保护数据的完整性。
12.2、HTTP请求
HTTP请求是客户端往服务端发送请求动作,告知服务器自己的要求。HTTP请求由状态行、请求头、请求正文三部分组成:
- 状态行:包括请求方式Method、资源路径URL、协议版本Version;
- 请求头:包括一些访问的域名、用户代理、Cookie等信息;
- 请求正文:就是HTTP请求的数据。
注:请求方式Method一般有GET、POST、PUT、DELETE,含义分别是获取、修改、上传、删除,其中GET方式仅仅为获取服务器资源,方式较为简单,因此在请求方式为GET的HTTP请求数据中,请求正文部分可以省略,直接将想要获取的资源添加到URL中。下图所示就是GET的请求,没有请求正文。
12.3、HTTP响应
1)、响应数据格式
服务器收到了客户端发来的HTTP请求后,根据HTTP请求中的动作要求,服务端做出具体的动作,将结果回应给客户端,称为HTTP响应。HTTP响应由三部分组成:
- 状态行:包括协议版本Version、状态码Status Code、回应短语;
- 响应头:包括搭建服务器的软件,发送响应的时间,回应数据的格式等信息;
- 响应正文:就是响应的具体数据。
注:主要关心并且能够在客户端浏览器看得到的是三位数的状态码,不同的状态码代表不同的含义,其中
- 1xx:表示HTTP请求已经接受,继续处理请求;
- 2xx:表示HTTP请求已经处理完成;
- 3xx:表示请求访问URL重定向到其他目录;
- 4xx:表示客户端出现错误;
- 5xx:表示服务器出现错误。
2)、常见状态码的含义
- 200:请求已经正常处理完毕;
- 301:请求永久重定向;
- 302:请求临时重定向;
- 304:请求被重定向到客户端本地缓存;
- 400:客户端请求存在语法错误;
- 401:客户端请求没有经过授权;
- 403:客户端的请求被服务器拒绝,一般为客户端没有访问权限;
- 404:客户端请求的URL在服务端不存在;
- 500:服务端永久错误;
- 503:服务端发生临时错误。
3)、HTTP响应模型
服务器收到HTTP请求之后,会有多种方法响应这个请求,下面是HTTP响应的四种模型:
- 单进程I/O模型:服务端开启一个进程,一个进程仅能处理一个请求,并且对请求顺序处理;
- 多进程I/O模型:服务端并行开启多个进程,同样的一个进程只能处理一个请求,这样服务端就可以同时处理多个请求;
- 复用I/O模型:服务端开启一个进程,但是同时开启多个线程,一个线程响应一个请求,同样可以达到同时处理多个请求,线程间并发执行;
- 复用多线程I/O模型:服务端并行开启多个进程,同时每个进程开启多个线程,这样服务端可以同时处理进程数M*每个进程的线程数N个请求。
12.4、HTTP报文格式
HTTP报文是HTTP应用程序之间传输的数据块,HTTP报文分为HTTP请求报文和HTTP响应报文,但是无论哪种报文,他的整体格式是类似的,大致都是由起始、首部、主体三部分组成,起始说明报文的动作,首部说明报文的属性,主体则是报文的数据。
HTTP请求报文
请求报文的起始由请求行构成(有些资料称为状态行,名字不一样而已,都是指的一个东西),用来说明该请求想要做什么,由<Method>、<URL>、<Version> 三个字段组成,注意每个字段之间都有一个空格。
HTTP响应报文
响应报文的起始由状态行构成,用来说明服务器做了什么,由<Version>、<Status-Code>、<Phrase>三个字段组成,同样的每个字段之间留有空格;
12.5、HTTP协议版本更替
- HTTP/0.9: HTTP协议的最初版本,功能简陋,仅支持请求方式GET,并且仅能请求访问HTML格式的资源;
- HTTP/1.0:在0.9版本上做了进步,增加了请求方式POST和HEAD;不再局限于0.9版本的HTML格式,根据Content-Type可以支持多种数据格式,即MIME多用途互联网邮件扩展,例如text/html、image/jpeg等;同时也开始支持cache,就是当客户端在规定时间内访问统一网站,直接访问cache即可。 但是1.0版本的工作方式是每次TCP连接只能发送一个请求,当服务器响应后就会关闭这次连接,下一个请求需要再次建立TCP连接,就是不支持keepalive;
- HTTP/1.1 :解决了1.0版本的keepalive问题,1.1版本加入了持久连接,一个TCP连接可以允许多个HTTP请求; 加入了管道机制,一个TCP连接同时允许多个请求同时发送,增加了并发性;新增了请求方式PUT、PATCH、DELETE等。但是还存在一些问题,服务端是按队列顺序处理请求的,假如一个请求处理时间很长,则会导致后边的请求无法处理,这样就造成了队头阻塞的问题;同时HTTP是无状态的连接,因此每次请求都需要添加重复的字段,降低了带宽的利用率;
- HTTP/2.0:为了解决1.1版本利用率不高的问题,提出了HTTP/2.0版本。增加双工模式,即不仅客户端能够同时发送多个请求,服务端也能同时处理多个请求,解决了队头堵塞的问题;HTTP请求和响应中,状态行和请求/响应头都是些信息字段,并没有真正的数据,因此在2.0版本中将所有的信息字段建立一张表,为表中的每个字段建立索引,客户端和服务端共同使用这个表,他们之间就以索引号来表示信息字段,这样就避免了1.0旧版本的重复繁琐的字段,并以压缩的方式传输,提高利用率。
注:当前主流的协议版本还是HTTP/1.1版本。
12.6、网站访问量
- IP访问量:相同的公网IP计算一次,就是同一个局域网内的所有用户访问一个网站,但是他们都是借助一个公网IP去访问那个网站的(NAT),因此这也只能算作一个IP访问量。换一次公网IP则会加1。
- PV 网页访问量:用户访问的页面数就是PV访问量,同一个局域网的不同用户,而且就算是同一个用户,只要刷新一次网站页面,PV访问量就加1,三个访问量的值往往数PV的值最大。
- UV 访客访问量:这里的访客不是用户,而是电脑,一台电脑算一个访客,即使是同一台电脑的不同用户,访问同一个网站UV也只能加1,只有更换电脑才会使UV加1,因为服务端会记录客户端电脑的信息。
12.7、HTTP优化方案
- TCP复用:TCP连接复用是将多个客户端的HTTP请求复用到一个服务器端TCP连接上,而HTTP复用则是一个客户端的多个HTTP请求通过一个TCP连接进行处理。前者是负载均衡设备的独特功能;而后者是HTTP 1.1协议所支持的新功能,目前被大多数浏览器所支持;
- 内容缓存:将经常用到的内容进行缓存起来,那么客户端就可以直接在内存中获取相应的数据了;
- 压缩:将文本数据进行压缩,减少带宽;
- SSL加速(SSL Acceleration):使用SSL协议对HTTP协议进行加密,在通道内加密并加速;
- TCP缓冲:通过采用TCP缓冲技术,可以提高服务器端响应时间和处理效率,减少由于通信链路问题给服务器造成的连接负担。
13、HTTPS协议
13.1、原理
- 首先HTTP请求服务端生成证书,客户端对证书的有效期、合法性、域名是否与请求的域名一致、证书的公钥(RSA加密)等进行校验;
- 客户端如果校验通过后,就根据证书的公钥的有效, 生成随机数,随机数使用公钥进行加密(RSA加密);
- 消息体产生的后,对它的摘要进行MD5(或者SHA1)算法加密,此时就得到了RSA签名;
- 发送给服务端,此时只有服务端(RSA私钥)能解密;
- 解密得到的随机数,再用AES加密,作为密钥(此时的密钥只有客户端和服务端知道)。
13.2、HTTPS特点
- 内容加密:采用混合加密技术,中间者无法直接查看明文内容;
- 验证身份:通过证书认证客户端访问的是自己的服务器;
- 保护数据完整性:防止传输的内容被中间人冒充或者篡改。
13.3、HTTP和HTTPS的区别
- HTTP 的URL 以http:// 开头,而HTTPS 的URL 以https:// 开头;
- HTTP 是不安全的,而 HTTPS 是安全的;
- HTTP 标准端口是80 ,而 HTTPS 的标准端口是443;
- 在OSI 网络模型中,HTTP工作于应用层,而HTTPS 的安全传输机制工作在传输层;
- HTTP 无法加密,而HTTPS 对传输的数据进行加密;
- HTTP无需证书,而HTTPS 需要CA机构wosign的颁发的SSL证书。
14、URI、URL和URN
14.1、URI(uniform resource identifier):统一资源标识符,用来唯一的标识一个资源。
- Web上可用的每种资源如HTML文档、图像、视频片段、程序等都是一个来URI来定位的
- URI一般由三部组成:①访问资源的命名机制,②存放资源的主机名,③资源自身的名称,由路径表示,着重强调于资源。
14.2、URL(uniform resource locator)统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
- URL是Internet上用来描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上,特别是著名的Mosaic。
- 采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。URL一般由三部组成:①协议(或称为服务方式),②存有该资源的主机IP地址(有时也包括端口号),③主机资源的具体地址。如目录和文件名等。
14.3、URN(uniform resource name)统一资源命名,是通过名字来标识资源,比如mailto:java-net@java.sun.com。URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。笼统地说,每个 URL 都是 URI,但不一定每个 URI 都是 URL。这是因为 URI 还包括一个子类,即统一资源名称 (URN),它命名资源但不指定如何定位资源。上面的 mailto、news 和 isbn URI 都是 URN 的示例。
《框架类》
spring
1、什么是spring
Spring是个java企业级应用的开源的开发框架。Spring主要用来开发java应用,但是有些扩展是针对J2EE平台的web应用。Spring框架目标是简化java企业级应用的开发,并通过pojo为基础的编程模型促进良好的编程习惯。
2、Spring框架的好处
- 轻量:spring是轻量的,基本的版本大约2MB;
- 控制反转:spring通过控制反转实现了松散耦合,对象们给出他们的依赖,而不是创建或查找依赖的对象们;
- 面向切面编程(AOP):spring支持面向切面的编程,并且应用业务逻辑和系统服务分开;
- 容器:spring包含并管理应用中对象的生命周期和配置;
- MVC框架:spring的web框架是个精心设计的框架,是web框架的一个很好的替代品;
- 事务管理:spring提供一个特殊的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA);
- 异常处理:spring提供方便的API把具体技术相关的异常(列如JDBC)转化为一致的unchecked异常;
3、Spring的组成模块
- Core module
- Bean module
- Context module
- Expression Language module
- JDBC module
- ORM module
- OXM module
- Java Messaging Service(JMS) module
- Transaction module
- Web module
- Web-Servlet module
- Web-Struts module
- Web-Portlet module
4、spring中有哪些设计模式
- 简单工厂模式:又叫静态工厂方法模式,spring中的BeanFactory就是一个简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象;
- 工厂方法模式:通常有应用程序直接使用new创建新的对象,为了将对象的创建和使用分离,采用工厂模式,即应用程序将对象的创建及初始化职责交给工厂对象。一般情况下,应用程序有自己的工厂对象类创建bean,如果将应用程序自己的工厂对象交给spring管理,那么spring管理的就不是bean,而是工厂bean;
- 单列模式:保证一个类仅有一个实例,并提供一个访问他的全局访问点。在spring中提供了全局的访问点BeanFactory,但没有从构造器级别去控制单列,这是因为spring管理的是任意的java对象;
- 适配器模式:在spring中的AOP中,使用Advice(通知)来增强被代理类的功能。spring实现这一AOP功能的原理就是代理模式(jdk动态代理和CGLib代理)对类进行方法级别的切面增强,来实现的面向切面编程;
- 包装器模式:spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名含有Decorator。(多数据源的切换列子);
- 代理模式:为其他对象提供一种代理以控制对这个对象的访问(spring的Proxy模式在AOP中有体现);
- 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于他的对象都得到通知并自动更新;(spring中Observer模式常用的地方是listener的实现);
- 策略模式:定义一系列的算法,把他们一个个封装起来,并且使他们可互相替换。(spring中在实例化对象的时候用到Strategy模式);
- 模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中(spring中的JdbcTemplate的使用);
5、Spring AOP
AOP(可以说是对oop的补充和完善)它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
核心概念:
- 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点;
- 切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象;
- 连接点(joinpoint):被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器;
- 切入点(pointcut):对连接点进行拦截的定义;
- 通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类目标对象;
- 目标对象:代理的目标对象;
- 织入(weave):将切面应用到目标对象并导致代理对象创建的过程;
- 引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段;
6、Spring IOC
IOC(控制反转)意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
6.1、谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;
- 谁控制谁?当然是IoC 容器控制了对象;
- 控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
6.2、为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;
- 为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;
- 哪些方面反转了?依赖对象的获取被反转了。
6.3、IOC能做什么:IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
6.4、依赖注入方式:
- 构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
- Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。
7、Spring DI
DI(依赖注入)组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
- 谁依赖于谁:当然是应用程序依赖于IoC容器;
- 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
- 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
- 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
8、Spring 支持的几种bean的作用域
- singleton : bean在每个Spring ioc 容器中只有一个实例(是缺省的Spring bean 作用域)。
- prototype:一个bean的定义可以有多个实例。
- request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
- session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
- global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
9、Spring bean的生命周期
- Spring容器 从XML 文件中读取bean的定义,并实例化bean;
- Spring根据bean的定义填充所有的属性;
- 如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。
- 如果Bean 实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。
- 如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们。
- 如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。
- 如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将被调用。
- 如果bean实现了 DisposableBean,它将调用destroy()方法。
10、Spring 注入java集合的方式
- <list>类型用于注入一列值,允许有相同的值。
- <set> 类型用于注入一组值,不允许有相同的值。
- <map> 类型用于注入一组键值对,键和值都可以为任意类型。
- <props>类型用于注入一组键值对,键和值都只能为String类型。
11、Spring bean的自动装配
Spring 容器能够自动装配相互合作的bean,这意味着容器不需要<constructor-arg>和<property>配置,能通过Bean工厂自动处理bean之间的协作。
有五种自动装配的方式,可以用来指导Spring容器用自动装配方式来进行依赖注入。
- no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
- byName:通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。
- byType::通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
- constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
- autodetect:首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。
12、Spring 注解
- @Configuration:它用来标记类可以当做一个bean的定义,被Spring IOC容器使用。
- @Bean:它表示此方法将要返回一个对象,作为一个bean注册进Spring应用上下文。
- @Required:这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出BeanInitializationException。
- @Autowired:提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和@Required一样,修饰setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。
- @Qualifier:当有多个相同类型的bean却只有一个需要自动装配时,将@Qualifier 注解和@Autowire 注解结合使用以消除这种混淆,指定需要装配的确切的bean。
- @Autowired和@Resource的区别:@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
@Autowired和@Resource比较:
1)、相同点:两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。
2)、不同点:
- @Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入;
- @Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用;
- @Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
- @Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。
注:最好是将@Resource放在setter方法上,因为这样更符合面向对象的思想,通过set、get去操作属性,而不是直接去操作属性。
13、Spring 的事务管理
13.1、Spring支持两种类型的事务管理:
- 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
- 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。
13.2、事务管理的优点:
- 它为不同的事务API 如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式;
- 它为编程式事务管理提供了一套简单的API而不是一些复杂的事务API如;
- 它支持声明式事务管理;
- 它和Spring各种数据访问抽象层很好得集成。
Spring MVC
1、什么是SpringMVC
SpringMVC是一个基于MVC架构的用来简化web应用程序开发的应用开发架构,他是spring的一个模块,无需中间整合层来整合,他和Struts2一样都属于表现层的框架。在web模型中,MVC是一种很流行的框架,通过把Model,View,Controller分离,把较为复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
2、SpringMVC原理
- 用户发送请求至前端控制器DispatcherServlet;
- DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;
- 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
- DispatcherServlet通过HandlerAdapter处理器适配器调用处理器;
- 执行处理器(Handler,也叫后端控制器);
- Handler执行完成返回ModelAndView;
- HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
- DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
- ViewResolver解析后返回具体View;
- DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
- DispatcherServlet响应用户。
3、springMVC优点
- 它是基于组件技术的。全部的应用对象,无论控制器和视图,还是业务对象之类的都是 java组件.并且和Spring提供的其他基础结构紧密集成;
- 不依赖于Servlet API(目标虽是如此,但是在实现的时候确实是依赖于Servlet的);
- 可以任意使用各种视图技术,而不仅仅局限于JSP;
- 支持各种请求资源的映射策略;
- 它应是易于扩展的。
4、SpringMVC与Struts2的区别
- springmvc的入口是一个servlet即前端控制器(DispatchServlet),而struts2入口是一个filter过虑器(StrutsPrepareAndExecuteFilter)。
- springmvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。
- Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。
5、SpringMVC注解
- @Conntroller: 用于控制层注解,不能用用别的注解代替;
- @RequestMapping: 用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径(method=RequestMethod.GET)。
- @ResponseBody: 将内容或对象作为 HTTP 响应正文返回,并调用适合HttpMessageConverter的Adapter转换对象,写入输出流;
- @RequestBody:将HTTP请求正文转换为适合的HttpMessageConverter对象;
- @SessionAttributes:用来在controller内部共享model属性的;
- @RequestParam:绑定单个请求参数值;
- @PathVariable:绑定URI模板变量值;
- @ModelAttribute:可以应用在方法参数上或方法上,他的作用主要是当注解在方法参数上时会将注解的参数对象添加到Model中;当注解在请求处理方法Action上时会将该方法变成一个非请求处理的方法,但其它Action被调用时会首先调用该方法。
- @Component:在类定义之前添加@Component注解,他会被spring容器识别,并转为bean;
- @Repository:对dao实现类进行注解;
- @Service:用于对业务逻辑层进行注解;
SpringBoot
1、什么是springboot
Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。
2、springboot优点
- 独立运行
- 简化配置
- 自动配置
- 无代码生成和JavaConfig有助于避免使用XML
- 应用监控
- 上手容易
3、springboot的核心配置文件
- application:主要用于springboot项目的自动化配置;
- bootstrap:
-
- 使用SpringCloud Config配置中心时,需要在bootstrap配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
- 一些固定的不能被覆盖的属性;
- 一些加密或解密的场景;
-
4、springboot配置文件的格式
主要有.properties 和 .yml两种,它们的区别主要是书写格式不同。另外,.yml格式不支持@PropertySource注解导入配置。
5、springboot注解
- @SpringBootApplication:启动类上面的注解,是springboot的核心注解;
- @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能;
- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class });
- @ComponentScan:Spring组件扫描。
6、开启springboot特性的方式
- 继承spring-boot-starter-parent项目;
- 导入spring-boot-dependencies项目依赖;
- springboot的运行方式
- 打包用命令(java –jar )或放到容器中运行;
- 用maven或gradle插件运行;
- 直接执行main方法运行。
7、springboot自动配置的原理
注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自动配置的核心,首先它得是一个配置文件,其次根据类路径下是否有这个类去自动配置。
8、springboot中的starters
Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成 Spring 及其他技术,而不需要到处找示例代码和依赖包。如你想使用 Spring JPA 访问数据库,只要加入 spring-boot-starter-data-jpa 启动器依赖就能使用了。
9、springboot支持的日志框架
支持 Java Util Logging、Log4j2、Lockback 作为日志框架,如果你使用 Starters 启动器,Spring Boot 将使用 Logback 作为默认日志框架
10、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
11、什么是JavaConfig
Spring JavaConfig是Spring社区的产品,它提供了配置Spring IoC容器的纯Java方法。因此它有助于避免使用XML配置。使用JavaConfig的优点在于:
面向对象的配置。由于配置被定义为JavaConfig中的类,因此用户可以充分利用Java中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean方法等。
减少或消除XML配置。基于依赖注入原则的外化配置的好处已被证明。但是,许多开发人员不希望在XML和Java之间来回切换。
JavaConfig为开发人员提供了一种纯Java方法来配置与XML配置概念相似的Spring容器。
从技术角度来讲,只使用JavaConfig配置类来配置容器是可行的,但实际上很多人认为将JavaConfig与XML混合匹配是理想的。
类型安全和重构友好。JavaConfig提供了一种类型安全的方法来配置Spring容器。由于Java 5.0对泛型的支持,现在可以按类型而不是按名称检索bean,不需要任何强制转换或基于字符串的查找。
12、SpringBoot中的监视器
Spring boot actuator是spring启动框架中的重要功能之一。Spring boot监视器可帮助您访问生产环境中正在运行的应用程序的当前状态。
有几个指标必须在生产环境中进行检查和监控。即使一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器模块公开了一组可直接作为HTTP URL访问的REST端点来检查状态。
安全性是使用标准的HttpServletRequest.isUserInRole方法实施的。 我们可以使用management.security.enabled = false 来禁用安全性。只有在执行机构端点在防火墙后访问时,才建议禁用安全性。
SpringCloud
1、什么是微服务
微服务是指开发一个单个小型的但有业务功能的服务,每个服务都有自己的处理和轻量通讯机制,可以部署在单个或多个服务器上。微服务也指一种种松耦合的、有一定的有界上下文的面向服务架构。也就是说,如果每个服务都要同时修改,那么它们就不是微服务,因为它们紧耦合在一起;如果你需要掌握一个服务太多的上下文场景使用条件,那么它就是一个有上下文边界的服务,这个定义来自DDD领域驱动设计。
相对于单体架构和SOA,它的主要特点是组件化、松耦合、自治、去中心化,体现在以下几个方面:
- 一组小的服务:服务粒度要小,而每个服务是针对一个单一职责的业务能力的封装,专注做好一件事情。
- 独立部署运行和扩展:每个服务能够独立被部署并运行在一个进程内。这种运行和部署方式能够赋予系统灵活的代码组织方式和发布节奏,使得快速交付和应对变化成为可能。
- 独立开发和演化:技术选型灵活,不受遗留系统技术约束。合适的业务问题选择合适的技术可以独立演化。服务与服务之间采取与语言无关的API进行集成。相对单体架构,微服务架构是更面向业务创新的一种架构模式。
- 独立团队和自治:团队对服务的整个生命周期负责,工作在独立的上下文中,自己决策自己治理,而不需要统一的指挥中心。团队和团队之间通过松散的社区部落进行衔接。
2、SpringCloud的核心组件
-
Eureka:服务注册于发现。
-
Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。
-
Ribbon:实现负载均衡,从一个服务的多台机器中选择一台。
-
Hystrix:提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。
-
Zuul:网关管理,由 Zuul 网关转发请求给对应的服务。
-
Spring Cloud Config:分布式统一配置管理
3、SpringCloud的优势
- 与分布式系统相关的复杂性-这种开销包括网络问题,延迟开销,带宽问题,安全问题。
- 服务发现-服务发现工具管理群集中的流程和服务如何查找和互相交谈。它涉及一个服务目录,在该目录中注册服务,然后能够查找并连接到该目录中的服务。
- 冗余-分布式系统中的冗余问题。
- 负载平衡 --负载平衡改善跨多个计算资源的工作负荷,诸如计算机,计算机集群,网络链路,中央处理单元,或磁盘驱动器的分布。
- 性能-问题 由于各种运营开销导致的性能问题。
- 部署复杂性-Devops技能的要求。
4、SpringCloud服务注册与发现
服务在发布时 指定对应的服务名(服务名包括了IP地址和端口) 将服务注册到注册中心(eureka、zookeeper等),这一过程是springcloud自动实现 只需要在main方法添加@EnableDisscoveryClient 同一个服务修改端口就可以启动多个实例调用方法:传递服务名称通过注册中心获取所有的可用实例 通过负载均衡策略调用(ribbon和feign)对应的服务。
5、Ribbon和Feign区别
Ribbon添加maven依赖 spring-starter-ribbon 使用@RibbonClient(value="服务名称") 使用RestTemplate调用远程服务对应的方法。
Feign添加maven依赖 spring-starter-feign 服务提供方提供对外接口 调用方使用 在接口上使用@FeignClient("指定服务名")。
区别:
- 启动类使用的注解不同,Ribbon用的是@RibbonClient,Feign用的是@EnableFeignClients。
- 服务的指定位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。
- 调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。
6、SpringCloud断容器的作用
当一个服务调用另一个服务由于网络原因或者自身原因出现问题时 调用者就会等待被调用者的响应 当更多的服务请求到这些资源时,导致更多的请求等待 这样就会发生连锁效应(雪崩效应) 断路器就是解决这一问题。
断路器有三种状态:
- 打开状态:一定时间内达到一定的次数无法调用并且多次检测没有恢复的迹象断路器完全打开,那么下次请求就不会请求到该服务;
- 半开状态:短时间内有恢复迹象断路器会将部分请求发给该服务 当能正常调用时断路器关闭;
- 关闭状态:当服务一直处于正常状态能正常调用断路器关闭;
7、微服务之间独立通信
远程过程调用(Remote Procedure Invocation):也就是我们常说的服务的注册与发现,直接通过远程过程调用来访问别的service。
- 优点: 简单,常见,因为没有中间件代理,系统更简单
- 缺点: 只支持请求/响应的模式,不支持别的,比如通知、请求/异步响应、发布/订阅、发布/异步响应; 降低了可用性,因为客户端和服务端在请求过程中必须都是可用的
消息: 使用异步消息来做服务间通信。服务间通过消息管道来交换消息,从而通信。
- 优点: 把客户端和服务端解耦,更松耦合; 提高可用性,因为消息中间件缓存了消息,直到消费者可以消费; 支持很多通信机制比如通知、请求/异步响应、发布/订阅、发布/异步响应。
- 缺点: 消息中间件有额外的复杂。
8、负载均衡
负载均衡可以改善跨计算机,计算机集群,网络链接,中央处理单元或磁盘驱动器等多种计算资源的工作负载分布。负载均衡旨在优化资源使用,最大吞吐量,最小响应时间并避免任何单一资源的过载。使用多个组件进行负载均衡而不是单个组件可能会通过冗余来提高可靠性和可用性。负载平衡通常涉及专用软件或硬件,例如多层交换机或域名系统服务进程。
Dubbo
1、Dubbo简介
Dubbo是一个由阿里巴巴开源的、分布式的RPC(Remote Procedure Call Protocol-远程过程调用)和微服务框架,现已成为 Apache 基金会孵化项目。Dubbo提供了三个关键功能:基于接口的远程调用,容错与负载均衡,服务自动注册与发现。
Dubbo的结构图:
2、Dubbo支持的协议
- dubbo(推荐):Dubbo 缺省协议是dubbo协议,采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低;
- rmi:RMI协议采用阻塞式(同步)短连接和 JDK 标准序列化方式。适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件;
- hessian:Hessian底层采用Http通讯(同步),采用Servlet暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。适用于传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件;
- http:基于 HTTP 表单的远程调用协议,采用 Spring 的 HttpInvoker 实现;
- webservice:基于 WebService 的远程调用协议,基于 ApacheCXF的
frontend-simple
和transports-http
实现; - thrift:当前dubbo支持的thrift协议是对thrift原生协议的扩展,在原生协议的基础上添加了一些额外的头信息,比如 service name,magic number 等。使用 dubbo thrift 协议同样需要使用 thrift 的 idl compiler 编译生成相应的 java 代码,后续版本中会在这方面做一些增强;
- memcached:基于 memcached实现的RPC协议 ;
- redis:基于Redis实现的RPC协议。
注:Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议。
3、为什么要用Dubbo
因为是阿里开源项目,国内很多互联网公司都在用,已经经过很多线上考验。内部使用了 Netty、Zookeeper,保证了高性能高可用性。
使用 Dubbo 可以将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,可用于提高业务复用灵活扩展,使前端应用能更快速的响应多变的市场需求。
4、Dubbo和SpringCloud的区别
两个没关联,如果硬要说区别,有以下两点:
- 通信方式不同:Dubbo 使用的是 RPC 通信,而 Spring Cloud 使用的是 HTTP RESTFul 方式。
- 组成部分不同:dubbo使用zookeeper作为注册中心,而springcloud使用pringcloud Netflix Eureka,dubbo使用Dubbo-monitor作为服务监控的,而springcloud使用springboot Admin作为服务的监控,springcloud还有网关、配置中心、服务跟踪、消息总线、数据流、批量任务等组件,dubbo是没有的。
5、在Provider上可以配置的Consumer 端的属性有哪些
- timeout:方法调用超时;
- retries:失败重试次数,默认重试 2 次;
- loadbalance:负载均衡算法,默认随机;
- actives: 消费者端,最大并发调用限制;
6、Dubbo启动时如果依赖的服务不可用会怎样
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,默认 check="true",可以通过 check="false" 关闭检查。
7、Dubbo推荐使用什么序列化框架,你知道的还有哪些
推荐使用Hessian序列化,还有Duddo、FastJson、Java自带序列化。
8、Dubbo默认使用的是什么通信框架,还有别的选择吗
Dubbo 默认使用 Netty 框架,也是推荐的选择,另外内容还集成有Mina、Grizzly。
9、服务上线怎么兼容旧版本
可以用版本号(version)过渡,多个不同版本的服务注册到注册中心,版本号不同的服务相互间不引用。这个和服务分组的概念有一点类似。
10、Dubbo可以对结果进行缓存吗
可以,Dubbo 提供了声明式缓存,用于加速热门数据的访问速度,以减少用户加缓存的工作量。
11、Dubbo服务之间的调用是阻塞的吗
默认是同步等待结果阻塞的,支持异步调用。
Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。
异步调用流程图:
12、Dubbo如何优雅停机
Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果使用 kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才会执行。
13、服务读写推荐的容错策略是怎样的
- 读操作建议使用 Failover 失败自动切换,默认重试两次其他服务器。
- 写操作建议使用 Failfast 快速失败,发一次调用失败就立即报错。
14、Dubbo的管理控制台能做什么
- 路由规则
- 动态配置
- 服务降级
- 访问控制
- 权重调整
- 负载均衡
15、Dubbo 服务暴露的过程
Dubbo 会在 Spring 实例化完 bean 之后,在刷新容器最后一步发布 ContextRefreshEvent 事件的时候,通知实现了 ApplicationListener 的 ServiceBean 类进行回调 onApplicationEvent 事件方法,Dubbo 会在这个方法中调用 ServiceBean 父类 ServiceConfig 的 export 方法,而该方法真正实现了服务的(异步或者非异步)发布。
16、注册中心
16.1、zookeeper (推荐)
Zookeeper 是 Apacahe Hadoop 的子项目,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,并推荐使用 。
流程说明:
- 服务提供者启动时: 向
/dubbo/com.foo.BarService/providers
目录下写入自己的 URL 地址 - 服务消费者启动时: 订阅
/dubbo/com.foo.BarService/providers
目录下的提供者 URL 地址。并向/dubbo/com.foo.BarService/consumers
目录下写入自己的 URL 地址 - 监控中心启动时: 订阅
/dubbo/com.foo.BarService
目录下的所有提供者和消费者 URL 地址。
支持以下功能:
- 当提供者出现断电等异常停机时,注册中心能自动删除提供者信息
- 当注册中心重启时,能自动恢复注册数据,以及订阅请求
- 当会话过期时,能自动恢复注册数据,以及订阅请求
- 当设置
<dubbo:registry check="false" />
时,记录失败注册和订阅请求,后台定时重试 - 可通过
<dubbo:registry username="admin" password="1234" />
设置 zookeeper 登录信息 - 可通过
<dubbo:registry group="dubbo" />
设置 zookeeper 的根节点,不设置将使用无根树 - 支持
*
号通配符<dubbo:reference group="*" version="*" />
,可订阅服务的所有分组和所有版本的提供者
16.2、Redis :
使用 Redis 的 Key/Map 结构存储数据结构:
- 主 Key 为服务名和类型;
- Map 中的 Key 为 URL 地址;
- Map 中的 Value 为过期时间,用于判断脏数据,脏数据由监控中心删除 。
使用 Redis 的 Publish/Subscribe 事件通知数据变更:
- 通过事件的值区分事件类型:
register
,unregister
,subscribe
,unsubscribe;
- 普通消费者直接订阅指定服务提供者的 Key,只会收到指定服务的
register
,unregister
事件; - 监控中心通过
psubscribe
功能订阅/dubbo/*
,会收到所有服务的所有变更事件。
调用过程:
- 服务提供方启动时,向
Key:/dubbo/com.foo.BarService/providers
下,添加当前提供者的地址; - 并向
Channel:/dubbo/com.foo.BarService/providers
发送register
事件; - 服务消费方启动时,从
Channel:/dubbo/com.foo.BarService/providers
订阅register
和unregister
事件; - 并向
Key:/dubbo/com.foo.BarService/providers
下,添加当前消费者的地址; - 服务消费方收到
register
和unregister
事件后,从Key:/dubbo/com.foo.BarService/providers
下获取提供者地址列表; - 服务监控中心启动时,从
Channel:/dubbo/*
订阅register
和unregister
,以及subscribe
和unsubsribe
事件; - 服务监控中心收到
register
和unregister
事件后,从Key:/dubbo/com.foo.BarService/providers
下获取提供者地址列表; - 服务监控中心收到
subscribe
和unsubsribe
事件后,从Key:/dubbo/com.foo.BarService/consumers
下获取消费者地址列表。
16.3、Multicast :
Multicast 注册中心不需要启动任何中心节点,只要广播地址一样,就可以互相发现。
- 提供方启动时广播自己的地址;
- 消费方启动时广播订阅请求;
- 提供方收到订阅请求时,单播自己的地址给订阅者,如果设置了
unicast=false
,则广播给订阅者; - 消费方收到提供方地址时,连接该地址进行 RPC 调用。
注:组播受网络结构限制,只适合小规模应用或开发阶段使用。
16.4、Simple :
Simple 注册中心本身就是一个普通的 Dubbo 服务,可以减少第三方依赖,使整体通讯方式一致。
17、Dubbo里面有哪几种节点角色
- provider:暴露服务的服务提供方;
- consumer:调用远程服务的服务消费方;
- registry:服务注册与发现的注册中心;
- monitor:统计服务的调用次数和调用时间的监控中心;
- container:服务运行容器。
18、Dubbo内置了哪几种服务容器
-
Spring Container
-
Jetty Container
-
Log4j Container
19、Dubbo 核心的配置
- dubbo:service:服务配置;
- dubbo:reference:引用配置;
- dubbo:protocol:协议配置;
- dubbo:application:应用配置;
- dubbo:module:模块配置;
- dubbo:registry:注册中心配置;
- dubbo:monitor:监控中心配置;
- dubbo:provider:提供方配置;
- dubbo:consumer:消费方配置;
- dubbo:method:方法配置;
- dubbo:argument:参数配置。
配置之间的关系图:
20、Dubbo有哪几种集群容错方案
- Failover Cluster(默认):失败自动切换,自动重试其他服务器;
- Failfast Cluster:快速失败,立即报错,只发起一次调用;
- Failsafe Cluster:失败安全,出现异常时,直接忽略;
- Failback Cluster:失败自动恢复,记录失败请求,定时重发;
- Failking Cluster:并行调用多个服务器,只要一个成功即返回;
- Broadcast Cluster:广播逐个调用所有提供者,任意一个报错则报错。
21、Dubbo有哪几种负载均衡策略
- Random LoadBalance(默认):随机,按权重设置随机概率;
- RoundRobin LoadBalance:轮询,按公约后的权重设置轮询比率;
- LeastActive LoadBalance:最少活跃调用数,相同活跃数的随机;
- ConsistentHash LoadBalance:一致性Hash,相同参数的请求总是发到同一提供者。
MyBatis
1、什么是MyBatis
- mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程;
- mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回;
- MyBatis 支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJO映射成数据库中的记录。
2、Mybait的优点
- 简单易学,容易上手(相比于Hibernate) —- 基于SQL编程;
- JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
- 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持,而JDBC提供了可扩展性,所以只要这个数据库有针对Java的jar包就可以就可以与MyBatis兼容),开发人员不需要考虑数据库的差异性。
- 提供了很多第三方插件(分页插件 / 逆向工程);
- 能够与Spring很好的集成;
- MyBatis相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL写在XML里,从程序代码中彻底分离,解除sql与程序代码的耦合,便于统一管理和优化,并可重用。
- 提供XML标签,支持编写动态SQL语句。
- 提供映射标签,支持对象与数据库的ORM字段关系映射。
- 提供对象关系映射标签,支持对象关系组建维护。
3、MyBatis框架的缺点
- SQL语句的编写工作量较大,尤其是字段多、关联表多时,更是如此,对开发人员编写SQL语句的功底有一定要求;
- SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
4、MyBatis与Hibernate区别
- Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象;
- Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大;
- Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的缺点是学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
5、#{}和${}的区别
- #{}是预编译处理,${}是字符串替换;
- Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
- Mybatis在处理${}时,就是把${}替换成变量的值;
- 使用#{}可以有效的防止SQL注入,提高系统安全性;
- 使用#{}时,会加单引号的,而${}是不会加单引号的。
6、Xml映射文件与Mapper接口的工作原理
- 接口的全限名,就是映射文件中的namespace的值;
- 接口的方法名,就是映射文件中MappedStatement的id值;
- 接口方法内的参数,就是传递给sql的参数;
- Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement;
- Mapper接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。
Mapper接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。
7、Mybatis的分页与分页插件的原理
分页:Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件:是基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
8、Mybatis的一级、二级缓存
- 一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
- 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置<cache/> ;
- 对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
9、Mybatis与IBatis比较
- 有接口绑定,包括注解绑定sql和xml绑定Sql;
- 动态sql由原来的节点配置变成OGNL表达式;
- 在一对一,一对多的时候引进了association,在一对多的时候引入了collection 节点,不过都是在resultMap里面配置;
- IBatis里面的核心处理类交SqlMapClient, MyBatis里面的核心处理类叫做SqlSession;
- 在sql里面变量命名有原来的#变量# 变成了#{变量} 原来的$变量$变成了${变量},;
- 原来在sql节点里面的class都换名字交type;
- 原来的queryForObject、queryForList 变成了selectOne、selectList;
- 原来的别名设置在映射文件里面放在了核心配置文件。
10、MyBatis编程步骤
- 创建SqlSessionFactory ;
- 通过SqlSessionFactory创建SqlSession ;
- 通过sqlsession执行数据库操作 ;
- 调用session.commit()提交事务 ;
- 调用session.close()关闭会话。
Hibernate
1、、Hibernate工作原理
- 读取并解析配置文件
- 读取并解析映射信息,创建SessionFactory
- 打开Sesssion
- 创建事务Transation
- 持久化操作
- 提交事务
- 关闭Session
- 关闭SesstionFactory
优点:
- 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码;
- Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现。他很大程度的简化DAO层的编码工作;
- hibernate使用Java反射机制,而不是字节码增强程序来实现透明性;
- hibernate的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系。
2、hibernate的三种状态之间如何转换
- 当对象由瞬时状态(Transient)一save()时,就变成了持久化状态。 该对象还没有被持久化【没有保存在数据库中】,不受Session的管理;
- 当游离状态(Detached)update()时,又变为了持久状态(Persistent)。在数据库有对应的数据,受Session的管理;
- 当持久状态(Persistent)delete()时,又变为了瞬时状态(Transient), 此时,数据库中没有与之对应的记录。不处于session的管理,数据库中有对应的记录。
3、比较hibernate的三种检索策略优缺点
3.1、立即检索
- 优点:对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便的从一个对象导航到与它关联的对象。
- 缺点: select语句太多、可能会加载应用程序不需要访问的对象白白浪费许多内存空间。
3.2、延迟检索
- 优点: 由应用程序决定需要加载哪些对象,可以避免可执行多余的select语句,以及避免加载应用程序不需要访问的对象。因此能提高检索性能,并且能节省内存空间。
- 缺点: 应用程序如果希望访问游离状态代理类实例,必须保证他在持久化状态时已经被初始化。
3.3、迫切左外连接检索
- 优点: 对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便地冲一个对象导航到与它关联的对象。使用了外连接,select语句数目少。
- 缺点: 可能会加载应用程序不需要访问的对象,白白浪费许多内存空间;复杂的数据库表连接也会影响检索性能。
4、hibernate的缓存策略
- Read-only: 这种策略适用于那些频繁读取却不会更新的数据,这是目前为止最简单和最有效的缓存策略 ;
- Read/write:这种策略适用于需要被更新的数据,比read-only更耗费资源,在非JTA环境下,每个事务需要在session.close和session.disconnect()被调用 ;
- Nonstrict read/write: 这种策略不保障两个同时进行的事务会修改同一块数据,这种策略适用于那些经常读取但是极少更新的数据 ;
- Transactional: 这种策略是完全事务化得缓存策略,可以用在JTA环境下。
5、Hibernate的缓存机制
一级缓存:
- Hibenate中一级缓存,也叫做session的缓存,它可以在session范围内减少数据库的访问次数! 只在session范围有效! Session关闭,一级缓存失效;
- 只要是持久化对象状态的,都受Session管理,也就是说,都会在Session缓存中;
- Session的缓存由hibernate维护,用户不能操作缓存内容; 如果想操作缓存内容,必须通过hibernate提供的evit/clear方法操作。
二级缓存:
- 二级缓存是基于应用程序的缓存,所有的Session都可以使用;
- Hibernate提供的二级缓存有默认的实现,且是一种可插配的缓存框架。如果用户想用二级缓存,只需要在hibernate.cfg.xml中配置即可; 不想用,直接移除,不影响代码。
- 如果用户觉得hibernate提供的框架框架不好用,自己可以换其他的缓存框架或自己实现缓存框架都可以;
- Hibernate二级缓存:存储的是常用的类。
6、如何优化Hibernate
- 使用双向一对多关联,不使用单向一对多
- 灵活使用单向一对多关联
- 不用一对一,用多对一取代
- 配置对象缓存,不使用集合缓存
- 一对多集合使用Bag,多对多集合使用Set
- 继承类使用显式多态
- 表字段要少,表关联不要怕多,有二级缓存撑腰
7、什么是SessionFactory,线程安全么
SessionFactory 是Hibrenate单例数据存储和线程安全的,以至于可以多线程同时访问。一个SessionFactory 在启动的时候只能建立一次。SessionFactory应该包装各种单例以至于它能很简单的在一个应用代码中储存。
8、Hibernate的五个核心接口
- Configuration 接口:配置Hibernate,根据其启动hibernate,创建SessionFactory 对象;
- SessionFactory 接口:初始化Hibernate,充当数据存储源的代理,创建 session 对象,sessionFactory 是线程安全的,意味着它的同一个实例可以被应 用的多个线程共享,是重量级、二级缓存;
- Session 接口:负责保存、更新、删除、加载和查询对象,是线程不安全的, 避免多个线程共享同一个session,是轻量级、一级缓存;
- Transaction 接口:管理事务;
- Query 和Criteria 接口:执行数据库的查询。
9、get和load区别
- get()立即查询
- load()懒加载
- get如果没有找到会返回null, load如果没有找到会抛出异常。
- get会先查一级缓存, 再查二级缓存,然后查数据库;load会先查一级缓存,如果没有找到,就创建代理对象, 等需要的时候去查询二级缓存和数据库。
10、persist和save的区别
- persist不保证立即执行,可能要等到flush;
- persist不更新缓存;
- save, 把一个瞬态的实例持久化标识符,及时的产生,它要返回标识符,所以它会立即执行Sql insert;
- 使用 save() 方法保存持久化对象时,该方法返回该持久化对象的标识属性值(即对应记录的主键值);
- 使用 persist() 方法来保存持久化对象时,该方法没有任何返回值。
11、主键的自动生成策略
- identity 自增长(mysql,db2);
- sequence 自增长(序列), oracle中自增长是以序列方法实现**;
- native 自增长【会根据底层数据库自增长的方式选择identity或sequence】;
- 如果是mysql数据库, 采用的自增长方式是identity;
- 如果是oracle数据库, 使用sequence序列的方式实现自增长;
- increment 自增长(会有并发访问的问题,一般在服务器集群环境使用会存在问题。);
- assigned:指定主键生成策略为手动指定主键的值;
- uuid:指定主键生成策略为UUID生成的值;
- foreign(外键的方式)。
Storm
1、什么是Storm
Storm是一个免费开源、分布式、高容错的实时计算系统,利用storm可以很容易做到可靠的处理无线数据流。Storm经常用于在实时分析、在线机器学习、持续计算、分布式远程调用和ETL等领域。
2、Storm的特点
- 编程简单:开发人员只需要关注应用逻辑,而且跟Hadoop类似,Storm提供的编程原语也很简单;
- 高性能,低延迟:可以应用于广告搜索引擎这种要求对广告主的操作进行实时响应的场景;
- 分布式:可以轻松应对数据量大,单机搞不定的场景;
- 可扩展: 随着业务发展,数据量和计算量越来越大,系统可水平扩展;
- 容错:单个节点挂了不影响应用;
- 消息不丢失:保证消息处理。
使用Storm时需要关注的点:
- 如果使用的是自己的消息队列,需要加入消息队列做数据的来源和产出的代码;
- 需要考虑如何做故障处理:如何记录消息队列处理的进度,应对Storm重启,挂掉的场景;
- 需要考虑如何做消息的回退:如果某些消息处理一直失败怎么办?
3、Storm的核心组件
- Nimbus:即Storm的Master,负责资源分配和任务调度。一个Storm集群只有一个Nimbus。
- Supervisor:即Storm的Slave,负责接收Nimbus分配的任务,管理所有Worker,一个Supervisor节点中包含多个Worker进程。
- Worker:工作进程,每个工作进程中都有多个Task。
- Task:任务,在 Storm 集群中每个 Spout 和 Bolt 都由若干个任务(tasks)来执行。每个任务都与一个执行线程相对应。
- Topology:计算拓扑,Storm 的拓扑是对实时计算应用逻辑的封装,它的作用与 MapReduce 的任务(Job)很相似,区别在于 MapReduce 的一个 Job 在得到结果之后总会结束,而拓扑会一直在集群中运行,直到你手动去终止它。拓扑还可以理解成由一系列通过数据流(Stream Grouping)相互关联的 Spout 和 Bolt 组成的的拓扑结构。
- Stream:数据流(Streams)是 Storm 中最核心的抽象概念。一个数据流指的是在分布式环境中并行创建、处理的一组元组(tuple)的无界序列。数据流可以由一种能够表述数据流中元组的域(fields)的模式来定义。
- Spout:数据源(Spout)是拓扑中数据流的来源。一般 Spout 会从一个外部的数据源读取元组然后将他们发送到拓扑中。根据需求的不同,Spout 既可以定义为可靠的数据源,也可以定义为不可靠的数据源。一个可靠的 Spout能够在它发送的元组处理失败时重新发送该元组,以确保所有的元组都能得到正确的处理;相对应的,不可靠的 Spout 就不会在元组发送之后对元组进行任何其他的处理。一个 Spout可以发送多个数据流。
- Bolt:拓扑中所有的数据处理均是由 Bolt 完成的。通过数据过滤(filtering)、函数处理(functions)、聚合(aggregations)、联结(joins)、数据库交互等功能,Bolt 几乎能够完成任何一种数据处理需求。一个 Bolt 可以实现简单的数据流转换,而更复杂的数据流变换通常需要使用多个 Bolt 并通过多个步骤完成。
- Stream grouping:流分组。为拓扑中的每个 Bolt 的确定输入数据流是定义一个拓扑的重要环节。数据流分组定义了在 Bolt 的不同任务(tasks)中划分数据流的方式。在 Storm 中有八种内置的数据流分组方式。
- Reliability:可靠性。Storm 可以通过拓扑来确保每个发送的元组都能得到正确处理。通过跟踪由 Spout 发出的每个元组构成的元组树可以确定元组是否已经完成处理。每个拓扑都有一个“消息延时”参数,如果 Storm 在延时时间内没有检测到元组是否处理完成,就会将该元组标记为处理失败,并会在稍后重新发送该元组。
- Tuple:元组。元组是Storm提供的一个轻量级的数据格式,可以用来包装你需要实际处理的数据。元组是一次消息传递的基本单元。一个元组是一个命名的值列表,其中的每个值都可以是任意类型的。元组是动态地进行类型转化的--字段的类型不需要事先声明。在Storm中编程时,就是在操作和转换由元组组成的流。通常,元组包含整数,字节,字符串,浮点数,布尔值和字节数组等类型。要想在元组中使用自定义类型,就需要实现自己的序列化方式。
- Component:组件。组件(component)是对Bolt和Spout的统称。
4、Storm 的容错
- 工作进程worker 失效:如果一个节点的工作进程worker“死掉”,supervisor 进程会尝试重启该worker。如果连续重启worker 失败或者worker 不能定期向Nimbus 报告“心跳”,Nimbus 会分配该任务到集群其他的节点上执行。
- 集群节点失效:如果集群中某个节点失效,分配给该节点的所有任务会因超时而失败,Nimbus 会将分配给该节点的所有任务重新分配给集群中的其他节点。
- Nimbus 或者supervisor 守护进程失败:Nimbus 和supervisor 都被设计成快速失败(遇到未知错误时迅速自我失败)和无状态的(所有的状态信息都保存在Zookeeper 上或者是磁盘上)。Nimbus 和supervisor 守护进程必须在一些监控工具(例如,daemontools 或者monitor)的辅助下运行,一旦Nimbus 或者supervisor 失败,可以立刻重启它们,整个集群就好像什么事情也没发生。最重要的是,没有工作进程worker 会因为Nimbus 或supervisor 的失败而受到影响,Storm 的这个特性和Hadoop 形成了鲜明的对比,如果JobTracker 失效,所有的任务都会失败。
- Nimbus 所在的节点失效:如果Nimbus 守护进程驻留的节点失败,工作节点上的工作进程worker 会继续执行计算任务,而且,如果worker 进程失败,supervisor 进程会在该节点上重启失败的worker 任务。但是,没有Nimbus的影响时,所有worker 任务不会分配到其他的工作节点机器上,即使该worker所在的机器失效。
Flume
1、什么是Flume
Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。
当前Flume有两个版本Flume 0.9X版本的统称Flume-og,Flume1.X版本的统称Flume-ng。由于Flume-ng经过重大重构,与Flume-og有很大不同,使用时请注意区分。改动的另一原因是将 Flume 纳入 apache 旗下,cloudera Flume 改名为 Apache Flume。
2、特证
- Flume可以高效率的将多个网站服务器中收集的日志信息存入HDFS/HBase中;
- 使用Flume,我们可以将从多个服务器中获取的数据迅速的移交给Hadoop中;
- 除了日志信息,Flume同时也可以用来接入收集规模宏大的社交网络节点事件数据,比如facebook,twitter,电商网站如亚马逊,flipkart等;
- 支持各种接入资源数据的类型以及接出数据类型;
- 支持多路径流量,多管道接入流量,多管道接出流量,上下文路由等;
- 可以被水平扩展。
3、优势
- Flume可以将应用产生的数据存储到任何集中存储器中,比如HDFS,HBase;
- 当收集数据的速度超过将写入数据的时候,也就是当收集信息遇到峰值时,这时候收集的信息非常大,甚至超过了系统的写入数据能力,这时候,Flume会在数据生产者和数据收容器间做出调整,保证其能够在两者之间提供平稳的数据;
- 提供上下文路由特征;
- Flume的管道是基于事务,保证了数据在传送和接收时的一致性;
- Flume是可靠的,容错性高的,可升级的,易管理的,并且可定制的。
4、核心组件
- Client:Client生产数据,运行在一个独立的线程。
- Event: 一个数据单元,消息头和消息体组成。(Events可以是日志记录、 avro 对象等。)
- Flow: Event从源点到达目的点的迁移的抽象。
- Agent: 一个独立的Flume进程,包含组件Source、 Channel、 Sink。(Agent使用JVM 运行Flume。每台机器运行一个agent,但是可以在一个agent中包含多个sources和sinks。)
- Source: 数据收集组件。(source从Client收集数据,传递给Channel)
- Channel: 中转Event的一个临时存储,保存由Source组件传递过来的Event。(Channel连接 sources 和 sinks ,这个有点像一个队列。)
- Sink: 从Channel中读取并移除Event, 将Event传递到FlowPipeline中的下一个Agent(如果有的话)(Sink从Channel收集数据,运行在一个独立线程。)
5、Flume拦截器、数据流以及可靠性
5.1、拦截器
当我们需要对数据进行过滤时,除了我们在Source、 Channel和Sink进行代码修改之外, Flume为我们提供了拦截器,拦截器也是chain形式的。
拦截器的位置在Source和Channel之间,当我们为Source指定拦截器后,我们在拦截器中会得到event,根据需求我们可以对event进行保留还是抛弃,抛弃的数据不会进入Channel中。
5.2、数据流
- Flume 的核心是把数据从数据源收集过来,再送到目的地。为了保证输送一定成功,在送到目的地之前,会先缓存数据,待数据真正到达目的地后,删除自己缓存的数据。
- Flume 传输的数据的基本单位是 Event,如果是文本文件,通常是一行记录,这也是事务的基本单位。 Event 从 Source,流向 Channel,再到 Sink,本身为一个 byte 数组,并可携带 headers 信息。 Event 代表着一个数据流的最小完整单元,从外部数据源来,向外部的目的地去。
注意:Flume提供了大量内置的Source、Channel和Sink类型。不同类型的Source,Channel和Sink可以自由组合。组合方式基于用户设置的配置文件,非常灵活。如:Channel可以把事件暂存在内存里,也可以持久化到本地硬盘上。
5.3、可靠性
Flume 使用事务性的方式保证传送Event整个过程的可靠性。 Sink 必须在Event 被存入 Channel 后,或者,已经被传达到下一站agent里,又或者,已经被存入外部数据目的地之后,才能把 Event 从 Channel 中 remove 掉。这样数据流里的 event 无论是在一个 agent 里还是多个 agent 之间流转,都能保证可靠,因为以上的事务保证了 event 会被成功存储起来。比如 Flume支持在本地保存一份文件 channel 作为备份,而memory channel 将event存在内存 queue 里,速度快,但丢失的话无法恢复。
Zookeeper
1、ZooKeeper是什么
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了监听器,这个监听器也是由所连接的zookeeper机器来处理。对于写请求,这些请求会同时发给其他zookeeper机器并且达成一致后,请求才会返回成功。因此,随着zookeeper的集群机器增多,读请求的吞吐会提高但是写请求的吞吐会下降。
有序性是zookeeper中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为zxid(Zookeeper Transaction Id)。而读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个zookeeper最新的zxid。
2、Zookeeper工作原理
Zookeeper 的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
3、ZooKeeper 特点
- 顺序一致性:从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
- 原子性:所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
- 单一系统映像:无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
- 可靠性:一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。
4、Zookeeper文件系统
Zookeeper提供一个多层级的节点命名空间(节点称为znode)。与文件系统不同的是,这些节点都可以设置关联的数据,而文件系统中只有文件节点可以存放数据而目录节点不行。Zookeeper为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得Zookeeper不能用于存放大量的数据,每个节点的存放数据上限为1M。
5、四种类型的znode
- PERSISTENT-持久化目录节点 :客户端与zookeeper断开连接后,该节点依旧存在 ;
- PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点:客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号 ;
- EPHEMERAL-临时目录节点:客户端与zookeeper断开连接后,该节点被删除 ;
- EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点:客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号。
6、Zookeeper通知机制
client端会对某个znode建立一个watcher事件,当该znode发生变化时,这些client会收到zk的通知,然后client可以根据znode变化来做出业务上的改变等。
7、Zookeeper
7.1、命名服务(文件系统)
命名服务是指通过指定的名字来获取资源或者服务的地址,利用zk创建一个全局的路径,即是唯一的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等。
7.2、配置管理(文件系统、通知机制)
程序分布式的部署在不同的机器上,将程序的配置信息放在zk的znode下,当有配置发生改变时,也就是znode发生变化时,可以通过改变zk中某个目录节点的内容,利用watcher通知给各个客户端,从而更改配置。
7.3、集群管理(文件系统、通知机制)
所谓集群管理无在乎两点:是否有机器退出和加入、选举master。
- 对于第一点,所有机器约定在父目录下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个兄弟目录被删除,于是,所有人都知道:它上船了。新机器加入也是类似,所有机器收到通知:新兄弟目录加入,highcount又有了;
- 对于第二点,我们稍微改变一下,所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为master就好。
7.4、分布式锁(文件系统、通知机制)
有了zookeeper的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是保持独占,另一个是控制时序。
- 对于第一类,我们将zookeeper上的一个znode看作是一把锁,通过createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。用完删除掉自己创建的distribute_lock 节点就释放出锁;
- 对于第二类, /distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁,用完删除,依次方便。
7.5、队列管理(文件系统、通知机制)
两种类型的队列:
- 同步队列,当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。 也就是在约定目录下创建临时目录节点,监听节点数目是否是我们要求的数目。
- 队列按照 FIFO 方式进行入队和出队操作。 和分布式锁服务中的控制时序场景基本原理一致,入列有编号,出列按编号。在特定的目录下创建PERSISTENT_SEQUENTIAL节点,创建成功时Watcher通知等待的队列,队列删除序列号最小的节点用以消费。此场景下Zookeeper的znode用于消息存储,znode存储的数据就是消息队列中的消息内容,SEQUENTIAL序列号就是消息的编号,按序取出即可。由于创建的节点是持久化的,所以不必担心队列消息的丢失问题。
8、Zookeeper数据复制
Zookeeper作为一个集群提供一致的数据服务,自然,它要在所有机器间做数据复制。数据复制的好处:
- 容错:一个节点出错,不致于让整个系统停止工作,别的节点可以接管它的工作;
- 提高系统的扩展能力 :把负载分布到多个节点上,或者增加节点来提高系统的负载能力;
- 提高性能:让客户端本地访问就近的节点,提高用户访问速度。
从客户端读写访问的透明度来看,数据复制集群系统分下面两种:
- 写主(WriteMaster) :对数据的修改提交给指定的节点。读无此限制,可以读取任何一个节点。这种情况下客户端需要对读与写进行区别,俗称读写分离;
- 写任意(Write Any):对数据的修改可提交给任意的节点,跟读一样。这种情况下,客户端对集群节点的角色与变化透明。
对zookeeper来说,它采用的方式是写任意。通过增加机器,它的读吞吐能力和响应能力扩展性非常好,而写,随着机器的增多吞吐能力肯定下降(这也是它建立observer的原因),而响应能力则取决于具体实现方式,是延迟复制保持最终一致性,还是立即复制快速响应。
9、zookeeper事务的顺序一致性
zookeeper采用了递增的事务Id来标识,所有的proposal(提议)都在被提出的时候加上了zxid,zxid实际上是一个64位的数字,高32位是epoch(时期; 纪元; 世; 新时代)用来标识leader是否发生改变,如果有新的leader产生出来,epoch会自增,低32位用来递增计数。当新产生proposal的时候,会依据数据库的两阶段过程,首先会向其他的server发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行。
10、Zookeeper Server的工作状态
每个Server在工作过程中有三种状态:
- LOOKING:当前Server不知道leader是谁,正在搜寻;
- LEADING:当前Server即为选举出来的leader;
- FOLLOWING:leader已经选举出来,当前Server与之同步。
11、分布式通知和协调
- 对于系统调度来说:操作人员发送通知实际是通过控制台改变某个节点的状态,然后zk将这些变化发送给注册了这个节点的watcher的所有客户端;
- 对于执行情况汇报:每个工作进程都在某个目录下创建一个临时节点。并携带工作的进度数据,这样汇总的进程可以监控目录子节点的变化获得工作进度的实时的全局情况。
12、zk节点宕机处理
Zookeeper本身也是集群,推荐配置不少于3个服务器。Zookeeper自身也要保证当一个节点宕机时,其他节点会继续提供服务。
- 如果是一个Follower宕机,还有2台服务器提供访问,因为Zookeeper上的数据是有多个副本的,数据并不会丢失;
- 如果是一个Leader宕机,Zookeeper会选举出新的Leader。
13、zookeeper watch机制
一个Watch事件是一个一次性的触发器,当被设置了Watch的数据发生了改变的时候,则服务器将这个改变发送给设置了Watch的客户端,以便通知它们。
Zookeeper机制的特点:
- 一次性触发数据发生改变时,一个watcher event会被发送到client,但是client只会收到一次这样的信息;
- watcher event异步发送watcher的通知事件从server发送到client是异步的,这就存在一个问题,不同的客户端和服务器之间通过socket进行通信,由于网络延迟或其他因素导致客户端在不通的时刻监听到事件,由于Zookeeper本身提供了ordering guarantee,即客户端监听事件后,才会感知它所监视znode发生了变化。所以我们使用Zookeeper不能期望能够监控到节点每次的变化。Zookeeper只能保证最终的一致性,而无法保证强一致性;
- 数据监视Zookeeper有数据监视和子数据监视getdata() and exists()设置数据监视,getchildren()设置了子节点监视;
- 注册watcher getData、exists、getChildren;
- 触发watcher create、delete、setData;
- setData()会触发znode上设置的data watch(如果set成功的话)。一个成功的create() 操作会触发被创建的znode上的数据watch,以及其父节点上的child watch。而一个成功的delete()操作将会同时触发一个znode的data watch和child watch(因为这样就没有子节点了),同时也会触发其父节点的child watch;
- 当一个客户端连接到一个新的服务器上时,watch将会被以任意会话事件触发。当与一个服务器失去连接的时候,是无法接收到watch的。而当client重新连接时,如果需要的话,所有先前注册过的watch,都会被重新注册。通常这是完全透明的。只有在一个特殊情况下,watch可能会丢失:对于一个未创建的znode的exist watch,如果在客户端断开连接期间被创建了,并且随后在客户端连接上之前又删除了,这种情况下,这个watch事件可能会被丢失;
- Watch是轻量级的,其实就是本地JVM的Callback,服务器端只是存了是否有设置了Watcher的布尔类型。
RabbitMQ
1、什么是 rabbitmq
采用 AMQP 高级消息队列协议的一种消息队列技术,最大的特点就是消费并不需 要确保提供方存在,实现了服务之间的高度解耦
2、为什么要使用 rabbitmq
- 在分布式系统下具备异步,削峰,负载均衡等一系列高级功能;
- 拥有持久化的机制,进程消息,队列中的信息也可以保存下来。
- 实现消费者和生产者之间的解耦。
- 对于高并发场景下,利用消息队列可以使得同步访问变为串行访问达到一定量 的限流,利于数据库的操作。
- 可以使用消息队列达到异步下单的效果,排队中,后台进行逻辑下单。
3、使用 rabbitmq 的场景
- 服务间异步通信
- 顺序消费
- 定时任务
- 请求削峰
4、如何确保消息正确地发送至 RabbitMQ? 如何确保消息接 收方消费了消息?
发送方确认模式
将信道设置成 confirm 模式(发送方确认模式),则所有在信道上发布的消息都 会被指派一个唯一的 ID。
一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信 道会发送一个确认给生产者(包含消息唯一 ID)。
如果 RabbitMQ 发生内部错误从而导致消息丢失,会发送一条 nack(not acknowledged,未确认)消息。 发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消 息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来 处理确认消息。
接收方确认机制
接收方消息确认机制
消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操 作)。只有消费者确认了消息,RabbitMQ 才能安全地把消息从队列中删除。 这里并没有用到超时机制,RabbitMQ 仅通过 Consumer 的连接中断来确认是否 需要重新发送消息。也就是说,只要连接不中断,RabbitMQ 给了 Consumer 足 够长的时间来处理消息。保证数据的最终一致性;
下面罗列几种特殊情况
如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ 会认为 消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消 费的隐患,需要去重)
如果消费者接收到消息却没有确认消息,连接也未断开,则 RabbitMQ 认为该消 费者繁忙,将不会给该消费者分发更多的消息。
5.如何避免消息重复投递或重复消费?
在消息生产时,MQ 内部针对每条生产者发送的消息生成一个 inner-msg-id,作 为去重的依据(消息投递失败并重传),避免重复的消息进入队列;
在消息消费时,要求消息体中必须要有一个 bizId(对于同一业务全局唯一,如支 付 ID、订单 ID、帖子 ID 等)作为去重的依据,避免同一条消息被重复消费。
6、消息基于什么传输?
由于 TCP 连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶 颈。RabbitMQ 使用信道的方式来传输数据。信道是建立在真实的 TCP 连接内的 虚拟连接,且每条 TCP 连接上的信道数量没有限制。
7、消息如何分发?
若该队列至少有一个消费者订阅,消息将以循环(round-robin)的方式发送给消 费者。每条消息只会分发给一个订阅的消费者(前提是消费者能够正常处理消息 并进行确认)。
通过路由可实现多消费的功能
8、消息怎么路由?
消息提供方->路由->一至多个队列
消息发布到交换器时,消息将拥有一个路由键(routing key),在消息创建时设 定。
通过队列路由键,可以把队列绑定到交换器上。
消息到达交换器后,RabbitMQ 会将消息的路由键与队列的路由键进行匹配(针 对不同的交换器有不同的路由规则);
常用的交换器主要分为一下三种
- fanout:如果交换器收到消息,将会广播到所有绑定的队列上
- direct:如果路由键完全匹配,消息就被投递到相应的队列
- topic:可以使来自不同源头的消息能够到达同一个队列。 使用 topic 交换器时, 可以使用通配符。
9、如何确保消息不丢失?
消息持久化,当然前提是队列必须持久化 RabbitMQ 确保持久性消息能从服务器重启中恢复的方式是,将它们写入磁盘上 的一个持久化日志文件,当发布一条持久性消息到持久交换器上时,Rabbit 会在 消息提交到日志文件后才发送响应。
一旦消费者从持久队列中消费了一条持久化消息,RabbitMQ 会在持久化日志中 把这条消息标记为等待垃圾收集。如果持久化消息在被消费之前 RabbitMQ 重启, 那么 Rabbit 会自动重建交换器和队列(以及绑定),并重新发布持久化日志文件 中的消息到合适的队列。
10、使用 RabbitMQ 有什么好处?
- 服务间高度解耦
- 异步通信性能高
- 流量削峰
11、RabbitMQ 的集群
镜像集群模式 你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,然后 每次你写消息到 queue 的时候,都会自动把消息到多个实例的 queue 里进行消息 同步。
好处在于,你任何一个机器宕机了,没事儿,别的机器都可以用。坏处在于,第 一,这个性能开销也太大了吧,消息同步所有机器,导致网络带宽压力和消耗很重!第二,这么玩儿,就没有扩展性可言了,如果某个 queue 负载很重,你加机 器,新增的机器也包含了这个 queue 的所有数据,并没有办法线性扩展你的 queue。
12、mq 的缺点
系统可用性降低 系统引入的外部依赖越多,越容易挂掉,本来你就是 A 系统调用 BCD 三个系统的 接口就好了,人 ABCD 四个系统好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整?MQ 挂了,整套系统崩溃了,你不就完了么。
系统复杂性提高
硬生生加个 MQ 进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况? 怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已
一致性问题
A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要 是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整? 你这数据就不一致了。
所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对 它带来的坏处做各种额外的技术方案和架构来规避掉,最好之后,你会发现,妈 呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。
《数据库类》
MySQL
1、键
- 主 键:数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。
- 超 键:在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以为作为一个超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。
- 候选键:是最小超键,即没有冗余元素的超键。
- 外 键:在一个表中存在的另一个表的主键称此表的外键。
2、事务的四个特性
数据库事务transanction正确执行的四个基本要素。ACID,原子性(Atomicity)、一致性(Correspondence)、隔离性(Isolation)、持久性(Durability)。
- 原子性:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
- 隔离性:隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行 相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请 求,使得在同一时间仅有一个请求用于同一数据。
- 持久性:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
3、视图的作用
视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询;不包含任何列或数据。使用视图可以简化复杂的sql操作,隐藏具体的细节,保护数据;视图创建后,可以使用与表相同的方式利用它们。
视图不能被索引,也不能有关联的触发器或默认值,如果视图本身内有order by 则对视图再次order by将被覆盖。
创建视图:
create view XXX as XXX;
对于某些视图比如未使用联结子查询分组聚集函数Distinct Union等,是可以对其更新的,对视图的更新将对基表进行更新;但是视图主要用于简化检索,保护数据,并不用于更新,而且大部分视图都不可以更新。
4.drop、delete与truncate的区别
drop直接删掉表 truncate删除表中数据,再插入时自增长id又从1开始 delete删除表中数据,可以加where字句。
- DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。
- 表和索引所占空间。当表被TRUNCATE 后,这个表和索引所占用的空间会恢复到初始大小,而DELETE操作不会减少表或索引所占用的空间。drop语句将表所占用的空间全释放掉。
- 一般而言,drop > truncate > delete
- 应用范围。TRUNCATE 只能对TABLE;DELETE可以是table和view
- TRUNCATE 和DELETE只删除数据,而DROP则删除整个表(结构和数据)。
- truncate与不带where的delete :只删除数据,而不删除表的结构(定义)drop语句将删除表的结构被依赖的约束(constrain),触发器(trigger)索引(index);依赖于该表的存储过程/函数将被保留,但其状态会变为:invalid。
- delete语句为DML(data maintain Language),这个操作会被放到 rollback segment中,事务提交后才生效。如果有相应的 tigger,执行的时候将被触发。
- truncate、drop是DLL(data define language),操作立即生效,原数据不放到 rollback segment中,不能回滚
- 在没有备份情况下,谨慎使用 drop 与 truncate。要删除部分数据行采用delete且注意结合where来约束影响范围。回滚段要足够大。要删除表用drop;若想保留表而将表中数据删除,如果于事务无关,用truncate即可实现。如果和事务有关,或老师想触发trigger,还是用delete。
- Truncate table 表名 速度快,而且效率高,因为truncate table 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。
- TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 DROP TABLE 语句。
- 对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。
5、索引
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
为表设置索引要付出代价的:
- 一是增加了数据库的存储空间;
- 二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)。
5.1、优点:
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性;
- 可以大大加快数据的检索速度,这也是创建索引的最主要的原因;
- 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义;
- 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间;
- 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
5.2、缺点:
- 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加;
- 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大;
- 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
5.3、不具备创建索引列的特点:
- 对于那些在查询中很少使用或者参考的列不应该创建索引。这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
- 对于那些只有很少数据值的列也不应该增加索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
- 对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。
- 当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。
5.4、数据库的三种索引
- 唯一索引:是不允许其中任何两行具有相同索引值的索引。当现有数据中存在重复的键值时,大多数数据库不允许将新创建的唯一索引与表一起保存。数据库还可能防止添加将在表中创建重复键值的新数据。
- 主键索引 :数据库表经常有一列或列组合,其值唯一标识表中的每一行。该列称为表的主键。 在数据库关系图中为表定义主键将自动创建主键索引,主键索引是唯一索引的特定类型。
- 聚集索引 :在聚集索引中,表中行的物理顺序与键值的逻辑(索引)顺序相同。一个表只能包含一个聚集索引。
6、连接查询
6.1、外连接
- 包括左向外联接、右向外联接或完整外部联接。
1)、左连接:left join 或 left outer join
左向外联接的结果集包括 LEFT OUTER 子句中指定的左表的所有行,而不仅仅是联接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表列均为空值(null)。
select * from table1 left join table2 on table1.id=table2.id
2)、右连接:right join 或 right outer join
右向外联接是左向外联接的反向联接。将返回右表的所有行。如果右表的某行在左表中没有匹配行,则将为左表返回空值。
select * from table1 right join table2 on table1.id=table2.id
3)、完整外部联接:full join 或 full outer join
完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。
select * from table1 full join table2 on table1.id=table2.id
6.2、内连接
- 内联接是用比较运算符比较要联接列的值的联接
1)、内连接:join 或 inner join
select * from table1 join table2 on table1.id=table2.id
2)、等价(与下列执行效果相同)
select a.*,b.* from table1 a,table2 b where a.id=b.id
select * from table1 cross join table2 where table1.id=table2.id #注:cross join后加条件只能用where,不能用on
6.3、交叉连接(完全)
没有 WHERE 子句的交叉联接将产生联接所涉及的表的笛卡尔积。第一个表的行数乘以第二个表的行数等于笛卡尔积结果集的大小。(table1和table2交叉连接产生3*3=9条记录)
1)、交叉连接:cross join (不带条件where...)
select * from table1 cross join table2
2)、等价(与下列执行效果相同)
select * from table1,table2
7、数据库范式
7.1、第一范式(1NF)
- 在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。
- 所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,第一范式就是无重复的列。
7.2、第二范式(2NF)
- 是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。
- 第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是非主属性非部分依赖于主关键字。
7.3、第三范式(3NF)
- 满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。
8、SQL语句优化
- 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描;
- 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描。如:select id from t where num is null可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:select id from t where num=0;
- 很多时候用 exists 代替 in 是一个好的选择;
- 用Where子句替换HAVING 子句 因为HAVING 只会在检索出所有记录之后才对结果集进行过滤;
- 在表中建立索引,优先考虑where、group by使用到的字段;
- 尽量避免使用select *,返回无用的字段会降低查询效率;
- 尽量避免使用in和not in,会导致数据库引擎放弃索引进行全表扫描;
- 尽量避免使用or,会导致数据库引擎放弃索引进行全表扫描;
- 尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引进行全表扫描。
9、数据库结构优化
- 范式优化: 比如消除冗余(节省空间);
- 反范式优化:比如适当加冗余等(减少join);
- 拆分表(垂直拆分和水平拆分): 分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。
10、MySQL中myisam与innodb的区别
- InnoDB支持事物,而MyISAM不支持事物;
- InnoDB支持行级锁,而MyISAM支持表级锁;
- InnoDB支持MVCC, 而MyISAM不支持;
- InnoDB支持外键,而MyISAM不支持;
- InnoDB不支持全文索引,而MyISAM支持;
- InnoDB不能通过直接拷贝表文件的方法拷贝表到另外一台机器, myisam 支持;
- InnoDB表支持多种行格式, myisam 不支持;
- InnoDB是索引组织表, myisam 是堆表。
11、Innodb引擎的4大特性
- 插入缓冲(insert buffer)
- 二次写(double write)
- 自适应哈希索引(ahi)
- 预读(read ahead)
12、四种事务隔离级别
- 读未提交(read uncommitted) :可以读取其他 session 未提交的脏数据。
- 读已提交(read committed) :允许不可重复读取,但不允许脏读取。提交后,其他会话可以看到提交的数据。
- 可重复读(repeatable read) :禁止不可重复读取和脏读取、以及幻读(innodb 独有)。
- 串行(serializable):事务只能一个接着一个地执行,但不能并发执行。事务隔离级别最高。
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
注:mysql默认是 可重复读(repeatable-read),oracle默认是读已提交(read committed)。
13、实例
13.1、组合两张表
- 表1:
Person 字段:
PersonId 、FirstName、LastName,PersonId 是上表主键; - 表2:
Address 字段:
AddressId、PersonId、City、State,AddressId 是上表主键,PersonId是外键;
编写一个 SQL 查询,满足条件:无论 person 是否有地址信息,都需要基于上述两表提供 person 的以下信息:
- FirstName, LastName, City, State
SQL语句:
select p.FirstName, p.LastName, a.City, a.State from Person p outer join Address a on p.PersonId = a.AddressId;
13.2、查询返回第二高的薪水
编写一个 SQL 查询,获取 Employee
表中第二高的薪水(Salary):
SQL语句:
select IFNULL((select distinct Salary from Employee order by desc limit 1 OFFSET 1),null) as SecondHighestSalary;
13.3、分数排名
编写一个 SQL 实现分数排名,如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。
SQL语句:
select Score, (select count(distinct Score) from Score where Score >= s.Score) as Rank from Score s order by Score desc;
14、MySQL B+Tree索引和Hash索引的区别
- Hash索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位;
- B+树索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问。
15、乐观锁和悲观锁
悲观锁(Pessimistic Lock)的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。通常所说的“一锁二查三更新”即指的是使用悲观锁。通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select … for update操作来实现悲观锁。当数据库执行select for update时会获取被select中的数据行的行锁,因此其他并发执行的select for update如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。
乐观锁(Optimistic Lock),也叫乐观并发控制,它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,那么当前正在提交的事务会进行回滚。乐观锁的特点先进行业务操作,不到万不得已不去拿锁。即“乐观”的认为拿锁多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。
总结
悲观锁和乐观锁是数据库用来保证数据并发安全防止更新丢失的两种方法,例子在select ... for update
前加个事务就可以防止更新丢失。悲观锁和乐观锁大部分场景下差异不大,一些独特场景下有一些差别,一般我们可以从如下几个方面来判断。
- 响应速度:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。
- 冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率,如果冲突频率大,乐观锁会需要多次重试才能成功,代价比较大。
- 重试代价:如果重试代价大,建议采用悲观锁。
16、非关系型数据库和关系型数据库区别
- 性能:NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。
- 可扩展性:同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。
- 复杂查询:可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
- 事务支持:使得对于安全性能很高的数据访问要求得以实现。
17、EXPLAIN
17.1、语法
EXPLAIN SELECT
- EXPLAIN EXTENDED SELECT 将执行计划“反编译”成SELECT语句,运行SHOW WARNINGS 可得到被MySQL优化器优化后的查询语句 ;
- EXPLAIN PARTITIONS SELECT 用于分区表的EXPLAIN;
- 在Navicat图形化界面中,点击“解释”出现执行计划的信息。
17.2、执行计划中的信息
1、id:包含一组数字,表示查询中执行select子句或操作表的顺序。id相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行。
2、select_type:主要用于区别普通查询, 联合查询, 子查询等复杂查询。
- SIMPLE:查询中不包含子查询或者UNION
- 查询中若包含任何复杂的子部分,最外层查询则被标记为:PRIMARY
- 在SELECT或WHERE列表中包含了子查询,该子查询被标记为:SUBQUERY
- 在FROM列表中包含的子查询被标记为:DERIVED(衍生)
- 若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED
- 从UNION表获取结果的SELECT被标记为:UNION RESULT
3、type:表示MySQL在表中找到所需行的方式,又称“访问类型”(ALL、index、range、ref、eq_ref、const、system、NULL),由左至右,由最差到最好
- ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
- index:Full Index Scan,index与ALL区别为index类型只遍历索引树
- range:索引范围扫描,对索引的扫描开始于某一点,返回匹配值域的行,常见于between、<、>等的查询
- ref:非唯一性索引扫描,返回匹配某个单独值的所有行。常见于使用非唯一索引即唯一索引的非唯一前缀进行的查找
- eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
- const、system:当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量
- system是const类型的特例,当查询的表只有一行的情况下, 使用system
- NULL:MySQL在优化过程中分解语句,执行时甚至不用访问表或索引
4、possible_keys:指出MySQL能使用哪个索引在表中找到行,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用
5、key:显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL
查询中若使用了覆盖索引,则该索引仅出现在key列表中
6、key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度
key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的
7、ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
8、rows:表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数
9、Extra:包含不适合在其他列中显示但十分重要的额外信息
- Using index:该值表示相应的select操作中使用了覆盖索引(Covering Index)。
- MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件包含所有满足查询需要的数据的索引称为 覆盖索引(Covering Index)
- Using where:表示MySQL服务器在存储引擎受到记录后进行“后过滤”(Post-filter),如果查询未能使用索引,Using where的作用只是提醒我们MySQL将用where子句来过滤结果集
- Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询
- Using filesort:MySQL中无法利用索引完成的排序操作称为“文件排序”
注意:如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select *,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降
17.3、执行计划的局限
- EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况
- EXPLAIN不考虑各种Cache
- EXPLAIN不能显示MySQL在执行查询时所作的优化工作
- 部分统计信息是估算的,并非精确值
- EXPALIN只能解释SELECT操作,其他操作要重写为SELECT后查看执行计划
Oracle
1、Oracle分页
select * from
( select rownum r,a from tabName where rownum <= 20 )
where r > 10
2、MySQL分页
select * from student limit 9,4; # 表示从9开始查询,返回4行
slect * from student limit 4 offset 9; # 4表示返回4行,9表示从表的第十行开始
Redis
1、什么是Redis
Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API的非关系型数据库。
传统数据库遵循 ACID 规则。而 Nosql(Not Only SQL 的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称) 一般为分布式而分布式一般遵循 CAP 定理。
2、Redis支持的数据类型
- String:string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。string类型是Redis最基本的数据类型,一个键最大能存储512MB;
- Hash:hash 是一个键值(key=>value)对集合。Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象;
- List:列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边);
- Set:Set是string类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1);
- zset(有序集合): zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
3、Redis的持久化
3.1、持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。Redis 提供了两种持久化方式:
- RDB(默认):Redis DataBase缩写,功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数;
- AOF :Append-only file缩写,每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作aof写入保存:1).WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件;2).SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
3.2、存储结构:内容是redis通讯协议(RESP )格式的命令文本存储。
3.3、RDB和AOF的比较:
- aof文件比rdb更新频率高,优先使用aof还原数据;
- aof比rdb更安全也更大;
- rdb性能比aof好;
- 如果两个都配了优先加载AOF。
3.4、RESP: 是redis客户端和服务端之前使用的一种通讯协议;RESP 的特点:实现简单、快速解析、可读性好
4、Redis 的架构模式
4.1 单机版
- 特点:简单
- 问题:内存容量有限 、处理能力有限 、无法高可用。
4.2、主从复制:Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。 只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。
- 特点:master/slave 角色、master/slave 数据相同、降低 master 读压力在转交从库;
- 问题:无法保证高可用、没有解决 master 写的压力。
4.3、哨兵:Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移。
特性:
- 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
- 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
- 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。
特点:保证高可用、监控各个节点、自动故障迁移;
缺点:主从模式,切换需要时间丢数据、没有解决 master 写的压力。
4.4、集群(proxy 型):Twemproxy 是一个 Twitter 开源的一个 redis 和 memcache 快速/轻量级代理服务器; Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议。
特点:
- 多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins ;
- 支持失败节点自动删除;
- 后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致。
缺点:增加了新的 proxy,需要维护其高可用。
4.5、集群(直连型):从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
特点:
- 无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层;
- 数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布;
- 可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除;
- 高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本;
- 实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。
缺点:
- 资源隔离性较差,容易出现相互影响的情况;
- 数据通过异步复制,不保证数据的强一致性。
5、缓存穿透和缓存雪崩
5.1、缓存穿透
理解一:一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
如何避免:
- 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
- 对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。
理解二:缓存穿透是指查询一个一定不存在的数据。由于缓存不命中,并且出于容错考虑,如果从数据库查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,失去了缓存的意义。
- 请求的数据在缓存大量不命中,导致请求走数据库。
解决方法:
- 由于请求的参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(BloomFilter)或者压缩filter提前拦截,不合法就不让这个请求到数据库层;
- 当我们从数据库找不到的时候,我们也将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓存里边获取了。
5.2、缓存雪崩
理解一:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
如何避免:
- 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
- 做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
- 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
理解二:
- Redis挂掉了,请求全部走数据库;
- 对缓存数据设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。
解决方法:
1)、对于“Redis挂掉了,请求全部走数据库”这种情况,可以有以下的思路:
- 事发前:实现Redis的高可用(主从架构+Sentinel 或者Redis Cluster),尽量避免Redis挂掉这种情况发生;
- 事发中:万一Redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的);
- 事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。
2)、对于“对缓存数据设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。”这种情况,非常好解决:
- 在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。
6、为什么redis需要把所有数据放到内存中
Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
7、Redis的单进程单线程
Redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。
8、Redis的回收策略
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰;
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰;
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰;
- allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰;
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰;
- no-enviction(驱逐):禁止驱逐数据。
9、使用Redis的好处
- 速度快:因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
- 支持丰富数据类型:支持string,list,set,sorted set,hash;
- 支持事务:操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行;
- 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除。
10、Redis 最适合的场景
10.1、会话缓存(Session Cache)
最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。
除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。
如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。
Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:
当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:
ZRANGE user_scores 0 10 WITHSCORES
Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。
Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统。
11、Redis支持的Java客户端
- Redisson(官方推荐使用):是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象;
- Jedis:Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;
- lettuce
Redisson和Jedis的区别:Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
12、Redis哈希槽
Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽
13、Redis管道的作用
一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。
14、Redis事务
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
Redis事务相关的命令:MULTI、EXEC、DISCARD、WATCH
15、Redis的分布式事务
15.1、对于读操作
- 如果我们的数据在缓存里边有,那么就直接取缓存的;
- 如果缓存里没有我们想要的数据,我们会先去查询数据库,然后将数据库查出来的数据写到缓存中;
- 最后将数据返回给请求。
15.2、对于更新操作
一般来说,执行更新操作时,我们会有两种选择:
- 先操作数据库,再操作缓存
- 先操作缓存,再操作数据库
如果原子性被破坏了,可能会有以下的情况:
- 操作数据库成功了,操作缓存失败了。
- 操作缓存成功了,操作数据库失败了。
15.2.1、操作缓存
- 更新缓存
- 删除缓存
一般我们都是采取删除缓存缓存策略的,原因如下:
- 高并发环境下,无论是先操作数据库还是后操作数据库而言,如果加上更新缓存,那就更加容易导致数据库与缓存数据不一致问题。(删除缓存直接和简单很多)
- 如果每次更新了数据库,都要更新缓存【这里指的是频繁更新的场景,这会耗费一定的性能】,倒不如直接删除掉。等再次读取时,缓存里没有,那我到数据库找,在数据库找到再写到缓存里边(体现懒加载)
基于这两点,对于缓存在更新时而言,都是建议执行删除操作!
15.2.2、先更新数据库,再删除缓存
- 先操作数据库,成功;
- 再删除缓存,也成功;
如果原子性被破坏了:
- 第一步成功(操作数据库),第二步失败(删除缓存),会导致数据库里是新数据,而缓存里是旧数据。
- 如果第一步(操作数据库)就失败了,我们可以直接返回错误(Exception),不会出现数据不一致。
如果在高并发的场景下,出现数据库与缓存数据不一致的概率特别低,也不是没有:
- 缓存刚好失效
- 线程A查询数据库,得一个旧值
- 线程B将新值写入数据库
- 线程B删除缓存
- 线程A将查到的旧值写入缓存
要达成上述情况,还是说一句概率特别低:
因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。
删除缓存失败的解决思路:
- 将需要删除的key发送到消息队列中
- 自己消费消息,获得需要删除的key
- 不断重试删除操作,直到成功
15.2.3、先删除缓存,再更新数据库
- 先删除缓存,成功;
- 再更新数据库,也成功;
如果原子性被破坏了:
- 第一步成功(删除缓存),第二步失败(更新数据库),数据库和缓存的数据还是一致的。
- 如果第一步(删除缓存)就失败了,我们可以直接返回错误(Exception),数据库和缓存的数据还是一致的。
看起来是很美好,但是我们在并发场景下分析一下,就知道还是有问题的了:
- 线程A删除了缓存
- 线程B查询,发现缓存已不存在
- 线程B去数据库查询得到旧值
- 线程B将旧值写入缓存
- 线程A将新值写入数据库
所以也会导致数据库和缓存不一致的问题,并发下解决数据库与缓存不一致的思路:
-
将删除缓存、修改数据库、读取缓存等的操作积压到队列里边,实现串行化。
15.2.4、对比两种策略
我们可以发现,两种策略各自有优缺点:
-
先删除缓存,再更新数据库
-
在高并发下表现不如意,在原子性被破坏时表现优异
-
-
先更新数据库,再删除缓存(
Cache Aside Pattern
设计模式)-
在高并发下表现优异,在原子性被破坏时表现不如意
-
16、Redis与Memcached的区别与比较
- 1Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String;
- Redis支持数据的备份,即master-slave模式的数据备份;
- Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中;
- Redis的速度比memcached快很多;
- Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的IO复用模型。
MongoDB
MongoDB
是基于分布式文件存储的数据库,由C++
语言编写。旨在为WEB
应用提供可扩展的高性能数据存储解决方案,且MongodDB
是一个介于关系数据库与非关系数据库之间的产品,是非关系型数据库中功能最丰富,最像关系数据库。
1、MongoDB
的优势有哪些
-
面向集合(
Collection
)和文档(document
)的存储,以JSON格式的文档保存数据。 -
高性能,支持
Document
中嵌入Document
减少了数据库系统上的I/O操作以及具有完整的索引支持,支持快速查询 -
高效的传统存储方式:支持二进制数据及大型对象
-
高可用性,数据复制集,MongoDB 数据库支持服务器之间的数据复制来提供自动故障转移(
automatic failover
) -
高可扩展性,分片(
sharding
)将数据分布在多个数据中心,MongoDB支持基于分片键创建数据区域. -
丰富的查询功能, 聚合管道(
Aggregation Pipeline
)、全文搜索(Text Search
)以及地理空间查询(Geospatial Queries
) -
支持多个存储引擎,WiredTiger存储引、In-Memory存储引擎
2、MongoDB
支持哪些数据类型
2.1、java类似数据类型:
类型 | 解析 |
---|---|
String | 字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的 |
Integer | 整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位 |
Double | 双精度浮点值。用于存储浮点值 |
Boolean | 布尔值。用于存储布尔值(真/假) |
Arrays | 用于将数组或列表或多个值存储为一个键 |
Datetime | 记录文档修改或添加的具体时间 |
2.2、MongoDB特有数据类型:
类型 | 解析 |
---|---|
ObjectId | 用于存储文档 id ,ObjectId 是基于分布式主键的实现MongoDB 分片也可继续使用 |
Min/Max Keys | 将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比 |
Code | 用于在文档中存储 JavaScript 代码 |
Regular Expression | 用于在文档中存储正则表达式 |
Binary Data | 二进制数据。用于存储二进制数据 |
Null | 用于创建空值 |
Object | 用于内嵌文档 |
3、什么是集合Collection
、文档Document
,以及与关系型数据库术语类比
-
集合
Collection
位于单独的一个数据库MongoDB 文档Document
集合,它类似关系型数据库(RDBMS)中的表Table
。一个集合Collection
内的多个文档Document
可以有多个不同的字段。通常情况下,集合Collection
中的文档Document
有着相同含义。 -
文档
Document
由key-value构成。文档Document
是动态模式,这说明同一集合里的文档不需要有相同的字段和结构。类似于关系型数据库中table中的每一条记录。 -
与关系型数据库术语类比
mongodb | 关系型数据库 |
---|---|
Database | Database |
Collection | Table |
Document | Record/Row |
Filed | Column |
Embedded Documents | Table join |
4、什么是Mongod
,以及MongoDB
命令
mongod
是处理MongoDB
系统的主要进程。它处理数据请求,管理数据存储,和执行后台管理操作。当我们运行mongod
命令意味着正在启动MongoDB
进程,并且在后台运行。
MongoDB
命令:
命令 | 说明 |
---|---|
use database_name | 切换数据库 |
db.myCollection.find().pretty() | 格式化打印结果 |
db.getCollection(collectionName).find() | 修改Collection名称 |
5、Mongod
默认参数有
-
传递数据库存储路径,默认是
"/data/db"
-
端口号 默认是 "27017"
6、MySQL
和mongodb
的区别
形式 | MongoDB | MySQL |
---|---|---|
数据库模型 | 非关系型 | 关系型 |
存储方式 | 虚拟内存+持久化 | 不同的引擎有不同的存储方式 |
查询语句 | 独特的MongoDB查询方式 | 传统SQL语句 |
架构特点 | 副本集以及分片 | 常见单点、M-S、MHA、MMM等架构方式 |
数据处理方式 | 基于内存,将热数据存在物理内存中,从而达到高速读写 | 不同的引擎拥有自己的特点 |
使用场景 | 事件的记录,内容管理或者博客平台等数据大且非结构化数据的场景 | 适用于数据量少且很多结构化数据 |
7、问mongodb
和redis
区别以及选择原因
形式 | MongoDB | redis |
---|---|---|
内存管理机制 | Redis 数据全部存在内存,定期写入磁盘,当内存不够时,可以选择指定的 LRU 算法删除数据 | MongoDB 数据存在内存,由 linux系统 mmap 实现,当内存不够时,只将热点数据放入内存,其他数据存在磁盘 |
支持的数据结构 | Redis 支持的数据结构丰富,包括hash、set、list等 | MongoDB 数据结构比较单一,但是支持丰富的数据表达,索引 |
性能 | mongodb依赖内存,TPS较高 | Redis依赖内存,TPS非常高。性能上Redis优于MongoDB |
可靠性 | 支持持久化以及复制集增加可靠性 | Redis依赖快照进行持久化;AOF增强可靠性;增强可靠性的同时,影响访问性能 |
数据分析 | mongodb内置数据分析功能(mapreduce) | Redis不支持 |
事务支持情况 | 只支持单文档事务,需要复杂事务支持的场景暂时不适合 | Redis 事务支持比较弱,只能保证事务中的每个操作连续执行 |
集群 | MongoDB 集群技术比较成熟 | Redis从3.0开始支持集群 |
选择原因:
-
架构简单
-
没有复杂的连接
-
深度查询能力,
MongoDB
支持动态查询。 -
容易调试
-
容易扩展
-
不需要转化/映射应用对象到数据库对象
-
使用内部内存作为存储工作区,以便更快的存取数据。
8、如何执行事务/加锁
mongodb
没有使用传统的锁或者复杂的带回滚的事务,因为它设计的宗旨是轻量,快速以及可预计的高性能.可以把它类比成mysql mylsam
的自动提交模式.通过精简对事务的支持,性能得到了提升,特别是在一个可能会穿过多个服务器的系统里.
9、更新操作会立刻fsync到磁盘
不会,磁盘写操作默认是延迟执行的.写操作可能在两三秒(默认在60秒内)后到达磁盘,通过 syncPeriodSecs
启动参数,可以进行配置.例如,如果一秒内数据库收到一千个对一个对象递增的操作,仅刷新磁盘一次.
10、 索引类型有哪些
-
单字段索引(
Single Field Indexes
) -
复合索引(
Compound Indexes
) -
多键索引(
Multikey Indexes
) -
全文索引(
text Indexes
) -
Hash 索引(
Hash Indexes
) -
通配符索引(
Wildcard Index
) -
2dsphere索引(
2dsphere Indexes
)
11、MongoDB
在A:{B,C}上建立索引,查询A:{B,C}和A:{C,B}都会使用索引吗
由于MongoDB
索引使用B-tree
树原理,只会在A:{B,C}上使用索引
-
创建索引,需要考虑的问题
-
索引限制问题
-
索引类型详细解析
-
索引的种类问题
12、什么是聚合
聚合操作能够处理数据记录并返回计算结果。聚合操作能将多个文档中的值组合起来,对成组数据执行各种操作,返回单一的结果。它相当于 SQ
L 中的 count(*)
组合 group by
。对于 MongoDB
中的聚合操作,应该使用aggregate()
方法。
详情可查看文章【MongoDB系列--深入理解MongoDB聚合(Aggregation)】,其中包括很多聚合的问题:
-
聚合管道(
aggregation pipeline
)的问题 -
Aggregation Pipeline
优化等问题 -
Map-Reduce函数的问题
13、monogodb
中的分片sharding
分片sharding
是将数据水平切分到不同的物理节点。当应用数据越来越大的时候,数据量也会越来越大。当数据量增长 时,单台机器有可能无法存储数据或可接受的读取写入吞吐量。利用分片技术可以添加更多的机器来应对数据量增加 以及读写操作的要求。
14、分片(Shard
)和复制(replication
)是怎样工作的
每一个分片(shard
)是一个分区数据的逻辑集合。分片可能由单一服务器或者集群组成,我们推荐为每一个分片(shard
)使用集群。
15、如果块移动操作(moveChunk
)失败了,我需要手动清除部分转移的文档吗
不需要,移动操作是一致(consistent
)并且是确定性的(deterministic
)。
-
一次失败后,移动操作会不断重试。
-
当完成后,数据只会出现在新的分片里(shard)
16、数据在什么时候才会扩展到多个分片(Shard
)里
MongoDB
分片是基于区域(range
)的。所以一个集合(collection
)中的所有的对象都被存放到一个块(chunk
)中,默认块的大小是 64Mb。当数据容量超过64 Mb,才有可能实施一个迁移,只有当存在不止一个块的时候,才会有多个分片获取数据的选项。
17、更新一个正在被迁移的块(Chunk)上的文档时会发生什么
更新操作会立即发生在旧的块(Chunk)上,然后更改才会在所有权转移前复制到新的分片上。
18、如果一个分片(Shard)停止或很慢的时候,发起一个查询会怎样
如果一个分片停止了,除非查询设置了 “Partial
” 选项,否则查询会返回一个错误。如果一个分片响应很慢,MongoDB
会等待它的响应。
19、MongoDB
副本集实现高可用的原理
MongoDB
使用了其复制(Replica Set
)方案,实现自动容错机制为高可用提供了基础。目前,MongoDB
支持两种复制模式:
-
Master
/Slave
,主从复制,角色包括Master
和Slave
。 -
Replica Set
,复制集复制,角色包括Primary
和Secondary
以及Arbiter
。(生产环境必选)
20、什么是master
或primary
副本集只能有一个主节点能够确认写入操作来接收所有写操作,并记录其操作日志中的数据集的所有更改(记录在oplog中)。在集群中,当主节点(master
)失效,Secondary节点会变为master
21、什么是Slave
或Secondary
复制主节点的oplog并将oplog记录的操作应用于其数据集,如果主节点宕机了,将从符合条件的从节点选举选出新的主节点。
22、什么是Arbiter
仲裁节点不维护数据集。 仲裁节点的目的是通过响应其他副本集节点的心跳和选举请求来维护副本集中的仲裁
23、复制集节点类型有哪些
-
优先级0型(
Priority 0
)节点 -
隐藏型(
Hidden
)节点 -
延迟型(
Delayed
)节点 -
投票型(
Vote
)节点以及不可投票节点
24、启用备份故障恢复需要多久
从备份数据库声明主数据库宕机到选出一个备份数据库作为新的主数据库将花费10到30秒时间.这期间在主数据库上的操作将会失败–包括写入和强一致性读取(strong consistent read
)操作.然而,你还能在第二数据库上执行最终一致性查询(eventually consistent query
)(在slaveok
模式下),即使在这段时间里.
25、raft
选举过程,投票规则
选举过程:
当系统启动好之后,初始选举后系统由1个Leader
和若干个Follower
角色组成。然后突然由于某个异常原因,Leader
服务出现了异常,导致Follower
角色检测到和Leader
的上次RPC更新时间超过给定阈值时间时。此时Followe
r会认为Leader
服务已出现异常,然后它将会发起一次新的Leader
选举行为,同时将自身的状态从Follower
切换为Candidate
身份。随后请求其它Follower
投票选择自己。
投票规则:
-
当一个候选人获得了同一个任期号内的大多数选票,就成为领导人。
-
每个节点最多在一个任期内投出一张选票。并且按照先来先服务的原则。
-
一旦候选人赢得选举,立刻成为领导,并发送心跳维持权威,同时阻止新领导人的诞生。
《前端》
JS
1、js的数据类型
基本数据类型:Null,undefined,Boolean,Number,String,Symbol,BigInt
引用数据类型:Object(array,function、object、Date、regExp、Math)
2、闭包
可以访问其他函数内部定义的变量的函数。闭包可以理解为"定义在一个函数内部的函数"。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
- 意义:可以延长变量的生命周期,可以创建私有的环境。
- 优点:可以读取其他函数的内部变量、将变量始终保存在内存中、可以封装对象的私有属性和方法。
- 缺点:消耗内存、使用不当会造成内存溢出问题。
注:闭包中的this指向的是window
3、var let const 区别
- var: 存在变量提升、存在变量覆盖,已经被定义且赋值的变量,如果再次被赋值,则以后一次值为准;没有块级作用域;
- const:定义的是常量,声明之后必须赋值;定义的值不能去修改,否则报错;有块级作用域;不存在变量提升和变量覆盖;对于数组和对象的元素修改,不算做对常量的修改,不会报错。
- let: 有块级作用域;不存在变量提升和变量覆盖;let不允许在相同的作用域中重复声明,注意是相同作用域,不同作用域重复声明不会报错。
4、Promise
promise的构造函数是同步执行的,当new Promise的一瞬间,就立刻被执行,而 .then方法是异步执行的。
4.1、特点
- Promise 对象代表一个异步操作,对象的状态不受外界影响,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败);只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态;
- 一旦状态改变就不会再变,Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled,从 pending 变为 rejected;如果改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果;这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的;
- 构造函数 Promise 必须接收一个函数(handle)作为参数,函数又包含 resolve 和 reject ;
- then 、catch 支持链式调用;
4.2、缺点
- Promise 无法取消:一旦新建就会执行,无法中途取消;
- 如果不设置回调函数,Promise 内部抛出的错误不会反应到外部;
- 当处于 pendding 状态时,无法知道目前进展到哪一个阶段;
4.3、场景
- 有效的解决 js 异步回调地狱问题;
- 将业务逻辑与数据处理分隔开使代码更优雅,方便阅读,更有利于代码维护;
5、cookie、localStorage、sessionStorage的区别
5.1、共同点: 都是保存在浏览器端、且同源的
5.2、不同点:cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。
5.3、存储大小限制也不同:
- cookie数据不能超过4K,sessionStorage和localStorage可以达到5M
- sessionStorage:仅在当前浏览器窗口关闭之前有效;
- localStorage:始终有效,窗口或浏览器关闭也一直保存,本地存储,因此用作持久数据;
- cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
5.4、作用域不同:
- sessionStorage:不在不同的浏览器窗口中共享,即使是同一个页面;
- localstorage:在所有同源窗口中都是共享的;也就是说只要浏览器不关闭,数据仍然存在
- cookie: 也是在所有同源窗口中都是共享的.也就是说只要浏览器不关闭,数据仍然存在
6、async 和 await 的区别
async:
- async 是“异步”的简写, async 用于申明一个异步的 function。
- async是一个加在函数前的修饰符,被async定义的函数会默认返回一个Promise对象resolve的值。因此对
- async函数可以直接then,返回值就是then方法传入的函数。async声明的函数的返回本质是一个Promise。
await:
- await 可以认为是 async wait 的简写,await 用于等待一个异步方法执行完成。
- await 也是一个修饰符,只能放在async定义的函数内。可以理解为等待。
- await 修饰的如果是Promise对象:可以获取Promise中返回的内容(resolve或reject的参数),且取到值后语句才会往下执行;如果不是Promise对象:把这个非promise的东西当做await表达式的结果。
区别:
- Promise的出现解决了传统callback函数导致的“地狱回调”问题,但它的语法导致了它向纵向发展行成了一个回调链,遇到复杂的业务场景,这样的语法显然也是不美观的。而async await代码看起来会简洁些,使得异步代码看起来像同步代码,await的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。
- async await与Promise一样,是非阻塞的。
- async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数。
7、this指向
this总是指向函数的直接调用者。如果有new关键字,this指向new出来的对象。在事件中,this指向触发这个事件的对象。
8、map和forEach的区别
- forEach方法,是最基本的方法,遍历和循环。默认有3个参数:分别是遍历的每一个元素item,遍历的索引index,遍历的数组array
- map方法,和foreach一致,不同的是会返回一个新的数组,所以callback需要有return返回值,如果没有会返回undefined
9、箭头函数和普通函数的区别
普通函数
- 可以通过bind、call、apply改变this方向
- 可以使用new,因为有原型
箭头函数
- 不可以使用new,因为箭头函数没有constructor
- 不可以通过bind、call、apply改变this方向
- 本身没有this,因为没有原型
- 它的this是其包裹它的外部普通函数,如果没有则指向window,返回undefined
- 如果包裹它的外部普通函数的this发生改变,那么箭头函数中的this也会发生改变
10、数组方法汇总
- map 循环遍历数组、返回一个新的数组
- forEach 循环遍历数组,不改变原数组
- push/pop 在数组的末尾添加/删除元素 改变原数组
- unshift/ shift 在数组的头部添加/删除元素,改变原数组
- join 把数组转化为字符串
- some 有一项返回为true,则整体为true
- every 有一项返回为true,则整体为false
- filter 数组过滤
- slice(start, end) 数组截取,包括开头,不包括截取,返回一个新的数组
- splice(start, number, value) 删除数组元素,改变原数组
- indexof/lastindexof: 查找数组项,返回对应的下标
- concat:数组的拼接,不影响原数组,浅拷贝
- sort:数组排序 改变原数组
- reverse: 数组反转,改变原数组
11、浏览器渲染机制
- 解析HTML文件并构建DOM树
- 解析CSS并构建CSSOM树
- 将DOM和CSSOM合并成渲染树(render tree)
- 根据渲染树来布局,计算每个节点的位置
- 调用GPU绘制,合成图层,显示在屏幕上
12、常见的内存泄露
- 函数中的全局变量:每次执行该函数时都会生成该变量,并且不会随着函数执行结束后而释放
- 未清除的定时器:它内部引用变量不会释放
- 脱离DOM的元素引用:一个DOM容器删除之后,变量未设置null,则其内部的dom元素不会释放
- 持续绑定的事件:函数中addEventListener绑定事件,函数执行多次,绑定便会产生多次,产生内存泄露
- 闭包
- log:console.log的对象是不会被垃圾回收
13、GET和POST区别
- get请求不安全,post安全
- get请求数据有大小限制,受浏览器URL的长度限制,chrome最大长度8182byte,超过服务器返回414标识
- get参数显示在URL中,容易被别人窃取,POST在请求体中
- post需要设置请求体
- get是从服务器请求数据,post是向服务器发送数据
14、同步和异步
- 同步是阻塞模式,如果一个请求需要等待回调,那么后面代码会一直等待下去,知道返回结果
- 异步是非阻塞模式,无需等前面代码回调,即可执行下去
15、for...in for...of 和 object.keys
- ..in 会遍历自身属性和继承的原型属性,使用hasOwnProperty可以判断自身属性
- ..of ES6新增,遍历所有数据结构的统一方法,遍历数组,Set和Map、某些类似数组的对象,如arguments对象,DOM对象,Generator对象
- keys 只遍历自身属性,不会遍历继承的原型属性
16、set 、 map 、object
- Map是键值对,键和值可以是任何类型,object的键只能是String、number或symbol,Set是值的集合
- Map可以通过get获取值,set设置值,size获取长度,..of遍历,Object可以通过属性获取值,通过遍历获取长度。
- Set通过has来判断是否有值,且值是唯一性,用于去重
- from(arr):可将一个类数组的对象或者可遍历对象转换成一个正真的数组
- [...new Set(arr)]
- Map可以作为存储数据
17、防抖和节流
- 防抖:一段时间后执行,如果有重复触发,则重新计时
- 场景:登陆按钮、窗口调整大小resize、输入框实时保存
- 节流:一段时间内只执行一次,不论触发多少次
- scroll滚动事件,一定时间内只执行一次
- 浏览器播放时间,一定时间内读取一次后续数据
18、require 和 import
- require 对应导出的方法是export,import对应的方法是export 、export default
- require 对应是CommonJs的语法,commonjs是nodejs中的模块化规范,import是ES6的语法
- require是动态加载,运行时加载模块的所有方法,import是静态加载,编译的时候调用,代码提升顶部
- require 引入的是整个模块里面的对象,import按需加载模块中的对象
- require 导出的是值的拷贝,import导出的是值的引用
19、JSON.stringify() 特性
- 对 undefined、函数和symbol的值
- 对象:stirngify({a: undefined})序列化时会忽略跳过
- 数组:stirngify([undefined、Fn、symbol]) === null 序列化后变成 null
- 单独:stirngify(undefined/Fn/symbol) === undefined 序列化后变成 undefined
20、输入一个URL到页面过程发生了什么
- 首选在浏览器输入URL
- 查找缓存,看看浏览器缓存-系统缓存-路由缓存中是否该地址资源
- DNS解析,向DNS服务器发送请求,DNS服务器解析成IP,DNS基于UDP协议
- 建立TCP连接
- 发起HTTP请求:将请求报文作为三次握手第三次数据发送给服务器
- 服务器发送响应的数据
- 关闭TCP连接:四次挥手释放TCP
- 浏览器渲染
- 构建DOM树
- 构建CSS规则树
- 结合DOM树和CSS规则树,构建渲染树
- 计算每个节点在页面的位置
- 通过GPU进行渲染绘制
CSS
1、CSS布局
table布局、float布局、flxe布局、响应式布局、grid布局
- 静态布局
- 流式布局(栅栏、百分比、vw、vh):宽高随着屏幕大小等比例缩放
- 自适应布局(@media):不同尺寸屏幕布局会改变,但元素大小不会变
- 响应式布局(@media 和 流式布局):不同尺寸屏幕布局和元素大小都会改变
- 弹性布局(rem / em)
2、Css优先级
important > 内联样式 > ID选择器 》 类、属性、伪类(:hover,:actived)选择器 > 标签 、伪元素(::before, ::after)选择器 > * 通配符
3、垂直居中布局
- position:absolute + transform:translate(-50%, -50%);
- position: absolute + margin;
- display: flex; jusitify-content: center; align-items: center;
- gird布局
- table-cell
VUE
1、对于MVVM的理解
MVVM 是 Model-View-ViewModel 的缩写。
- Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;
- View 代表UI 组件,它负责将数据模型转化成UI 展现出来;
- ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。
在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
2、Vue的生命周期
- beforeCreate(创建前) 在数据观测和初始化事件还未开始
- created(创建后) 完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来
- beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。
- mounted(载入后) 在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
- beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
- updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
- beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。
- destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
3、数据双向绑定的原理
vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。
4、Vue的路由实现:hash模式 和 history模式
4.1、hash模式
在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;
特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。
hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
4.2、history模式
history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。
history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。
5、vue路由的钩子函数
- beforeEach主要有3个参数to,from,next:
- to:route即将进入的目标路由对象;
- from:route当前导航正要离开的路由;
- next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。
6、v-model的原理
vue是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
7.v-if与v-show的区别?使用场景分别是什么?
- v-if:显示隐藏是将dom元素整个添加或删除;适用于运行时条件很少改变。
- v-show:隐藏则是为该元素添加css–display:none,dom元素依旧还在;适用于非常频繁地切换。
8、router和route的区别
- route是当前正在跳转的路由对象,可以从route里面获取hash,name,path,query,mathsr等属性方法;
- router跳转连接就可以使用。
9、query和params 的区别
- query用path编写传参地址,而params用name编写传参地址;
- query刷新页面时参数不会消失,而params刷新页面时参数会消失;
- query传的参数会显示在url地址栏中,而params传参不会显示在地址栏中。
10、vue中的data为什么是一个函数?
Vue 中的 data 必须是个函数,因为当 data 是函数时,组件实例化的时候这个函数将会被调用,返回一个对象,计算机会给这个对象分配一个内存地址,实例化几次就分配几个内存地址,他们的地址都不一样,所以每个组件中的数据不会相互干扰,改变其中一个组件的状态,其它组件不变。
简单来说,就是为了保证组件的独立性和可复用性,如果 data 是个函数的话,每复用一次组件就会返回新的 data,类似于给每个组件实例创建一个私有的数据空间,保护各自的数据互不影响。
11、打包后 dist 目录过大,解决办法?
- dist打包生成的文件中有 .map 文件,可以删除。在 vue.config.js文件中配置:productionSourceMap: false
- 组价和路由使用懒加载、按需引入等
- 对于文件和图片进行压缩。 安装压缩组件: compression-webpack-plugin
12、父组件给子组件传递数据
通过给子组件身上绑定自定义属性,然后再子组件里使用props属性来接收
13、子组件给父组件传递数据
- 第一种方式:通过父组件给子组件传递函数类型的props实现:子组件给父组件传递数据
- 第二种方式:通过父组件给子组件绑定一个自定义事件实现:子组件给父组件传递数据
- 第三种方式:通过父组件给子组件绑定一个自定义事件实现:使用ref实现
14、跨域的解决方法
只要协议、域名和端口号有一个不相同就会产生跨域问题。浏览器处于安全方面的考虑,只允许本域名下的接口交互,不同源的客户端脚本,在没有明确授权的情况下,不能读写对方的资源。
解决办法:
- JSONP:利用了script标签不受浏览器同源策略的限制
- CORS:跨域资源共享
- 搭建Node代理服务器
- Nginx反向代理
- postMessage
- Websocket:本身不存在跨域问题
总结:
- jsonp的原理是利用了script标签不受浏览器同源策略的限制,img和link标签也是不受浏览器同源策略限制的。
- 跨域是浏览器限制,服务端和服务端之间通信是不受浏览器同源策略限制的。
- 所有跨域的解决方案都是需要服务端配合的。
- 最常用的跨域解决方案是CORS、Node代理服务器和Nginx反向代理方式。
- postMessage更多的是用在多个文档,窗口之间发送数据。
15、nextTick使用场景
- 在created操作dom,此时DOM并没有渲染
- 某个操作改变数据,并改变dom
VUE3
优势:
- 更小
- 更快
- 加强 TypeScript 支持
- 加强 API 设计一致性
- 提高自身可维护性
- 开放更多底层功能
劣势:
- 稍微拔高了一点萌新学习门槛
1、设计目标
Vue3
之前面临的问题:
-
随着功能的增长,复杂组件的代码变得越来越难以维护
-
缺少一种比较「干净」的在多个组件之间提取和复用逻辑的机制
-
类型推断不够友好
-
bundle
的时间太久了
Vue3
经过长达两三年时间的筹备,做了哪些事情?
-
更小
-
更快
-
TypeScript支持
-
API设计一致性
-
提高自身可维护性
-
开放更多底层功能
注: 一句话概述,就是更小更快更友好了。
1.1、更小
Vue3
移除一些不常用的 API
,引入tree-shaking
,可以将无用模块“剪辑”,仅打包需要的,使打包的整体体积变小了。
1.2、更快
主要体现在编译方面:
-
diff算法优化
-
静态提升
-
事件监听缓存
-
SSR优化
1.3、更友好
vue3
在兼顾vue2
的options API
的同时还推出了composition API
,大大增加了代码的逻辑组织和代码复用能力。
2、优化方案
2.1、vue3
从很多层面都做了优化,可以分成三个方面:
-
源码
-
性能
-
语法 API
2.2、源码可以从两个层面展开:
-
源码管理
-
TypeScript
3、TypeScript
Vue3
是基于typeScript
编写的,提供了更好的类型检查,能支持复杂的类型推导。
4、性能
-
体积优化
-
编译优化
-
数据劫持优化
在vue2
中,数据劫持是通过Object.defineProperty
,这个 API 有一些缺陷,并不能检测对象属性的添加和删除。
尽管Vue
为了解决这个问题提供了 set
和delete
实例方法,但是对于用户来说,还是增加了一定的心智负担,同时在面对嵌套层级比较深的情况下,就存在性能问题。
相比之下,vue3
是通过proxy
监听整个对象,那么对于删除还是监听当然也能监听到,同时Proxy
并不能监听到内部深层次的对象变化,而 Vue3
的处理方式是在getter
中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,而不是无脑递归。
5、语法 API
就是composition API
,其两大显著的优化:
-
优化逻辑组织
-
优化逻辑复用
5.1、逻辑组织
一张图,我们可以很直观地感受到 Composition API
在逻辑组织方面的优势
相同功能的代码编写在一块,而不像options API
那样,各个功能的代码混成一块。
5.2、逻辑复用
在vue2
中,我们是通过mixin
实现功能混合,如果多个mixin
混合,会存在两个非常明显的问题:命名冲突和数据来源不清晰。
而通过composition
这种形式,可以将一些复用的代码抽离出来作为一个函数,只要的使用的地方直接进行调用即可。
持续更新...
更多推荐
所有评论(0)