Java面试复习
2021-02-18 17:13:49 13 举报
AI智能生成
Java面试复习主要包括对Java基础知识的掌握,如数据类型、运算符、流程控制、类和对象、继承和多态等。同时,也需要熟悉Java的异常处理机制,理解并能够使用Java的集合框架。此外,对于Java的内存模型、垃圾回收机制、多线程编程等高级主题也要有所了解。在复习过程中,可以通过刷题来提高编程能力和解决问题的能力。同时,也要关注实际项目中的应用,理解和掌握常用的设计模式和架构模式。最后,面试时要注意展示自己的逻辑思维能力,清晰、准确地表达自己的观点。
作者其他创作
大纲/内容
Java SE
基础
⾯向对象和⾯向过程的区别?
⾯向过程
⾯向过程性能⽐⾯向对象⾼。 因为类调⽤时需要实例化,开销比较⼤,比较消耗资源,所以当性能是最重要的考量因素的时候,⽐如单⽚机、嵌⼊式开发、Linux/Unix 等⼀般采⽤⾯向过程开发。但是⾯向过程没有⾯向对象易维护、易复⽤、易扩展。
⾯向对象
⾯向对象易维护、易复⽤、易扩展。
java抽象类和普通类的区别?
抽象类可以有构造函数,抽象方法不能被声明为静态。
抽象类不能被实例化
Java 方法访问权限修饰?
private 私有成员属性和方法 只有本类可以调用,除内部类特殊情况
默认不写 只有本类 同一个包下面的类
protected 本类 同包 不同包的子类
public 本类 同包 不同包的类
Java内部类
内部类种类
成员内部类
成员内部类可以无条件访问外部类所有的成员属性和成员方法(包括private成员)
如果要访问外部类的同名成员,需要以下方式
外部类.this.成员变量
外部类.this.成员方法
在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问
要创建成员内部类的对象,前提是必须存在一个外部类的对象
第一种方式:Outter outter = new Outter();Outter.Inner inner = outter.new Inner();//必须通过Outter对象来创建
第二种方式:Outter.Inner inner1 = outter.getInnerInstance();
内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限
成员内部类不允许 static修饰 属性 和 方法
静态内部类
静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法
向外访问 只能直接访问static修饰属性和方法,同名用 Outter.str2。
非静态的外部属性方法需要实例化外部对象调用
可以有 main方法
方法内部类
不允许使用访问权限修饰符该类堆方法以外的全部隐藏,除了此方法其他的都不能访问
匿名内部类
匿名内部类必须继承一个抽象类或者实现一个接口。
匿名内部类没有类名,因此没有构造方法。匿名内部类是唯一一种没有构造器的类
深入理解内部类
为什么局部内部类和匿名内部类只能访问局部final变量?
背景: 当一个方法执行完成之后,局部变量的生命周期也就结束了,而此时Thread对象的生命周期很可能还没有结束,那么在Thread的run方法中继续访问这个变量就不可能了。但是又必须需要这个变量,怎么办呢?Java采用了复制的手段来解决这个问题造器传参的方式来对拷贝进行初始化赋值。
方案∶也就说如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。
问题∶当在run方法中改变变量a的值的话,会造成数据不一致性
解决:java编译器就限定必须将入参变量限制为final变量
注意:在JDK8版本之中;,方法内部类中调用方法中的局部变量,可以不需要修饰为final,匿名内部类也是一样的,主要是JDK8之后堪加了Effectively final功能
内部类的有点
一个内部类的对象能够访问创建它的对象的实现,包括私有数据
对于同一个包中的其他类来说,内部类能够隐藏起来
匿名内部类可以很方便的定义回调
使用内部类可以非常方便的编写事件驱动程序。
在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类
能够非常好的解决多重继承的问题
内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独
字符串
String
String类底层原理?
String为什么保证不可变?
使用 + 连接符 来进行String的拼接原理?
用常量字符串赋值给String引用 和 用new来创建字符串对象 的区别?
StringBuilder
StringBuffer
字符串常量池在哪儿?
Java 字符串常量存放在堆内存还是JAVA方法区?
JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
String创建对象
String str= "abc" 创建方式
创建对象的过程
1 首先在常量池中查找是否存在内容为"abc"字符串对象
2 如果不存在则在常量池中创建"abc",并让str引用该对象
3 如果存在则直接让str引用该对象
1 首先在常量池中查找是否存在内容为"abc"字符串对象
2 如果不存在则在常量池中创建"abc",并让str引用该对象
3 如果存在则直接让str引用该对象
String str= new String("abc")创建方式
创建对象的过程
1 首先在堆中(不是常量池)创建一个指定的对象,并让str引用指向该对象。
2 在字符串常量池中查看,是否存在内容为"abc"字符串对象
3 若存在,则将new出来的字符串对象与字符串常量池中的对象联系起来(即让那个特殊的成员变量value的指针指向它)
4 若不存在,则在字符串常量池中创建一个内容为"abc"的字符串对象,并将堆中的对象与之联系起来。(有可能此时常量池中的"abc"已经被回收,所以要先创建一个内容 为"abc"的字符串对象)
1 首先在堆中(不是常量池)创建一个指定的对象,并让str引用指向该对象。
2 在字符串常量池中查看,是否存在内容为"abc"字符串对象
3 若存在,则将new出来的字符串对象与字符串常量池中的对象联系起来(即让那个特殊的成员变量value的指针指向它)
4 若不存在,则在字符串常量池中创建一个内容为"abc"的字符串对象,并将堆中的对象与之联系起来。(有可能此时常量池中的"abc"已经被回收,所以要先创建一个内容 为"abc"的字符串对象)
str 放在栈上,用 new 创建出来的字符串对象放在堆上,而 abc 这个字面量是放在常量池中。
内存溢出和内存泄漏的区别
内存溢出 没有足够的内存可以使用
内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
代码中存在死循环或循环产生过多重复的对象实体
启动参数内存值设定的过小
内存泄漏 强引用对象没有没有释放使用的空间长时间的堆积造成
为什么 Java 中只有值传递?
值传递:是指在调用函数时,将实际参数复制一份传递给函数,这样在函数中修改参数时,不会影响到实际参数
引用传递:是指在调用函数时,将实际参数的地址传递给函数,这样在函数中对参数的修改,将影响到实际参数
然后 Java中 将一个值传递到方法中是不会影响原的值的,是通过复制的方式。
1. 基本数据类型 放在栈中 每次赋值后都不会影响原来的值
2. 引用数据类型 复制的是栈中的 指向堆 对象的地址 或者 句柄 现在指向的和原来指向的都是同一个对象。 你改了这个对象的值
3. String类型 string 每次 new 改变对象
1. 基本数据类型 放在栈中 每次赋值后都不会影响原来的值
2. 引用数据类型 复制的是栈中的 指向堆 对象的地址 或者 句柄 现在指向的和原来指向的都是同一个对象。 你改了这个对象的值
3. String类型 string 每次 new 改变对象
常见异常?
- ArithmeticException(算术异常)
- ClassCastException (类转换异常)
- IllegalArgumentException (非法参数异常)
- IndexOutOfBoundsException (下标越界异常)
- NullPointerException (空指针异常)
- SecurityException (安全异常)
运行时异常与受检异常有何异同?
异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常
操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就
不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可
能因使用的问题而引发。Java 编译器要求方法必须声明抛出可能发生的受检异常,
但是并不要求必须声明抛出未被捕获的运行时异常。
操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就
不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可
能因使用的问题而引发。Java 编译器要求方法必须声明抛出可能发生的受检异常,
但是并不要求必须声明抛出未被捕获的运行时异常。
里氏代换原则[能使用父类型的地方一定能使用子类型]
JDK1.8新特性?
方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
Lambda 表达式
Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。
对接口的要求:Lambda 规定接口中只能有一个需要被实现的方法.
语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。
方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有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应用。
面向对象特点?
封装 继承 多态 抽象类 接口
Java 程序如打日志?
注解
反射
IO
既然有了字节流,为什么还要有字符流?
字符流是由JVM将字节流抓换得到的。方便我们平时对 字符 进行流操作
如果是 图片 视屏 音频 等媒体文件用字节流 如果是字符 用字符流操作
如果是 图片 视屏 音频 等媒体文件用字节流 如果是字符 用字符流操作
集合
Collection
List
存取有序,元素可以重复,有序就有索引,有索引就可以通过索引操作元素。遍历方式:普通for,增强for,Iterator,ListIterator,集合转数组
ArrayList
不安全,效率高,数组结构:增删慢;查询快
LinkedList
不安全,效率高,链表结构:增删快;查询慢
Vector
数组结构 安全 效率低
存取无序,元素唯一。遍历方式:增强for,Iterator,集合转数组
Set
HashSet
哈希算法 哈希结构 存取无序 元素唯一
TreeSet
二叉树算法可以排序
Map
双列集合,键唯一,值可以重复,遍历方式:根据键找值,根据键值对找键和值
HashMap
⾮线程安全 null可以作为键 初始值16 原容量的2倍 数组+链表/红⿊⼆叉树 适用于在Map中插入、删除和定位元素
HashTable
线程安全 null不可以作为键 初始值11 原容量2倍加1 数组+链表
TreeMap
基于红黑树实现 适用于按自然顺序或自定义顺序遍历键(key)。
LinkedHashMap
ConcurrentHashMap
改进
将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构.
原理
默认初期长度为16,当往map中继续添加元素的时候,通过hash值跟数组长度取与来决定放在数组的哪个位置,如果出现放在同一个位置的时候,优先以链表的形式存放,在同一个位置的个数又达到了8个以上,如果数组的长度还小于64的时候,则会扩容数组。如果数组的长度大于等于64了的话,在会将该节点的链表转换成树。
如果某个节点的是树,同时现在该节点的个数又小于等于6个了,则会将该树转为链表
JVM
Java实现跨平台的原理就是 一次编译,到处运行 将源文件编译成class文件。 只有提供并且安装了相对应的虚拟机就可以跨该平台
了解JVM的好处
- 遇到内存溢出可以知道什么原因
- 知道代码运行原理,提高代码执行效率
- JVM调优
JVM 模型及功能
程序计数器
当前线程所执⾏的字节码解释器。
- ⼯作时通过改变这个计数器的值来选取下一条需要执行的字节码指令实现代码流程控制,分⽀、循环、跳转、异常处理、
- 保证每条线程切换后可以恢复到正确的位置,每条线程在方法执行时都需要一个私有的计数器,相互独立,互不干扰。
虚拟机栈
在JVM运行时存放局部变量、 对象引⽤地址、方法参数的内存区域。
每一个方法在被调用时都对应一个栈帧内存区,⽽每个栈帧中都拥有:局部变量表、操作数栈、动态链接、⽅法出⼝信息。执行完就释放空间
每一个方法在被调用时都对应一个栈帧内存区,⽽每个栈帧中都拥有:局部变量表、操作数栈、动态链接、⽅法出⼝信息。执行完就释放空间
局部变量表
用来存放 局部变量 方法参数
- 8种基本数据类型
- 对象引用
- ReturnAddress 返回 字节码地址
操作数栈
在一步细化后的栈 就是方法中的 计算操作需要的临时空间,也是栈结构(先进后出)列如:加减乘除
存储的数据与局部变量表一致含int、long、float、double、reference、returnType,操作数栈中byte、short、char压栈前(bipush)会被转为int。
动态链接
⽅法出⼝信息
记录方法被调用返回的位置
本地方法栈
java虚拟机栈相似,只是针对的是 native 修饰的方法 就是不是Java实现的方法
本地⽅法被执⾏的时候,在本地⽅法栈也会创建⼀个栈帧,⽤于存放该本地⽅法的局部变量表、操作数 栈、动态链接、出⼝信息
本地⽅法被执⾏的时候,在本地⽅法栈也会创建⼀个栈帧,⽤于存放该本地⽅法的局部变量表、操作数 栈、动态链接、出⼝信息
方法区(永久代)
主要存放的是 类信息、常量、静 态变量、即时编译器编译后的代码等数据
在JDK1.7之前存在 其实方法去属于 堆的逻辑部分 但 是它却有⼀个别名叫做 Non-Heap(⾮堆),⽬的应该是与 Java 堆区分开来
方法区和永久代的区别
⽅法区和永久代的关系很像Java中接⼝和类的关系,类实现了接⼝,⽽永久代就是HotSpot虚拟机对虚拟机规范中⽅法区的⼀种实现⽅式。 也就是说,永久代是HotSpot的概念,⽅法区是Java虚拟机规范中的定义,是⼀种规范,⽽永久代是⼀种实现,⼀个是标准⼀个是实现,其他的虚拟机实现并没有永久带这⼀说法。
-XX:PermSize=N //⽅法区(永久代)初始⼤⼩
-XX:MaxPermSize=N //⽅法区(永久代)最⼤⼤⼩,超过这个值将会抛出
OutOfMemoryError异常:java.lang.OutOfMemoryError: PermGen
-XX:MaxPermSize=N //⽅法区(永久代)最⼤⼤⼩,超过这个值将会抛出
OutOfMemoryError异常:java.lang.OutOfMemoryError: PermGen
元空间
JDK 1.8 的时候,⽅法区(HotSpot的永久代)被彻底移除了(JDK1.7就已经开始了),取⽽代之是元空间,元空间使⽤的是直接内存。
-XX:MetaspaceSize=N //设置Metaspace的初始(和最⼩⼤⼩)
-XX:MaxMetaspaceSize=N //设置Metaspace的最⼤⼤⼩
-XX:MaxMetaspaceSize=N //设置Metaspace的最⼤⼤⼩
为什么移除永久代? 有了元空间就不再会出现永久代OOM问题了
运⾏时常量池
运⾏时常量池是⽅法区的⼀部分,JDK1.7及之后版本的 JVM 已经将运⾏时常量池从⽅法区中移了出来,在 Java 堆(Heap)中开辟了⼀块区域存放运⾏时常量池。
常量池包含的内容
字面量
文本字符串
被声明为final的常量值
基本数据类型的值
符号引用
类和结构的完全限定名
字段名称和描述符
方法名称和描述符
堆 Heap
Java 虚拟机所管理的内存中最⼤的⼀块,此内存区域的唯⼀⽬的就是存放对象实例,⼏乎所有的对象实例以及数组都在这⾥分配内存
新生代 (Eden)——>幸存者S0(From Survivor) <——> 幸存者S1(To Survivor)——>老年代(tentired)
年轻代 Minor GC 清理年轻代 三部分的8:1:1。 经过多次Minor GC 还存活的对象(计数器年龄达到15)被移到老年代
老年代 Mejor GC 清理老年代。老年代存储长期存活的对象,占满时会触发Major GC=Full GC
Major GC=Full GC: 清理整个堆空间,包括年轻代和永久代。GC期间会停止所有线程等待GC完成(STW stop work)
为什么分代?
将对象根据存活概率进行分类,对存活时间长的对象,放到固定区从而减少扫描垃圾时间及GC频率。针对分类进行不同的垃圾回收算法,对算法扬长避短。
将对象根据存活概率进行分类,对存活时间长的对象,放到固定区从而减少扫描垃圾时间及GC频率。针对分类进行不同的垃圾回收算法,对算法扬长避短。
GC垃圾回收
JVM内存分配与回收
对象优先在Eden区分配
对象在新生代中 Eden 区分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
大对象直接进入老年代
如果对象超过设置大小会直接进入老年代,不会进入年轻代
长期存活的对象将进入老年代
多次Miner GC后存活下来的对象
对象动态年龄判断
Minor gc后存活的对象Survivor区放不下
这种情况会把存活的对象部分挪到老年代,部分可能还会放在Survivor区
老年代空间分配担保机制
Eden与Survivor区默认8:1:1
如何判断对象可以被回收?
引用计数算法
算法逻辑是这样的:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加 1;当引用失效时,计数器就减 1;任何时刻计数器为 0 的对象就是不可能再被使用的
可达性分析算法
可达性分析来判定对象是否存活的。这个算法的基本思路就是通过一系列被称为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain), 当一个对象到 GC Roots 没有任何引用链相连(即从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的。
引用分类
强引用
指在程序代码之中普遍存在的,类似 “Object obj = new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
软引用
软引用:用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列入回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。JDK1.2 之后,提供了 SoftReference 类来实现软引用。
弱引用
弱引用:也是用来描述非必须对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾回收发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。JDK1.2 之后,提供了 WeakReference 类来实现弱引用。
虚引用
虚引用:是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。JDK1.2 之后,提供了 PhantomReference 类来实现虚引用。
生存还是死亡?
即使在可达性分析算法中不可达的对象,也并非是 “非死不可”的,这时候它们暂时处于 “缓刑” 阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为 “没有必要执行”。
如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象将会放置在一个叫做 F-Queue 的队列之中,并在稍后由一个虚拟机自动创建的、低优先级的 Finalizer 线程去执行它。这里的 “执行” 是指虚拟机会触发这个方法,但不承诺会等待它运行结束,这样做的原因是,如果一个对象在 finalize() 方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致 F-Queue 队列中其他对象永久处于等待,甚至导致整个内存回收系统崩溃。finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己,只要重新与引用链上的任何一个对象建立关联即可,比如在自己(this 关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出 “即将回收” 的集合;如果对象这个时候还没有逃脱,那基本上它就真的被回收了。
如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象将会放置在一个叫做 F-Queue 的队列之中,并在稍后由一个虚拟机自动创建的、低优先级的 Finalizer 线程去执行它。这里的 “执行” 是指虚拟机会触发这个方法,但不承诺会等待它运行结束,这样做的原因是,如果一个对象在 finalize() 方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致 F-Queue 队列中其他对象永久处于等待,甚至导致整个内存回收系统崩溃。finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己,只要重新与引用链上的任何一个对象建立关联即可,比如在自己(this 关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出 “即将回收” 的集合;如果对象这个时候还没有逃脱,那基本上它就真的被回收了。
垃圾回收算法
标记-清除(Mark-Sweep)
GC分为两个阶段,标记和清除。首先标记所有可回收的对象,在标记完成后统一回收所有被标记的对象。同时会产生不连续的内存碎片。碎片过多会导致以后程序运行时需要分配较大对象时,无法找到足够的连续内存,而不得已再次触发GC。
复制算法(Copy)
将内存按容量划分为两块,每次只使用其中一块。当这一块内存用完了,就将存活的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。这样使得每次都是对半个内存区回收,也不用考虑内存碎片问题,简单高效。缺点需要两倍的内存空间。
标记-整理算法(Mark-Compact)
也分为两个阶段,首先标记可回收的对象,再将存活的对象都向一端移动,然后清理掉边界以外的内存。此方法避免标记-清除算法的碎片问题,同时也避免了复制算法的空间问题。
一般年轻代中执行GC后,会有少量的对象存活,就会选用复制算法,只要付出少量的存活对象复制成本就可以完成收集。而老年代中因为对象存活率高,没有额外过多内存空间分配,就需要使用标记-清理或者标记-整理算法来进行回收。
一般年轻代中执行GC后,会有少量的对象存活,就会选用复制算法,只要付出少量的存活对象复制成本就可以完成收集。而老年代中因为对象存活率高,没有额外过多内存空间分配,就需要使用标记-清理或者标记-整理算法来进行回收。
分代收集算法
现在的商用虚拟机的垃圾收集器基本都采用"分代收集"算法,这种算法就是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
在新生代中,每次收集都有大量对象死去,所以可以选择复制算法,只要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率时比较高的,而且没有额外的空间对它进行分配担保,就必须选择“标记-清除”或者“标记-整理”算法进行垃圾收集
垃圾收集器
串行收集器(Serial)
比较老的收集器,单线程。收集时,必须暂停应用的工作线程,直到收集结束。
并行收集器(Parallel)
多条垃圾收集线程并行工作,在多核CPU下效率更高,应用线程仍然处于等待状态。
CMS收集器(Concurrent Mark Sweep)
CMS收集器是缩短暂停应用时间为目标而设计的,是基于标记-清除算法实现,整个过程分为4个步骤,包括:
初始标记(Initial Mark)
并发标记(Concurrent Mark)
重新标记(Remark)
并发清除(Concurrent Sweep)
其中,初始标记、重新标记这两个步骤仍然需要暂停应用线程。初始标记只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段是标记可回收对象,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作导致标记产生变动的那一部分对象的标记记录,这个阶段暂停时间比初始标记阶段稍长一点,但远比并发标记时间段。
由于整个过程中消耗最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,CMS收集器内存回收与用户一起并发执行的,大大减少了暂停时间。
初始标记(Initial Mark)
并发标记(Concurrent Mark)
重新标记(Remark)
并发清除(Concurrent Sweep)
其中,初始标记、重新标记这两个步骤仍然需要暂停应用线程。初始标记只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段是标记可回收对象,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作导致标记产生变动的那一部分对象的标记记录,这个阶段暂停时间比初始标记阶段稍长一点,但远比并发标记时间段。
由于整个过程中消耗最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,CMS收集器内存回收与用户一起并发执行的,大大减少了暂停时间。
G1收集器(Garbage First)
G1收集器将堆内存划分多个大小相等的独立区域(Region),并且能预测暂停时间,能预测原因它能避免对整个堆进行全区收集。G1跟踪各个Region里的垃圾堆积价值大小(所获得空间大小以及回收所需时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,从而保证了再有限时间内获得更高的收集效率。
G1收集器工作工程分为4个步骤,包括:
初始标记(Initial Mark)
并发标记(Concurrent Mark)
最终标记(Final Mark)
筛选回收(Live Data Counting and Evacuation)
初始标记与CMS一样,标记一下GC Roots能直接关联到的对象。并发标记从GC Root开始标记存活对象,这个阶段耗时比较长,但也可以与应用线程并发执行。而最终标记也是为了修正在并发标记期间因用户程序继续运作而导致标记产生变化的那一部分标记记录。最后在筛选回收阶段对各个Region回收价值和成本进行排序,根据用户所期望的GC暂停时间来执行回收。
G1收集器工作工程分为4个步骤,包括:
初始标记(Initial Mark)
并发标记(Concurrent Mark)
最终标记(Final Mark)
筛选回收(Live Data Counting and Evacuation)
初始标记与CMS一样,标记一下GC Roots能直接关联到的对象。并发标记从GC Root开始标记存活对象,这个阶段耗时比较长,但也可以与应用线程并发执行。而最终标记也是为了修正在并发标记期间因用户程序继续运作而导致标记产生变化的那一部分标记记录。最后在筛选回收阶段对各个Region回收价值和成本进行排序,根据用户所期望的GC暂停时间来执行回收。
如何判断一个类是无用的类?
方法区主要回收的是无用的类,判断类无用同时满足三个条件
- 该类所有的实例都已经被回收,也就是 Java堆中不存在该类的任何实例。
- 加载该类的 ClassLoader 已经被回收。
- 该类对应的 java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
如何判断⼀个常量是废弃常量?
假如在常量池中存在字符串 "abc",如果当前没有任何String对象引⽤该字符串常量的话,就说明常量
"abc" 就是废弃常量,如果这时发⽣内存回收的话⽽且有必要的话,"abc" 就会被系统清理出常量池。
"abc" 就是废弃常量,如果这时发⽣内存回收的话⽽且有必要的话,"abc" 就会被系统清理出常量池。
JVM 调优
jvisualvm java 虚拟机诊断工具 Visual VM插件
JVM调优的目的是?
减少 full GC过程中的 STW(stop work)时间
减少 full GC次数
能否对JVM调优,让其几乎不发生full gc?
描述一下 JVM 加载 class 文件的原理机制?
说⼀下Java对象的创建过程
类加载检查
分配内存
初始话零值
设置对象头
执⾏ init ⽅法
设计模式
单例模式
一个类中只有一个实例对象,构造器是被private修饰。
工厂模式
提供一个用于创建对象的接口(工厂接口),让其实现类(工厂实现类)决定实例化哪一个类(产品类),并且由该实现类创建对应类的实例。
代理模式
代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象
静态代理
需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
可以做到在不修改目标对象的功能前提下,对目标功能扩展.
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
动态代理
代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理
Cglib代理
观察者模式
对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
builder模式
生产者/消费者模式
并发编程
Java内存模型
基本概念
可见性
一个线程在本地内存中修改了共享内存的数据,对于其他持有该数据的线程是“可见”的。用volatile修饰的变量,就会具有可见性
原子性
sychronized 保证原子性 不可分割,同生共死。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。
有序性
此规则决定了持有同一个对象锁的两个同步块只能串行执行。volatile 和 synchronized 两个关键字来保证线程之间操作的有序性
内存模型结构
主内存
缓存一致性协议(总线嗅探机制)
工作内存(高速缓存 变量副本)
Java线程 计算操作
原子操作
read 读取 从主内存中读取数据
load 载入 将从主内存中读取的数据加载到工作内存的副本中
use 使用 将工作内存中的数据做计算操作
assign 赋值 将计算结果值赋值到工作内存中
store 存储 将工作内存的值 赋值到主内存
write 写入 将store中的变量赋值到主内存的变量中
lock 加锁 将主内存中的变量锁 表示变量被线程独占状态
unlock 解锁 将主内存中的变量解锁 解锁后其他线程可以锁定
多线程
什么是线程和进程?
进程是程序的⼀次执⾏过程,是系统运⾏程序的基本单位,因此进程是动态的
线程是⼀个⽐进程更⼩的执⾏单位。⼀个进程在其执⾏的过程中可以产⽣多个线程
程序计数器为什么是私有的?
1. 字节码解释器通过改变程序计数器来依次读取指令,从⽽实现代码的流程控制,如:顺序执⾏、选择、循环、异常处理。
2.通过字节码解释器的地址,线程切换回来可以知道上一次执行到哪了
虚拟机栈和本地⽅法栈为什么是私有的?
虚拟机栈
每个 Java ⽅法在执⾏的同时会创建⼀个栈帧⽤于存储局部变量表、操作数栈、常量池引⽤等信息。
从⽅法调⽤直⾄执⾏完的过程,就对应着⼀个栈帧在 Java 虚拟机栈中⼊栈和出栈的过程
从⽅法调⽤直⾄执⾏完的过程,就对应着⼀个栈帧在 Java 虚拟机栈中⼊栈和出栈的过程
本地⽅法栈
本地⽅法栈则为虚拟机使⽤到的 Native ⽅法服务
说说并发与并⾏的区别?
并发:同⼀时间段,多个任务都在执⾏ (单位时间内不⼀定同时执⾏) A 执行一段时间 让B执行一段时间
并行:单位时间内有多个任务在执行 A B 同事执行
为什么要使⽤多线程呢?
从计算机底层来说: 线程可以⽐作是轻量级的进程,是程序执⾏的最⼩单位,线程间的切换和调度的成本远远⼩于进程
现在的系统动不动就要求百万级甚⾄千万级的并发量,⽽多线程并发编程正是开发⾼并发系统的基础,利⽤好多线程机制可以⼤⼤提⾼系统整体的并发能⼒以及性能
提高CPU的利用率 目前大多数CPU都是多核的 可以都利用起来
守护线程的优先级很低 GC就是一个经典的守护线程
说说线程的⽣命周期和状态?
新生状态
在执行new Thread(s),线程对象一旦创建就进入新生状态
就绪状态
在线程对象创建完成后调用start方法,但是线程不会立刻调度执行,而是进入就绪状态,因为在运行前还有一些准备工作要做
运行状态
线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法
阻塞状态
waiting sleep 超时等待 time waiting 正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态
终止状态 dead
①run方法正常退出而自然死亡;
②一个未捕获的异常终止了run方法而使线程猝死
②一个未捕获的异常终止了run方法而使线程猝死
使⽤多线程可能带来什么问题?
并发编程的⽬的就是为了能提⾼程序的执⾏效率提⾼程序运⾏速度,但是并发编程并不总是能提⾼程序
运⾏速度的,⽽且并发编程可能会遇到很多问题,⽐如:内存泄漏、上下⽂切换、死锁还有受限于硬件
和软件的资源闲置问题。
运⾏速度的,⽽且并发编程可能会遇到很多问题,⽐如:内存泄漏、上下⽂切换、死锁还有受限于硬件
和软件的资源闲置问题。
线程创建方式?
继承 Thread
实现 Runnable
使用匿名内部类
callable
线程池创建方式
守护线程?
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作
将一个用户线程设置为守护线程的方式是在 线程对象创建 之前 用线程对象的setDaemon方法
Thread API
start()
启动当前线程 调用当前线程的 run 方法
run()
线程要执行的操作
yield()
释放当前CPU的执行权
join()
在线程A中调用线程B的 join 方法,此时线程A将进入阻塞状态,等B执行完成后,线程 A才结束阻塞状态.
sleep()
让当前线程睡眠 xxx 毫秒,在指定的时间内线程处于阻塞状态
isAlive()
判单线程是否存活
wait()
让当前线程从运行状态转变成休眠状态,释放锁的资源
notify()
唤醒指定被暂停的线程
notifyAll()
唤醒所有的被暂停的线程
说说 sleep() ⽅法和 wait() ⽅法区别和共同点?
两者都可以暂停线程的执⾏。
两者最主要的区别在于:sleep ⽅法没有释放锁,⽽ wait ⽅法释放了锁 。
Wait 通常被⽤于线程间交互/通信,sleep 通常被⽤于暂停执⾏。
wait() ⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify() 或者notifyAll() ⽅法。
sleep() ⽅法执⾏完成后,线程会⾃动苏醒。或者可以使⽤ wait(long timeout)超时后线程会⾃动苏醒。
sleep() ⽅法执⾏完成后,线程会⾃动苏醒。或者可以使⽤ wait(long timeout)超时后线程会⾃动苏醒。
为什么我们调⽤ start() ⽅法时会执⾏ run() ⽅法,为什么我们不能直接调⽤run() ⽅法?
new ⼀个 Thread,线程进⼊了新建状态;调⽤ start() ⽅法,会启动⼀个线程并使线程进⼊了就绪状态,
当分配到时间⽚后就可以开始运⾏了。 start() 会执⾏线程的相应准备⼯作,然后⾃动执⾏run() ⽅法的内容,这是真正的多线程⼯作。
⽽直接执⾏ run() ⽅法,会把 run ⽅法当成⼀个 main线程下的普通⽅法去执⾏,并不会在某个线程中执⾏它,所以这并不是多线程⼯作。
当分配到时间⽚后就可以开始运⾏了。 start() 会执⾏线程的相应准备⼯作,然后⾃动执⾏run() ⽅法的内容,这是真正的多线程⼯作。
⽽直接执⾏ run() ⽅法,会把 run ⽅法当成⼀个 main线程下的普通⽅法去执⾏,并不会在某个线程中执⾏它,所以这并不是多线程⼯作。
线程优先级调度?
同优先级的线程 先进先出队列,先来先服务。
高优先级的线程 使用优先调度的抢占策略。高优先级的线程会抢占低优先级的执行权
setPriority()
getPriority()
getPriority()
使⽤多线程可能带来什么问题?
并发编程的⽬的就是为了能提⾼程序的执⾏效率提⾼程序运⾏速度,但是并发编程并不总是能提⾼程序
运⾏速度的,⽽且并发编程可能会遇到很多问题
运⾏速度的,⽽且并发编程可能会遇到很多问题
内存泄漏、上下⽂切换、死锁还有受限于硬件和软件的资源闲置问题
什么是上下⽂切换?
线程任务从保存到再次加载的过程就是一次上下文切换
当前任务在执⾏完 CPU 时间⽚切换到另⼀个任务之前会先保存⾃⼰的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态
什么是线程安全问题?
多个线程共享一个主内存变量时,在写操作可能受其他线程的影响发生数据冲突
线程安全解决方法?
1、同步代码块 2、同步方法 3、锁机制Lock
什么是线程死锁?
由于线程被⽆限期地阻塞,因此程序不可能正常终⽌。
产⽣死锁必须具备以下四个条件
互斥条件:该资源任意⼀个时刻只由⼀个线程占⽤。
请求与保持条件:⼀个进程因请求资源⽽阻塞时,对已获得的资源保持不放。
不剥夺条件: 线程已获得的资源在末使⽤完之前不能被其他线程强⾏剥夺,只有⾃⼰使⽤完毕后才释放资源。
循环等待条件: 若⼲进程之间形成⼀种头尾相接的循环等待资源关系
如何避免线程死锁?
破坏互斥条件
这个条件我们没有办法破坏,因为我们⽤锁本来就是想让他们互斥的
破坏请求与保持条件
⼀次性申请所有的资源
破坏不剥夺条件
占⽤部分资源的线程进⼀步申请其他资源时,如果申请不到,可以主动释放它占有的资源
破坏循环等待条件
靠按序申请资源来预防。按某⼀顺序申请资源,释放资源则反序释放。破坏循环等待条件
synchronized
synchronized 关键字 主要保证它所修饰的方法或者代码块在同一时间只有一个线程执行
三种使⽤⽅式
修饰实例⽅法
作用于当前 实例对象本身 加锁,进入同步代码前要获得当前对象实例的锁
修饰静态⽅法
作用于当前 类对象本身 加锁,进入同步代码前要获得当前类对象的锁。本类的其他静态方法都要等该方法释放锁
修饰代码块
指定加锁对象(实例对象、类),对给定对象加锁,进入同步代码库前要获得给定对象的锁
说说⾃⼰是怎么使⽤ synchronized 关键字,在项⽬中⽤到了吗?
在去年的项目钟 短信发送进程中有用到
synchronized 关键字的底层原理
JDK1.6 之后的synchronized 关键字底层做了哪些优化
如偏向锁、轻量级锁、⾃旋锁、适应性⾃旋锁、锁消除、锁粗化等技术来减少锁操作的开销
锁主要存在四种状态,依次是:⽆锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈⽽逐渐升级。
Lock锁 和 synchronized 锁
区别
synchronized 依赖于 JVM 虚拟机层⾯实现的 ,是Java语言的关键字
Lock 依赖于 API 是 JDK 层⾯实现,Lock是一个接口。lock() 和 unlock() ⽅法配合try/finally 语句块来完成
Lock 依赖于 API 是 JDK 层⾯实现,Lock是一个接口。lock() 和 unlock() ⽅法配合try/finally 语句块来完成
synchronized 使用中不需要手动解锁,Lock 需要手动解锁。代码实现 synchronized 要简单
两者都是可重⼊锁
Sync是不可中断的。除非抛出异常或者正常运行完成
synchronized 非公平锁,Lock可以通过实现类 ReentrantLock 的构造方法决定是是否公平
在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,
但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。
但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。
可实现选择性通知,可以指定唤醒哪个线程,notify()唤醒那个线程由JVM决定
可以实现多路通知功能也就是在⼀个Lock对象中可以创建多个Condition实例(即对象监视器)线程对象可以注册在指定的Condition中,
从⽽可以有选择性的进⾏线程通知,在调度线程上更加灵活
可以实现多路通知功能也就是在⼀个Lock对象中可以创建多个Condition实例(即对象监视器)线程对象可以注册在指定的Condition中,
从⽽可以有选择性的进⾏线程通知,在调度线程上更加灵活
Lock API
void lock()
获取锁
void lockInterruptibly()
如果当前线程未被中断,则获取锁,可以响应中断
Condition newCondition()
返回绑定到此 Lock 实例的新 Condition 实例
boolean tryLock()
仅在调用时锁为空闲状态才获取该锁,可以响应中断
boolean tryLock(long time, TimeUnit unit)
如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁
void unlock()
释放锁
ReadWriteLock锁
ReadWriteLock 接口只有两个方法:
Lock readLock() //返回用于读取操作的锁
Lock writeLock() //返回用于写入操作的锁
Lock writeLock() //返回用于写入操作的锁
ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持,而写入锁是独占的。
可重⼊锁
⾃⼰可以再次获取⾃⼰的内部锁,同⼀个线程每次获取锁,锁的计数器都⾃增1,所以要等到锁的计数器下降为0时才能释放锁
比如 在一个同步方法中调用了 另外一个同步锁
比如 在一个同步方法中调用了 另外一个同步锁
可中断锁
响应中断的锁
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。在前面演示tryLock(long time, TimeUnit unit)和lockInterruptibly()的用法时已经体现了Lock的可中断性
公平锁
尽可能的按照先后顺序获取锁。synchronized就是非公平锁,Lock可选择
volatile 原理
主要作⽤就是保证变量的可见性。然后还有⼀个作⽤是防⽌指令重排序
停止线程方式
可以通过标记判断走完run()代码 或者 异常
ThreadLocal
ThreadLocal简介
创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题
底层
使用的是类似 Hashmap 的结构
ThreadLocal特性
Synchronized是通过线程等待,牺牲时间来解决访问冲突
ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,
并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,
并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
内存泄漏问题
使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。
ThreadLocalMap 中使⽤的 key 为 ThreadLocal 的弱引⽤,⽽ value 是强引⽤。
如果ThreadLocal 没有被外部强引⽤的情况下,在垃圾回收的时候,key 会被清理掉,⽽ value 不会被清理掉。
这样⼀来, ThreadLocalMap 中就会出现key为null的Entry。
假如我们不做任何措施的话,value 永远⽆法被GC 回收,这个时候就可能会产⽣内存泄露。
如果ThreadLocal 没有被外部强引⽤的情况下,在垃圾回收的时候,key 会被清理掉,⽽ value 不会被清理掉。
这样⼀来, ThreadLocalMap 中就会出现key为null的Entry。
假如我们不做任何措施的话,value 永远⽆法被GC 回收,这个时候就可能会产⽣内存泄露。
JAVA多线程中线程之间的通信方式
同步 这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信。
while轮询的方式
wait/notify机制
管道通信
线程池
1. 为什么要⽤线程池?
降低资源消耗
通过重复利用已创建的线程减少线程的创建和销毁造成的消耗
提高响应速度
当任务到达时,可以不用等待线程创建就可以直接执行
提⾼线程的可管理性
线程时稀缺资源,不断地无限制创建,不仅会消耗系统的资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优、监管
2.线程池的创建
Executors.newCachedThreadPool()
创建一个可缓存的线程池
这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
Executors.newFixedThreadPool(3)
创建固定大小的线程池
每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
LinkedBlockingQueue 核心线程和最大线程是相同的
Executors.newSingleThreadExecutor()
创建一个单线程化的线程池
这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
核心线程和最大都是一个 LinkedBlockingQueue
Executors.newScheduledThreadPool(5)
创建一个定长线程池。此线程池支持定时以及周期性执行任务的需求。——延迟执行
ThreadPoolExecutor的方式
程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
3.执⾏execute()⽅法和submit()⽅法的区别是什么呢?
execute()方法没有返回值,所以⽆法判断任务是否被线程池执⾏成功与否
submit()有返回值,线程池会返回⼀个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执⾏成功
4.如何终止线程池?
singleThreadPool.shutdownNow();//对正在执行的任务停止
singleThreadPool.shutdown();//只是不接受新任务
singleThreadPool.shutdown();//只是不接受新任务
5.ThreadPoolExecutor
corePoolSize
线程池中核心线程数的最大值
maximumPoolSize
线程池中能拥有最多线程数
workQueue
用于缓存任务的阻塞队列
(1)如果没有空闲的线程执行该任务且当前运行的线程数少于corePoolSize,则添加新的线程执行该任务。
(2)如果没有空闲的线程执行该任务且当前的线程数等于corePoolSize同时阻塞队列未满,则将任务入队列,而不添加新的线程。
(3)如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数小于maximumPoolSize,则创建新的线程执行任务。
(4)如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数等于maximumPoolSize,则根据构造函数中的handler指定的策略来拒绝新的任务。
(2)如果没有空闲的线程执行该任务且当前的线程数等于corePoolSize同时阻塞队列未满,则将任务入队列,而不添加新的线程。
(3)如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数小于maximumPoolSize,则创建新的线程执行任务。
(4)如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数等于maximumPoolSize,则根据构造函数中的handler指定的策略来拒绝新的任务。
keepAliveTime
表示空闲线程的存活时间
TimeUnitunit
表示keepAliveTime的单位
handler
workQueue
threadFactory
数据库
MySQL
MySQL和Oracle的区别
MySQL 开源 轻量级 免费
Oracle 非开源 重量级 收费
事务
MySQL对于事务默认是不支持的,innodb可以支持
Oracle对于事务是完全支持
并发性
MySQL以表级锁为主,对资源锁定的粒度很大,如果一个session对一个表加锁时间过长,会让其他session无法更新此表中的数据。
虽然InnoDB引擎的表可以用行级锁,但这个行级锁的机制依赖于表的索引,如果表没有索引,或者sql语句没有使用索引,那么仍然使用表级锁。
Oracle使用行级锁,对资源锁定的粒度要小很多,只是锁定sql需要的资源,并且加锁是在数据库中的数据行上,不依赖与索引。所以Oracle对并
发性的支持要好很多。
虽然InnoDB引擎的表可以用行级锁,但这个行级锁的机制依赖于表的索引,如果表没有索引,或者sql语句没有使用索引,那么仍然使用表级锁。
Oracle使用行级锁,对资源锁定的粒度要小很多,只是锁定sql需要的资源,并且加锁是在数据库中的数据行上,不依赖与索引。所以Oracle对并
发性的支持要好很多。
操作
分页方式不同
MySQL使用 limit分页公式 Oracle 使用 伪列ROWNUM
字符串 单双引号不同
主键
MySQL 可以设置自增,Oracle使用序列
时间
MYSQL日期字段分DATE和TIME两种,ORACLE日期字段只有DATE,包含年月日时分秒信息,用当前数据库的系统时间为SYSDATE, 精确到秒
MyISAM和InnoDB区别
事物的四⼤特性(ACID)
原子性(Atomicity): 事务是最⼩的执⾏单位,不允许分割。事务的原⼦性确保动作要么全部 完成,要么完全不起作⽤;
一致性(Consistency):执行事务前后,数据保持⼀致,多个事务对同一个数据读取的结果是 相同的;
隔离性(Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的
持久性(Durability):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据 库发⽣故障也不应该对其有任何影响。
并发事务带来哪些问题
脏读(没有取到最新数据)
前一个修改了还没提交事务,第二个事务访问这个数据,并使用了它。
丢失修改(第二个覆盖了第一个)
在一个事务读取⼀个数据时,另外一个事务也访问了该数据, 那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。第一个事务内的修改结果就被丢失,因此称为丢失修改
不可重复读(两次读取不一样)
指在一个事务内多次读同一数据。在这个事务还没有结束 时,另一个事务也访问该数据。那么在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读
幻读(读取到下一个事务的信息)
幻读与不可重复读类似。它发生在一个事务(T1)读取了多次数据,接 着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
事务隔离级别有哪些?MySQL的默认隔离级别是?
READ-UNCOMMITTED(读取未提交)
最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
READ-COMMITTED(读取已提交)
允许读取并发事务已经提交的数据,可以阻⽌脏读,但是幻读或不可重复读仍有可能发⽣。
REPEATABLE-READ(可重复读)
对同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改,可以阻⽌脏读和不可重复读,但幻读仍有可能发⽣。
SERIALIZABLE(可串⾏化)
最⾼的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰,也就是说,该级别可以防⽌脏读、不可重复读以及幻读
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。
InnoDB 存储引擎默认使⽤ REPEAaTABLE-READ(可重读) 并不会有任何性能损失。 InnoDB 存储引擎在 REPEATABLE-READ(可重读) 事务隔离级别下使⽤的是Next-Key Lock 锁算法,因此可以避免幻读的产⽣,
InnoDB 存储引擎在 分布式事务 的情况下⼀般会⽤到 SERIALIZABLE(可串⾏化) 隔离级别。
⼤表优化
当MySQL单表记录数过⼤时,数据库的CRUD性能会明显下降,会使用优化。
限定数据的范围(按时间分表)
务必禁⽌不带任何限制数据范围条件的查询语句。⽐如:我们当⽤户在查询订单历史的时候,我们可以
控制在⼀个⽉的范围内;
控制在⼀个⽉的范围内;
读/写分离(没用过)
经典的数据库拆分⽅案,主库负责写,从库负责读;
垂直分区(拆表)
把⼀张列⽐较多的表拆分为多张表
⽔平分区(分库)
相同的表放在不同的数据库中去
解释⼀下什么是池化设计思想。什么是数据库连接池?为什么需要数据库连接池?
在连接池中,创建连接后,将其放置在池中,并再次使⽤它,因此不必建⽴新的连接。如果使⽤了所有连接,则会建⽴⼀个新连接并将其添加到池中。
连接池还减少了⽤户必须等待建⽴与数据库的连接的时间。
分库分表之后,id 主键如何处理?
Mysql 当时用的是 主键自增的方式 用过的方法时有一张表专门用来记录自增主键的id。每次查询出最大值,然后自增1000缓存起来,然后每次新增是用到的就直接取。用完在取,参数都是可配置的。
Oracle 系统时间 + 随机数
一条SQL语句执行得很慢的原因有哪些?
1、大多数情况是正常的,只是偶尔会出现很慢的情况。
要执行的SQL语句,刚好这条语句涉及到的表,别人在用,并且加锁了,我们拿不到锁,只能慢慢等待别人释放锁了。或者,表没有加锁,但要使用到的某一行被加锁了,这也会造成上述问题。
2、在数据量不变的情况下,这条SQL语句一直以来都执行的很慢。
没有加索引 或 索引没有生效
3.SQL语句需要优化
- 避免全表扫描
- 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描
- 应尽量避免在 where 子句中对字段进行 null 值判断
- 比如子查询的效率没有关联查询快,in 可以换左右链接组合的方式。
sql 的 on 和 where
on条件是在生成临时表时使用的条件
where条件是在临时表生成好后,再对临时表进行过滤的条件。
MySQL主键设计
设计原则
MySQL主键应当是对用户没有意义的。
MySQL主键应该是单列的,以便提高连接和筛选操作的效率
永远也不要更新MySQL主键
MySQL主键不应包含动态变化的数据,如时间戳、创建时间列、修改时间列等
MySQL主键应当有计算机自动生成。
MySQL主键应该是单列的,以便提高连接和筛选操作的效率
永远也不要更新MySQL主键
MySQL主键不应包含动态变化的数据,如时间戳、创建时间列、修改时间列等
MySQL主键应当有计算机自动生成。
自增ID
数据库自动编号,速度快,而且是增量增长,聚集型主键按顺序存放,对于检索非常有利。
数字型,占用空间小,易排序,在程序中传递方便。
缺点
不支持水平分片架构,水平分片的设计当中,这种方法显然不能保证全局唯一。
子主题
子主题
sql优化
避免全表扫描,在where 和 order by 涉及的列上面建索引
避免索引失效,否则将导致引擎放弃使用索引而进行全表扫描
where 子句中对字段进行 null 值判断(col is null)。使用默认值代替null
where 子句中使用!=或<>操作符
where 子句中使用 or 来连接条件
like以%开头
where 子句中对字段进行表达式操作
where子句中对字段进行函数操作
复合索引没有按照左从原则
如果mysql觉得全表扫描更快时(数据少)
exists 和 in 使用原则
in() 适合B表比A表数据小的情况
exists() 适合B表比A表数据大的情况
违反创建索引原则
索引
1.什么是索引?
索引是一种数据结构,可以帮助我们快速的进行数据的查找.
实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。
索引是一个文件,它是要占据物理空间的。
实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。
索引是一个文件,它是要占据物理空间的。
2、索引有哪些优缺点?
索引的优点
(1)可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
(2)通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
(2)通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
索引的缺点
(1)时间方面:创建索引和维护索引要耗费时间,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率,因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。
(2)空间方面:建立索引会占用磁盘空间的索引文件。
(2)空间方面:建立索引会占用磁盘空间的索引文件。
3、索引使用场景
- Where 有索引的字段作为条件,可以提高查询效率。
- Order by 如果字段已经建立索引,则索引本身就是有序的,因此直接按照索引的顺序和映射关系逐条取出数据即可。
- Join on 对join语句匹配关系(on)涉及的字段建立索引能够提高效率
4、索引有哪几种类型?
主键索引
数据列不允许重复,不允许为NULL,一个表只能有一个主键。
唯一索引
数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引
ALTER TABLE table_name ADD UNIQUE (column); -- 创建唯一索引
ALTER TABLE table_name ADD UNIQUE (column1,column2); -- 创建唯一组合索引
ALTER TABLE table_name ADD UNIQUE (column1,column2); -- 创建唯一组合索引
普通索引
基本的索引类型,没有唯一性的限制,允许为NULL值
ALTER TABLE table_name ADD INDEX index_name (column);-- 创建普通索引
ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);-- 创建组合索引
-- 只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。
ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);-- 创建组合索引
-- 只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。
全文索引
是目前搜索引擎使用的一种关键技术
ALTER TABLE table_name ADD FULLTEXT (column); -- 创建全文索引
5、索引的数据结构
索引的数据结构和具体存储引擎的实现有关, 在MySQL中使用较多的索引有Hash索引,B+树索引等.
我们经常使用的InnoDB存储引擎的默认索引实现为:B+树索引.
我们经常使用的InnoDB存储引擎的默认索引实现为:B+树索引.
6、索引的基本原理
索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。
索引的原理很简单,就是把无序的数据变成有序的查询
(1)把创建了索引的列的内容进行排序
(2)对排序结果生成倒排表
(3)在倒排表内容上拼上数据地址链
(4)在查询的时候,**先拿到倒排表内容,再取出数据地址链**,从而拿到具体数据
(1)把创建了索引的列的内容进行排序
(2)对排序结果生成倒排表
(3)在倒排表内容上拼上数据地址链
(4)在查询的时候,**先拿到倒排表内容,再取出数据地址链**,从而拿到具体数据
7.Hash索引和B+树所有有什么区别或者说优劣呢?
8、索引设计的原则?
- (1)适合索引的列是出现在where子句中的列,或者连接子句中指定的列
(2)基数较小的类,索引效果较差,没有必要在此列建立索引
(3)使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间
(4)不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
9、创建索引的原则(重中之重)
索引虽好,但也不是无限制的使用,最好符合一下几个原则
1) 最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
2)较频繁作为查询条件的字段才去创建索引
3)更新频繁字段不适合创建索引
4)若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
5)尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
6)定义有外键的数据列一定要建立索引。
7)对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
8)对于定义为text、image和bit的数据类型的列不要建立索引。
1) 最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
2)较频繁作为查询条件的字段才去创建索引
3)更新频繁字段不适合创建索引
4)若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
5)尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
6)定义有外键的数据列一定要建立索引。
7)对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
8)对于定义为text、image和bit的数据类型的列不要建立索引。
10、创建索引时需要注意什么?
非空字段
应该指定列为NOT NULL,除非你想存储NULL。在mysql中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值
取值离散大的字段
变量各个取值之间的差异程度)的列放到联合索引的前面,可以通过count()函数查看字段的差异值,返回值越大说明字段的唯一值越多字段的离散程度高;
索引字段越小越好
数据库的数据存储以页为单位一页存储的数据越多一次IO操作获取的数据越大效率越高
Redis
简介
Redis 是非关系型数据库,与传统的数据库不统在于它存在内存中
作用
⾼性能:
⽤户第⼀次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该⽤户访问的数据存在缓存中,这样下⼀次再访问这些数据的时候就可以直接从缓存中获取了
⾼并发:
直接操作缓存能够承受的请求是远远⼤于直接访问数据库的
redis 的线程模型 单线程的模型
redis 和 memcached 的区别
redis⽀持更丰富的数据类型(⽀持更复杂的应⽤场景)
Redis⽀持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进⾏使⽤,⽽Memecache把数据全部存在内存之中。
集群模式:memcached没有原⽣的集群模式,需要依靠客户端来实现往集群中分⽚写⼊数据;但是 redis ⽬前是原⽣⽀持 cluster 模式的.
Memcached是多线程,⾮阻塞IO复⽤的⽹络模型;Redis使⽤单线程的多路 IO 复⽤模型。
redis 常⻅数据结构以及使⽤场景分析
String
的key-value类型 常规计数:微博数,粉丝数等。
Hash
sh 是⼀个 string 类型的 field 和 value 的映射表 hash 特别适合⽤于存储对象
List
list 就是链表
Redis最重要的数据结构之⼀,⽐如微博的关注列表,粉丝列表,消息列表等功能都可以⽤Redis的 list 结构来实现
Redis最重要的数据结构之⼀,⽐如微博的关注列表,粉丝列表,消息列表等功能都可以⽤Redis的 list 结构来实现
Set
set 对外提供的功能与list类似是⼀个列表的功能,特殊之处在于 set 是可以⾃动排重的
Sorted Set
和set相⽐,sorted set增加了⼀个权重参数score,使得集合中的元素能够按score进⾏有序排列
在直播系统中,实时排⾏信息包含直播间在线⽤户列表,各种礼物排⾏榜,弹幕消息
在直播系统中,实时排⾏信息包含直播间在线⽤户列表,各种礼物排⾏榜,弹幕消息
如何解决 Redis 的并发竞争 Key 问题
分布式锁
准备一个分布式锁,大家去抢锁,抢到锁就做set操作
利用消息队列
并发量过大的情况下,可以通过消息中间件进行处理,把并行读写进行串行化
ElasticSearch
为什么要使用Elasticsearch?
因为在一个商城中商品的信息数据会很多,使用模糊查询方式 因为前置规则会放弃索引,查询效率会非常低。而ES可以实现全文检索索引,提高查询的效率。将常用的数据字段放在ES中可以很快的查询到结果。
快速存储、搜索、分析数据
Elasticsearch是如何实现Master选举的?
子主题
子主题
Mongodb
开源框架
Spring
对spring框架的理解
spring是一个轻量级的框架,能简化企业级开发,减少开发过程的代码量,也能很好的整合其它框架。
spring的核心是IOC和AOP,IOC说白了就是提供对象的一个容器,DI依赖注入就是属性赋值,AOP就是根据动态代理实现的业务流程以外的扩展功能(事务处理、日志管理、权限控制),分为切面、切入点和通知点。
spring还提供了Jdbc框架的轻量级封装,还提供声明是事务,还跟据MVC结构开发了Spring MVC框架。
spring的核心是IOC和AOP,IOC说白了就是提供对象的一个容器,DI依赖注入就是属性赋值,AOP就是根据动态代理实现的业务流程以外的扩展功能(事务处理、日志管理、权限控制),分为切面、切入点和通知点。
spring还提供了Jdbc框架的轻量级封装,还提供声明是事务,还跟据MVC结构开发了Spring MVC框架。
spring七大模块
Spring Core : 基础模块 提供IOC容器 实现对Bean的管理
Spring AOP :提供了⾯向切⾯的编程实现。
Spring Web : 为创建Web应⽤程序提供⽀持
Spring JDBC : Java数据库连接。
Spring Aspects : 该模块为与AspectJ的集成提供⽀持。
Spring JMS :Java消息服务。
pring ORM : ⽤于⽀持Hibernate等ORM⼯具。
Spring IOC原理
IOC的理解
IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 容器是 Spring用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注⼊。这样可以很⼤程度上简化应⽤的开发,把应⽤从复杂的依赖关系中解放出来。 IoC 容器就像是⼀个⼯⼚⼀样,当我们需要创建⼀个对象的时候,只需要配置好配置⽂件/注解即可,完全不⽤考虑对象是如何被创建出来的。在实际项⽬中⼀个 Service 类可能有⼏百甚⾄上千个类作为它的底层,假如我们需要实例化这个Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把⼈逼疯。如果利⽤IoC 的话,你只需要配置好,然后在需要的地⽅引⽤就⾏了,这⼤⼤增加了项⽬的可维护性且降低了开发难度。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注⼊。这样可以很⼤程度上简化应⽤的开发,把应⽤从复杂的依赖关系中解放出来。 IoC 容器就像是⼀个⼯⼚⼀样,当我们需要创建⼀个对象的时候,只需要配置好配置⽂件/注解即可,完全不⽤考虑对象是如何被创建出来的。在实际项⽬中⼀个 Service 类可能有⼏百甚⾄上千个类作为它的底层,假如我们需要实例化这个Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把⼈逼疯。如果利⽤IoC 的话,你只需要配置好,然后在需要的地⽅引⽤就⾏了,这⼤⼤增加了项⽬的可维护性且降低了开发难度。
描述
IoC(Inverse of Control:控制反转)是⼀种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。
好处
第一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度。
它生成的bean完全由自己管理,生命周期可以自己控制
传统的方式没有任何容器去管理它的生命周期,完全是靠回收机制回收
传统的方式没有任何容器去管理它的生命周期,完全是靠回收机制回收
技术
工厂模式 反射 xml解析
反射机制
首先得获得类的全路径,该类还得有构造器,如果你要是写了有参构造器的话,必须得显示写无参构造器。默认情况下,该类是自带无参构造器。
容器特点
根据唯一标示 获取该实例
容器实现方式
xml配置方式 注解的方式
IOC初始化流程?
子主题
DI依赖注入流程?
控制指的是什么?
指的是构建实例的控制权
反转指的是什么?
控制权由其他类(引用类)转为spring了
容器底层的数据结构
IoC 容器是 Spring⽤来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
BeanFactory和ApplicationContext的区别
BeanFactory
是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;IOC容器的基本实现,一般不会直接使用。
BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化;
ApplicationContext
应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能;开发使用
1) 国际化(MessageSource)
2) 访问资源,如URL和文件(ResourceLoader)
3) 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
4) 消息发送、响应机制(ApplicationEventPublisher)
5) AOP(拦截器)
2) 访问资源,如URL和文件(ResourceLoader)
3) 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
4) 消息发送、响应机制(ApplicationEventPublisher)
5) AOP(拦截器)
ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;
三个常用的实现类
ClassPathXmlApplicationContext:可以加载类路径下的配置文件,要求配置文件必须在类路径下面
对于 ClassPathXmlApplicationContext 的使用:
classpath: 前缀是可加可不加的 , 默认就是指项目的 classpath 路径下面。
如果要使用绝对路径 , 需要加上 file: , 前缀表示这是绝对路径。
classpath: 前缀是可加可不加的 , 默认就是指项目的 classpath 路径下面。
如果要使用绝对路径 , 需要加上 file: , 前缀表示这是绝对路径。
FileSystemXmlApplicationContext:可以加载磁盘任意路径下的配置文件
对于 FileSystemXmlApplicationContext 的使用:
没有盘符的是项目工作路径 , 即项目的根目录。
有盘符表示的是文件绝对路径 ,file: 可加可不加。
如果要使用 classpath 路径 , 需要前缀 classpath:。
没有盘符的是项目工作路径 , 即项目的根目录。
有盘符表示的是文件绝对路径 ,file: 可加可不加。
如果要使用 classpath 路径 , 需要前缀 classpath:。
AnnotationConfigApplicationContext:用于读取注解创建容器
Spring AOP原理
AOP是什么?
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
AOP的应用场景有哪些呢?
日志记录
权限验证
事务管理(spring 的事务就是用AOP实现的)
效率检查(个人在代码上,喜欢用注解+切面,实现校验,redis分布式锁等功能)
springAop的底层是怎样实现的?
Spring AOP就是基于动态代理实现的;
- 如果要代理的对象实现了某个接⼝,那么Spring AOP会使⽤JDK Proxy去创建代理对象;
- ⽽对于没有实现接⼝的对象就⽆法使⽤ JDK Proxy 去进⾏代理了,这时候Spring AOP会使⽤ Cglib ⽣成⼀个被代理对象的⼦类来作为代理.
AOP底层是动态代理
有接口 使用 JDK 代理 创建接口实现类的代理增强类的方法
没有接口 使用 CGlib代理 创建一个被代理的子类来做代理(重写增强子类方法)
是编译时期进行织入,还是运行期进行织入?
运行期,生成字节码,再加载到虚拟机中,JDK是利用反射原理,CGLIB使用了ASM原理。
初始化时期织入还是获取对象时织入?
初始化的时候,已经将目标对象进行代理,放入到spring 容器中
spring AOP 默认使用jdk动态代理还是cglib?
要看条件,如果实现了接口的类,是使用jdk。如果没实现接口,就使用cglib。
AOP的基本概念?
连接点
哪些方法可以被增强,这些方法就是连接点
切入点
实际被增强的方法就是切入点
通知(增强)
实际增强的逻辑部分称为通知 有before,after,afterReturning,afterThrowing,around
切面
把增强动作应用到切入点的过程
操作
通知(增强)实现
前置通知:在我们执行目标方法之前运行(@Before)
后置通知:在我们目标方法运行结束之后 ,不管有没有异常(@After)
异常通知:在我们的目标方法出现异常后运行(@AfterThrowing)
最终通知:在我们的目标方法正常返回值后运行(@AfterReturning)
环绕通知:动态代理, 需要手动执行joinPoint.procced()(其实就是执行我们的目标方法执行之前相当于前置通知, 执行之后就相当于我们后置通知(@Around)
增强类 基于注解的方式
@Aspect 定义切面类
@Component 加载到IOC容器
定义切入点函数
execution([权限修饰符][返回类型][全路径][方法名称][参数])
@Before("execution(* com.nine.spring.springaop.IUserDao.addUser(..))")
@Before("execution(* com.nine.spring.springaop.IUserDao.addUser(..))")
@Pointcut注解进行定义切入点函数 表达式只用一次 相同接入点
启动@aspectj的自动代理支持 <aop:aspectj-autoproxy /> spring boot @EnableAspectJAutoProxy
spring AOP 和 AspectJ的关系?
AspectJ 不属于 spring框架,独立的AOP框架。 一般在spring中两者同时使用实现AOP操作。
Spring AOP 属于运⾏时增强,⽽ AspectJ 是编译时增强。
Spring AOP 基于代理(Proxying),⽽AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java ⽣态系统中最完整的 AOP 框架了。
Spring AOP 基于代理(Proxying),⽽AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java ⽣态系统中最完整的 AOP 框架了。
AspectJ 相⽐于 Spring AOP 功能更加强⼤,但是 Spring AOP 相对来说更简单,如果我们的切⾯比较少,那么两者性能差异不⼤。但是,当切⾯太多的话,最好选择 AspectJ ,它⽐Spring AOP 快很多。
容器bean管理
创建Bean的三种方式
第一种方式:使用默认构造函数创建
第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)创建jar包中的类时可使用此方法
第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
Spring 中的 bean 的作⽤域有哪些? scope属性取值
singleton:单例模式(默认的)
prototype:多例模式
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global session:作用于集群环境的会话范围,当不是集群的时候就是session
bean 的生命周期
<!-- bean 的生命周期
单例对象
出生:当容器创建时对象出生
活着:只要容器还在对象就活着
死亡:单例对象的生命周期和容器的生命周期相同
多利对象
出生:当我们使用对象是 spring框架为我们创建对象
活着:只要对象还在使用过程中就还活着
死亡:对象长时间不用,且没有别的对象引用的时候,由Java的垃圾回收器回收。
-->
单例对象
出生:当容器创建时对象出生
活着:只要容器还在对象就活着
死亡:单例对象的生命周期和容器的生命周期相同
多利对象
出生:当我们使用对象是 spring框架为我们创建对象
活着:只要对象还在使用过程中就还活着
死亡:对象长时间不用,且没有别的对象引用的时候,由Java的垃圾回收器回收。
-->
注入的方式有3种
第一种使用:构造方法 第二种使用:set方法 第三种使用:注解的方式
注解
将⼀个类声明为Spring的 bean 的注解有哪些?
@Component:相当于把对象存在容器中 (没有明确对象)
@Service:在业务逻辑层使用
@Repository:在数据访问层使用
@Controller:在展现层使用,控制器的声明
用于注入数据时
@Autowired
自动按照类型注入,只要容器中有唯一的一个bean类型和要注入的变量匹配,就可以注入成功。
如果ioc容器中没有任何bean类型和变量匹配,则注入失败。
如果IOC容器中有多个bean匹配时,根据先按照类型 再 使用变量名称去匹配
如果ioc容器中没有任何bean类型和变量匹配,则注入失败。
如果IOC容器中有多个bean匹配时,根据先按照类型 再 使用变量名称去匹配
@Qualifier
做用:在按照类中注入的基础之上 在同过名称注入。它在给类成员注入时不能单独使用。但是在给方法注入时可以
Qualifier 组合 Autowired 使用
Qualifier 组合 Autowired 使用
@Resource
直接按照bean的Id注入 可以单独使用
@value
属性:value用于指定数据的值 它可以使用spring的SpEl (就是Spring的el表达式)
在spring ioc的过程中,优先解析@Component,@Service,@Controller注解的类。其次解析配置类,也就是@Configuration标注的类。最后开始解析配置类中定义的bean。
Spring注解实现原理
Spring 中的单例 bean 的线程安全问题了解吗?
⼤部分时候我们并没有在系统中使⽤多线程,所以很少有⼈会关注这个问题。单例 bean 存在线程问
题,主要是因为当多个线程操作同⼀个对象的时候,对这个对象的⾮静态成员变量的写操作会存在线程
安全问题。
题,主要是因为当多个线程操作同⼀个对象的时候,对这个对象的⾮静态成员变量的写操作会存在线程
安全问题。
怎么检测是否循环依赖?
Bean创建的时候可以给Bean打标,如果递归调用回来发现正在创建种,说明循环依赖了
spring怎么解决循环依赖问题?
spring种用了哪些设计模式?
代理模式
AOP功能的原理就是使用了代理模式的思想(1.JDK动态代理,2.CGLib 字节码生成技术代理)
单例模式
提供了全局的访问点Bean Factory
工厂模式
spring的beanFactory就是简单的工厂模式,根据传入唯一标识获得bean对象
装饰器模式
依赖注入需要使用BeanWrapper
观察者模式
spring 的监听机制 需要继承ApplicationEvent
策略模式
Bean实例化的时候决定采用哪种方式实现Bean实例(反射或者CGLib动态字节码实现)
JDK代理模式
动态代理和静态代理的区别
CGLib和JDK代理的区别
Spring声明式事务配置
事物配置中有哪些属性可以配置?以下只是简单的使用参考
事务的传播性:@Transactional(propagation=Propagation.REQUIRED)
事务的隔离级别:@Transactional(isolation = Isolation.READ_UNCOMMITTED)
只读:@Transactional(readOnly=true)该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。
事务的超时性:@Transactional(timeout=30)
回滚:
指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
事务的传播性:@Transactional(propagation=Propagation.REQUIRED)
事务的隔离级别:@Transactional(isolation = Isolation.READ_UNCOMMITTED)
只读:@Transactional(readOnly=true)该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。
事务的超时性:@Transactional(timeout=30)
回滚:
指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
Spring MVC
SpringSecurity
Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。可以通(FiltkAgP等技术来实现,Spring Security对Web资源的保护是靠Filter实现的,所以从这kFilter入手,逐步深入Spring Security原理。
当初始化Spring Security时,会创建一个名为springSecurityFilterchain的Servlet过滤器,类型为argspringframework.security.web.FilterChainProxy,它实现了javax.serlet.Filter,因此外部的请求会经过此类
当初始化Spring Security时,会创建一个名为springSecurityFilterchain的Servlet过滤器,类型为argspringframework.security.web.FilterChainProxy,它实现了javax.serlet.Filter,因此外部的请求会经过此类
UsernamePasswordAuthenticationFilter
子主题
Springboot
为什么用springboot?
设计目的是简化Spring应用的搭建和开发过程,为Spring开发提供更加简单的使用和快速开发的技巧
大量的自动配置,不需要编写太多的xml配置文件
内置tomcat服务器,不需要配置tomcat容器就可以直接运行
具有功能更加强大的服务体系,包括嵌入式服务、安全、性能指标、健康检口具有功能更加强大的服务体系,包括嵌入式服务、安全、性能指标、健康检查等
可以很好的兼容第三方插件,只要引入相关依赖就可以实现
注解
spring boot
@Configuration
@ConditionalOnBean
条件注入
子主题
spring boot
@SpringBootApplication
启动类
@EnableEurekaServer
子主题
Restful风格
spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接, 我们只需要传入url及返回值类型即可
SpringCloud
为什么选择springcloud做微服务架构?
整体解决方案和框架成熟 社区热度高 可维护性 学习曲线
什么是springcloud?
Spring Cloud 是一套完整的微服务解决方案,它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
Spring Cloud特别适合中小型互联网公司开发自己的分布式系统基础设施,从容应对业务发展,大大减少开发成本
Spring Cloud特别适合中小型互联网公司开发自己的分布式系统基础设施,从容应对业务发展,大大减少开发成本
分布式微服务下的一站式解决方案,是各个微服务技术落地的技术集合,微服务全家桶。
Spring Cloud是一个微服务框架,相比Dubbo等RPC框架, Spring Cloud提供的全套的分布式系统解决方案。
Spring Cloud对微服务基础框架Netflix的多个开源组件进行了封装,同时又实现了和云端平台以及和Spring Boot开发框架的集成。
Spring Cloud为微服务架构开发涉及的配置管理,服务治理,熔断机制,智能路由,微代理,控制总线,一次性token,全局一致性锁,leader选举,分布式session,集群状态管理等操作提供了一种简单的开发方式。
Spring Cloud 为开发者提供了快速构建分布式系统的工具,开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。
springcloud做用?
为各个微服务之间提供:配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话
springcloud和springboot的关系?
springboot专注于单个个体微服务的快速开发(微观);
SpringCloud是关注全局的微服务协调整理治理框架,整合并管理各个微服务,为各个微服务之间提供,配置管理,服务发现,断路器,路由,事件总线等集成服务
springboot 可以离开 springcloud单独开发应用,但是springcloud 不能离开pringboot
springcloud 是很多组件,都是依赖SpringBoot启动的 这么理解
springcloud 是很多组件,都是依赖SpringBoot启动的 这么理解
springcloud核心组件?
Eureka
服务注册于发现
Ribbon
实现负载均衡,从一个服务的多台机器中选择一台
Feign
基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。
Hystrix
提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。
zuul
网关管理,由 Zuul 网关转发请求给对应的服务。
springcloud和dobbo对比
最大的区别:spring cloud抛弃了dubbo的RPC通信,使用HTTP的REST方式
REST比RPC更加灵活,服务提供方和服务调用方约定协议,不需要代码级别的强依赖,这在强调快速演化的微服务下,显得更加合适
REST比RPC更加灵活,服务提供方和服务调用方约定协议,不需要代码级别的强依赖,这在强调快速演化的微服务下,显得更加合适
Spring Cloud Eureka
Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。
任何一个服务都不能直接去掉用,都需要通过注册中心来调用。通过服务中心来获取服务你不需要关注你调用的项目IP地址,由几台服务器组成,每次直接去服务中心获取可以使用的服务去调用既可。
各个微服务启动时,会通过 Eureka Client 向 Eureka Server 注册自己,Eureka Server 会存储该服务的信息
也就是说,每个微服务的客户端和服务端,都会注册到 Eureka Server,这就衍生出了微服务相互识别的话题
同步:每个 Eureka Server 同时也是 Eureka Client(逻辑上的)
多个 Eureka Server 之间通过复制的方式完成服务注册表的同步,形成 Eureka 的高可用
识别:Eureka Client 会缓存 Eureka Server 中的信息
即使所有 Eureka Server 节点都宕掉,服务消费者仍可使用缓存中的信息找到服务提供者(笔者已亲测)
续约:微服务会周期性(默认30s)地向 Eureka Server 发送心跳以Renew(续约)信息(类似于heartbeat)
续期:Eureka Server 会定期(默认60s)执行一次失效服务检测功能
它会检查超过一定时间(默认90s)没有Renew的微服务,发现则会注销该微服务节点
也就是说,每个微服务的客户端和服务端,都会注册到 Eureka Server,这就衍生出了微服务相互识别的话题
同步:每个 Eureka Server 同时也是 Eureka Client(逻辑上的)
多个 Eureka Server 之间通过复制的方式完成服务注册表的同步,形成 Eureka 的高可用
识别:Eureka Client 会缓存 Eureka Server 中的信息
即使所有 Eureka Server 节点都宕掉,服务消费者仍可使用缓存中的信息找到服务提供者(笔者已亲测)
续约:微服务会周期性(默认30s)地向 Eureka Server 发送心跳以Renew(续约)信息(类似于heartbeat)
续期:Eureka Server 会定期(默认60s)执行一次失效服务检测功能
它会检查超过一定时间(默认90s)没有Renew的微服务,发现则会注销该微服务节点
Eureka Server
Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Server本身也是一个服务,默认情况下会自动注册到Eureka注册中心。
Eureka Server本身也是一个服务,默认情况下会自动注册到Eureka注册中心。
服务注册
服务提供者启动时,会通过 Eureka Client 向 Eureka Server 注册信息,Eureka Server 会存储该服务的信息,Eureka Server 内部有二层缓存机制来维护整个注册表
提供注册表
服务消费者在调用服务时,如果 Eureka Client 没有缓存注册表的话,会从 Eureka Server 获取最新的注册表
同步状态
Eureka Client 通过注册、心跳机制和 Eureka Server 同步当前客户端的状态。
Eureka Client
Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
Eureka Client 是一个 Java 客户端,用于简化与 Eureka Server 的交互。Eureka Client 会拉取、更新和缓存 Eureka Server 中的信息。因此当所有的 Eureka Server 节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者,但是当服务有更改的时候会出现信息不一致。
Eureka客户端是一个java客户端,用来简化与服务器的交互、作为轮询负载均衡器,并提供服务的故障切换支持
搭建流程
创建 Eureka Server
@EnableEurekaServer
注册 Eureka Client 在服务提供端
spring-cloud-starter-eureka
spring-boot-starter-actuator
@EnableEurekaServer
Eureka 自我保护机制
当Eureka Server 节点在短时间内丢失了过多实例的连接时(比如网络故障或频繁启动关闭客户端)节点会进入自我保护模式,保护注册信息,不再删除注册数据,故障恢复时,自动退出自我保护模式。
Eureke 集群搭建
CAP原则
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
作为注册中心 Eureka 和 zookeeper区别
1.ZooKeeper保证的是CP,Eureka保证的是AP
ZooKeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的
Eureka各个节点是平等关系,只要有一台Eureka就可以保证服务可用,而查询到的数据并不是最新的
自我保护机制会导致Eureka不再从注册列表移除因长时间没收到心跳而应该过期的服务
Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(高可用)
当网络稳定时,当前实例新的注册信息会被同步到其他节点中(最终一致性)
Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像ZooKeeper一样使得整个注册系统瘫痪
2.ZooKeeper有Leader和Follower角色,Eureka各个节点平等
3.ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题
4.Eureka本质上是一个工程,而ZooKeeper只是一个进程
ZooKeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的
Eureka各个节点是平等关系,只要有一台Eureka就可以保证服务可用,而查询到的数据并不是最新的
自我保护机制会导致Eureka不再从注册列表移除因长时间没收到心跳而应该过期的服务
Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(高可用)
当网络稳定时,当前实例新的注册信息会被同步到其他节点中(最终一致性)
Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像ZooKeeper一样使得整个注册系统瘫痪
2.ZooKeeper有Leader和Follower角色,Eureka各个节点平等
3.ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题
4.Eureka本质上是一个工程,而ZooKeeper只是一个进程
Spring Cloud Ribbon
Ribbon 是什么?
spring-cloud-starter-eureka-server
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现
通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用
Ribbon 能干吗?
LB 负债均衡(load balance):将用户的请求均摊分配到多个服务上 ,从而达到系统的高可用。旨在优化资源使用,最大吞吐量,最小响应时间并避免任何单一资源的过载
常见的负载均衡的工具:Nginx,Lvs等
dubbo 和 springcloud 都提供了负载均衡 spring cloud负载均衡算法可以自定义
负载均衡的分类
集中式 LB
子主题
进程式
Ribbon中的IRule
负载均衡器Ribbon中的IRule负责选择什么样的负载均衡算法
Ribbon算法
RoundRobinRule:轮询(默认)
RandomRule:随机
RetryRule:重试(先按照轮询规则获取服务,如果获取服务失败则在指定时间内进行重试)
AvailabilityFilteringRule:会先过滤掉由于多次访问故障而处于断路器状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问;
WeightedResponseTimeRule:根据平均响应时间计算所有服务的权重,响应时间越快的服务权重越大被选中的概率越大。刚启动时如果统计信息不足,则使用RoundRobinRule(轮询)策略,等统计信息足够,会切换到WeightedResponseTimeRule;
BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务;
ZoneAvoidanceRule:复合判断Server所在区域的性能和Server的可用性选择服务器;
自定义算法
在配置类中注入相应的Bean
@Bean //修改轮询规则为随机
public IRule iRule(){
return new RandomRule();
}
public IRule iRule(){
return new RandomRule();
}
编写自定义的轮询规则
编写自定义的轮写规则类 继承 AbstractLoadBalancerRule
编写配置类将自定义的规则类注入到IOC容器中
@RibbonClient(name = “PROVIDERPRODUCT”,configuration = MyRuleConfig.class)
Ribbon中的IRule自定义原则
请注意,自定义的必须是@Configuration,它不在主应用程序上下文的@ComponentScan中,否则将由所有@RibbonClients共享。
如果您使用@ComponentScan(或@SpringBootApplication),则需要采取措施避免包含(例如将其放在一个单独的,不重叠的包中,或者指定要在@ComponentScan)。
如果您使用@ComponentScan(或@SpringBootApplication),则需要采取措施避免包含(例如将其放在一个单独的,不重叠的包中,或者指定要在@ComponentScan)。
Spring Cloud Feign
Feign是什么?
Feign是一个封装了http请求的轻量级框架,通过接口注解的方式实现HTTP调用(面向接口的编程方式)
Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
Feign做什么?
封装了Http调用流程,更适合面向接口化的变成习惯
Feign是如何设计的?
子主题
子主题
子主题
子主题
子主题
子主题
子主题
feign和ribbon区别?
两个都是实现软负载均衡的组件。Ribbon和Feign都是用于调用其它服务
Feign 是在 Ribbon 的基础上进行了一次改进,是一个使用起来更加方便的 HTTP 客户端。采用接口的方式, 只需要创建一个接口,然后在上面添加注解即可 ,将需要调用的其他服务的方法定义成抽象方法即可, 不需要自己构建 http 请求。然后就像是调用自身工程的方法调用,而感觉不到是调用远程方法,使得编写 客户端变得非常容易
不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致
不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致
Ribbon 是一个基于 HTTP 和 TCP 客户端 的负载均衡的工具。它可以 在客户端 配置 RibbonServerList(服务端列表),使用 HttpClient 或 RestTemplate 模拟 http 请求
Spring Cloud Hystrix
雪崩效应
简单的来说就是由于服务提供者A不可用,导致服务调用者B对A的请求阻塞,没有相关的机制通知或解决请求阻塞,导致在服务调用者B对A请求的阻塞越来越多,阻塞请求变多并且不断对A进行请求重试导致服务调用者B所在的系统的资源会被耗尽,而服务调用者B所在的系统可能并不会只有对A的调用,还有存在对其他服务提供者的调用,因为调用A把系统资源已经耗尽了,导致也无法处理对非A请求,而且这种不可用可能沿请求调用链向上传递,比如说服务调用者C会调用B的服务,因为B所在的系统不可用,导致C也不可用,这样级联导致阻塞请求越来越多,表现为多个系统都不可用了,这种现象被称为"雪崩效应"。
常见的导致雪崩的情况有以下几种
程序bug导致服务不可用,或者运行缓慢
缓存击穿,导致调用全部访问某服务,导致down掉
访问量的突然激增。
硬件问题,这感觉只能说是点背了
解决方案
横向扩充
限流
熔断
什么是Hystrix?
在分布式环境中,不可避免地,许多服务依赖关系中的一些会失败。hystrix是一个库,它通过增加延迟容差和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止跨服务的级联故障以及提供回退选项来实现这一点,所有这些都提高了系统的整体弹性。
在分布式系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是依赖服务,有的时候某些依赖服务出现故障也是很常见的。
Hystrix 可以让我们在分布式系统中对服务间的调用进行控制,加入一些调用延迟或者依赖故障的容错机制。Hystrix 通过将依赖服务进行资源隔离,进而阻止某个依赖服务出现故障时在整个系统所有的依赖服务调用中进行蔓延;同时Hystrix 还提供故障时的 fallback 降级机制。
总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。
Hystrix 可以让我们在分布式系统中对服务间的调用进行控制,加入一些调用延迟或者依赖故障的容错机制。Hystrix 通过将依赖服务进行资源隔离,进而阻止某个依赖服务出现故障时在整个系统所有的依赖服务调用中进行蔓延;同时Hystrix 还提供故障时的 fallback 降级机制。
总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。
Hystrix什么作用?
通过第三方客户端库提供对延迟和依赖访问(通常通过网络)故障的保护和控制
停止复杂分布式系统中的级联故障
快速失败并迅速恢复
在可能的情况下,后退并优雅地降级。
实现近实时监控,警报和操作控制。
Hystrix能干嘛?
服务熔断
服务熔断 在服务端实现
启动类:@EnableCircuitBreaker 添加对熔断器的配置
服务异常的方法可以注释:@HystrixCommand 然后指定处理方式
当某个异常条件被触发就直接熔断整个服务,而不是一直等到此服务超时
服务降级
服务降级在客户端实现:当线程达到阈值的时候就启动服务降级,如果其他请求继续访问就直接返回fallback的默认值
1.implements FallbackFactory 写一个服务处理工厂
2.@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceFallbackFactory.class)
和feign一起使用
和feign一起使用
Hystrix监控
创建监控页面:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
将服务加入监控中:@EnableCircuitBreaker //添加对熔断器的配置
在服务端:添加一个servlet bean
在服务端:添加一个servlet bean
服务限流
springcloud断路器作用?
当一个服务调用另一个服务由于网络原因或自身原因出现问题,调用者就会等待被调用者的响应 当更多的服务请求到这些资源导致更多的请求等待,发生连锁效应(雪崩效应)
断路器有完全打开状态:一段时间内 达到一定的次数无法调用 并且多次监测没有恢复的迹象 断路器完全打开 那么下次请求就不会请求到该服务
半开:短时间内 有恢复迹象 断路器会将部分请求发给该服务,正常调用时 断路器关闭
关闭:当服务一直处于正常状态 能正常调用
断路器有完全打开状态:一段时间内 达到一定的次数无法调用 并且多次监测没有恢复的迹象 断路器完全打开 那么下次请求就不会请求到该服务
半开:短时间内 有恢复迹象 断路器会将部分请求发给该服务,正常调用时 断路器关闭
关闭:当服务一直处于正常状态 能正常调用
Spring Cloud Zuul
什么是zuul
zuul旨在实现动态路由、监控、弹性和安全性。它还能够根据需要将请求路由到多个amazon自动伸缩组。
路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础,
过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。
Zuul和Eureka进行整合, 将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。
Zuul和Eureka进行整合, 将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
子主题
什么是SpringCloudConfig?
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。
在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。
在spring cloud config 组件中,分两个角色,一是config server,二是config client。
在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。
在spring cloud config 组件中,分两个角色,一是config server,二是config client。
1、添加pom依赖
2、配置文件添加相关配置
3、启动类添加注解@EnableConfigServer
2、配置文件添加相关配置
3、启动类添加注解@EnableConfigServer
bootstrap.yml和application.yml
bootstrap.yml(bootstrap.properties)用来在程序引导时执行,应用于更加早期配置信息读取,如可以使用来配置application.yml中使用到参数等
application.yml(application.properties) 应用程序特有配置信息,可以用来配置后续各个模块中需使用的公共参数等。
bootstrap.yml 先于 application.yml 加载
application.yml(application.properties) 应用程序特有配置信息,可以用来配置后续各个模块中需使用的公共参数等。
bootstrap.yml 先于 application.yml 加载
当使用 Spring Cloud Config Server 的时候,你应该在 bootstrap.yml 里面指定 spring.application.name 和 spring.cloud.config.server.git.uri
和一些加密/解密的信息
和一些加密/解密的信息
什么是Spring Cloud Bus?
spring cloud bus(消息总线) 将分布式的节点用轻量的消息代理连接起来,它可以用于广播配置文件的更改或者服务直接的通讯,也可用于监控。
如果修改了配置文件,发送一次请求,所有的客户端便会重新读取配置文件。
如果修改了配置文件,发送一次请求,所有的客户端便会重新读取配置文件。
1.添加依赖,2.配置rabbimq
Mybatis
#{}和${}的区别是什么?
${} 是 Properties ⽂件中的变量占位符,它可以⽤于标签属性值和 sql 内部,属于静态⽂本替换,⽐如${driver}会被静态替换为 com.mysql.jdbc.Driver 。
#{} 是 sql 的参数占位符,Mybatis 会将 sql 中的 #{} 替换为?号 #{item.name}
当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
查询的sql语句中定义字段名的别名
通过<resultMap>来映射字段名和实体类属性名的一一对应的关系
模糊查询like语句该怎么写?
在Java代码中添加sql通配符
在sql语句中拼接通配符,会引起sql注入
Dao 接⼝的⼯作原理是什么?Dao 接⼝⾥的⽅法,参数不同时,⽅法能重载吗?
Dao接口的名称: 就是对应的Mapper.xml中namespace的值
Dao接口的方法名:就是映射文件中Mapper的Statement的id值
Dao接口方法内的参数就是传递给sql的参数
Dao接口中方法是不能重载的,因为是全限名+方法名的保存和寻找策略。
Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
Mybatis是如何进行分页的?分页插件的原理是什么?
可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
Mybatis - 通用分页拦截器 PageHelper
项目地址 : http://git.oschina.net/free/Mybatis_PageHelper
项目地址 : http://git.oschina.net/free/Mybatis_PageHelper
Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
第一种是使用<resultMap>标签,逐一定义数据库列名和对象属性名之间的映射关系。
第二种是使用sql列的别名功能,将列的别名书写为对象属性名。
第二种是使用sql列的别名功能,将列的别名书写为对象属性名。
如何执行批量插入?
设置SqlSession为批量操作类型
通过foreach 遍历需要操作的值
如何获取自动生成的(主)键值?
传入的对象中可以取到
在mapper中如何传递多个参数?
- 直接接口方法中传入参数
- 注解@param
- 封装Map集合
Mybatis动态sql有什么用?执行原理?有哪些动态sql?
Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接sql的功能。
Mybatis提供了9种动态sql标签:trim | where | set | foreach | if | choose | when | otherwise | bind。
Mybatis提供了9种动态sql标签:trim | where | set | foreach | if | choose | when | otherwise | bind。
Xml 映射⽂件中,除了常⻅的 select|insert|updae|delete 标签之外,还有哪些标签?
还有很多其他的标签, <resultMap> 、 <parameterMap> 、 <sql> 、 <include> 、 <selectKey> ,加上动态 sql的 9 个标签, trim|where|set|foreach|if|choose|when|otherwise|bind 等,其中为 sql⽚段标签,通过 <include> 标签引⼊ sql ⽚段, <selectKey> 为不⽀持⾃增的主键⽣成策略标签。
MyBatis实现一对一有几种方式?具体怎么操作的?
有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成;
嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置,但另外一个表的查询通过select属性配置。
嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置,但另外一个表的查询通过select属性配置。
MyBatis实现一对多有几种方式,怎么操作的?
有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的collection节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的 结果的外键id,去再另外一个表里面查询数据,也是通过配置collection,但另外一个表的查询通过select节点配置。
Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
association指的就是一对一
collection指的就是一对多查询
collection指的就是一对多查询
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
Mybatis的一级、二级缓存
1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置<cache/> ;
3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置<cache/> ;
3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新。
什么是MyBatis的接口绑定?有哪些实现方式?
接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。
接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定;另外一种就是通过xml里面写SQL来绑定, 在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定, 当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。
接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定;另外一种就是通过xml里面写SQL来绑定, 在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定, 当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。
使用MyBatis的mapper接口调用时有哪些要求?
① Mapper接口方法名和mapper.xml中定义的每个sql的id相同;
② Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;
③ Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;
④ Mapper.xml文件中的namespace即是mapper接口的类路径。
② Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;
③ Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;
④ Mapper.xml文件中的namespace即是mapper接口的类路径。
简述Mybatis的插件运行原理,以及如何编写一个插件。
Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
编写插件:实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
编写插件:实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
Mybatis自动生成插件 generatorConfiguration
mapper xml文件
生成实体类 实体Example
生成mapper接口
生成sql映射文件
sql注入
sql中的通配符 替换的不是变量,而是一段sql片段,导致sql结构变化。
高级编程
软件架构
MVC架构
其实MVC架构就是一个单体架构。
代表技术:Struts2、springMVC、Spring、Mybatis 等等。
RPC架构
RPC(Remote Procedure Call)远程过程调用,他是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议
代表技术:Thrift、Hessian等等。
SOA架构
SOA(Service Oriented Architecture)面向服务架构。
代表技术:Mule、WSO2
SOA是基于分布式架构演变过来的,SOA架构代表面向服务架构,面向与业务逻辑层。
将共同的业务代码抽取出来服务化,给其他的接口调用。服务与服务之间调用 用的是RPC远程调用技术
将共同的业务代码抽取出来服务化,给其他的接口调用。服务与服务之间调用 用的是RPC远程调用技术
SOA实现方式:底层基于SOAP和ESB(数据总线)实现的
ESB(企业服务总线),简单 来说 ESB 就是一根管道,用来连接各个服务节点。为了集 成不同系统,不同协议的服务,ESB 做了消息的转化解释和路由工作,让不同的服务互联互通。
SOAP 是一种简单的基于 XML 的协议,它使应用程序通过 HTTP 来交换信息。
SOA架构特点
系统集成
站在系统的角度,解决企业系统间的通信问 题,把原先散乱、无规划的系统间的网状结构,梳理成 规整、可治理的系统间星形结构,这一步往往需要引入 一些产品,比如 ESB、以及技术规范、服务管理规范; 这一步解决的核心问题是【有序】
系统的服务化
站在功能的角度,把业务逻辑抽象成 可复用、可组装的服务,通过服务的编排实现业务的 快速再生,目的:把原先固有的业务功能转变为通用 的业务服务,实现业务逻辑的快速复用;这一步解决 的核心问题是【复用】
业务的服务化
站在企业的角度,把企业职能抽象成 可复用、可组装的服务;把原先职能化的企业架构转变为服务化的企业架构,进一步提升企业的对外服务能力;“前面两步都是从技术层面来解决系统调用、系统功能复用的问题”。第三步,则是以业务驱动把一个业务单元封装成一项服务。这一步解决的核心问题是【高效】
SOA架构和微服务架构的区别
1.SOA(Service Oriented Architecture)“面向服务的架构”: 其中包含多个服务, 服务之间通过相互依赖最终提供一系列的功能。一个服务 通常以独立的形式存在与操作系统进程中。各个服务之间 通过网络调用。
2.微服务架构:其实和 SOA 架构类似,微服务是在 SOA 上做的升华,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。
2.微服务架构:其实和 SOA 架构类似,微服务是在 SOA 上做的升华,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。
微服务架构
微服务其实就是一个轻量级的服务治理方案
代表技术:SpringCloud、dubbo等等
微服务
定义
微服务是一种架构风格:微服务的核心就是将一站式应用,根据业务拆分成一个个的服务,每个服务提供单个业务功能的服务。
马丁福勒
微服务这种架构风格就是把一组小服务演化成为一个单一的应用的一种方法。每个应用都运行在自己的进程中,并通过轻量级的机制保持通信,就像HTTP这样的API。这些服务要基于业务场景,并使用自动化布署工具进行独立的发布。可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以使用不同的数据存储
优点
单一职责功能,每个服务都很简单,只关注于一个业务功能
易于规模化开发,多个开发团队可以并行开发,每个团队负责一项服务,容器启动很快,快速加载较小的项目,这使开发人员的工作效率更高
每个单体应用不局限于固定的技术栈,开发者可以自由选择开发技术,提供API服务。
每个微服务独立的开发,部署
每个服务都可以拥有自己独立的数据库
改善故障隔离。一个服务宕机不会影响其他的服务
缺点
数据的一致性
多个微服务之间的原子事务通常是不可能的。业务需求必须包含多个微服务之间的最终一致性
服务的容错性
依赖的下游系统宕机或者由于网络问题导致服务长时间不能响应
系统的性能
当用户对系统发起一个请求后,最上游的服务要经过多次网络传输、多次请求/应答数据解析才能完成一个整体的请求交互
实施复杂
小而多的应用服务必然需要更多的部署节点
我们必须接受自动化文化,自动化部署和自动化测试方案必须要在微服务实施开始前就规划好
开发人员需要处理分布式系统的复杂性
随着服务的增加,运维难度增加
系统部署服务之间的依赖关系
系统的集成测试难度增加
微服务技术栈
服务开发 SpringBoot、Spring、SpringMVC
服务配置与管理 Netflix公司的Archaius、阿里的Diamind等
服务注册与发现 Eureka、Consul、Zookeeper等
服务调用 Rest、RPC、gRPC
服务熔断器 Hystrix、Envoy等
负载均衡 Ribbon、Nginx等
服务接口调用(客户端调用服务的简化工具) Feign等
消息队列 Kafka、RabbitMQ、ActiveMQ等
服务配置中心管理 SpringCloudConfig、Chef等
服务路由(API网关) Zuul等
服务监控 Zabbix、Nagios、Metrics、Spectator
全链路追踪 Zipkin、Brave、Dapper等
服务部署 Docker、OpernStack、Kubernetes
数据流操作开发包 SpringCloud Stream(封装与Redis,Rabbit,Kafaka等发送接收消息)
事件消息总线 Spring Cloud Bus
服务配置与管理 Netflix公司的Archaius、阿里的Diamind等
服务注册与发现 Eureka、Consul、Zookeeper等
服务调用 Rest、RPC、gRPC
服务熔断器 Hystrix、Envoy等
负载均衡 Ribbon、Nginx等
服务接口调用(客户端调用服务的简化工具) Feign等
消息队列 Kafka、RabbitMQ、ActiveMQ等
服务配置中心管理 SpringCloudConfig、Chef等
服务路由(API网关) Zuul等
服务监控 Zabbix、Nagios、Metrics、Spectator
全链路追踪 Zipkin、Brave、Dapper等
服务部署 Docker、OpernStack、Kubernetes
数据流操作开发包 SpringCloud Stream(封装与Redis,Rabbit,Kafaka等发送接收消息)
事件消息总线 Spring Cloud Bus
微服务架构有哪些?
SpringCloud,阿里 dubbo / HSF
微服务方案
一站式解决方案 spring cloud NetFlix
半自动 dubbo zookeeper
一站式解决方案 spring cloud Alibaba
微服务之间是如何独立通讯的?
远程过程调用
也就是我们常说的服务的注册与发现,直接通过远程过程调用来访问别的service。
优点:简单,常见,因为没有中间件代理,系统更简单
缺点:
只支持请求/响应的模式,不支持别的,比如通知、请求/异步响应、发布/订阅、发布/异步响应
降低了可用性,因为客户端和服务端在请求过程中必须都是可用的
优点:简单,常见,因为没有中间件代理,系统更简单
缺点:
只支持请求/响应的模式,不支持别的,比如通知、请求/异步响应、发布/订阅、发布/异步响应
降低了可用性,因为客户端和服务端在请求过程中必须都是可用的
消息
使用异步消息来做服务间通信。服务间通过消息管道来交换消息,从而通信。
优点:
把客户端和服务端解耦,更松耦合
提高可用性,因为消息中间件缓存了消息,直到消费者可以消费
支持很多通信机制比如通知、请求/异步响应、发布/订阅、发布/异步响应
缺点:消息中间件有额外的复杂
优点:
把客户端和服务端解耦,更松耦合
提高可用性,因为消息中间件缓存了消息,直到消费者可以消费
支持很多通信机制比如通知、请求/异步响应、发布/订阅、发布/异步响应
缺点:消息中间件有额外的复杂
服务提供者与服务消费者
服务提供者 provide:被其他微服务调用的微服务
服务消费者 customer :调用的其他微服务的微服务
分布式
定义
- 一个业务拆分成多个子业务,每个子业务分别部署在不同的服务器上
- 分布式一定是微服务,微服务不一定是分布式。
- 把一个服务拆分成多个子服务,分别放在不同的服务器上。微服务可以放在同一个服务器上,也可以放在不同的服务器上。
分布式 CAP
任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。
分布式事务
两阶段提交(2PC)
两阶段提交(Two-phase Commit,2PC),通过引入 协调者(Coordinator)来协调 参与者 的行为,并最终决定这些 参与者 是否要真正执行事务。
准备阶段
协调者询问参与者事务是否执行成功,参与者发回事务执行结果。
提交阶段
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
存在的问题
同步阻塞 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。
单点问题 协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
数据不一致 在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
太过保守 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
补偿事务(TCC)
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作
Try 阶段主要是对业务系统做检测及资源预留
Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功
Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。
本地消息表
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。
- 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
- 之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
- 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
- 优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。
- 缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
MQ 事务消息
分布式锁
为什么要使用分布式锁
为了保证高并发情况下,同一个方法或者一个变量只能被同一个线程执行
在单体应用种 可以直接加 ReentrantLock 和 Synchronized
由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效
分布式锁应该具备哪些条件
1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、高可用的获取锁与释放锁;
3、高性能的获取锁与释放锁;
4、具备可重入特性;
5、具备锁失效机制,防止死锁;
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
分布式锁的三种实现方式
基于数据库实现分布式锁;
基于数据库实现分布式锁;
基于数据库实现分布式锁;
集群
定义
同一个业务,部署在多个服务器上
分布式与集群的区别是什么?
集群是个物理形态,分布式是个工作方式
分布式 是将不同的业务放在不同的服务器上面执行,集群是同一个业务同一时刻在不同的服务器上面运行
分布式实现的是缩短单个任务的执行时间来提高效率, 集群 是单位时间内执行的任务数量提高效率(高可用)
分布式:不同的业务模块部署到不同的服务器上同时执行(高并发)
集群 : 同一业务在不同服务器上面执行(高可用)
集群 : 同一业务在不同服务器上面执行(高可用)
高可用
设计模式 和 微服务拆分思想
Linux操作
常用命令
mkdir
# "指定目录"下创建文件夹:"home"目录下创建"a"文件夹,-p 确保目录名称存在,不存在的就建一个。
mkdir -p /home/a
# "在当前目录"下创建文件夹
mkdir a
# "在当前目录"下创建"多个"文件夹
mkdir a b
mkdir -p /home/a
# "在当前目录"下创建文件夹
mkdir a
# "在当前目录"下创建"多个"文件夹
mkdir a b
touch
# "指定"目录下创建 a.txt文件
touch /home/a.txt
# "在当前"目录下一次性创建多个文件
touch a.java b.java
touch /home/a.txt
# "在当前"目录下一次性创建多个文件
touch a.java b.java
vim
# "指定"目录下创建 a.txt文件
vim /home/a.txt
# "在当前"目录下创建文件
vim a.txt
子主题
Redis
数据库表数据变大后的影响?
再有索引的情况下,增删改会有很大的影响(索引维护)。
- 查询简单,访问量小,查询速度影响不大。
- 高并发时,带宽会影响查询速度。
简介
Redis 是非关系型数据库,与传统的数据库不同在于它存在内存中。
Redis应用场景
数据库
缓存
消息代理
作用
⾼性能
⽤户第⼀次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该⽤户访问的数据存在缓存中,这样下⼀次再访问这些数据的时候就可以直接从缓存中获取了
⾼并发
直接操作缓存能够承受的请求是远远⼤于直接访问数据库的
Redis提供数据结构Key—Value
字符串,哈希,列表,集合,带范围查询的排序集合,位图,超日志,地理空间索引和流
redis 和 memcached 的区别
性能区别
redis 只使用单核线程。平均每一个核上 redis 在存储小数据时比 memcached 性能更高
memcached 可以使用多核。在 100k 以上的数据中,memcached 性能要高于 redis
memcached 可以使用多核。在 100k 以上的数据中,memcached 性能要高于 redis
数据类型不同
redis有五种(list set string hash zset) ⽀持更丰富的数据类型(⽀持更复杂的应⽤场景)
memcached结构单一 只有String
memcached结构单一 只有String
持久化
Redis支持持久化(数据备份) redis支持(快照、AOF):依赖快照进行持久化,aof增强了可靠性的同时,对性能有所影响
Redis⽀持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进⾏使⽤,⽽。
Redis⽀持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进⾏使⽤,⽽。
Memecache把数据全部存在内存之中,不支持持久化,通常用在做缓存,提升性能
集群模式:memcached没有原⽣的集群模式,需要依靠客户端来实现往集群中分⽚写⼊数据;但是 redis ⽬前是原⽣⽀持 cluster 模式的.
Memcached是多线程,⾮阻塞IO复⽤的⽹络模型;Redis使⽤单线程的多路 IO 复⽤模型。
redis 单线程模型也能效率这么高?
- 纯内存操作
- 核心是基于非阻塞的 IO 多路复用机制
- 单线程反而避免了多线程的频繁上下文切换问题
redis 常⻅数据结构以及使⽤场景分析
String
的key-value类型 常规计数:微博数,粉丝数等。
Hash
sh 是⼀个 string 类型的 field 和 value 的映射表 hash 特别适合⽤于存储对象
List
list 就是链表
Redis最重要的数据结构之⼀,⽐如微博的关注列表,粉丝列表,消息列表等功能都可以⽤Redis的 list 结构来实现
Redis最重要的数据结构之⼀,⽐如微博的关注列表,粉丝列表,消息列表等功能都可以⽤Redis的 list 结构来实现
Set
set 对外提供的功能与list类似是⼀个列表的功能,特殊之处在于 set 是可以⾃动排重的
Sorted Set
和set相⽐,sorted set增加了⼀个权重参数score,使得集合中的元素能够按score进⾏有序排列
在直播系统中,实时排⾏信息包含直播间在线⽤户列表,各种礼物排⾏榜,弹幕消息
在直播系统中,实时排⾏信息包含直播间在线⽤户列表,各种礼物排⾏榜,弹幕消息
如何解决 Redis 的并发竞争 Key 问题
分布式锁
准备一个分布式锁,大家去抢锁,抢到锁就做set操作
利用消息队列
并发量过大的情况下,可以通过消息中间件进行处理,把并行读写进行串行化
子主题
子主题
子主题
开发中常用技巧
Swagger
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务
Nginx
Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。
正向代理和反向代理
正向代理
正向代理类似一个跳板机,代理访问外部资源
通过VPN的方式访问就是正向代理。
正向代理即是客户端代理, 代理客户端, 服务端不知道实际发起请求的客户端.
反向代理
正向代理即是客户端代理, 代理客户端, 服务端不知道实际发起请求的客户端.
通过Nginx的方式访问就是反向代理。
反向代理即是服务端代理, 代理服务端, 客户端不知道实际提供服务的服务端
保证内网的安全,阻止web攻击,大型网站,通常将反向代理作为公网访问地址,Web服务器是内网
负载均衡,通过反向代理服务器来优化网站的负载
负载均衡,通过反向代理服务器来优化网站的负载
负债均衡
轮询法(Nginx默认方式)
轮询很容易实现,将请求按顺序轮流分配到后台服务器上,均衡的对待每一台服务器,而不关心服务器实际的连接数和当前的系统负载
随机法
通过系统随机函数,根据后台服务器列表的大小值来随机选取其中一台进行访问。由概率概率统计理论可以得知,随着调用量的增大,其实际效果越来越接近于平均分配流量到后台的每一台服务器,也就是轮询法的效果。
源地址哈希法
源地址哈希法的思想是根据服务消费者请求客户端的IP地址,通过哈希函数计算得到一个哈希值,将此哈希值和服务器列表的大小进行取模运算,得到的结果便是要访问的服务器地址的序号。采用源地址哈希法进行负载均衡,相同的IP客户端,如果服务器列表不变,将映射到同一个后台服务器进行访问。
加权轮询法
跟配置高、负载低的机器分配更高的权重,使其能处理更多的请求,而配置低、负载高的机器,则给其分配较低的权重,降低其系统负载,加权轮询很好的处理了这一问题,并将请求按照顺序且根据权重分配给后端
加权随机
加权随机法跟加权轮询法类似,根据后台服务器不同的配置和负载情况,配置不同的权重。不同的是,它是按照权重来随机选取服务器的,而非顺序。
最小连接数法
最小连接数法比较灵活和智能,由于后台服务器的配置不尽相同,对请求的处理有快有慢,它正是根据后端服务器当前的连接情况,动态的选取其中当前积压连接数最少的一台服务器来处理当前请求,尽可能的提高后台服务器利用率,将负载合理的分流到每一台服务器
动静分离
将静态页面与动态页面或者静态内容接口和动态内容接口分开不同系统访问的架构设计方法,进而提升整个服务访问性能和可维护性。
安装
下载——解压——自动配置(./configure)——make——make install
常用命令
安装后的位置
cd /usr/local/nginx/sbin/
启动
./nginx
停止
./nginx -s stop
安全退出
./nginx -s quit
重新加载配置文件
./nginx -s reload
查看进程
ps -aux|grep nginx
注解和反射
0 条评论
下一页