├── Java核心面试知识集—Dubbo面试题.md ├── Java核心面试知识集—JVM面试题.md ├── Java核心面试知识集—Java基础知识面试题.md ├── Java核心面试知识集—Java并发编程面试题.md ├── Java核心面试知识集—Java异常面试题.md ├── Java核心面试知识集—Java集合容器面试题.md ├── Java核心面试知识集—Kafka面试题.md ├── Java核心面试知识集—Linux面试题.md ├── Java核心面试知识集—MongDB面试题.md ├── Java核心面试知识集—MyBatis面试题.md ├── Java核心面试知识集—MySQL面试题.md ├── Java核心面试知识集—Netty面试题.md ├── Java核心面试知识集—RabbitMQ面试题.md ├── Java核心面试知识集—Redis面试题.md ├── Java核心面试知识集—Spring Cloud面试题.md ├── Java核心面试知识集—SpringBoot面试题.md ├── Java核心面试知识集—SpringMVC面试题.md ├── Java核心面试知识集—Spring面试题.md ├── Java核心面试知识集—Tomcat面试题.md ├── Java核心面试知识集—zookeeper面试题.md ├── Java核心面试知识集—大厂数据库面试题.md ├── Java核心面试知识集—常见面试算法题.md ├── Java核心面试知识集—架构设计&分布式&数据结构与算法面试题.md ├── Java核心面试知识集—计算机网络基础.md ├── Java核心面试知识集—设计模式.md └── README.md /Java核心面试知识集—Dubbo面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ## 1、为什么要用Dubbo? 26 | 27 | 随着服务化的进一步发展,服务越来越多,服务之间的调用和依赖关系也越来越复杂,诞生了面向服务的架构体系(SOA), 28 | 29 | 也因此衍生出了一系列相应的技术,如对服务提供、服务调用、连接处理、通信协议、序列化方式、服务发现、服务路由、日志输出等行为进行封装的服务框架。 30 | 31 | 就这样为分布式系统的服务治理框架就出现了,Dubbo也就这样产生了。 32 | 33 | ## 2、Dubbo 的整体架构设计有哪些分层? 34 | 35 | 接口服务层(Service):该层与业务逻辑相关,根据 provider 和 consumer 的业务设计对应的接口和实现 36 | 37 | 配置层(Config):对外配置接口,以 ServiceConfig 和 ReferenceConfig 为中心 38 | 39 | 服务代理层(Proxy):服务接口透明代理,生成服务的客户端 Stub 和 服务端的 Skeleton,以 ServiceProxy 为中心,扩展接口为 ProxyFactory 40 | 41 | 服务注册层(Registry):封装服务地址的注册和发现,以服务 URL 为中心,扩展接口为 RegistryFactory、Registry、RegistryService 42 | 43 | 路由层(Cluster):封装多个提供者的路由和负载均衡,并桥接注册中心,以Invoker 为中心,扩展接口为 Cluster、Directory、Router和LoadBlancce 44 | 45 | 监控层(Monitor):RPC调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory、Monitor和MonitorService 46 | 47 | 远程调用层(Protocal):封装 RPC 调用,以 Invocation 和 Result 为中心,扩展接口为 Protocal、Invoker和Exporter 48 | 49 | 信息交换层(Exchange):封装请求响应模式,同步转异步。以 Request 和 Response 为中心,扩展接口为 Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer 50 | 51 | 网络传输层(Transport):抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为Channel、Transporter、Client、Server和Codec 52 | 53 | 数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool 54 | 55 | ## 3、默认使用的是什么通信框架,还有别的选择吗? 56 | 57 | 默认也推荐使用netty框架,还有mina。 58 | 59 | ## 4、服务调用是阻塞的吗? 60 | 61 | 默认是阻塞的,可以异步调用,没有返回值的可以这么做。 62 | 63 | Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。 64 | 65 | ## 5、一般使用什么注册中心?还有别的选择吗? 66 | 67 | 推荐使用 Zookeeper 作为注册中心,还有 Redis、Multicast、Simple 注册中心,但不推荐。 68 | 69 | ## 6、默认使用什么序列化框架,你知道的还有哪些? 70 | 71 | 推荐使用Hessian序列化,还有Duddo、FastJson、Java自带序列化。 72 | 73 | ## 7、服务提供者能实现失效踢出是什么原理? 74 | 75 | 服务失效踢出基于zookeeper的临时节点原理。 76 | 77 | ## 8、服务上线怎么不影响旧版本? 78 | 79 | 采用多版本开发,不影响旧版本。 80 | 81 | ## 9、如何解决服务调用链过长的问题? 82 | 83 | 可以结合zipkin实现分布式服务追踪。 84 | 85 | ## 10、说说核心的配置有哪些? 86 | 87 | | 配置 | 配置说明 | 88 | 89 | | --- | --- | 90 | 91 | | dubbo:service | 服务配置 | 92 | 93 | | dubbo:reference | 引用配置 | 94 | 95 | | dubbo:protocol | 协议配置 | 96 | 97 | | dubbo:application | 应用配置 | 98 | 99 | | dubbo:module | 模块配置 | 100 | 101 | | dubbo:registry | 注册中心配置 | 102 | 103 | | dubbo:monitor | 监控中心配置 | 104 | 105 | | dubbo:provider | 提供方配置 | 106 | 107 | | dubbo:consumer | 消费方配置 | 108 | 109 | | dubbo:method | 方法配置 | 110 | 111 | | dubbo:argument | 参数配置 | 112 | 113 | ## 11、Dubbo 推荐用什么协议? 114 | 115 | · dubbo://(推荐) 116 | 117 | · rmi:// 118 | 119 | · hessian:// 120 | 121 | · http:// 122 | 123 | · webservice:// 124 | 125 | · thrift:// 126 | 127 | · memcached:// 128 | 129 | · redis:// 130 | 131 | · rest:// 132 | 133 | ## 12、同一个服务多个注册的情况下可以直连某一个服务吗? 134 | 135 | 可以点对点直连,修改配置即可,也可以通过telnet直接某个服务。 136 | 137 | ## 13、画一画服务注册与发现的流程图? 138 | 139 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-be34bef59137b1aa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 140 | 141 | ## 14、Dubbo 集群容错有几种方案? 142 | 143 | | 集群容错方案 | 说明 | 144 | 145 | | --- | --- | 146 | 147 | | Failover Cluster | 失败自动切换,自动重试其它服务器(默认) | 148 | 149 | | Failfast Cluster | 快速失败,立即报错,只发起一次调用 | 150 | 151 | | Failsafe Cluster | 失败安全,出现异常时,直接忽略 | 152 | 153 | | Failback Cluster | 失败自动恢复,记录失败请求,定时重发 | 154 | 155 | | Forking Cluster | 并行调用多个服务器,只要一个成功即返回 | 156 | 157 | | Broadcast Cluster | 广播逐个调用所有提供者,任意一个报错则报错 | 158 | 159 | ## 15、Dubbo 服务降级,失败重试怎么做? 160 | 161 | 162 | 163 | ## 16、Dubbo 使用过程中都遇到了些什么问题? 164 | 165 | 166 | 167 | ## 17、Dubbo Monitor 实现原理? 168 | 169 | 170 | 171 | ## 18、Dubbo 用到哪些设计模式? 172 | 173 | 174 | 175 | ## 19、Dubbo 配置文件是如何加载到Spring中的? 176 | 177 | 178 | 179 | ## 20、Dubbo SPI 和 Java SPI 区别? 180 | 181 | 182 | 183 | ## 21、Dubbo 支持分布式事务吗? 184 | 185 | 186 | 187 | ## 22、Dubbo 可以对结果进行缓存吗? 188 | 189 | 190 | 191 | ## 23、服务上线怎么兼容旧版本? 192 | 193 | 194 | 195 | ## 24、Dubbo必须依赖的包有哪些? 196 | 197 | 198 | 199 | ## 25、Dubbo telnet 命令能做什么? 200 | 201 | 202 | 203 | ## 26、Dubbo 支持服务降级吗? 204 | 205 | 206 | 207 | ## 27、Dubbo 如何优雅停机? 208 | 209 | 210 | 211 | ## 28、Dubbo 和 Dubbox 之间的区别? 212 | 213 | 214 | 215 | ## 29、Dubbo 和 Spring Cloud 的区别? 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /Java核心面试知识集—JVM面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ## Java内存区域 26 | 27 | ### 说一下 JVM 的主要组成部分及其作用? 28 | 29 | ![img](https://img-blog.csdnimg.cn/20200103213149526.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly90aGlua3dvbi5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) 30 | 31 | JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。 32 | 33 | - Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。 34 | - Execution engine(执行引擎):执行classes中的指令。 35 | - Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。 36 | - Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。 37 | 38 | **作用** :首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。 39 | 40 | **下面是Java程序运行机制详细说明** 41 | 42 | Java程序运行机制步骤 43 | 44 | - 首先利用IDE集成开发工具编写Java源代码,源文件的后缀为.java; 45 | - 再利用编译器(javac命令)将源代码编译成字节码文件,字节码文件的后缀名为.class; 46 | - 运行字节码的工作是由解释器(java命令)来完成的。 47 | 48 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020031416414486.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 49 | 50 | 从上图可以看,java文件通过编译器变成了.class文件,接下来类加载器又将这些.class文件加载到JVM中。 51 | 其实可以一句话来解释:类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。 52 | 53 | ### 说一下 JVM 运行时数据区 54 | 55 | **Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域**。这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。Java 虚拟机所管理的内存被划分为如下几个区域: 56 | 57 | ![img](https://img-blog.csdnimg.cn/20200103213220764.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly90aGlua3dvbi5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) 58 | 59 | 不同虚拟机的运行时数据区可能略微有所不同,但都会遵从 Java 虚拟机规范, Java 虚拟机规范规定的区域分为以下 5 个部分: 60 | 61 | - 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成; 62 | - Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息; 63 | - 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的; 64 | - Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存; 65 | - 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。 66 | 67 | ### 深拷贝和浅拷贝 68 | 69 | 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址, 70 | 71 | 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存, 72 | 73 | 使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。 74 | 75 | 浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。 76 | 77 | 深复制:在计算机中开辟一块**新的内存地址**用于存放复制的对象。 78 | 79 | ### 说一下堆栈的区别? 80 | 81 | 物理地址 82 | 83 | 堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩) 84 | 85 | 栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。 86 | 87 | 内存分别 88 | 89 | 堆因为是不连续的,所以分配的内存是在`运行期`确认的,因此大小不固定。一般堆大小远远大于栈。 90 | 91 | 栈是连续的,所以分配的内存大小要在`编译期`就确认,大小是固定的。 92 | 93 | 存放的内容 94 | 95 | 堆存放的是对象的实例和数组。因此该区更关注的是数据的存储 96 | 97 | 栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。 98 | 99 | PS: 100 | 101 | 1. 静态变量放在方法区 102 | 2. 静态的对象还是放在堆。 103 | 104 | 程序的可见度 105 | 106 | 堆对于整个应用程序都是共享、可见的。 107 | 108 | 栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。 109 | 110 | ### 队列和栈是什么?有什么区别? 111 | 112 | 队列和栈都是被用来预存储数据的。 113 | 114 | - 操作的名称不同。队列的插入称为入队,队列的删除称为出队。栈的插入称为进栈,栈的删除称为出栈。 115 | - 可操作的方式不同。队列是在队尾入队,队头出队,即两边都可操作。而栈的进栈和出栈都是在栈顶进行的,无法对栈底直接进行操作。 116 | - 操作的方法不同。队列是先进先出(FIFO),即队列的修改是依先进先出的原则进行的。新来的成员总是加入队尾(不能从中间插入),每次离开的成员总是队列头上(不允许中途离队)。而栈为后进先出(LIFO),即每次删除(出栈)的总是当前栈中最新的元素,即最后插入(进栈)的元素,而最先插入的被放在栈的底部,要到最后才能删除。 117 | 118 | ## HotSpot虚拟机对象探秘 119 | 120 | ### 对象的创建 121 | 122 | 说到对象的创建,首先让我们看看 `Java` 中提供的几种对象创建方式: 123 | 124 | | Header | 解释 | 125 | | ---------------------------------- | ---------------- | 126 | | 使用new关键字 | 调用了构造函数 | 127 | | 使用Class的newInstance方法 | 调用了构造函数 | 128 | | 使用Constructor类的newInstance方法 | 调用了构造函数 | 129 | | 使用clone方法 | 没有调用构造函数 | 130 | | 使用反序列化 | 没有调用构造函数 | 131 | 132 | 下面是对象创建的主要流程: 133 | 134 | ![img](https://img-blog.csdnimg.cn/20200103213726902.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly90aGlua3dvbi5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) 135 | 136 | 虚拟机遇到一条new指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载。类加载通过后,接下来分配内存。若Java堆中内存是绝对规整的,使用“指针碰撞“方式分配内存;如果不是规整的,就从空闲列表中分配,叫做”空闲列表“方式。划分内存时还需要考虑一个问题-并发,也有两种方式: CAS同步处理,或者本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。然后内存空间初始化操作,接着是做一些必要的对象设置(元信息、哈希码…),最后执行``方法。 137 | 138 | ### 为对象分配内存 139 | 140 | 类加载完成后,接着会在Java堆中划分一块内存分配给对象。内存分配根据Java堆是否规整,有两种方式: 141 | 142 | - 指针碰撞:如果Java堆的内存是规整,即所有用过的内存放在一边,而空闲的的放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样便完成分配内存工作。 143 | - 空闲列表:如果Java堆的内存不是规整的,则需要由虚拟机维护一个列表来记录那些内存是可用的,这样在分配的时候可以从列表中查询到足够大的内存分配给对象,并在分配后更新列表记录。 144 | 145 | 选择哪种分配方式是由 Java 堆是否规整来决定的,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。 146 | 147 | ![内存分配的两种方式](https://img-blog.csdnimg.cn/20200103213812259.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly90aGlua3dvbi5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) 148 | 149 | ### 处理并发安全问题 150 | 151 | 对象的创建在虚拟机中是一个非常频繁的行为,哪怕只是修改一个指针所指向的位置,在并发情况下也是不安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案: 152 | 153 | - 对分配内存空间的动作进行同步处理(采用 CAS + 失败重试来保障更新操作的原子性); 154 | - 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 时,才需要同步锁。通过-XX:+/-UserTLAB参数来设定虚拟机是否使用TLAB。 155 | 156 | ![内存分配时保证线程安全的两种方式](https://img-blog.csdnimg.cn/20200103213833317.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly90aGlua3dvbi5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) 157 | 158 | ### 对象的访问定位 159 | 160 | `Java`程序需要通过 `JVM` 栈上的引用访问堆中的具体对象。对象的访问方式取决于 `JVM` 虚拟机的实现。目前主流的访问方式有 **句柄** 和 **直接指针** 两种方式。 161 | 162 | > **指针:** 指向对象,代表一个对象在内存中的起始地址。 163 | > 164 | > **句柄:** 可以理解为指向指针的指针,维护着对象的指针。句柄不直接指向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址),再由对象的指针指向对象的真实内存地址。 165 | 166 | #### 句柄访问 167 | 168 | `Java`堆中划分出一块内存来作为**句柄池**,引用中存储对象的**句柄地址**,而句柄中包含了**对象实例数据**与**对象类型数据**各自的**具体地址**信息,具体构造如下图所示: 169 | 170 | ![img](https://img-blog.csdnimg.cn/20200103213926911.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly90aGlua3dvbi5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) 171 | 172 | **优势**:引用中存储的是**稳定**的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变**句柄中**的**实例数据指针**,而**引用**本身不需要修改。 173 | 174 | #### 直接指针 175 | 176 | 如果使用**直接指针**访问,**引用** 中存储的直接就是**对象地址**,那么`Java`堆对象内部的布局中就必须考虑如何放置访问**类型数据**的相关信息。 177 | 178 | ![img](https://img-blog.csdnimg.cn/20200103213948956.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly90aGlua3dvbi5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) 179 | 180 | **优势**:速度更**快**,节省了**一次指针定位**的时间开销。由于对象的访问在`Java`中非常频繁,因此这类开销积少成多后也是非常可观的执行成本。HotSpot 中采用的就是这种方式。 181 | 182 | ## 内存溢出异常 183 | 184 | ### Java会存在内存泄漏吗?请简单描述 185 | 186 | 内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。 187 | 188 | 但是,即使这样,Java也还是存在着内存泄漏的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。 189 | 190 | ## 垃圾收集器 191 | 192 | ### 简述Java垃圾回收机制 193 | 194 | 在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。 195 | 196 | ### GC是什么?为什么要GC 197 | 198 | GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存 199 | 200 | 回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动 201 | 202 | 回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。 203 | 204 | ### 垃圾回收的优点和原理。并考虑2种回收机制 205 | 206 | java语言最显著的特点就是引入了垃圾回收机制,它使java程序员在编写程序时不再考虑内存管理的问题。 207 | 208 | 由于有这个垃圾回收机制,java中的对象不再有“作用域”的概念,只有引用的对象才有“作用域”。 209 | 210 | 垃圾回收机制有效的防止了内存泄露,可以有效的使用可使用的内存。 211 | 212 | 垃圾回收器通常作为一个单独的低级别的线程运行,在不可预知的情况下对内存堆中已经死亡的或很长时间没有用过的对象进行清除和回收。 213 | 214 | 程序员不能实时的对某个对象或所有对象调用垃圾回收器进行垃圾回收。 215 | 216 | 垃圾回收有分代复制垃圾回收、标记垃圾回收、增量垃圾回收。 217 | 218 | ### 垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收? 219 | 220 | 对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。 221 | 222 | 通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。 223 | 224 | 可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。 225 | 226 | ### Java 中都有哪些引用类型? 227 | 228 | - 强引用:发生 gc 的时候不会被回收。 229 | - 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。 230 | - 弱引用:有用但不是必须的对象,在下一次GC时会被回收。 231 | - 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。 232 | 233 | ### 怎么判断对象是否可以被回收? 234 | 235 | 垃圾收集器在做垃圾回收的时候,首先需要判定的就是哪些内存是需要被回收的,哪些对象是「存活」的,是不可以被回收的;哪些对象已经「死掉」了,需要被回收。 236 | 237 | 一般有两种方法来判断: 238 | 239 | - 引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题; 240 | - 可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。 241 | 242 | ### 在Java中,对象什么时候可以被垃圾回收 243 | 244 | 当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。 245 | 垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。 246 | 247 | ### JVM中的永久代中会发生垃圾回收吗 248 | 249 | 垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区 250 | (译者注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区) 251 | 252 | ### 说一下 JVM 有哪些垃圾回收算法? 253 | 254 | - 标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。 255 | - 复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。 256 | - 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。 257 | - 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。 258 | 259 | #### 标记-清除算法 260 | 261 | 标记无用对象,然后进行清除回收。 262 | 263 | 标记-清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,它将垃圾收集分为两个阶段: 264 | 265 | - 标记阶段:标记出可以回收的对象。 266 | - 清除阶段:回收被标记的对象所占用的空间。 267 | 268 | 标记-清除算法之所以是基础的,是因为后面讲到的垃圾收集算法都是在此算法的基础上进行改进的。 269 | 270 | **优点**:实现简单,不需要对象进行移动。 271 | 272 | **缺点**:标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。 273 | 274 | 标记-清除算法的执行的过程如下图所示 275 | 276 | ![img](https://img-blog.csdnimg.cn/20200104115917418.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly90aGlua3dvbi5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) 277 | 278 | #### 复制算法 279 | 280 | 为了解决标记-清除算法的效率不高的问题,产生了复制算法。它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。 281 | 282 | **优点**:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。 283 | 284 | **缺点**:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。 285 | 286 | 复制算法的执行过程如下图所示 287 | 288 | ![img](https://img-blog.csdnimg.cn/20200104115940771.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly90aGlua3dvbi5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) 289 | 290 | #### 标记-整理算法 291 | 292 | 在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记-清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记-整理算法(Mark-Compact)算法,与标记-整理算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。回收后,已用和未用的内存都各自一边。 293 | 294 | **优点**:解决了标记-清理算法存在的内存碎片问题。 295 | 296 | **缺点**:仍需要进行局部对象移动,一定程度上降低了效率。 297 | 298 | 标记-整理算法的执行过程如下图所示 299 | 300 | ![img](https://img-blog.csdnimg.cn/20200104120006513.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly90aGlua3dvbi5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) 301 | 302 | #### 分代收集算法 303 | 304 | 当前商业虚拟机都采用**分代收集**的垃圾收集算法。分代收集算法,顾名思义是根据对象的**存活周期**将内存划分为几块。一般包括**年轻代**、**老年代** 和 **永久代**,如图所示: 305 | 306 | ![img](https://img-blog.csdnimg.cn/20200104120031885.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly90aGlua3dvbi5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) 307 | 308 | ### 说一下 JVM 有哪些垃圾回收器? 309 | 310 | 如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展示了7种作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。 311 | 312 | ![img](https://img-blog.csdnimg.cn/20200104120144820.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly90aGlua3dvbi5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) 313 | 314 | - Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效; 315 | - ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现; 316 | - Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景; 317 | - Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本; 318 | - Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本; 319 | - CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。 320 | - G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。 321 | 322 | ### 详细介绍一下 CMS 垃圾回收器? 323 | 324 | CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。 325 | 326 | CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。 327 | 328 | ### 新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别? 329 | 330 | - 新生代回收器:Serial、ParNew、Parallel Scavenge 331 | - 老年代回收器:Serial Old、Parallel Old、CMS 332 | - 整堆回收器:G1 333 | 334 | 新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。 335 | 336 | ### 简述分代垃圾回收器是怎么工作的? 337 | 338 | 分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。 339 | 340 | 新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下: 341 | 342 | - 把 Eden + From Survivor 存活的对象放入 To Survivor 区; 343 | - 清空 Eden 和 From Survivor 分区; 344 | - From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。 345 | 346 | 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。 347 | 348 | 老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。 349 | 350 | ## 内存分配策略 351 | 352 | ### 简述java内存分配与回收策率以及Minor GC和Major GC 353 | 354 | 所谓自动内存管理,最终要解决的也就是内存分配和内存回收两个问题。前面我们介绍了内存回收,这里我们再来聊聊内存分配。 355 | 356 | 对象的内存分配通常是在 Java 堆上分配(随着虚拟机优化技术的诞生,某些场景下也会在栈上分配,后面会详细介绍),对象主要分配在新生代的 Eden 区,如果启动了本地线程缓冲,将按照线程优先在 TLAB 上分配。少数情况下也会直接在老年代上分配。总的来说分配规则不是百分百固定的,其细节取决于哪一种垃圾收集器组合以及虚拟机相关参数有关,但是虚拟机对于内存的分配还是会遵循以下几种「普世」规则: 357 | 358 | #### 对象优先在 Eden 区分配 359 | 360 | 多数情况,对象都在新生代 Eden 区分配。当 Eden 区分配没有足够的空间进行分配时,虚拟机将会发起一次 Minor GC。如果本次 GC 后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。 361 | 362 | 这里我们提到 Minor GC,如果你仔细观察过 GC 日常,通常我们还能从日志中发现 Major GC/Full GC。 363 | 364 | - **Minor GC** 是指发生在新生代的 GC,因为 Java 对象大多都是朝生夕死,所有 Minor GC 非常频繁,一般回收速度也非常快; 365 | - **Major GC/Full GC** 是指发生在老年代的 GC,出现了 Major GC 通常会伴随至少一次 Minor GC。Major GC 的速度通常会比 Minor GC 慢 10 倍以上。 366 | 367 | #### 大对象直接进入老年代 368 | 369 | 所谓大对象是指需要大量连续内存空间的对象,频繁出现大对象是致命的,会导致在内存还有不少空间的情况下提前触发 GC 以获取足够的连续空间来安置新对象。 370 | 371 | 前面我们介绍过新生代使用的是标记-清除算法来处理垃圾回收的,如果大对象直接在新生代分配就会导致 Eden 区和两个 Survivor 区之间发生大量的内存复制。因此对于大对象都会直接在老年代进行分配。 372 | 373 | #### 长期存活对象将进入老年代 374 | 375 | 虚拟机采用分代收集的思想来管理内存,那么内存回收时就必须判断哪些对象应该放在新生代,哪些对象应该放在老年代。因此虚拟机给每个对象定义了一个对象年龄的计数器,如果对象在 Eden 区出生,并且能够被 Survivor 容纳,将被移动到 Survivor 空间中,这时设置对象年龄为 1。对象在 Survivor 区中每「熬过」一次 Minor GC 年龄就加 1,当年龄达到一定程度(默认 15) 就会被晋升到老年代。 376 | 377 | ## 虚拟机类加载机制 378 | 379 | ### 简述java类加载机制? 380 | 381 | 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的java类型。 382 | 383 | ### 描述一下JVM加载Class文件的原理机制 384 | 385 | Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。 386 | 387 | 类装载方式,有两种 : 388 | 389 | 1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中, 390 | 391 | 2.显式装载, 通过class.forname()等方法,显式加载需要的类 392 | 393 | Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。 394 | 395 | ### 什么是类加载器,类加载器有哪些? 396 | 397 | 实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。 398 | 399 | 主要有一下四种类加载器: 400 | 401 | 1. 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。 402 | 2. 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。 403 | 3. 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。 404 | 4. 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。 405 | 406 | ### 说一下类装载的执行过程? 407 | 408 | 类装载分为以下 5 个步骤: 409 | 410 | - 加载:根据查找路径找到相应的 class 文件然后导入; 411 | - 验证:检查加载的 class 文件的正确性; 412 | - 准备:给类中的静态变量分配内存空间; 413 | - 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址; 414 | - 初始化:对静态变量和静态代码块执行初始化工作。 415 | 416 | ### 什么是双亲委派模型? 417 | 418 | 在介绍双亲委派模型之前先说下类加载器。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。 419 | 420 | ![img](https://img-blog.csdnimg.cn/20200104165551656.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly90aGlua3dvbi5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) 421 | 422 | 类加载器分类: 423 | 424 | - 启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库; 425 | - 其他类加载器: 426 | - 扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库; 427 | - 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。 428 | 429 | 双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。 430 | 431 | 当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。 432 | 433 | ## JVM调优 434 | 435 | ### 说一下 JVM 调优的工具? 436 | 437 | JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。 438 | 439 | - jconsole:用于对 JVM 中的内存、线程和类等进行监控; 440 | - jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。 441 | 442 | ### 常用的 JVM 调优的参数都有哪些? 443 | 444 | - -Xms2g:初始化推大小为 2g; 445 | - -Xmx2g:堆最大内存为 2g; 446 | - -XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4; 447 | - -XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2; 448 | - –XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合; 449 | - -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合; 450 | - -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合; 451 | - -XX:+PrintGC:开启打印 gc 信息; 452 | - -XX:+PrintGCDetails:打印 gc 详细信息。 453 | 454 | -------------------------------------------------------------------------------- /Java核心面试知识集—Java异常面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ## Java异常架构与异常关键字 26 | 27 | ### Java异常简介 28 | 29 | Java异常是Java提供的一种识别及响应错误的一致性机制。 30 | Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答what, where, why这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪”抛出,异常信息回答了“为什么”会抛出。 31 | 32 | ### Java异常架构 33 | 34 | ![img](https://img-blog.csdnimg.cn/20200314173417278.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 35 | 36 | #### 1. Throwable 37 | 38 | Throwable 是 Java 语言中所有错误与异常的超类。 39 | 40 | Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。 41 | 42 | Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。 43 | 44 | #### 2. Error(错误) 45 | 46 | **定义**:Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。 47 | 48 | **特点**:此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。 49 | 50 | 这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的! 51 | 52 | #### 3. Exception(异常) 53 | 54 | 程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。 55 | 56 | ##### 运行时异常 57 | 58 | **定义**:RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。 59 | 60 | **特点**:Java 编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。比如NullPointerException空指针异常、ArrayIndexOutBoundException数组下标越界异常、ClassCastException类型转换异常、ArithmeticExecption算术异常。此类异常属于不受检异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。虽然 Java 编译器不会检查运行时异常,但是我们也可以通过 throws 进行声明抛出,也可以通过 try-catch 对它进行捕获处理。如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生! 61 | 62 | RuntimeException 异常会由 Java 虚拟机自动抛出并自动捕获(**就算我们没写异常捕获语句运行时也会抛出错误**!!),此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。 63 | 64 | ##### 编译时异常 65 | 66 | **定义**: Exception 中除 RuntimeException 及其子类之外的异常。 67 | 68 | **特点**: Java 编译器会检查它。如果程序中出现此类异常,比如 ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。**该异常我们必须手动在代码里添加捕获语句来处理该异常**。 69 | 70 | #### 4. 受检异常与非受检异常 71 | 72 | Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。 73 | 74 | ##### 受检异常 75 | 76 | 编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。**除 RuntimeException 及其子类外,其他的 Exception 异常都属于受检异常**。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。 77 | 78 | ##### 非受检异常 79 | 80 | 编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。**该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。** 81 | 82 | ### Java异常关键字 83 | 84 | • **try** – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。 85 | • **catch** – 用于捕获异常。catch用来捕获try语句块中发生的异常。 86 | • **finally** – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。 87 | • **throw** – 用于抛出异常。 88 | • **throws** – 用在方法签名中,用于声明该方法可能抛出的异常。 89 | 90 | ## Java异常处理 91 | 92 | ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAxOS8xMS8xMC8xNmU1NWYyYzMyMWQ5MDlk?x-oss-process=image/format,png) 93 | 94 | Java 通过面向对象的方法进行异常处理,一旦方法抛出异常,系统自动根据该异常对象寻找合适异常处理器(Exception Handler)来处理该异常,把各种不同的异常进行分类,并提供了良好的接口。在 Java 中,每个异常都是一个对象,它是 Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java 的异常处理是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally。 95 | 96 | 在Java应用中,异常的处理机制分为声明异常,抛出异常和捕获异常。 97 | 98 | ### 声明异常 99 | 100 | 通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 **throws** 关键字声明可能会抛出的异常。 101 | 102 | 注意 103 | 104 | - 非检查异常(Error、RuntimeException 或它们的子类)不可使用 throws 关键字来声明要抛出的异常。 105 | - 一个方法出现编译时异常,就需要 try-catch/ throws 处理,否则会导致编译错误。 106 | 107 | ### 抛出异常 108 | 109 | 如果你觉得解决不了某些异常问题,且不需要调用者处理,那么你可以抛出异常。 110 | 111 | throw关键字作用是在方法内部抛出一个`Throwable`类型的异常。任何Java代码都可以通过throw语句抛出异常。 112 | 113 | ### 捕获异常 114 | 115 | 程序通常在运行之前不报错,但是运行后可能会出现某些未知的错误,但是还不想直接抛出到上一级,那么就需要通过try…catch…的形式进行异常捕获,之后根据不同的异常情况来进行相应的处理。 116 | 117 | ### 如何选择异常类型 118 | 119 | 可以根据下图来选择是捕获异常,声明异常还是抛出异常 120 | 121 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200314173209267.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 122 | 123 | ### 常见异常处理方式 124 | 125 | #### 直接抛出异常 126 | 127 | 通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 **throws** 关键字声明可能会抛出的异常。 128 | 129 | ```java 130 | private static void readFile(String filePath) throws IOException { 131 | File file = new File(filePath); 132 | String result; 133 | BufferedReader reader = new BufferedReader(new FileReader(file)); 134 | while((result = reader.readLine())!=null) { 135 | System.out.println(result); 136 | } 137 | reader.close(); 138 | } 139 | ``` 140 | 141 | #### 封装异常再抛出 142 | 143 | 有时我们会从 catch 中抛出一个异常,目的是为了改变异常的类型。多用于在多系统集成时,当某个子系统故障,异常类型可能有多种,可以用统一的异常类型向外暴露,不需暴露太多内部异常细节。 144 | 145 | ```java 146 | private static void readFile(String filePath) throws MyException { 147 | try { 148 | // code 149 | } catch (IOException e) { 150 | MyException ex = new MyException("read file failed."); 151 | ex.initCause(e); 152 | throw ex; 153 | } 154 | } 155 | ``` 156 | 157 | #### 捕获异常 158 | 159 | 在一个 try-catch 语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理 160 | 161 | ```java 162 | private static void readFile(String filePath) { 163 | try { 164 | // code 165 | } catch (FileNotFoundException e) { 166 | // handle FileNotFoundException 167 | } catch (IOException e){ 168 | // handle IOException 169 | } 170 | } 171 | ``` 172 | 173 | 同一个 catch 也可以捕获多种类型异常,用 | 隔开 174 | 175 | ```java 176 | private static void readFile(String filePath) { 177 | try { 178 | // code 179 | } catch (FileNotFoundException | UnknownHostException e) { 180 | // handle FileNotFoundException or UnknownHostException 181 | } catch (IOException e){ 182 | // handle IOException 183 | } 184 | } 185 | ``` 186 | 187 | #### 自定义异常 188 | 189 | 习惯上,定义一个异常类应包含两个构造函数,一个无参构造函数和一个带有详细描述信息的构造函数(Throwable 的 toString 方法会打印这些详细信息,调试时很有用) 190 | 191 | ```java 192 | public class MyException extends Exception { 193 | public MyException(){ } 194 | public MyException(String msg){ 195 | super(msg); 196 | } 197 | // ... 198 | } 199 | ``` 200 | 201 | #### try-catch-finally 202 | 203 | 当方法中发生异常,异常处之后的代码不会再执行,如果之前获取了一些本地资源需要释放,则需要在方法正常结束时和 catch 语句中都调用释放本地资源的代码,显得代码比较繁琐,finally 语句可以解决这个问题。 204 | 205 | ```java 206 | private static void readFile(String filePath) throws MyException { 207 | File file = new File(filePath); 208 | String result; 209 | BufferedReader reader = null; 210 | try { 211 | reader = new BufferedReader(new FileReader(file)); 212 | while((result = reader.readLine())!=null) { 213 | System.out.println(result); 214 | } 215 | } catch (IOException e) { 216 | System.out.println("readFile method catch block."); 217 | MyException ex = new MyException("read file failed."); 218 | ex.initCause(e); 219 | throw ex; 220 | } finally { 221 | System.out.println("readFile method finally block."); 222 | if (null != reader) { 223 | try { 224 | reader.close(); 225 | } catch (IOException e) { 226 | e.printStackTrace(); 227 | } 228 | } 229 | } 230 | } 231 | ``` 232 | 233 | 调用该方法时,读取文件时若发生异常,代码会进入 catch 代码块,之后进入 finally 代码块;若读取文件时未发生异常,则会跳过 catch 代码块直接进入 finally 代码块。所以无论代码中是否发生异常,fianlly 中的代码都会执行。 234 | 235 | 若 catch 代码块中包含 return 语句,finally 中的代码还会执行吗?将以上代码中的 catch 子句修改如下: 236 | 237 | ```java 238 | catch (IOException e) { 239 | System.out.println("readFile method catch block."); 240 | return; 241 | } 242 | ``` 243 | 244 | 调用 readFile 方法,观察当 catch 子句中调用 return 语句时,finally 子句是否执行 245 | 246 | ```java 247 | readFile method catch block. 248 | readFile method finally block. 249 | ``` 250 | 251 | 可见,即使 catch 中包含了 return 语句,finally 子句依然会执行。若 finally 中也包含 return 语句,finally 中的 return 会覆盖前面的 return. 252 | 253 | #### try-with-resource 254 | 255 | 上面例子中,finally 中的 close 方法也可能抛出 IOException, 从而覆盖了原始异常。JAVA 7 提供了更优雅的方式来实现资源的自动释放,自动释放的资源需要是实现了 AutoCloseable 接口的类。 256 | 257 | ```java 258 | private static void tryWithResourceTest(){ 259 | try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){ 260 | // code 261 | } catch (IOException e){ 262 | // handle exception 263 | } 264 | } 265 | ``` 266 | 267 | try 代码块退出时,会自动调用 scanner.close 方法,和把 scanner.close 方法放在 finally 代码块中不同的是,若 scanner.close 抛出异常,则会被抑制,抛出的仍然为原始异常。被抑制的异常会由 addSusppressed 方法添加到原来的异常,如果想要获取被抑制的异常列表,可以调用 getSuppressed 方法来获取。 268 | 269 | ## Java异常常见面试题 270 | 271 | ### 1. Error 和 Exception 区别是什么? 272 | 273 | Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复; 274 | 275 | Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。 276 | 277 | ### 2. 运行时异常和一般异常(受检异常)区别是什么? 278 | 279 | 运行时异常包括 RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。 Java 编译器不会检查运行时异常。 280 | 281 | 受检异常是Exception 中除 RuntimeException 及其子类之外的异常。 Java 编译器会检查受检异常。 282 | 283 | **RuntimeException异常和受检异常之间的区别**:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。 284 | 285 | ### 3. JVM 是如何处理异常的? 286 | 287 | 在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。 288 | 289 | JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM 发现可以处理异常的代码时,会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。 290 | 291 | ### 4. throw 和 throws 的区别是什么? 292 | 293 | Java 中的异常处理除了包括捕获异常和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在方法上声明该方法要拋出的异常,或者在方法内部通过 throw 拋出异常对象。 294 | 295 | **throws 关键字和 throw 关键字在使用上的几点区别如下**: 296 | 297 | - throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。 298 | - throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。 299 | 300 | ### 5. final、finally、finalize 有什么区别? 301 | 302 | - final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。 303 | - finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。 304 | - finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。 305 | 306 | ### 6. NoClassDefFoundError 和 ClassNotFoundException 区别? 307 | 308 | NoClassDefFoundError 是一个 Error 类型的异常,是由 JVM 引起的,不应该尝试捕获这个异常。 309 | 310 | 引起该异常的原因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译时该类存在,但是在运行时却找不到了,可能是变异后被删除了等原因导致; 311 | 312 | ClassNotFoundException 是一个受查异常,需要显式地使用 try-catch 对其进行捕获和处理,或在方法签名中用 throws 关键字进行声明。当使用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 动态加载类到内存的时候,通过传入的类路径参数没有找到该类,就会抛出该异常;另一种抛出该异常的可能原因是某个类已经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。 313 | 314 | ### 7. try-catch-finally 中哪个部分可以省略? 315 | 316 | 答:catch 可以省略 317 | 318 | **原因** 319 | 320 | 更为严格的说法其实是:try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得无可厚非。 321 | 322 | 理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。但是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。如果是普通异常,编译器要求必须用catch捕获以便进一步处理;如果运行时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕获以便进一步处理。 323 | 324 | 至于加上finally,则是在不管有没捕获异常,都要进行的“扫尾”处理。 325 | 326 | ### 8. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗? 327 | 328 | 答:会执行,在 return 前执行。 329 | 330 | **注意**:在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会返回修改后的值。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也可以通过提升编译器的语法检查级别来产生警告或错误。 331 | 332 | **代码示例1:** 333 | 334 | ```java 335 | public static int getInt() { 336 | int a = 10; 337 | try { 338 | System.out.println(a / 0); 339 | a = 20; 340 | } catch (ArithmeticException e) { 341 | a = 30; 342 | return a; 343 | /* 344 | * return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就形成了 345 | * 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40 346 | * 再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是常量30 347 | */ 348 | } finally { 349 | a = 40; 350 | } 351 | return a; 352 | } 353 | ``` 354 | 355 | 执行结果:30 356 | 357 | **代码示例2:** 358 | 359 | ```java 360 | public static int getInt() { 361 | int a = 10; 362 | try { 363 | System.out.println(a / 0); 364 | a = 20; 365 | } catch (ArithmeticException e) { 366 | a = 30; 367 | return a; 368 | } finally { 369 | a = 40; 370 | //如果这样,就又重新形成了一条返回路径,由于只能通过1个return返回,所以这里直接返回40 371 | return a; 372 | } 373 | 374 | } 375 | ``` 376 | 377 | 执行结果:40 378 | 379 | ### 9. 类 ExampleA 继承 Exception,类 ExampleB 继承ExampleA。 380 | 381 | 有如下代码片断: 382 | 383 | ```java 384 | try { 385 | throw new ExampleB("b") 386 | } catch(ExampleA e){ 387 | System.out.println("ExampleA"); 388 | } catch(Exception e){ 389 | System.out.println("Exception"); 390 | } 391 | ``` 392 | 393 | 请问执行此段代码的输出是什么? 394 | 395 | **答**: 396 | 397 | 输出:ExampleA。(根据里氏代换原则[能使用父类型的地方一定能使用子类型],抓取 ExampleA 类型异常的 catch 块能够抓住 try 块中抛出的 ExampleB 类型的异常) 398 | 399 | 面试题 - 说出下面代码的运行结果。(此题的出处是《Java 编程思想》一书) 400 | 401 | ```java 402 | class Annoyance extends Exception { 403 | } 404 | class Sneeze extends Annoyance { 405 | } 406 | class Human { 407 | public static void main(String[] args) 408 | throws Exception { 409 | try { 410 | try { 411 | throw new Sneeze(); 412 | } catch ( Annoyance a ) { 413 | System.out.println("Caught Annoyance"); 414 | throw a; 415 | } 416 | } catch ( Sneeze s ) { 417 | System.out.println("Caught Sneeze"); 418 | return ; 419 | } finally { 420 | System.out.println("Hello World!"); 421 | } 422 | } 423 | } 424 | ``` 425 | 426 | 结果 427 | 428 | ```java 429 | Caught Annoyance 430 | Caught Sneeze 431 | Hello World! 432 | ``` 433 | 434 | ### 10. 常见的 RuntimeException 有哪些? 435 | 436 | - ClassCastException(类转换异常) 437 | - IndexOutOfBoundsException(数组越界) 438 | - NullPointerException(空指针) 439 | - ArrayStoreException(数据存储异常,操作数组时类型不一致) 440 | - 还有IO操作的BufferOverflowException异常 441 | 442 | ### 11. Java常见异常有哪些 443 | 444 | java.lang.IllegalAccessError:违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。 445 | 446 | java.lang.InstantiationError:实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常. 447 | 448 | java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。 449 | 450 | java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误。 451 | 452 | java.lang.ClassCastException:类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。 453 | 454 | java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。 455 | 456 | java.lang.ArithmeticException:算术条件异常。譬如:整数除零等。 457 | 458 | java.lang.ArrayIndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。 459 | 460 | java.lang.IndexOutOfBoundsException:索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。 461 | 462 | java.lang.InstantiationException:实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。 463 | 464 | java.lang.NoSuchFieldException:属性不存在异常。当访问某个类的不存在的属性时抛出该异常。 465 | 466 | java.lang.NoSuchMethodException:方法不存在异常。当访问某个类的不存在的方法时抛出该异常。 467 | 468 | java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。 469 | 470 | java.lang.NumberFormatException:数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。 471 | 472 | java.lang.StringIndexOutOfBoundsException:字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。 473 | 474 | ## Java异常处理最佳实践 475 | 476 | 在 Java 中处理异常并不是一个简单的事情。不仅仅初学者很难理解,即使一些有经验的开发者也需要花费很多时间来思考如何处理异常,包括需要处理哪些异常,怎样处理等等。这也是绝大多数开发团队都会制定一些规则来规范进行异常处理的原因。而团队之间的这些规范往往是截然不同的。 477 | 478 | 本文给出几个被很多团队使用的异常处理最佳实践。 479 | 480 | ### 1. 在 finally 块中清理资源或者使用 try-with-resource 语句 481 | 482 | 当使用类似InputStream这种需要使用后关闭的资源时,一个常见的错误就是在try块的最后关闭资源。 483 | 484 | ```java 485 | public void doNotCloseResourceInTry() { 486 | FileInputStream inputStream = null; 487 | try { 488 | File file = new File("./tmp.txt"); 489 | inputStream = new FileInputStream(file); 490 | // use the inputStream to read a file 491 | // do NOT do this 492 | inputStream.close(); 493 | } catch (FileNotFoundException e) { 494 | log.error(e); 495 | } catch (IOException e) { 496 | log.error(e); 497 | } 498 | } 499 | ``` 500 | 501 | 问题就是,只有没有异常抛出的时候,这段代码才可以正常工作。try 代码块内代码会正常执行,并且资源可以正常关闭。但是,使用 try 代码块是有原因的,一般调用一个或多个可能抛出异常的方法,而且,你自己也可能会抛出一个异常,这意味着代码可能不会执行到 try 代码块的最后部分。结果就是,你并没有关闭资源。 502 | 503 | 所以,你应该把清理工作的代码放到 finally 里去,或者使用 try-with-resource 特性。 504 | 505 | #### 1.1 使用 finally 代码块 506 | 507 | 与前面几行 try 代码块不同,finally 代码块总是会被执行。不管 try 代码块成功执行之后还是你在 catch 代码块中处理完异常后都会执行。因此,你可以确保你清理了所有打开的资源。 508 | 509 | ```java 510 | public void closeResourceInFinally() { 511 | FileInputStream inputStream = null; 512 | try { 513 | File file = new File("./tmp.txt"); 514 | inputStream = new FileInputStream(file); 515 | // use the inputStream to read a file 516 | } catch (FileNotFoundException e) { 517 | log.error(e); 518 | } finally { 519 | if (inputStream != null) { 520 | try { 521 | inputStream.close(); 522 | } catch (IOException e) { 523 | log.error(e); 524 | } 525 | } 526 | } 527 | } 528 | ``` 529 | 530 | #### 1.2 Java 7 的 try-with-resource 语法 531 | 532 | 如果你的资源实现了 AutoCloseable 接口,你可以使用这个语法。大多数的 Java 标准资源都继承了这个接口。当你在 try 子句中打开资源,资源会在 try 代码块执行后或异常处理后自动关闭。 533 | 534 | ```java 535 | public void automaticallyCloseResource() { 536 | File file = new File("./tmp.txt"); 537 | try (FileInputStream inputStream = new FileInputStream(file);) { 538 | // use the inputStream to read a file 539 | } catch (FileNotFoundException e) { 540 | log.error(e); 541 | } catch (IOException e) { 542 | log.error(e); 543 | } 544 | } 545 | ``` 546 | 547 | ### 2. 优先明确的异常 548 | 549 | 你抛出的异常越明确越好,永远记住,你的同事或者几个月之后的你,将会调用你的方法并且处理异常。 550 | 551 | 因此需要保证提供给他们尽可能多的信息。这样你的 API 更容易被理解。你的方法的调用者能够更好的处理异常并且避免额外的检查。 552 | 553 | 因此,总是尝试寻找最适合你的异常事件的类,例如,抛出一个 NumberFormatException 来替换一个 IllegalArgumentException 。避免抛出一个不明确的异常。 554 | 555 | ```java 556 | public void doNotDoThis() throws Exception { 557 | ... 558 | } 559 | public void doThis() throws NumberFormatException { 560 | ... 561 | } 562 | ``` 563 | 564 | ### 3. 对异常进行文档说明 565 | 566 | 当在方法上声明抛出异常时,也需要进行文档说明。目的是为了给调用者提供尽可能多的信息,从而可以更好地避免或处理异常。 567 | 在 Javadoc 添加 @throws 声明,并且描述抛出异常的场景。 568 | 569 | ```java 570 | public void doSomething(String input) throws MyBusinessException { 571 | ... 572 | } 573 | ``` 574 | 575 | ### 4. 使用描述性消息抛出异常 576 | 577 | 在抛出异常时,需要尽可能精确地描述问题和相关信息,这样无论是打印到日志中还是在监控工具中,都能够更容易被人阅读,从而可以更好地定位具体错误信息、错误的严重程度等。 578 | 579 | 但这里并不是说要对错误信息长篇大论,因为本来 Exception 的类名就能够反映错误的原因,因此只需要用一到两句话描述即可。 580 | 581 | 如果抛出一个特定的异常,它的类名很可能已经描述了这种错误。所以,你不需要提供很多额外的信息。一个很好的例子是 NumberFormatException 。当你以错误的格式提供 String 时,它将被 java.lang.Long 类的构造函数抛出。 582 | 583 | ```java 584 | try { 585 | new Long("xyz"); 586 | } catch (NumberFormatException e) { 587 | log.error(e); 588 | } 589 | ``` 590 | 591 | ### 5. 优先捕获最具体的异常 592 | 593 | 大多数 IDE 都可以帮助你实现这个最佳实践。当你尝试首先捕获较不具体的异常时,它们会报告无法访问的代码块。 594 | 595 | 但问题在于,只有匹配异常的第一个 catch 块会被执行。 因此,如果首先捕获 IllegalArgumentException ,则永远不会到达应该处理更具体的 NumberFormatException 的 catch 块,因为它是 IllegalArgumentException 的子类。 596 | 597 | 总是优先捕获最具体的异常类,并将不太具体的 catch 块添加到列表的末尾。 598 | 599 | 你可以在下面的代码片断中看到这样一个 try-catch 语句的例子。 第一个 catch 块处理所有 NumberFormatException 异常,第二个处理所有非 NumberFormatException 异常的IllegalArgumentException 异常。 600 | 601 | ```java 602 | public void catchMostSpecificExceptionFirst() { 603 | try { 604 | doSomething("A message"); 605 | } catch (NumberFormatException e) { 606 | log.error(e); 607 | } catch (IllegalArgumentException e) { 608 | log.error(e) 609 | } 610 | } 611 | ``` 612 | 613 | ### 6. 不要捕获 Throwable 类 614 | 615 | Throwable 是所有异常和错误的超类。你可以在 catch 子句中使用它,但是你永远不应该这样做! 616 | 617 | 如果在 catch 子句中使用 Throwable ,它不仅会捕获所有异常,也将捕获所有的错误。JVM 抛出错误,指出不应该由应用程序处理的严重问题。 典型的例子是 OutOfMemoryError 或者 StackOverflowError 。两者都是由应用程序控制之外的情况引起的,无法处理。 618 | 619 | 所以,最好不要捕获 Throwable ,除非你确定自己处于一种特殊的情况下能够处理错误。 620 | 621 | ```java 622 | public void doNotCatchThrowable() { 623 | try { 624 | // do something 625 | } catch (Throwable t) { 626 | // don't do this! 627 | } 628 | } 629 | ``` 630 | 631 | ### 7. 不要忽略异常 632 | 633 | 很多时候,开发者很有自信不会抛出异常,因此写了一个catch块,但是没有做任何处理或者记录日志。 634 | 635 | ```java 636 | public void doNotIgnoreExceptions() { 637 | try { 638 | // do something 639 | } catch (NumberFormatException e) { 640 | // this will never happen 641 | } 642 | } 643 | ``` 644 | 645 | 但现实是经常会出现无法预料的异常,或者无法确定这里的代码未来是不是会改动(删除了阻止异常抛出的代码),而此时由于异常被捕获,使得无法拿到足够的错误信息来定位问题。 646 | 647 | 合理的做法是至少要记录异常的信息。 648 | 649 | ```java 650 | public void logAnException() { 651 | try { 652 | // do something 653 | } catch (NumberFormatException e) { 654 | log.error("This should never happen: " + e); 655 | } 656 | } 657 | ``` 658 | 659 | ### 8. 不要记录并抛出异常 660 | 661 | 这可能是本文中最常被忽略的最佳实践。可以发现很多代码甚至类库中都会有捕获异常、记录日志并再次抛出的逻辑。如下: 662 | 663 | ```java 664 | try { 665 | new Long("xyz"); 666 | } catch (NumberFormatException e) { 667 | log.error(e); 668 | throw e; 669 | } 670 | ``` 671 | 672 | 这个处理逻辑看着是合理的。但这经常会给同一个异常输出多条日志。如下: 673 | 674 | ```java 675 | 17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz" 676 | Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz" 677 | at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) 678 | at java.lang.Long.parseLong(Long.java:589) 679 | at java.lang.Long.(Long.java:965) 680 | at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63) 681 | at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58) 682 | ``` 683 | 684 | 如上所示,后面的日志也没有附加更有用的信息。如果想要提供更加有用的信息,那么可以将异常包装为自定义异常。 685 | 686 | ```java 687 | public void wrapException(String input) throws MyBusinessException { 688 | try { 689 | // do something 690 | } catch (NumberFormatException e) { 691 | throw new MyBusinessException("A message that describes the error.", e); 692 | } 693 | } 694 | ``` 695 | 696 | 因此,仅仅当想要处理异常时才去捕获,否则只需要在方法签名中声明让调用者去处理。 697 | 698 | ### 9. 包装异常时不要抛弃原始的异常 699 | 700 | 捕获标准异常并包装为自定义异常是一个很常见的做法。这样可以添加更为具体的异常信息并能够做针对的异常处理。 701 | 在你这样做时,请确保将原始异常设置为原因(注:参考下方代码 NumberFormatException e 中的原始异常 e )。Exception 类提供了特殊的构造函数方法,它接受一个 Throwable 作为参数。否则,你将会丢失堆栈跟踪和原始异常的消息,这将会使分析导致异常的异常事件变得困难。 702 | 703 | ```java 704 | public void wrapException(String input) throws MyBusinessException { 705 | try { 706 | // do something 707 | } catch (NumberFormatException e) { 708 | throw new MyBusinessException("A message that describes the error.", e); 709 | } 710 | } 711 | ``` 712 | 713 | ### 10. 不要使用异常控制程序的流程 714 | 715 | 不应该使用异常控制应用的执行流程,例如,本应该使用if语句进行条件判断的情况下,你却使用异常处理,这是非常不好的习惯,会严重影响应用的性能。 716 | 717 | ### 11. 使用标准异常 718 | 719 | 如果使用内建的异常可以解决问题,就不要定义自己的异常。Java API 提供了上百种针对不同情况的异常类型,在开发中首先尽可能使用 Java API 提供的异常,如果标准的异常不能满足你的要求,这时候创建自己的定制异常。尽可能得使用标准异常有利于新加入的开发者看懂项目代码。 720 | 721 | ### 12. 异常会影响性能 722 | 723 | 异常处理的性能成本非常高,每个 Java 程序员在开发时都应牢记这句话。创建一个异常非常慢,抛出一个异常又会消耗1~5ms,当一个异常在应用的多个层级之间传递时,会拖累整个应用的性能。 724 | 725 | - 仅在异常情况下使用异常; 726 | - 在可恢复的异常情况下使用异常; 727 | 728 | 尽管使用异常有利于 Java 开发,但是在应用中最好不要捕获太多的调用栈,因为在很多情况下都不需要打印调用栈就知道哪里出错了。因此,异常消息应该提供恰到好处的信息。 729 | 730 | ### 13. 总结 731 | 732 | 综上所述,当你抛出或捕获异常的时候,有很多不同的情况需要考虑,而且大部分事情都是为了改善代码的可读性或者 API 的可用性。 733 | 734 | 异常不仅仅是一个错误控制机制,也是一个通信媒介。因此,为了和同事更好的合作,一个团队必须要制定出一个最佳实践和规则,只有这样,团队成员才能理解这些通用概念,同时在工作中使用它。 735 | 736 | ### 异常处理-阿里巴巴Java开发手册 737 | 738 | 1. 【强制】Java 类库中定义的可以通过预检查方式规避的RuntimeException异常不应该通过catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException等等。 说明:无法通过预检查的异常除外,比如,在解析字符串形式的数字时,可能存在数字格式错误,不得不通过catch NumberFormatException来实现。 正例:if (obj != null) {…} 反例:try { obj.method(); } catch (NullPointerException e) {…} 739 | 2. 【强制】异常不要用来做流程控制,条件控制。 说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。 740 | 3. 【强制】catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理。 说明:对大段代码进行try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。 正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程序上作出分门别类的判断,并提示给用户。 741 | 4. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。 742 | 5. 【强制】有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务。 743 | 6. 【强制】finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch。 说明:如果JDK7及以上,可以使用try-with-resources方式。 744 | 7. 【强制】不要在finally块中使用return。 说明:try块中的return语句执行成功后,并不马上返回,而是继续执行finally块中的语句,如果此处存在return语句,则在此直接返回,无情丢弃掉try块中的返回点。 反例: 745 | 746 | ```java 747 | private int x = 0; 748 | public int checkReturn() { 749 | try { 750 | // x等于1,此处不返回 751 | return ++x; 752 | } finally { 753 | // 返回的结果是2 754 | return ++x; 755 | } 756 | } 757 | ``` 758 | 759 | 1. 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。 说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。 760 | 2. 【强制】在调用RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用Throwable类来进行拦截。 说明:通过反射机制来调用方法,如果找不到方法,抛出NoSuchMethodException。什么情况会抛出NoSuchMethodError呢?二方包在类冲突时,仲裁机制可能导致引入非预期的版本使类的方法签名不匹配,或者在字节码修改框架(比如:ASM)动态创建或修改类时,修改了相应的方法签名。这些情况,即使代码编译期是正确的,但在代码运行期时,会抛出NoSuchMethodError。 761 | 3. 【推荐】方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回null值。 说明:本手册明确防止NPE是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null的情况。 762 | 4. 【推荐】防止NPE,是程序员的基本修养,注意NPE产生的场景: 1) 返回类型为基本数据类型,return包装数据类型的对象时,自动拆箱有可能产生NPE。 反例:public int f() { return Integer对象}, 如果为null,自动解箱抛NPE。 2) 数据库的查询结果可能为null。 3) 集合里的元素即使isNotEmpty,取出的数据元素也可能为null。 4) 远程调用返回对象时,一律要求进行空指针判断,防止NPE。 5) 对于Session中获取的数据,建议进行NPE检查,避免空指针。 6) 级联调用obj.getA().getB().getC();一连串调用,易产生NPE。 763 | 正例:使用JDK8的Optional类来防止NPE问题。 764 | 5. 【推荐】定义时区分unchecked / checked 异常,避免直接抛出new RuntimeException(),更不允许抛出Exception或者Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException等。 765 | 6. 【参考】对于公司外的http/api开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间RPC调用优先考虑使用Result方式,封装isSuccess()方法、“错误码”、“错误简短信息”。 说明:关于RPC方法返回方式使用Result方式的理由: 1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。 2)如果不加栈信息,只是new自定义异常,加入自己的理解的error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。 766 | 7. 【参考】避免出现重复的代码(Don’t Repeat Yourself),即DRY原则。 说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。 正例:一个类中有多个public方法,都需要进行数行相同的参数校验操作,这个时候请抽取: 767 | private boolean checkParam(DTO dto) {…} 768 | 769 | -------------------------------------------------------------------------------- /Java核心面试知识集—Kafka面试题.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | ------ 4 | 5 | 注意一下咯:更多关于Java集合、JVM、多线程并发、spring原理、微服务、Netty 与RPC 、Kafka、日记、设计模式、Java算法、数据库、Zookeeper、分布式缓存、数据结构等等**加QQ群:578486082 管理员处可免费获取!** 6 | 7 | ------ 8 | 9 | 10 | 11 | # 基础篇 12 | 13 | ## 1、TCP、UDP的区别? 14 | 15 | TCP与UDP区别总结: 16 | 17 | 1)、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。 18 | 19 | 2)、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付 20 | 21 | 3)、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的 22 | UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等) 23 | 24 | 4)、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信 25 | 26 | 5)、TCP首部开销20字节;UDP的首部开销小,只有8个字节 27 | 28 | 6)、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道 29 | 30 | ## 2、TCP协议如何保证可靠传输? 31 | 32 | https://www.cnblogs.com/xiaokang01/p/10033267.html 33 | 34 | ## 3、TCP的握手、挥手机制? 35 | 36 | TCP的握手机制: 37 | 38 | ![img](file:///C:\Users\XXTG\AppData\Local\Temp\ksohtml13924\wps1.png) 39 | 40 | TCP的挥手机制: 41 | 42 | ![img](file:///C:\Users\XXTG\AppData\Local\Temp\ksohtml13924\wps2.png) 43 | 44 | 详情参考文章: 45 | https://blog.csdn.net/qzcsu/article/details/72861891 46 | 47 | ## 4、TCP的粘包/拆包原因及其解决方法是什么? 48 | 49 | 为什么会发生TCP粘包、拆包? 50 | 发生TCP粘包、拆包主要是由于下面一些原因: 51 | 52 | 1).应用程序写入的数据大于套接字缓冲区大小,这将会发生拆包。 53 | 54 | 2).应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包。 55 | 56 | 3).进行MSS(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包。 57 | 58 | 4).接收方法不及时读取套接字缓冲区数据,这将发生粘包。 59 | 60 | 粘包、拆包解决办法: 61 | 62 | TCP本身是面向流的,作为网络服务器,如何从这源源不断涌来的数据流中拆分出或者合并出有意义的信息呢?通常会有以下一些常用的方法: 63 | 64 | 1)、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。 65 | 66 | 2)、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。 67 | 68 | 3)、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。 69 | 70 | 详情参考文章:[https://www.cnblogs.com/panch...](https://www.cnblogs.com/panchanggui/p/9518735.html) 71 | 72 | ## 5、Netty的粘包/拆包是怎么处理的,有哪些实现? 73 | 74 | 对于粘包和拆包问题,常见的解决方案有四种: 75 | 76 | 1)、客户端在发送数据包的时候,每个包都固定长度,比如1024个字节大小,如果客户端发送的数据长度不足1024个字节,则通过补充空格的方式补全到指定长度;Netty提供的FixedLengthFrameDecoder 77 | 78 | 2)、客户端在每个包的末尾使用固定的分隔符,例如rn,如果一个包被拆分了,则等待下一个包发送过来之后找到其中的rn,然后对其拆分后的头部部分与前一个包的剩余部分进行合并,这样就得到了一个完整的包;Netty提供LineBasedFrameDecoder与DelimiterBasedFrameDecoder 79 | 80 | 3)、将消息分为头部和消息体,在头部中保存有当前整个消息的长度,只有在读取到足够长度的消息之后才算是读到了一个完整的消息;Netyy提供了LengthFieldBasedFrameDecoder与LengthFieldPrepender 81 | 82 | 4)、通过自定义协议进行粘包和拆包的处理。Netty提供了通过实现MessageToByteEncoder和ByteToMessageDecoder来实现 83 | 84 | 更详细请阅读文章:[https://www.cnblogs.com/AIPAO...](https://www.cnblogs.com/AIPAOJIAO/p/10631551.html) 85 | 86 | ## 6、同步与异步、阻塞与非阻塞的区别? 87 | 88 | 简单点理解就是: 89 | 90 | 1). 同步,就是我调用一个功能,该功能没有结束前,我死等结果。 91 | 92 | 2). 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知) 93 | 94 | 3). 阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。 95 | 96 | 4). 非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者 97 | 98 | 同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞 99 | 100 | 阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回 101 | 102 | ## 7、说说网络IO模型? 103 | 104 | ![img](file:///C:\Users\XXTG\AppData\Local\Temp\ksohtml13924\wps3.jpg) 105 | 106 | ## 8、BIO、NIO、AIO分别是什么? 107 | 108 | BIO:同步并阻塞 ,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。 109 | 110 | NIO:同步非阻塞 ,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。 111 | 112 | AIO:异步非阻塞 ,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。 113 | 114 | ## 9、select、poll、epoll的机制及其区别? 115 | 116 | 1).单个进程打开的文件描述符(fd文件句柄)不一致 117 | 118 | select :有最大连接数限制数为1024,单个进程所能打开的最大连接数由FD_ZETSIZE宏定义。 119 | 120 | poll:poll本质上与select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的。 121 | 122 | epoll:虽然连接有上限,但是很大,1G内存的机器可以打开10万左右的连接,以此类推。 123 | 124 | 2).监听Socket的方式不一致 125 | 126 | select :轮询的方式,一个一个的socket检查过去,发现有socket活跃时才进行处理,当线性socket增多时,轮询的速度将会变得很慢,造成线性造成性能下降问题。 127 | 128 | poll:对select稍微进行了优化,只是修改了文件描述符,但是监听socket的方式还是轮询。 129 | 130 | expoll:epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,通知expoll来处理这个socket。(会将连接的socket注册到epoll中, 相当于socket的花名册, 如果有一个socket活跃了, 会回调一个函数, 通知epoll,赶紧过来处理) 131 | 132 | 3).内存空间拷贝方式(消息传递方式)不一致 133 | 134 | select:内核想将消息传递到用户态,需要将数据从内核态拷贝到用户态,这个过程非常的耗时 135 | 136 | poll:同上 137 | 138 | epoll:epoll的内核和用户空间共享一块内存,因此内存态数据和用户态数据是共享的 139 | 140 | select、poll、epoll时间复杂度分别是:O(n)、O(n)、O(1) 141 | 142 | ## 10、说说你对Netty的了解? 143 | 144 | 这个没用过的话,就不要死撑了。用过的估计不用找了吧。 145 | 146 | ## 11、Netty跟Java NIO有什么不同,为什么不直接使用JDK NIO类库? 147 | 148 | 说说NIO有什么缺点吧: 149 | 150 | NIO的类库和API还是有点复杂,比如Buffer的使用 151 | Selector编写复杂,如果对某个事件注册后,业务代码过于耦合 152 | 需要了解很多多线程的知识,熟悉网络编程 153 | 面对断连重连、保丢失、粘包等,处理复杂 154 | NIO存在BUG,根据网上言论说是selector空轮训导致CPU飙升,具体有兴趣的可以看看JDK的官网 155 | 156 | Netty主要的优点有: 157 | 158 | 框架设计优雅,底层模型随意切换适应不同的网络协议要求 159 | 160 | 提供很多标准的协议、安全、编码解码的支持 161 | 162 | 解决了很多NIO不易用的问题 163 | 164 | 社区更为活跃,在很多开源框架中使用,如Dubbo、RocketMQ、Spark等 165 | 166 | 底层核心有:Zero-Copy-Capable 167 | Buffer,非常易用的灵拷贝Buffer(这个内容很有意思,稍后专门来说);统一的API;标准可扩展的时间模型 168 | 169 | 传输方面的支持有:管道通信(具体不知道干啥的,还请老司机指教);Http隧道;TCP与UDP 170 | 171 | 协议方面的支持有:基于原始文本和二进制的协议;解压缩;大文件传输;流媒体传输;protobuf编解码;安全认证;http和websocket 172 | 173 | 总之提供了很多现成的功能可以直接供开发者使用。 174 | 175 | ## 12、Netty组件有哪些,分别有什么关联? 176 | 177 | Channel ----Socket 178 | 179 | EventLoop ----控制流,多线程处理,并发; 180 | 181 | ChannelHandler和ChannelPipeline 182 | 183 | Bootstrap 和 ServerBootstrap 184 | 185 | 更多详情可以阅读:[https://blog.csdn.net/summerZ...](https://blog.csdn.net/summerZBH123/article/details/79344226) 186 | 187 | ## 13、说说Netty的执行流程? 188 | 189 | ![img](file:///C:\Users\XXTG\AppData\Local\Temp\ksohtml13924\wps4.jpg) 190 | 191 | ![img](file:///C:\Users\XXTG\AppData\Local\Temp\ksohtml13924\wps5.jpg) 192 | 193 | 1)、创建ServerBootStrap实例 194 | 195 | 2)、设置并绑定Reactor线程池:EventLoopGroup,EventLoop就是处理所有注册到本线程的Selector上面的Channel 196 | 197 | 3)、设置并绑定服务端的channel 198 | 199 | 4)、5)、创建处理网络事件的ChannelPipeline和handler,网络时间以流的形式在其中流转,handler完成多数的功能定制:比如编解码 SSl安全认证 200 | 201 | 6)、绑定并启动监听端口 202 | 203 | 7)、当轮训到准备就绪的channel后,由Reactor线程:NioEventLoop执行pipline中的方法,最终调度并执行channelHandler 204 | 205 | 更多请参考文章:[https://juejin.im/post/5bf8fb...](https://juejin.im/post/5bf8fbd4f265da617006cab8) 206 | 207 | # 高级篇 208 | 209 | ## 14、Netty高性能体现在哪些方面? 210 | 211 | [https://www.infoq.cn/article/...](#theCommentsSection) 212 | 213 | ## 15、Netty的线程模型是怎么样的? 214 | 215 | Reactor线程模型 216 | 217 | Reactor单线程模型 218 | 一个NIO线程+一个accept线程: 219 | ![img](file:///C:\Users\XXTG\AppData\Local\Temp\ksohtml13924\wps6.jpg) 220 | 221 | Reactor多线程模型 222 | 223 | ![img](file:///C:\Users\XXTG\AppData\Local\Temp\ksohtml13924\wps7.jpg) 224 | 225 | Reactor主从模型 226 | 主从Reactor多线程:多个acceptor的NIO线程池用于接受客户端的连接 227 | ![img](file:///C:\Users\XXTG\AppData\Local\Temp\ksohtml13924\wps8.jpg) 228 | 229 | Netty可以基于如上三种模型进行灵活的配置。 230 | 231 | 总结 232 | 233 | Netty是建立在NIO基础之上,Netty在NIO之上又提供了更高层次的抽象。在Netty里面,Accept连接可以使用单独的线程池去处理,读写操作又是另外的线程池来处理。Accept连接和读写操作也可以使用同一个线程池来进行处理。而请求处理逻辑既可以使用单独的线程池进行处理,也可以跟放在读写线程一块处理。线程池中的每一个线程都是NIO线程。用户可以根据实际情况进行组装,构造出满足系统需求的高性能并发模型。 234 | 235 | ## 16、Netty的零拷贝提体现在哪里,与操作系统上的有什么区别? 236 | 237 | 传统意义的拷贝是在发送数据的时候, 238 | 传统的实现方式是: 239 | 240 | \1. File.read(bytes) 241 | 242 | \2. Socket.send(bytes) 243 | 244 | 这种方式需要四次数据拷贝和四次上下文切换: 245 | 246 | \1. 数据从磁盘读取到内核的read buffer 247 | 248 | \2. 数据从内核缓冲区拷贝到用户缓冲区 249 | 250 | \3. 数据从用户缓冲区拷贝到内核的socket buffer 251 | 252 | \4. 数据从内核的socket buffer拷贝到网卡接口(硬件)的缓冲区 253 | 254 | 零拷贝的概念明显上面的第二步和第三步是没有必要的,通过java的FileChannel.transferTo方法,可以避免上面两次多余的拷贝(当然这需要底层操作系统支持) 255 | 256 | \1. 调用transferTo,数据从文件由DMA引擎拷贝到内核read buffer 257 | 258 | \2. 接着DMA从内核read buffer将数据拷贝到网卡接口buffer上面的两次操作都不需要CPU参与,所以就达到了零拷贝。 259 | 260 | Netty中的零拷贝主要体现在三个方面: 261 | 262 | 1、bytebufferNetty发送和接收消息主要使用bytebuffer,bytebuffer使用对外内存(DirectMemory)直接进行Socket读写。原因:如果使用传统的堆内存进行Socket读写,JVM会将堆内存buffer拷贝一份到直接内存中然后再写入socket,多了一次缓冲区的内存拷贝。DirectMemory中可以直接通过DMA发送到网卡接口 263 | 264 | 2、Composite Buffers传统的ByteBuffer,如果需要将两个ByteBuffer中的数据组合到一起,我们需要首先创建一个size=size1+size2大小的新的数组,然后将两个数组中的数据拷贝到新的数组中。但是使用Netty提供的组合ByteBuf,就可以避免这样的操作,因为CompositeByteBuf并没有真正将多个Buffer组合起来,而是保存了它们的引用,从而避免了数据的拷贝,实现了零拷贝。 265 | 266 | 3、对于FileChannel.transferTo的使用Netty中使用了FileChannel的transferTo方法,该方法依赖于操作系统实现零拷贝。 267 | 268 | ## 17、Netty的内存池是怎么实现的? 269 | 270 | netty内存池实现原理 271 | 272 | netty内存池可以分配堆内存和非堆内存(Direct内存),内存分配的核心算法是类似的,从堆内存分配代码入手来学习整个内存池的原理。netty框架处理IO事件时,使用ByteBuf承载数据。ByteBuf的内存分配由PooledByteBufAllocator来执行,最终的内存分配工作会被委托给PoolArena,堆内存分配的PoolArena实现是HeapArena。 273 | 274 | netty通常被用于高并发系统,多线程竞争加锁会影响内存分配的效率,为了缓解高并发时的线程竞争,netty允许使用者创建多个分配器(PoolArena)来分离线程竞争,提高内存分配效率。可通过PooledByteBufAllocator构造子中的nHeapArena参数来设置PoolArena的数量,或者直接取框架中的默认值,通过以下代码决定默认值,默认值根据CPU核心数、JVM最大可用内存以及默认内存块(PoolChunk)大小等参数来计算这个默认值,计算逻辑是: 275 | 276 | 1)获取系统变量io.netty.allocator.numHeapArenas,通过System.setProperty("io.netty.allocator.numHeapArenas",xxx)或者增加JVM启动参数-Dio.netty.allocator.numHeapArenas=xxx设置,如果设置了值,把这个值当做Arena的个数。 277 | 278 | 2)如果没有设置io.netty.allocator.numHeapArenas系统变量,计算CPU核心数*2和JVM最大可用内存/默认内存块大小/2/3,取其中一个较少的值当做PoolArena的个数。 279 | 280 | 确定PoolArena个数之后框架会创建一个PoolArena数组,数组中所有的PoolArena都会用来执行内存分配。线程申请内存分配时,线程会在这个PoolArena数组中挑选一个当前被占用次数最少的Arena执行内存分配。 281 | 282 | 此外,netty使用扩展的线程对象FastThreadLocalThread来优化ThreadLocal性能,具体的优化思路是:默认的ThreadLocal使用ThreadLocalMap存储线程局部变量,它的实现方式类似于HashMap,需要计算hashCode定位到线程局部变量所在Entry的索引,而FastThreadLocalThread使用FastThreadLocal代替ThreadLocal,FastThreadLocalThread用一个数组来维护线程变量,每个FastThreadLocal维护一个index,该index就是线程局部变量在数组中的位置,线程变量直接通过index访问无需计算hashCode,FastThreadLocal的优势是减少了hashCode的计算过程,虽然性能只会有轻微的提升,但在高并发系统中,即使只是轻微的提升也会成倍放大。 283 | 284 | 更多请阅读文章:[http://baijiahao.baidu.com/s?...](http://baijiahao.baidu.com/s?id=1640499946352354049&wfr=spider&for=pc) 285 | 286 | ## 18、Netty的对象池是怎么实现的? 287 | 288 | Netty 并没有使用第三方库实现对象池,而是自己实现了一个相对轻量的对象池。通过使用 threadLocal,避免了多线程下取数据时可能出现的线程安全问题,同时,为了实现多线程回收同一个实例,让每个线程对应一个队列,队列链接在 Stack 对象上形成链表,这样,就解决了多线程回收时的安全问题。同时,使用了软引用的map 和 软引用的 thradl 也避免了内存泄漏。 289 | 290 | ![img](file:///C:\Users\XXTG\AppData\Local\Temp\ksohtml13924\wps9.jpg) 291 | 292 | 更详细的可阅读文章: 293 | 294 | [https://www.jianshu.com/p/834...](https://www.jianshu.com/p/83469191509b) 295 | [https://www.cnblogs.com/hzmar...](https://www.cnblogs.com/hzmark/p/netty-object-pool.html) 296 | 297 | 298 | 299 | # 实战篇 300 | 301 | ## 19、在实际项目中,你们是怎么使用Netty的? 302 | 303 | ## 20、使用过Netty遇到过什么问题? 304 | 305 | (1)创建两个NioEventLoopGroup,用于逻辑隔离NIO Acceptor和NIO I/O线程 306 | 307 | (2)尽量不要在ChannelHandler中启动用户线程(解码后用于将POJO消息派发到后端业务线程的除外) 308 | 309 | (3)解码要放在NIO线程调用的解码Handler中进行,不要切换到用户线程完成消息的解码. 310 | 311 | (4)如果业务逻辑操作非常简单(纯内存操作),没有复杂的业务逻辑计算,也可能会导致线程被阻塞的磁盘操作,数据库操作,网络操作等,可以直接在NIO线程上完成业务逻辑编排,不需要切换到用户线程. 312 | 313 | (5)如果业务逻辑复杂,不要在NIO线程上完成,建议将解码后的POJO消息封装成任务,派发到业务线程池中由业务线程执行,以保证NIO线程尽快释放,处理其它I/O操作. 314 | 315 | (6)可能导致阻塞的操作,数据库操作,第三方服务调用,中间件服务调用,同步获取锁,Sleep等 316 | 317 | (7)Sharable注解的ChannelHandler要慎用 318 | 319 | (8)避免将ChannelHandler加入到不同的ChannelPipeline中,会出现并发问题. 320 | 321 | ![img](file:///C:\Users\XXTG\AppData\Local\Temp\ksohtml13924\wps10.jpg) 322 | 323 | 从上面的随便挑一个吹水就行。 324 | 325 | ## 21、netty的线程模型,netty如何基于reactor模型上实现的。 326 | 327 | 328 | [https://www.cnblogs.com/codin...](https://www.cnblogs.com/coding400/p/10865333.html) 329 | 330 | ![img](file:///C:\Users\XXTG\AppData\Local\Temp\ksohtml13924\wps11.jpg) 331 | 332 | ## 2 2 、netty的fashwheeltimer的用法,实现原理,是否出现过调用不够准时,怎么解决。 333 | 334 | [https://www.cnblogs.com/eryua...](https://www.cnblogs.com/eryuan/p/7955677.html) 335 | 336 | ## 2 3 、netty的心跳处理在弱网下怎么办。 337 | 338 | [https://blog.csdn.net/z691837...](https://blog.csdn.net/z69183787/article/details/52671543) 339 | 340 | ## 2 4 、netty的通讯协议是什么样的。 341 | 342 | [https://www.cnblogs.com/54929...](https://www.cnblogs.com/549294286/p/11241357.html) -------------------------------------------------------------------------------- /Java核心面试知识集—MongDB面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ------ 26 | 27 | 28 | 29 | ## 1\. 你说的NoSQL数据库是什么意思?NoSQL与RDBMS直接有什么区别?为什么要使用和不使用NoSQL数据库?说一说NoSQL数据库的几个优点? 30 | 31 | NoSQL是非关系型数据库,NoSQL = Not Only SQL。 32 | 33 | 关系型数据库采用的结构化的数据,NoSQL采用的是键值对的方式存储数据。 34 | 35 | 在处理非结构化/半结构化的大数据时;在水平方向上进行扩展时;随时应对动态增加的数据项时可以优先考虑使用NoSQL数据库。 36 | 37 | 在考虑数据库的成熟度;支持;分析和商业智能;管理及专业性等问题时,应优先考虑关系型数据库。 38 | 39 | ## 2\. NoSQL数据库有哪些类型? 40 | 41 | 42 | 43 | ## 3\. MySQL与MongoDB之间最基本的差别是什么? 44 | 45 | 46 | 47 | ## 4\. 你怎么比较MongoDB、CouchDB及CouchBase? 48 | 49 | 50 | 51 | ## 5\. MongoDB成为最好NoSQL数据库的原因是什么? 52 | 53 | 54 | 55 | ## 6.32位系统上有什么细微差别? 56 | 57 | 58 | 59 | ## 7\. journal回放在条目(entry)不完整时(比如恰巧有一个中途故障了)会遇到问题吗? 60 | 61 | 62 | 63 | ## 8\. 分析器在MongoDB中的作用是什么? 64 | 65 | 66 | 67 | ## 9\. 名字空间(namespace)是什么? 68 | 69 | 70 | 71 | ## 10\. 如果用户移除对象的属性,该属性是否从存储层中删除? 72 | 73 | 74 | 75 | ## 11\. 能否使用日志特征进行安全备份? 76 | 77 | 78 | 79 | ## 12\. 允许空值null吗? 80 | 81 | 82 | 83 | ## 13\. 更新操作立刻fsync到磁盘? 84 | 85 | 86 | 87 | ## 14\. 如何执行事务/加锁? 88 | 89 | 90 | 91 | ## 15\. 为什么我的数据文件如此庞大? 92 | 93 | 94 | 95 | ## 16\. 启用备份故障恢复需要多久? 96 | 97 | 98 | 99 | ## 17\. 什么是master或primary? 100 | 101 | 102 | 103 | ## 18\. 什么是secondary或slave? 104 | 105 | 106 | 107 | ## 19\. 我必须调用getLastError来确保写操作生效了么? 108 | 109 | 110 | 111 | ## 20\. 我应该启动一个集群分片(sharded)还是一个非集群分片的 MongoDB 环境? 112 | 113 | 114 | 115 | ## 21\. 分片(sharding)和复制(replication)是怎样工作的? 116 | 117 | 118 | 119 | ## 22\. 数据在什么时候才会扩展到多个分片(shard)里? 120 | 121 | 122 | 123 | ## 23\. 当我试图更新一个正在被迁移的块(chunk)上的文档时会发生什么? 124 | 125 | 126 | 127 | 24\. 如果在一个分片(shard)停止或者很慢的时候,我发起一个查询会怎样? 128 | 129 | 130 | 131 | ## 25\. 我可以把moveChunk目录里的旧文件删除吗? 132 | 133 | 134 | 135 | ## 26\. 我怎么查看 Mongo 正在使用的链接? 136 | 137 | 138 | 139 | ## 27\. 如果块移动操作(moveChunk)失败了,我需要手动清除部分转移的文档吗? 140 | 141 | 142 | 143 | ## 28\. 如果我在使用复制技术(replication),可以一部分使用日志(journaling)而其他部分则不使用吗? 144 | 145 | 146 | 147 | ## 29.当更新一个正在被迁移的块(Chunk)上的文档时会发生什么? 148 | 149 | 150 | 151 | ## 30.MongoDB在A:{B,C}上建立索引,查询A:{B,C}和A:{C,B}都会使用索引吗? 152 | 153 | 154 | 155 | ## 31.如果一个分片(Shard)停止或很慢的时候,发起一个查询会怎样? 156 | 157 | 158 | 159 | ## 32\. MongoDB支持存储过程吗?如果支持的话,怎么用? 160 | 161 | 162 | 163 | ## 33.如何理解MongoDB中的GridFS机制,MongoDB为何使用GridFS来存储文件? 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /Java核心面试知识集—MyBatis面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ## 1、什么是Mybatis? 26 | 27 | > 1.Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。 28 | > 29 | > 2.MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。 30 | > 31 | > 3.通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。 32 | 33 | ## **2、Mybaits的优点:** 34 | 35 | > 1.基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。 36 | > 37 | > 2.与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接; 38 | > 39 | > 3.很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。 40 | > 41 | > 4.能够与Spring很好的集成; 42 | > 43 | > 5.提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。 44 | 45 | ## **3.MyBatis框架的缺点:** 46 | 47 | > 1.SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。 48 | > 49 | > 2.SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。 50 | 51 | ## **4、MyBatis框架适用场合:** 52 | 53 | > 1.MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。 54 | > 55 | > 2.对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。 56 | 57 | ## **5、MyBatis与Hibernate有哪些不同?** 58 | 59 | > 1.Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。 60 | > 61 | > 2.Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。 62 | > 63 | > 3.Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。 64 | 65 | ## **6、#{}和${}的区别是什么?** 66 | 67 | > {}是预编译处理,${}是字符串替换。 68 | > 69 | > Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值; 70 | > 71 | > Mybatis在处理${}时,就是把${}替换成变量的值。 72 | > 73 | > 使用#{}可以有效的防止SQL注入,提高系统安全性。 74 | 75 | ##**7、当实体类中的属性名和表中的字段名不一样 ,怎么办 ?** 76 | 77 | 第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。 78 | 79 | ![24道Mybatis常见面试题总结及答案!](https://upload-images.jianshu.io/upload_images/11474088-809626bd6a539e0c?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 80 | 81 | 第2种: 通过 来映射字段名和实体类属性名的一一对应的关系。 82 | 83 | ![24道Mybatis常见面试题总结及答案!](https://upload-images.jianshu.io/upload_images/11474088-56a2dfdc7fde0de4?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 84 | 85 | ## **8、 模糊查询like语句该怎么写?** 86 | 87 | 第1种:在Java代码中添加sql通配符。 88 | 89 | ![24道Mybatis常见面试题总结及答案!](https://upload-images.jianshu.io/upload_images/11474088-2a758f3f971c91d0?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 90 | 91 | 第2种:在sql语句中拼接通配符,会引起sql注入 92 | 93 | ![24道Mybatis常见面试题总结及答案!](https://upload-images.jianshu.io/upload_images/11474088-31af7772855eb28d?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 94 | 95 | ## **9、通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?** 96 | 97 | > Dao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。 98 | > 99 | > Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个 **、、、**标签,都会被解析为一个MapperStatement对象。 100 | > 101 | > 举例: **com.mybatis3.mappers.StudentDao.findStudentById**,可以唯一找到namespace为 **com.mybatis3.mappers.StudentDao**下面 id 为 findStudentById 的 MapperStatement。 102 | > 103 | > Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。 104 | 105 | ## **10、Mybatis是如何进行分页的?分页插件的原理是什么?** 106 | 107 | > Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。 108 | > 109 | > 分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。 110 | 111 | ## **11、Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?** 112 | 113 | > 第一种是使用 标签,逐一定义数据库列名和对象属性名之间的映射关系。 114 | > 115 | > 第二种是使用sql列的别名功能,将列的别名书写为对象属性名。 116 | > 117 | > 有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。 118 | 119 | ## **12、如何执行批量插入?** 120 | 121 | 首先,创建一个简单的insert语句: 122 | 123 | ![24道Mybatis常见面试题总结及答案!](https://upload-images.jianshu.io/upload_images/11474088-415ec7b4a6ac80d6?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 124 | 125 | 然后在java代码中像下面这样执行批处理插入: 126 | 127 | ![24道Mybatis常见面试题总结及答案!](https://upload-images.jianshu.io/upload_images/11474088-9cc56016e24a00dd?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 128 | 129 | ## **13、如何获取自动生成的(主)键值?** 130 | 131 | insert 方法总是返回一个int值 ,这个值代表的是插入的行数。 132 | 133 | 如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。 134 | 135 | 示例: 136 | 137 | ![24道Mybatis常见面试题总结及答案!](https://upload-images.jianshu.io/upload_images/11474088-cf3f2d16f8b96bd2?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 138 | 139 | ## **14、Mybatis动态sql有什么用?执行原理?有哪些动态sql?** 140 | 141 | 142 | 143 | ## **15、Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?** 144 | 145 | 146 | 147 | ## **16、Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?** 148 | 149 | 150 | 151 | ## **17、为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?** 152 | 153 | 154 | 155 | ## **18、MyBatis实现一对一有几种方式?具体怎么操作的?** 156 | 157 | 158 | 159 | ## **19、MyBatis实现一对多有几种方式,怎么操作的?** 160 | 161 | 162 | 163 | ## **20、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?** 164 | 165 | 166 | 167 | ## **21、Mybatis的一级、二级缓存:** 168 | 169 | 170 | 171 | ## **22、什么是MyBatis的接口绑定?有哪些实现方式?** 172 | 173 | 174 | 175 | ## **23、使用MyBatis的mapper接口调用时有哪些要求?** 176 | 177 | 178 | 179 | ## **24、简述Mybatis的插件运行原理,以及如何编写一个插件。** 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /Java核心面试知识集—Netty面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ------ 26 | 27 | # 基础篇 28 | 29 | ## 1、TCP、UDP的区别? 30 | 31 | **TCP与UDP区别总结:** 32 | 33 | 1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。 34 | 1. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付 35 | 1. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的 UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等) 36 | 1. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信 37 | 1. TCP首部开销20字节;UDP的首部开销小,只有8个字节 38 | 1. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道 39 | 40 | ## 2、TCP协议如何保证可靠传输? 41 | 42 | ### 校验和,确认应答和序列号 43 | 44 | 序列号:TCP**传输时将每个字节的数据都进行了编号**,这就是序列号。 45 | 46 | 确认应答:TCP传输的过程中,每次**接收方收到数据后,都会对传输方进行确认应答**。也就是发送ACK报文。 47 | 48 | 这个ACK报文当中带有对应的**确认序列号**,告诉发送方,接收到了哪些数据,下一次的数据从哪里发。 49 | ![](https://upload-images.jianshu.io/upload_images/11474088-b064d6d53957a57c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 50 | 序列号的作用不仅仅是应答的作用,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据。这也是TCP传输可靠性的保证之一。 51 | 52 | ### 超时重传: 53 | 54 | 当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。 55 | 56 | ### 流量控制: 57 | 58 | TCP连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。 59 | 60 | TCP使用的流量控制协议是可变大小的滑动窗口协议。接收方有即时窗口(滑动窗口),随ACK报文发送 61 | 62 | ### 拥塞控制: 63 | 64 | 如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。 65 | 66 | 这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。 67 | 68 | ![img](https://img2018.cnblogs.com/blog/1396803/201811/1396803-20181129085146710-1797549698.png) 69 | 70 | **TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。** 71 | 72 | ## 3、TCP的握手、挥手机制? 73 | 74 | **TCP的握手机制:** 75 | 76 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-12f6498200184968.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 77 | 78 | **TCP的挥手机制:** 79 | 80 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-69e7075764046d8a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 81 | 82 | 详情参考: https://github.com/ThinkingHan/Java-interview-guide 83 | 84 | ## 4、TCP的粘包/拆包原因及其解决方法是什么? 85 | 86 | **为什么会发生TCP粘包、拆包? 发生TCP粘包、拆包主要是由于下面一些原因:** 87 | 88 | 1. 应用程序写入的数据大于套接字缓冲区大小,这将会发生拆包。 89 | 90 | 2. 应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包。 91 | 92 | 3. 进行MSS(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包。 93 | 94 | 4. 接收方法不及时读取套接字缓冲区数据,这将发生粘包。 95 | 96 | **粘包、拆包解决办法:** 97 | 98 | TCP本身是面向流的,作为网络服务器,如何从这源源不断涌来的数据流中拆分出或者合并出有意义的信息呢?通常会有以下一些常用的方法: 99 | 100 | 1. 发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。 101 | 102 | 2. 发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。 103 | 104 | 3. 可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。 105 | 106 | ## 5、Netty的粘包/拆包是怎么处理的,有哪些实现? 107 | 108 | **对于粘包和拆包问题,常见的解决方案有四种:** 109 | 110 | 1. 客户端在发送数据包的时候,每个包都固定长度,比如1024个字节大小,如果客户端发送的数据长度不足1024个字节,则通过补充空格的方式补全到指定长度;Netty提供的FixedLengthFrameDecoder 111 | 112 | 2. 客户端在每个包的末尾使用固定的分隔符,例如rn,如果一个包被拆分了,则等待下一个包发送过来之后找到其中的rn,然后对其拆分后的头部部分与前一个包的剩余部分进行合并,这样就得到了一个完整的包;Netty提供LineBasedFrameDecoder与DelimiterBasedFrameDecoder 113 | 114 | 3. 将消息分为头部和消息体,在头部中保存有当前整个消息的长度,只有在读取到足够长度的消息之后才算是读到了一个完整的消息;Netyy提供了LengthFieldBasedFrameDecoder与LengthFieldPrepender 115 | 116 | 4. 通过自定义协议进行粘包和拆包的处理。Netty提供了通过实现MessageToByteEncoder和ByteToMessageDecoder来实现 117 | 118 | ## 6、同步与异步、阻塞与非阻塞的区别? 119 | 120 | **简单点理解就是:** 121 | 122 | 1. 同步,就是我调用一个功能,该功能没有结束前,我死等结果。 123 | 124 | 2. 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知) 125 | 126 | 3. 阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。 127 | 128 | 4. 非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者 129 | 130 | **同步IO和异步IO的区别就在于:**数据拷贝的时候进程是否阻塞 131 | 132 | **阻塞IO和非阻塞IO的区别就在于:**应用程序的调用是否立即返回 133 | 134 | ## 7、说说网络IO模型? 135 | 136 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-1d78bf560a5a5aa3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 137 | 138 | ## 8、BIO、NIO、AIO分别是什么? 139 | 140 | **BIO:**同步并阻塞 ,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。 141 | 142 | **NIO:**同步非阻塞 ,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。 143 | 144 | **AIO:**异步非阻塞 ,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。 145 | 146 | ## 9、select、poll、epoll的机制及其区别? 147 | 148 | - **单个进程打开的文件描述符(fd文件句柄)不一致** 149 | 150 | > select :有最大连接数限制数为1024,单个进程所能打开的最大连接数由FD_ZETSIZE宏定义。 151 | > 152 | > poll:poll本质上与select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的。 153 | > 154 | > epoll:虽然连接有上限,但是很大,1G内存的机器可以打开10万左右的连接,以此类推。 155 | 156 | - **监听Socket的方式不一致** 157 | 158 | > select :轮询的方式,一个一个的socket检查过去,发现有socket活跃时才进行处理,当线性socket增多时,轮询的速度将会变得很慢,造成线性造成性能下降问题。 159 | > 160 | > poll:对select稍微进行了优化,只是修改了文件描述符,但是监听socket的方式还是轮询。 161 | > 162 | > expoll:epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,通知expoll来处理这个socket。(会将连接的socket注册到epoll中, 相当于socket的花名册, 如果有一个socket活跃了, 会回调一个函数, 通知epoll,赶紧过来处理) 163 | 164 | - 内存空间拷贝方式(消息传递方式)不一致 165 | 166 | > select:内核想将消息传递到用户态,需要将数据从内核态拷贝到用户态,这个过程非常的耗时 167 | > 168 | > poll:同上 169 | > 170 | > epoll:epoll的内核和用户空间共享一块内存,因此内存态数据和用户态数据是共享的select、poll、epoll时间复杂度分别是:O(n)、O(n)、O(1) 171 | 172 | ## 10、Netty跟Java NIO有什么不同,为什么不直接使用JDK NIO类库? 173 | 174 | **NIO有什么缺点:** 175 | 176 | NIO的类库和API还是有点复杂,比如Buffer的使用 Selector编写复杂,如果对某个事件注册后,业务代码过于耦合 需要了解很多多线程的知识,熟悉网络编程 面对断连重连、保丢失、粘包等,处理复杂 NIO存在BUG,根据网上言论说是selector空轮训导致CPU飙升,具体有兴趣的可以看看JDK的官网 177 | 178 | **Netty主要的优点有:** 179 | 180 | - 框架设计优雅,底层模型随意切换适应不同的网络协议要求 181 | 182 | - 提供很多标准的协议、安全、编码解码的支持 183 | 184 | - 解决了很多NIO不易用的问题 185 | 186 | - 在很多开源框架中使用,如Dubbo、RocketMQ、Spark等 187 | 188 | - **底层核心有:**Zero-Copy-Capable Buffer,非常易用的灵拷贝Buffer(这个内容很有意思,稍后专门来说);统一的API;标准可扩展的时间模型 189 | 190 | - 传输方面的支持有:管道通信(具体不知道干啥的,还请老司机指教);Http隧道;TCP与UDP 191 | 192 | - 协议方面的支持有:基于原始文本和二进制的协议;解压缩;大文件传输;流媒体传输;protobuf编解码;安全认证;http和websocket 193 | 194 | 总之提供了很多现成的功能可以直接供开发者使用。 195 | 196 | ## 12、Netty组件有哪些,分别有什么关联? 197 | 198 | - Channel ----Socket 199 | 200 | - EventLoop ----控制流,多线程处理,并发; 201 | 202 | - ChannelHandler和ChannelPipeline 203 | 204 | - Bootstrap 和 ServerBootstrap 205 | 206 | ## 13、说说Netty的执行流程? 207 | 208 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-1b0b3c0a552b7a30.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 209 | 210 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-dd8de53b44774da5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 211 | 212 | 1. 创建ServerBootStrap实例 213 | 214 | 2. 设置并绑定Reactor线程池:EventLoopGroup,EventLoop就是处理所有注册到本线程的Selector上面的Channel 215 | 216 | 3. 设置并绑定服务端的channel 217 | 218 | 4. 创建处理网络事件的ChannelPipeline和handler,网络时间以流的形式在其中流转,handler完成多数的功能定制:比如编解码 SSl安全认证 219 | 220 | 5. 绑定并启动监听端口 221 | 222 | 6. 当轮训到准备就绪的channel后,由Reactor线程:NioEventLoop执行pipline中的方法,最终调度并执行channelHandler 223 | 224 | 225 | 226 | # 高级篇 227 | 228 | ## 14、Netty高性能体现在哪些方面? 229 | 230 | [https://www.infoq.cn/article/...](#theCommentsSection) 231 | 232 | ## 15、Netty的线程模型是怎么样的? 233 | 234 | Reactor线程模型 235 | 236 | Reactor单线程模型 一个NIO线程+一个accept线程: 237 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-75ddcc5f07196304.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 238 | Reactor多线程模型 239 | 240 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-2f593fa22e13b9b0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 241 | Reactor主从模型 主从Reactor多线程:多个acceptor的NIO线程池用于接受客户端的连接 242 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-5d672c284bf203fc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 243 | 244 | 245 | Netty可以基于如上三种模型进行灵活的配置。 246 | 247 | 总结 248 | 249 | Netty是建立在NIO基础之上,Netty在NIO之上又提供了更高层次的抽象。在Netty里面,Accept连接可以使用单独的线程池去处理,读写操作又是另外的线程池来处理。Accept连接和读写操作也可以使用同一个线程池来进行处理。而请求处理逻辑既可以使用单独的线程池进行处理,也可以跟放在读写线程一块处理。线程池中的每一个线程都是NIO线程。用户可以根据实际情况进行组装,构造出满足系统需求的高性能并发模型。 250 | 251 | ## 16、Netty的零拷贝提体现在哪里,与操作系统上的有什么区别? 252 | 253 | 传统意义的拷贝是在发送数据的时候, 传统的实现方式是: 254 | 255 | \1\. File.read(bytes) 256 | 257 | \2\. Socket.send(bytes) 258 | 259 | 这种方式需要四次数据拷贝和四次上下文切换: 260 | 261 | \1\. 数据从磁盘读取到内核的read buffer 262 | 263 | \2\. 数据从内核缓冲区拷贝到用户缓冲区 264 | 265 | \3\. 数据从用户缓冲区拷贝到内核的socket buffer 266 | 267 | \4\. 数据从内核的socket buffer拷贝到网卡接口(硬件)的缓冲区 268 | 269 | 零拷贝的概念明显上面的第二步和第三步是没有必要的,通过java的FileChannel.transferTo方法,可以避免上面两次多余的拷贝(当然这需要底层操作系统支持) 270 | 271 | \1\. 调用transferTo,数据从文件由DMA引擎拷贝到内核read buffer 272 | 273 | \2\. 接着DMA从内核read buffer将数据拷贝到网卡接口buffer上面的两次操作都不需要CPU参与,所以就达到了零拷贝。 274 | 275 | Netty中的零拷贝主要体现在三个方面: 276 | 277 | 1、bytebufferNetty发送和接收消息主要使用bytebuffer,bytebuffer使用对外内存(DirectMemory)直接进行Socket读写。原因:如果使用传统的堆内存进行Socket读写,JVM会将堆内存buffer拷贝一份到直接内存中然后再写入socket,多了一次缓冲区的内存拷贝。DirectMemory中可以直接通过DMA发送到网卡接口 278 | 279 | 2、Composite Buffers传统的ByteBuffer,如果需要将两个ByteBuffer中的数据组合到一起,我们需要首先创建一个size=size1+size2大小的新的数组,然后将两个数组中的数据拷贝到新的数组中。但是使用Netty提供的组合ByteBuf,就可以避免这样的操作,因为CompositeByteBuf并没有真正将多个Buffer组合起来,而是保存了它们的引用,从而避免了数据的拷贝,实现了零拷贝。 280 | 281 | 3、对于FileChannel.transferTo的使用Netty中使用了FileChannel的transferTo方法,该方法依赖于操作系统实现零拷贝。 282 | 283 | ## 17、Netty的内存池是怎么实现的? 284 | 285 | netty内存池实现原理 286 | 287 | netty内存池可以分配堆内存和非堆内存(Direct内存),内存分配的核心算法是类似的,从堆内存分配代码入手来学习整个内存池的原理。netty框架处理IO事件时,使用ByteBuf承载数据。ByteBuf的内存分配由PooledByteBufAllocator来执行,最终的内存分配工作会被委托给PoolArena,堆内存分配的PoolArena实现是HeapArena。 288 | 289 | netty通常被用于高并发系统,多线程竞争加锁会影响内存分配的效率,为了缓解高并发时的线程竞争,netty允许使用者创建多个分配器(PoolArena)来分离线程竞争,提高内存分配效率。可通过PooledByteBufAllocator构造子中的nHeapArena参数来设置PoolArena的数量,或者直接取框架中的默认值,通过以下代码决定默认值,默认值根据CPU核心数、JVM最大可用内存以及默认内存块(PoolChunk)大小等参数来计算这个默认值,计算逻辑是: 290 | 291 | 1)获取系统变量io.netty.allocator.numHeapArenas,通过System.setProperty("io.netty.allocator.numHeapArenas",xxx)或者增加JVM启动参数-Dio.netty.allocator.numHeapArenas=xxx设置,如果设置了值,把这个值当做Arena的个数。 292 | 293 | 2)如果没有设置io.netty.allocator.numHeapArenas系统变量,计算CPU核心数*2和JVM最大可用内存/默认内存块大小/2/3,取其中一个较少的值当做PoolArena的个数。 294 | 295 | 确定PoolArena个数之后框架会创建一个PoolArena数组,数组中所有的PoolArena都会用来执行内存分配。线程申请内存分配时,线程会在这个PoolArena数组中挑选一个当前被占用次数最少的Arena执行内存分配。 296 | 297 | 此外,netty使用扩展的线程对象FastThreadLocalThread来优化ThreadLocal性能,具体的优化思路是:默认的ThreadLocal使用ThreadLocalMap存储线程局部变量,它的实现方式类似于HashMap,需要计算hashCode定位到线程局部变量所在Entry的索引,而FastThreadLocalThread使用FastThreadLocal代替ThreadLocal,FastThreadLocalThread用一个数组来维护线程变量,每个FastThreadLocal维护一个index,该index就是线程局部变量在数组中的位置,线程变量直接通过index访问无需计算hashCode,FastThreadLocal的优势是减少了hashCode的计算过程,虽然性能只会有轻微的提升,但在高并发系统中,即使只是轻微的提升也会成倍放大。 298 | 299 | 更多请阅读文章:[http://baijiahao.baidu.com/s?...](http://baijiahao.baidu.com/s?id=1640499946352354049&wfr=spider&for=pc) 300 | 301 | ## 18、Netty的对象池是怎么实现的? 302 | 303 | Netty 并没有使用第三方库实现对象池,而是自己实现了一个相对轻量的对象池。通过使用 threadLocal,避免了多线程下取数据时可能出现的线程安全问题,同时,为了实现多线程回收同一个实例,让每个线程对应一个队列,队列链接在 Stack 对象上形成链表,这样,就解决了多线程回收时的安全问题。同时,使用了软引用的map 和 软引用的 thradl 也避免了内存泄漏。 304 | 305 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-afc32c8f9aa1bd16.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 306 | 307 | 更详细的可阅读文章: 308 | 309 | [https://www.jianshu.com/p/834...](https://www.jianshu.com/p/83469191509b) [https://www.cnblogs.com/hzmar...](https://www.cnblogs.com/hzmark/p/netty-object-pool.html) 310 | 311 | # 实战篇 312 | 313 | ## 19、在实际项目中,你们是怎么使用Netty的? 314 | 315 | ## 20、使用过Netty遇到过什么问题? 316 | 317 | ## 21、netty的线程模型,netty如何基于reactor模型上实现的。 318 | 319 | 320 | 321 | ## 2 2 、netty的fashwheeltimer的用法,实现原理,是否出现过调用不够准时,怎么解决。 322 | 323 | 324 | 325 | ## 2 3 、netty的心跳处理在弱网下怎么办。 326 | 327 | 328 | 329 | ## 2 4 、netty的通讯协议是什么样的。 330 | 331 | -------------------------------------------------------------------------------- /Java核心面试知识集—RabbitMQ面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ------ 26 | 27 | ## 前言 28 | 29 | **来分享一下面试必备的RabbitMQ问题解析!**用XMind画了一张导图记录**RabbitMQ**的学习笔记和一些面试解析**(源文件对部分节点有详细备注和参考资料,欢迎进技术Q群拿下载链接,已经完善更新):** 30 | 31 | ![别找了,Java面试还愁被问RabbitMQ?看完这22道问题解析就够了!](https://upload-images.jianshu.io/upload_images/11474088-6cf3850e62e32b90?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 32 | 33 | ## 1、上千万条消息在mq中积压了几个小时还没解决: 34 | 35 | 1. 先修复consumer的问题,确保其恢复消费速度,然后将现有consumer都停掉; 36 | 37 | 2. 新建⼀个topic,partition是原来的10倍,临时建⽴好原先10倍或者20倍的queue数量; 38 | 39 | 3. 然后写⼀个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据;消费之后不做耗时的处理,直接均匀轮询写⼊临时建⽴好的10倍数量的queue; 40 | 41 | 4. 接着临时征⽤10倍的机器来部署consumer,每⼀批consumer消费⼀个临时queue的数据; 42 | 43 | 5. 这种做法相当于是临时将queue资源和consumer资源扩⼤10倍,以正常的10倍速度来消费数据; 44 | 45 | 6. 等快速消费完积压数据之后,得恢复原先部署架构,重新⽤原先的consumer机器来消费消息。 46 | 47 | **总结:** 48 | 49 | 1. 修复并停掉consumer; 50 | 51 | 2. 新建⼀个topic,partition是原来的10倍,建⽴临时queue,数量是原来的10倍或20倍; 52 | 53 | 3. 写临时consumer程序,临时征⽤10倍的机器去消费数据; 54 | 55 | 4. 消费完成之后,恢复原先consumer; 56 | 57 | ## 2、rabbitmq设置过期时间,部分消息丢失: 58 | 59 | > 采取批量重导⽅法:将丢失的那批数据查询导⼊到mq⾥⾯。 60 | 61 | ## **3、RabbitMQ 上的⼀个 queue 中存放的 message 是否有数量限制?** 62 | 63 | > 可以认为是⽆限制,因为限制取决于机器的内存,但是消息过多会导致处理效率的下降。 64 | 65 | ## 4、分布式部署: 66 | 67 | > RabbitMQ⽆法容忍不同数据中⼼之间⽹络延迟,但是可以通过3种⽅式实现分布式部署:Federation和Shovel。 68 | 69 | ## 5、如何确保消息正确地发送⾄RabbitMQ? 70 | 71 | **RabbitMQ使⽤发送⽅确认模式,确保消息正确地发送到RabbitMQ。** 72 | 73 | **发送⽅确认模式:**将信道设置成confirm模式(发送⽅确认模式),则所有在信道上发布的消息 74 | 75 | 都会被指派⼀个唯⼀的ID。⼀旦消息被投递到⽬的队列后,或者消息被写⼊磁盘后(可持久化 76 | 77 | 的消息),信道会发送⼀个确认给⽣产者(包含消息唯⼀ID)。如果RabbitMQ发⽣内部错误 78 | 79 | 从⽽导致消息丢失,会发送⼀条nack(not acknowledged,未确认)消息。 80 | 81 | > 发送⽅确认模式是异步的,⽣产者应⽤程序在等待确认的同时,可以继续发送消息。当确认消息到达⽣产者应⽤程序,⽣产者应⽤程序的回调⽅法就会被触发来处理确认消息。 82 | 83 | ## 6、如何确保消息接收⽅消费了消息? 84 | 85 | **接收⽅消息确认机制:**消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。 86 | 87 | 这⾥并没有⽤到超时机制,RabbitMQ仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer⾜够⻓的时间来处理消息。 88 | 89 | **特殊情况:** 90 | 91 | 1. 如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要根据bizId去重) 92 | 93 | 2. 如果消费者接收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多的消息。 94 | 95 | ## 7、如何避免消息重复投递或重复消费? 96 | 97 | > 在消息⽣产时,MQ内部针对每条⽣产者发送的消息⽣成⼀个inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进⼊队列;在消息消费时,要求消息体中必须要有⼀个bizId(对于同⼀业务全局唯⼀,如⽀付ID、订单ID、帖⼦ID等)作为去重和幂等的依据,避免同⼀条消息被重复消费。 98 | 99 | ## 8、消息基于什么传输? 100 | 101 | 由于TCP连接的创建和销毁开销较⼤,且并发数受系统资源限制,会造成性能瓶颈。 102 | 103 | RabbitMQ使⽤信道的⽅式来传输数据。信道是建⽴在真实的TCP连接内的虚拟连接,且每条 104 | 105 | TCP连接上的信道数量没有限制。 106 | 107 | 1. RabbitMQ采⽤类似NIO(Non-blocking I/O)做法,选择TCP连接复⽤,不仅可以减少性能开销,同时也便于管理。 108 | 109 | 2. 每个线程把持⼀个信道,所以信道服⽤了Connection的TCP连接。同时RabbitMQ可以确保每个线程的私密性,就像拥有独立的连接一样。 110 | 111 | ## 9、消息如何分发? 112 | 113 | > 若该队列⾄少有⼀个消费者订阅,消息将以循环(round-robin)的方式发送给消费者。每条消息只会分发给⼀个订阅的消费者(前提是消费者能够正常处理消息并进行确认)。 114 | 115 | ## 10、消息怎么路由? 116 | 117 | **从概念上来说,消息路由必须有三部分:**交换器、路由、绑定。⽣产者把消息发布到交换器上;绑定决定了消息如何从交换器路由到特定的队列;消息最终到达队列,并被消费者接收。 118 | 119 | 1. 消息发布到交换器时,消息将拥有⼀个路由键(routing key),在消息创建时设定。 120 | 121 | 2. 通过队列路由键,可以把队列绑定到交换器上。 122 | 123 | 3. 消息到达交换器后,RabbitMQ会将消息的路由键与队列的路由键进行匹配(针对不同的交换器有不同的路由规则)。 124 | 125 | 4. 如果能够匹配到队列,则消息会投递到相应队列中;如果不能匹配到任何队列,消息将进⼊ “⿊洞”。 126 | 127 | ## 11、如何确保消息不丢失? 128 | 129 | > 消息持久化的前提是:将交换器/队列的durable属性设置为true,表示交换器/队列是持久交换器/队列,在服务器崩溃或重启之后不需要重新创建交换器/队列(交换器/队列会⾃动创建)。 130 | 131 | **如果消息想要从Rabbit崩溃中恢复,那么消息必须:** 132 | 133 | 1. 在消息发布前,通过把它的 “投递模式” 选项设置为2(持久)来把消息标记成持久化 134 | 135 | 2. 将消息发送到持久交换器 136 | 137 | 3. 消息到达持久队列 138 | 139 | > RabbitMQ确保持久性消息能从服务器重启中恢复的⽅式是,将它们写⼊磁盘上的⼀个持久化⽇志⽂件,当发布⼀条持久性消息到持久交换器上时,Rabbit会在消息提交到⽇志⽂件后才发送响应(如果消息路由到了⾮持久队列,它会⾃动从持久化⽇志中移除)。⼀旦消费者从持久队列中消费了⼀条持久化消息,RabbitMQ会在持久化⽇志中把这条消息标记为等待垃圾收集。如果持久化消息在被消费之前RabbitMQ重启,那么Rabbit会⾃动重建交换器和队列(以及绑定),并重播持久化⽇志⽂件中的消息到合适的队列或者交换器上。 140 | 141 | ## 12、使⽤RabbitMQ有什么好处? 142 | 143 | 1. 应⽤解耦(系统拆分) 144 | 145 | 2. 异步处理(预约挂号业务处理成功后,异步发送短信、推送消息、⽇志记录等,可以⼤⼤减⼩响应时间) 146 | 147 | 3. 消息分发 148 | 149 | 4. **流量削峰:**将请求发送到队列中,短暂的⾼峰期积压是允许的。 150 | 151 | 5. 消息缓冲 152 | 153 | ## 13、消息队列有什么缺点? 154 | 155 | 1. **系统可⽤性降低:**消息队列出问题影响业务; 156 | 157 | 2. **系统复杂性增加:**加⼊消息队列,需要考虑很多⽅⾯的问题,⽐如:⼀致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。 158 | 159 | ## 14、MQ如何选型? 160 | 161 | ![别找了,Java面试还愁被问RabbitMQ?看完这22道问题解析就够了!](https://upload-images.jianshu.io/upload_images/11474088-963cbdf472061835?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 162 | 163 | 1. 中⼩型公司⾸选RabbitMQ:管理界⾯简单,⾼并发。 164 | 165 | 2. ⼤型公司可以选择RocketMQ:更⾼并发,可对rocketmq进⾏定制化开发。 166 | 167 | 3. ⽇志采集功能,⾸选kafka,专为⼤数据准备。 168 | 169 | ## 15、如何保证消息队列⾼可⽤? 170 | 171 | **1\. 集群:** 172 | 173 | ![别找了,Java面试还愁被问RabbitMQ?看完这22道问题解析就够了!](https://upload-images.jianshu.io/upload_images/11474088-db2bf3f27dfbf0ec?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 174 | 175 | * 集群可以扩展消息通信的吞吐量,但是不会备份消息,备份消息要通过镜像队列的⽅式解决。 176 | 177 | * 队列存储在单个节点、交换器存储在所有节点。 178 | 179 | **2\. 镜像队列:**将需要消费的队列变为镜像队列,存在于多个节点,这样就可以实现RabbitMQ 180 | 181 | 的HA⾼可⽤性。作⽤就是消息实体会主动在镜像节点之间实现同步,⽽不是像普通模式那样, 182 | 183 | 在consumer消费数据时临时读取。缺点就是,集群内部的同步通讯会占⽤⼤量的⽹络带宽。 184 | 185 | ![别找了,Java面试还愁被问RabbitMQ?看完这22道问题解析就够了!](https://upload-images.jianshu.io/upload_images/11474088-94a4a07bedf7b68b?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 186 | 187 | ## 16、如何保证消息的顺序性? 188 | 189 | 1. 通过某种算法,将需要保持先后顺序的消息放到同⼀个消息队列中(kafka中就是partition,rabbitMq中就是queue)。然后只⽤⼀个消费者去消费该队列。 190 | 191 | 2. 可以在消息体内添加全局有序标识来实现。 192 | 193 | ## 17、使用RabbitMQ增加rest服务吞吐量。 194 | 195 | ![别找了,Java面试还愁被问RabbitMQ?看完这22道问题解析就够了!](https://upload-images.jianshu.io/upload_images/11474088-07720ec1e97e13c0?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 196 | 197 | ## 18、RabbitMQ交换器有哪些类型? 198 | 199 | * **fanout交换器:**它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中; 200 | 201 | * **direct交换器:**direct类型的交换器路由规则很简单,它会把消息路由到哪些BindingKey和RoutingKey完全匹配的队列中; 202 | 203 | * **topic交换器:**匹配规则⽐direct更灵活。 204 | 205 | * **headers交换器:**根据发送消息内容的headers属性进⾏匹配(由于性能很差,不实⽤) 206 | 207 | **常⽤的交换器主要分为以下三种:** 208 | 209 | * **direct:**如果路由键完全匹配,消息就被投递到相应的队列 210 | 211 | * **fanout:**如果交换器收到消息,将会⼴播到所有绑定的队列上 212 | 213 | * **topic:**可以使来⾃不同源头的消息能够到达同⼀个队列。 使⽤topic交换器时,可以使⽤通配符,⽐如:“*” 匹配特定位置的任意⽂本, “.” 把路由键分为了⼏部分,“#” 匹配所有规则等。特别注意:发往topic交换器的消息不能随意的设置选择键(routing_key),必须是由"."隔开的⼀系列的标识符组成 214 | 215 | ## **19、RabbitMQ如何保证数据⼀致性?** 216 | 217 | 1. **⽣产者确认机制:**消息持久化后异步回调通知⽣产者,保证消息已经发出去; 218 | 219 | 2. **消息持久化:**设置消息持久化; 220 | 221 | 3. **消费者确认机制:**消费者成功消费消息之后,⼿动确认,保证消息已经消费。 222 | 223 | ## 20、RabbitMQ消费者自动扩展数量 224 | 225 | **SimpleMessageListenerContainer**可根据RabbitMQ消息堆积情况⾃动扩展消费者数量。 226 | 227 | ## 21、RabbitMQ结构: 228 | 229 | ![别找了,Java面试还愁被问RabbitMQ?看完这22道问题解析就够了!](https://upload-images.jianshu.io/upload_images/11474088-87561c902bbe4c68?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 230 | 231 | * Broker:简单来说就是消息队列服务器实体。 232 | 233 | * Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。 234 | 235 | * Queue:消息队列载体,每个消息都会被投⼊到⼀个或多个队列。 236 | 237 | * Binding:绑定,它的作⽤就是把exchange和queue按照路由规则绑定起来。 238 | 239 | * Routing Key:路由关键字,exchange根据这个关键字进⾏消息投递。 240 | 241 | * vhost:虚拟主机,⼀个broker⾥可以开设多个vhost,⽤作不同⽤户的权限分离。 242 | 243 | * producer:消息⽣产者,就是投递消息的程序。 244 | 245 | * consumer:消息消费者,就是接受消息的程序。 246 | 247 | * channel:消息通道,在客户端的每个连接⾥,可建⽴多个channel,每个channel代表⼀个会话任务。 248 | 249 | ## 22、rabbitmq队列与消费者的关系? 250 | 251 | 1. ⼀个队列可以绑定多个消费者; 252 | 253 | 2. 消息默认以循环的⽅式发送给消费者; 254 | 255 | 3. 消费者收到消息默认⾃动确认,也可以改成⼿动确认。 256 | 257 | -------------------------------------------------------------------------------- /Java核心面试知识集—Redis面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | 作为一个后端开发人员,不只是要求开发人员需要掌握 Redis,也要求运维人员也要懂 Redis。由于 Redis 的运用广泛,我们也知道它的重要性,至此面试中经常被问到。 26 | **用XMind画了一张导图记录Redis的学习笔记和一些面试解析及视频链接(源文件对部分节点有详细备注和参考资料,欢迎加入技术Q群拿下载链接,已经完善更新):** 27 | ![](https://upload-images.jianshu.io/upload_images/11474088-409a2bc6e3f17f9a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 28 | 29 | 30 | ## **一、Redis数据结构相关** 31 | ### **1.Redis 支持的数据类型** 32 | 33 | **String字符串** 34 | 35 | >**格式:**set key value 36 | >string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。 37 | >string类型是Redis最基本的数据类型,一个键最大能存储512MB。 38 | 39 | **Hash(哈希)** 40 | 41 | >**格式:** hmset name key1 value1 key2 value2 42 | >Redis hash 是一个键值(key=>value)对集合。 43 | >Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 44 | 45 | **List(列表)** 46 | >Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边) 47 | >**格式:**lpush name value 48 | >在 key 对应 list 的头部添加字符串元素 49 | >**格式:**rpush name value 50 | >在 key 对应 list 的尾部添加字符串元素 51 | >**格式:**lrem name index 52 | >key 对应 list 中删除 count 个和 value 相同的元素 53 | >**格式:**llen name 54 | >返回 key 对应 list 的长度 55 | 56 | **Set(集合)** 57 | 58 | >**格式:**sadd name value 59 | >Redis的Set是string类型的无序集合。 60 | >集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 61 | 62 | **zset(sorted set:有序集合)** 63 | 64 | >**格式:**zadd name score value 65 | >Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。 66 | >不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。 67 | >zset的成员是唯一的,但分数(score)却可以重复。 68 | 69 | ![](https://upload-images.jianshu.io/upload_images/11474088-6e4ca14ebdc13dc6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 70 | 71 | ### **2.Redis有哪些常用的命令?** 72 | [Redis命令大全](https://www.redis.net.cn/order/) 73 | 74 | ### **3.Redis有哪些应用场景** 75 | [查缺补漏,揭露Redis的秘密,巩固你的Redis知识体系](https://www.toutiao.com/i6857727790322549256/) 76 | 77 | ## 二、Redis事务 78 | ### 1.**什么是事务** 79 | 80 | Redis 中的事务是一组命令的集合,是 Redis 的最小执行单位,一个事务要么都执行,要么都不执行。**带有以下三个重要的保证:** 81 | 1. 批量操作在发送 EXEC 命令前被放入队列缓存。 82 | 1. 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。 83 | 1. 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。 84 | 85 | **Redis 事务的原理是**先将属于一个事务的命令发送给 Redis,然后依次执行这些命令。 86 | 87 | ### **2.为什么redis事务不具备原子性** 88 | >单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。 89 | >事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。 90 | 91 | ### **3. Redis 事务相关命令有哪些?** 92 | >**DISCARD:**取消事务,放弃执行事务块内的所有命令。 93 | >**EXEC:**执行所有事务块内的命令。 94 | >**MULTI:**标记一个事务块的开始。 95 | >**WATCH:**Redis Watch 命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断 96 | >**UNWATCH :**取消 WATCH 命令对所有 key 的监视。 97 | 98 | ## 三、Redis持久化和缓存管理 99 | ### **1.Redis持久化是什么?** 100 | >用一句话可以将持久化概括为:将数据(如内存中的对象)保存到可永久保存的存储设备中。 101 | >持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、 XML 数据文件中等等。 102 | >也可以从如下两个层面来理解持久化: 103 | >**应用层:**如果关闭( Close )你的应用,然后重新启动则先前的数据依然存在。 104 | >**系统层:**如果关闭( Shut Down )你的系统(电脑),然后重新启动则先前的数据依然存在。 105 | 106 | ### **2.Redis 持久化机制有哪些?** 107 | 108 | >Redis 提供两种方式进行持久化。 109 | >**RDB 持久化:**原理是将 Reids 在内存中的数据库记录定时 dump 到磁盘上的 RDB 持久化。 110 | >**AOF(append only file)持久化:**原理是将 Redis 的操作日志以追加的方式写入文件。 111 | 112 | ### **3.Redis 持久化机制 AOF 和 RDB 有什么区别?** 113 | 114 | aof,rdb是两种 redis持久化的机制。用于crash后,redis的恢复。 115 | **rdb的特性如下:** 116 | - fork一个进程,遍历hash table,利用copy on write,把整个db dump保存下来。 117 | - save, shutdown, slave 命令会触发这个操作。 118 | - 粒度比较大,如果save, shutdown, slave 之前crash了,则中间的操作没办法恢复。 119 | 120 | **aof有如下特性:** 121 | - 把写操作指令,持续的写到一个类似日志文件里。(类似于从postgresql等数据库导出sql一样,只记录写操作) 122 | - 粒度较小,crash之后,只有crash之前没有来得及做日志的操作没办法恢复。 123 | 124 | **两种区别就是,**一个是持续的用日志记录写操作,crash后利用日志恢复;一个是平时写操作的时候不触发写,只有手动提交save命令,或者是关闭命令时,才触发备份操作。 125 | 选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。 126 | 127 | ### **4.RDB和AOF 持久化机制的优缺点** 128 | 129 | **RDB优点** 130 | - RDB 是紧凑的二进制文件,比较适合备份,全量复制等场景 131 | - RDB 恢复数据远快于 AOF 132 | 133 | **RDB缺点** 134 | - RDB 无法实现实时或者秒级持久化; 135 | - 新老版本无法兼容 RDB 格式。 136 | 137 | **AOF优点** 138 | - 可以更好地保护数据不丢失; 139 | - appen-only 模式写入性能比较高; 140 | - 适合做灾难性的误删除紧急恢复。 141 | 142 | **AOF缺点:** 143 | - 对于同一份文件,AOF 文件要比 RDB 快照大; 144 | - AOF 开启后,会对写的 QPS 有所影响,相对于 RDB 来说 写 QPS 要下降; 145 | - 数据库恢复比较慢, 不合适做冷备。 146 | 147 | ### **5.Redis 淘汰策略有哪些?** 148 | 149 | Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。 150 | 151 | **Redis 的缓存淘汰策略有:** 152 | >**noeviction:**当内存不足以容纳新写入数据时,新写入操作会报错。 153 | >**allkeys-lru:**当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。 154 | >**allkeys-random:**当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。 155 | >**volatile-lru:**当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。 156 | >**volatile-random:**当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。 157 | 158 | ### **6.Redis 缓存失效策略有哪些?** 159 | 160 | **定时过期策略** 161 | 162 | 每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。 163 | 164 | **惰性过期策略** 165 | 166 | 只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。 167 | 168 | **定期过期策略** 169 | 170 | 每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。 171 | 172 | **Redis中同时使用了惰性过期和定期过期两种过期策略。** 173 | 174 | 所谓定期删除,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。 175 | 176 | **假设 redis 里放了 10w 个 key,都设置了过期时间,**你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key 上了。**注意,**这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的灾难。实际上 redis 是每隔 100ms 随机抽取一些 key 来检查和删除的。 177 | 178 | **但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?**所以就是惰性删除了。这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。 179 | 180 | **获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。**但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整?**答案是:走内存淘汰机制。** 181 | 182 | ## 四、Redis缓存异常方案 183 | 184 | ![](https://upload-images.jianshu.io/upload_images/11474088-d3276378ebe8a058.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 185 | 186 | ### **1.缓存雪崩** 187 | 188 | #### **1.1、什么是缓存雪崩?** 189 | 190 | 如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩由于原有缓存失效,新缓存未到期间所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。 191 | 192 | 举例来说, 我们在准备一项抢购的促销运营活动,活动期间将带来大量的商品信息、库存等相关信息的查询。 为了避免商品数据库的压力,将商品数据放入缓存中存储。 不巧的是,抢购活动期间,大量的热门商品缓存同时失效过期了,导致很大的查询流量落到了数据库之上。对于数据库来说造成很大的压力。 193 | 194 | #### **1.2、有什么解决方案来防止缓存雪崩?** 195 | 196 | > **1、加锁排队** 197 | > 198 | > mutex互斥锁解决,Redis的SETNX去set一个mutex key,当操作返回成功时,再进行加载数据库的操作并回设缓存,否则,就重试整个get缓存的方法 199 | > 200 | > **2、数据预热** 201 | > 202 | > 缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。用户直接查询事先被预热的缓存数据。可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key 203 | > 204 | > **3、双层缓存策略** 205 | > 206 | > C1为原始缓存,C2为拷贝缓存,C1失效时,可以访问C2,C1缓存失效时间设置为短期,C2设置为长期 207 | > 208 | > **4、定时更新缓存策略** 209 | > 210 | > 实效性要求不高的缓存,容器启动初始化加载,采用定时任务更新或移除缓存 211 | > 212 | > **5、设置不同的过期时间,让缓存失效的时间点尽量均匀** 213 | 214 | ### **2.缓存预热** 215 | 216 | #### **2.1.什么是缓存预热** 217 | 218 | 缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。**用户直接查询事先被预热的缓存数据。如图所示:** 219 | 220 | ![](https://upload-images.jianshu.io/upload_images/11474088-95aa0d0d1e375adb?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 221 | 222 | 如果不进行预热, 那么 Redis 初识状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。 223 | 224 | #### **2.2.有什么解决方案?** 225 | 226 | 1. 数据量不大的时候,工程启动的时候进行加载缓存动作; 227 | 2. 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新; 228 | 3. 数据量太大的时候,优先保证热点数据进行提前加载到缓存。 229 | 230 | ### **3.缓存穿透** 231 | 232 | #### **3.1、什么是缓存穿透?** 233 | 234 | 缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到对应key的value,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库 235 | 236 | #### **3.2、有什么解决方案来防止缓存穿透?** 237 | 238 | > **1、缓存空对象** 239 | > 240 | > 简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。 241 | > 242 | > **2、布隆过滤器** 243 | > 244 | > **优势:**占用内存空间很小,位存储;性能特别高,使用key的hash判断key存不存在 245 | > 将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力 246 | 247 | ### **4.缓存降级** 248 | 249 | 降级的情况,就是缓存失效或者缓存服务挂掉的情况下,我们也不去访问数据库。我们直接访问内存部分数据缓存或者直接返回默认数据。 250 | 251 | **举例来说:** 252 | 253 | > 对于应用的首页,一般是访问量非常大的地方,首页里面往往包含了部分推荐商品的展示信息。这些推荐商品都会放到缓存中进行存储,同时我们为了避免缓存的异常情况,对热点商品数据也存储到了内存中。同时内存中还保留了一些默认的商品信息。如下图所示: 254 | 255 | ![](https://upload-images.jianshu.io/upload_images/11474088-ed755e56c92703f8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 256 | 257 | 降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。 258 | 259 | ###**5.缓存击穿** 260 | 261 | #### **5.1、什么是缓存击穿?** 262 | 263 | 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力 264 | 265 | #### **5.2、会带来什么问题** 266 | 267 | 会造成某一时刻数据库请求量过大,压力剧增 268 | 269 | #### **5.3、如何解决** 270 | 271 | ##### **5.3.1.使用互斥锁(mutex key)** 272 | 这种解决方案思路比较简单,就是只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据就可以了。 如果是单机,可以用synchronized或者lock来处理,如果是分布式环境可以用分布式锁就可以了(分布式锁,可以用memcache的add, redis的setnx, zookeeper的添加节点操作)。 273 | ![](https://upload-images.jianshu.io/upload_images/11474088-0b556254199dec9a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 274 | 275 | ##### **5.3.2."永远不过期"** 276 | 277 | 1. 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。 278 | 2. 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期 279 | ![](https://upload-images.jianshu.io/upload_images/11474088-05ba12eea39351bc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 280 | 281 | ##### **5.3.3.缓存屏障** 282 | 该方法类似于方法一:使用countDownLatch和atomicInteger.compareAndSet()方法实现轻量级锁 283 | ``` 284 | class MyCache{ 285 | 286 | private ConcurrentHashMap map; 287 | 288 | private CountDownLatch countDownLatch; 289 | 290 | private AtomicInteger atomicInteger; 291 | 292 | public MyCache(ConcurrentHashMap map, CountDownLatch countDownLatch, 293 | AtomicInteger atomicInteger) { 294 | this.map = map; 295 | this.countDownLatch = countDownLatch; 296 | this.atomicInteger = atomicInteger; 297 | } 298 | 299 | public String get(String key){ 300 | 301 | String value = map.get(key); 302 | if (value != null){ 303 | System.out.println(Thread.currentThread().getName()+"\t 线程获取value值 value="+value); 304 | return value; 305 | } 306 | // 如果没获取到值 307 | // 首先尝试获取token,然后去查询db,初始化化缓存; 308 | // 如果没有获取到token,超时等待 309 | if (atomicInteger.compareAndSet(0,1)){ 310 | System.out.println(Thread.currentThread().getName()+"\t 线程获取token"); 311 | return null; 312 | } 313 | 314 | // 其他线程超时等待 315 | try { 316 | System.out.println(Thread.currentThread().getName()+"\t 线程没有获取token,等待中。。。"); 317 | countDownLatch.await(); 318 | } catch (InterruptedException e) { 319 | e.printStackTrace(); 320 | } 321 | // 初始化缓存成功,等待线程被唤醒 322 | // 等待线程等待超时,自动唤醒 323 | System.out.println(Thread.currentThread().getName()+"\t 线程被唤醒,获取value ="+map.get("key")); 324 | return map.get(key); 325 | } 326 | 327 | public void put(String key, String value){ 328 | 329 | try { 330 | Thread.sleep(2000); 331 | } catch (InterruptedException e) { 332 | e.printStackTrace(); 333 | } 334 | 335 | map.put(key, value); 336 | 337 | // 更新状态 338 | atomicInteger.compareAndSet(1, 2); 339 | 340 | // 通知其他线程 341 | countDownLatch.countDown(); 342 | System.out.println(); 343 | System.out.println(Thread.currentThread().getName()+"\t 线程初始化缓存成功!value ="+map.get("key")); 344 | } 345 | 346 | } 347 | 348 | class MyThread implements Runnable{ 349 | 350 | private MyCache myCache; 351 | 352 | public MyThread(MyCache myCache) { 353 | this.myCache = myCache; 354 | } 355 | 356 | @Override 357 | public void run() { 358 | String value = myCache.get("key"); 359 | if (value == null){ 360 | myCache.put("key","value"); 361 | } 362 | 363 | } 364 | } 365 | 366 | public class CountDownLatchDemo { 367 | public static void main(String[] args) { 368 | 369 | MyCache myCache = new MyCache(new ConcurrentHashMap<>(), new CountDownLatch(1), new AtomicInteger(0)); 370 | 371 | MyThread myThread = new MyThread(myCache); 372 | 373 | ExecutorService executorService = Executors.newFixedThreadPool(5); 374 | for (int i = 0; i < 5; i++) { 375 | executorService.execute(myThread); 376 | } 377 | } 378 | } 379 | ``` 380 | ## 五、Redis集群架构 381 | 382 | ### **1.实现主从复制的两种方式** 383 | 384 | #### **1.1slaveof命令** 385 | - 建立主从命令:slaveof ip port 386 | - 取消主从命令:slaveof no one 387 | 388 | #### **1.2.redis.conf配置文件配置** 389 | - 格式:slaveof ip port 390 | - 从节点只读:slave-read-only yes #配置只读 391 | 392 | ### **2.复制过程了解吗,讲一讲** 393 | 394 | 1. 从节点执行 slaveof 命令。 395 | 1. 从节点只是保存了 slaveof 命令中主节点的信息,并没有立即发起复制。 396 | 1. 从节点内部的定时任务发现有主节点的信息,开始使用 socket 连接主节点。 397 | 1. 连接建立成功后,发送 ping 命令,希望得到 pong 命令响应,否则会进行重连。 398 | 1. 如果主节点设置了权限,那么就需要进行权限验证,如果验证失败,复制终止。 399 | 1. 权限验证通过后,进行数据同步,这是耗时最长的操作,主节点将把所有的数据全部发送给从节点。 400 | 1. 当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。 401 | 402 | ### **3.redis的主从结构** 403 | 404 | >主从结构一是可以进行冗余备份,二是可以实现读写分离 405 | >**主从复制** 406 | >冗余备份(还可以称为:主从复制,数据冗余,数据备份,可以实现容灾快速恢复) 407 | >持久化保证了即使redis服务重启也会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过redis的主从复制机制就可以避免这种单点故障. 例如:我们搭建一个主叫做redis0,两个从,分别叫做redis1和redis2,即使一台redis服务器宕机其它两台redis服务也可以继续提供服务。主redis中的数据和从redis上的数据保持实时同步,当主redis写入数据时通过主从复制机制会复制到两个从redis服务上。 408 | >1.一个Master可以有多个Slave,不仅主服务器可以有从服务器,从服务器也可以有自己的从服务器 409 | >2.复制在Master端是非阻塞模式的,这意味着即便是多个Slave执行首次同步时,Master依然可以提供查询服务; 410 | >3.复制在Slave端也是非阻塞模式的:如果你在redis.conf做了设置,Slave在执行首次同步的时候仍可以使用旧数据集提供查询;你也可以配置为当Master与Slave失去联系时,让Slave返回客户端一个错误提示; 411 | >4.当Slave要删掉旧的数据集,并重新加载新版数据时,Slave会阻塞连接请求 412 | >**读写分离** 413 | >主从架构中,可以考虑关闭主服务器的数据持久化功能,只让从服务器进行持久化,这样可以提高主服务器的处理性能。从服务器通常被设置为只读模式,这样可以避免从服务器的数据被误修改。 414 | 415 | ### **4.什么是Redis 慢查询?怎么配置?** 416 | >慢查询就是指,系统执行命令之后,计算统计每条指令执行的时间,如果执行时间超过设置的阈值,就说明该命令为慢指令。 417 | >**Redis 慢查询配置参数为:** 418 | >slowlog-log-slower-than:设置慢查询定义阈值,超过这个阈值就是慢查询。单位为微妙,默认为 10000。 419 | >slowlog-max-len:慢查询的日志列表的最大长度,当慢查询日志列表处于最大长度的时候,最早插入的一个命令将从列表中移除。 420 | 421 | ### **5.Redis 有哪些架构模式?讲讲各自的特点** 422 | 423 | 主从模式,一般是一个主节点,一或多个从节点,为了保证我们的主节点宕机后,数据不丢失,我们将主节点的数据备份到从节点,从节点并不进行实际操作,只做实时同步操作,并不能起到高并发的目的。 424 | 425 | ![](https://upload-images.jianshu.io/upload_images/11474088-105b06979be3c851.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 426 | 427 | 哨兵模式,一个哨兵集群和一组主从架构组成。比主从更好的是当我们的主节点宕机以后,哨兵会主动选举出一个主节点继续向外提供服务。 428 | 429 | ![](https://upload-images.jianshu.io/upload_images/11474088-4d7071b29c120a9e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 430 | 431 | 集群架构,由很多个小主从聚集在一起,一起向外提供服务的,将16384个卡槽切分存储,并不是一个强一致性的集群架构,每一个小主从节点内会存在选举机制,保证对外的高可用架构。 432 | 433 | ## **六、Redis分布式锁** 434 | 435 | 有时候讲出来的更通俗 436 | [Redis分布式锁正确实现视频讲解](https://www.bilibili.com/video/av90214360) 437 | 438 | -------------------------------------------------------------------------------- /Java核心面试知识集—Spring Cloud面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ------ 26 | 27 | ## 前言 28 | 29 | **来分享一下面试必备的Spring Cloud问题解析!**用XMind画了一张导图记录**Spring Cloud**的学习笔记和一些面试解析(源文件对部分节点有详细备注和参考资料,欢迎加入技术Q群分享获取): 30 | 31 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p3.pstatp.com/large/pgc-image/827bdc3d84314d998fc5633710defa48) 32 | 33 | 34 | 35 | ## **1.什么是微服务** 36 | 37 | 1. 微服务是一种架构⻛格,也是一种服务; 38 | 2. 微服务的颗粒⽐较⼩,⼀个⼤型复杂软件应⽤由多个微服务组成,⽐如Netflix⽬前由500多的微服务组成; 39 | 3. 它采用UNIX设计的哲学,每种服务只做⼀件事,是一种松耦合的能够被独⽴开发和部署的⽆状态化服务(独⽴扩展、升级和可替换)。 40 | 41 | ## **2. 微服务之间是如何独立通讯的** 42 | 43 | 1. Dubbo 使用的是 RPC 通信,⼆进制传输,占⽤带宽⼩; 44 | 2. Spring Cloud 使⽤的是 HTTP RESTFul ⽅式。 45 | 46 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p3.pstatp.com/large/pgc-image/b392b4ad84b2487eb632f9d1ad0dcb1c) 47 | 48 | 49 | 50 | ## **3. springcloud和dubbo有哪些区别** 51 | 52 | 1. Dubbo具有调度、发现、监控、治理等功能,⽀持相当丰富的服务治理能力。Dubbo架构下,注册中⼼对等集群,并会缓存服务列表已被数据库失效时继续提供发现功能,本身的服务发现结构有很强的可⽤性与健壮性,⾜够⽀持⾼访问量的⽹站。 53 | 2. 虽然Dubbo ⽀持短连接⼤数据量的服务提供模式,但绝大多数情况下都是使⽤⻓连接⼩数据量的模式提供服务使用的。所以,对于类似于电商等同步调⽤场景多并且能⽀撑搭建Dubbo 这套⽐较复杂环境的成本的产品⽽⾔,Dubbo 确实是一个可以考虑的选择。但如果产品业务中由于后台业务逻辑复杂、时间⻓⽽导致异步逻辑⽐较多的话,可能Dubbo 并不合适。同时,对于⼈⼿不⾜的初创产品⽽⾔,这么重的架构维护起来也不是很方便。 54 | 3. Spring Cloud由众多⼦项⽬组成,如Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Consul 等,提供了搭建分布式系统及微服务常用的⼯具,如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性token、全局锁、选主、分布式会话和集群状态等,满足了构建微服务所需的所有解决⽅案。⽐如使⽤Spring Cloud Config 可以实现统⼀配置中⼼,对配置进⾏统⼀管理;使⽤Spring Cloud Netflix 可以实现Netflix 组件的功能 - 服务发现(Eureka)、智能路由(Zuul)、客户端负载均衡(Ribbon)。 55 | 4. Dubbo 提供了各种 Filter,对于上述中“⽆”的要素,可以通过扩展 Filter 来完善。 56 | 5. dubbo的开发难度较⼤,原因是dubbo的jar包依赖问题很多⼤型⼯程⽆法解决。 57 | 58 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p9.pstatp.com/large/pgc-image/d8f6598a96fd4bfcb418aeebe2edd0c0) 59 | 60 | 61 | 62 | ## **4. springboot和springcloud认识** 63 | 64 | - Spring Boot 是 Spring 的⼀套快速配置脚⼿架,可以基于Spring Boot 快速开发单个微服务,Spring Cloud是一个基于Spring Boot实现的云应⽤开发⼯具; 65 | - Spring Boot专注于快速、⽅便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架; 66 | - Spring Boot使⽤了默认⼤于配置的理念,很多集成⽅案已经帮你选择好了,能不配置就不配置; 67 | - Spring Cloud很⼤的⼀部分是基于Spring Boot来实现,可以不基于Spring Boot吗?不可以。 68 | 69 | ## 5. 什么是服务熔断,什么是服务降级 70 | 71 | **服务熔断:** 72 | 73 | 如果检查出来频繁超时,就把consumer调⽤provider的请求,直接短路掉,不实际调⽤,⽽是直接返回⼀个mock的值。 74 | 75 | **服务降级:** 76 | 77 | **consumer 端:**consumer 如果发现某个provider出现异常情况,⽐如,经常超时(可能是熔断引起的降级),数据错误,这时,consumer可以采取⼀定的策略,降级provider的逻辑,基本的有直接返回固定的数据。 78 | 79 | **provider 端:**当provider 发现流量激增的时候,为了保护⾃身的稳定性,也可能考虑降级服务。 80 | 81 | 1.直接给consumer返回固定数据 82 | 83 | 2.需要实时写⼊数据库的,先缓存到队列⾥,异步写⼊数据库。 84 | 85 | ## **6. 微服务的优缺点** 86 | 87 | **优点:** 88 | 89 | - 单⼀职责:每个微服务仅负责⾃⼰业务领域的功能; 90 | - ⾃治:⼀个微服务就是⼀个独⽴的实体,它可以独⽴部署、升级,服务与服务之间通过REST等形式的标准接⼝进⾏通信,并且⼀个微服务实例可以被替换成另⼀种实现,⽽对其它的微服务不产⽣影响。 91 | - 逻辑清晰:微服务单⼀职责特性使微服务看起来逻辑清晰,易于维护。 92 | - 简化部署:单系统中修改⼀处需要部署整个系统,⽽微服务中修改⼀处可单独部署⼀个服务 93 | - 可扩展:应对系统业务增长的⽅法通常采用横向(Scale out)或纵向(Scale up)的⽅向进⾏扩展。分布式系统中通常要采⽤Scale out的⽅式进⾏扩展。 94 | - 灵活组合: 95 | - 技术异构:不同的服务之间,可以根据⾃⼰的业务特点选择不通的技术架构,如数据库等。 96 | 97 | **缺点:** 98 | 99 | - **复杂度⾼:**服务调⽤要考虑被调⽤⽅故障、过载、消息丢失等各种异常情况,代码逻辑更加复杂;对于微服务间的事务性操作,因为不同的微服务采⽤了不同的数据库,将⽆法利⽤数据库本身的事务机制保证⼀致性,需要引⼊⼆阶段提交等技术。 100 | - **运维复杂:**系统由多个独⽴运⾏的微服务构成,需要⼀个设计良好的监控系统对各个微服务的运⾏状态进⾏监控。运维⼈员需要对系统有细致的了解才对够更好的运维系统。 101 | - **通信延迟:**微服务之间调⽤会有时间损耗,造成通信延迟。 102 | 103 | ## 7. 使⽤中碰到的坑 104 | 105 | - **超时:**确保Hystrix超时时间配置为⻓于配置的Ribbon超时时间 106 | - **feign path:**feign客户端在部署时若有contextpath应该设置 path="/***"来匹配你的服务名。 107 | - **版本:**springboot和springcloud版本要兼容。 108 | 109 | ## **8. 列举微服务技术栈** 110 | 111 | - 服务⽹关Zuul 112 | - 服务注册发现Eureka+Ribbon 113 | - 服务配置中⼼Apollo 114 | - 认证授权中⼼Spring Security OAuth2 115 | - 服务框架Spring Boot 116 | - 数据总线Kafka 117 | - ⽇志监控ELK 118 | - 调⽤链监控CAT 119 | - Metrics监控KairosDB 120 | - 健康检查和告警ZMon 121 | - 限流熔断和流聚合Hystrix/Turbine 122 | 123 | ## **9. eureka和zookeeper都可以提供服务的注册与发现功能,他们的区别** 124 | 125 | **Zookeeper保证CP** 126 | 127 | 当向注册中⼼查询服务列表时,我们可以容忍注册中⼼返回的是⼏分钟以前的注册信息,但不能接受服务直接down掉不可⽤。也就是说,服务注册功能对可⽤性的要求要⾼于⼀致性。但是zk会出现这样⼀种情况,当master节点因为⽹络故障与其他节点失去联系时,剩余节点会重新进⾏leader选举。问题在于,选举leader的时间太⻓,30 ~ 120s, 且选举期间整个zk集群都是不可⽤的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因⽹络问题使得zk集群失去master节点是较⼤概率会发⽣的事,虽然服务能够最终恢复,但是漫⻓的选举时间导致的注册⻓期不可⽤是不能容忍的。 128 | 129 | **Eureka保证AP** 130 | 131 | Eureka看明⽩了这⼀点,因此在设计时就优先保证可⽤性。Eureka各个节点都是平等的,⼏个节点挂掉不会影响正常节点的⼯作,剩余的节点依然可以提供注册和查询服务。⽽Eureka的客户端在向某个Eureka注册或如果发现连接失败,则会⾃动切换⾄其它节点,只要有⼀台Eureka还在,就能保证注册服务可⽤(保证可⽤性),只不过查到的信息可能不是最新的(不保证强⼀致性)。除此之外,Eureka还有⼀种⾃我保护机制,如果在15分钟内超过85%的节点都没有正常的⼼跳,那么Eureka就认为客户端与注册中⼼出现了⽹络故障,**此时会出现以下⼏种情况:** 132 | 133 | 1. Eureka不再从注册列表中移除因为⻓时间没收到⼼跳⽽应该过期的服务 134 | 2. Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可⽤) 135 | 3. 当⽹络稳定时,当前实例新的注册信息会被同步到其它节点中 136 | 137 | **因此, Eureka可以很好的应对因⽹络故障导致部分节点失去联系的情况,⽽不会像zookeeper那样使整个注册服务瘫痪。** 138 | 139 | ## **10. eureka服务注册与发现原理** 140 | 141 | - 每30s发送⼼跳检测重新进⾏租约,如果客户端不能多次更新租约,它将在90s内从服务器注册中⼼移除。 142 | - 注册信息和更新会被复制到其他Eureka 节点,来⾃任何区域的客户端可以查找到注册中⼼信息,每30s发⽣⼀次复制来定位他们的服务,并进⾏远程调⽤。 143 | - 客户端还可以缓存⼀些服务实例信息,所以即使Eureka全挂掉,客户端也是可以定位到服务地址的。 144 | 145 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p3.pstatp.com/large/pgc-image/760dbe42c99847a8851567fe1c26e37d) 146 | 147 | 148 | 149 | ## **11. dubbo服务注册与发现原理** 150 | 151 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p3.pstatp.com/large/pgc-image/c311d4d00a1049d5b1ba0d01b58b85d3) 152 | 153 | 154 | 155 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p1.pstatp.com/large/pgc-image/12ac18f472744e6697a6d8e8c4dc8c43) 156 | 157 | 158 | 159 | **调⽤关系说明:** 160 | 161 | 1. 服务容器负责启动,加载,运⾏服务提供者。 162 | 2. 服务提供者在启动时,向注册中⼼注册⾃⼰提供的服务。 163 | 3. 服务消费者在启动时,向注册中⼼订阅⾃⼰所需的服务。 164 | 4. 注册中⼼返回服务提供者地址列表给消费者,如果有变更,注册中⼼将基于⻓连接推送变更数据给消费者。 165 | 5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选⼀台提供者进⾏调⽤,如果调⽤失败,再选另⼀台调⽤。 166 | 6. 服务消费者和提供者,在内存中累计调⽤次数和调⽤时间,定时每分钟发送⼀次统计数据到监控中⼼。 167 | 168 | ## **12. 限流** 169 | 170 | ### **1、http限流:我们使⽤nginx的limitzone来完成:** 171 | 172 | ``` 173 | //这个表示使⽤ip进⾏限流 zone名称为req_one 分配了10m 空间使⽤漏桶算法 每秒钟允许1个请求 174 | limit_req_zone $binary_remote_addr zone=req_one:10m rate=1r/s; //这边burst表示可以瞬间超过20个请求 由于没有noDelay参数因此需要排队 如果超过这20个那么直接返回503 175 | limit_req zone=req_three burst=20; 176 | ``` 177 | 178 | ### **2、dubbo限流:dubbo提供了多个和请求相关的filter:ActiveLimitFilter ExecuteLimitFilter TPSLimiterFilter** 179 | 180 | **1. ActiveLimitFilter:** 181 | 182 | ``` 183 | @Activate(group = Constants.CONSUMER, value = Constants.ACTIVES_KEY) 184 | ``` 185 | 186 | 作⽤于客户端,主要作⽤是控制客户端⽅法的并发度; 187 | 188 | 当超过了指定的active值之后该请求将等待前⾯的请求完成【何时结束呢?依赖于该⽅法的timeout 如果没有设置timeout的话可能就是多个请求⼀直被阻塞然后等待随机唤醒。 189 | 190 | **2. ExecuteLimitFilter:** 191 | 192 | ``` 193 | @Activate(group = Constants.PROVIDER, value = Constants.EXECUTES_KEY) 194 | ``` 195 | 196 | 作⽤于服务端,⼀旦超出指定的数⽬直接报错 其实是指在服务端的并⾏度【需要注意这些都是指的是在单台服务上⽽不是整个服务集群】 197 | 198 | **3. TPSLimiterFilter:** 199 | 200 | ``` 201 | @Activate(group = Constants.PROVIDER, value = Constants.TPS_LIMIT_RATE_KEY) 202 | ``` 203 | 204 | - 作⽤于服务端,控制⼀段时间内的请求数; 205 | - 默认情况下取得tps.interval字段表示请求间隔 如果⽆法找到则使⽤60s 根据tps字段表示允许调⽤次数。 206 | - 使⽤AtomicInteger表示允许调⽤的次数 每次调⽤减少1次当结果⼩于0之后返回不允许调⽤ 207 | 208 | ### **3、springcloud限流:** 209 | 210 | 1.我们可以通过semaphore.maxConcurrentRequests,coreSize,maxQueueSize和queueSizeRejectionThreshold设置信号量模式下的最⼤并发量、线程池⼤⼩、缓冲区⼤⼩和缓冲区降级阈值。 211 | 212 | ``` 213 | #不设置缓冲区,当请求数超过coreSize时直接降级 214 | hystrix.threadpool.userThreadPool.maxQueueSize=-1 #超时时间⼤于我们的timeout接⼝返回时间 215 | hystrix.command.userCommandKey.execution.isolation.thread.timeoutInMilliseconds=15000 216 | ``` 217 | 218 | 这个时候我们连续多次请求/user/command/timeout接⼝,在第⼀个请求还没有成功返回时,查看输出⽇志可以发现只有第⼀个请求正常的进⼊到user-service的接⼝中,其它请求会直接返回降级信息。这样我们就实现了对服务请求的限流。 219 | 220 | **2.漏桶算法:**⽔(请求)先进⼊到漏桶⾥,漏桶以⼀定的速度出⽔,当⽔流⼊速度过⼤会直接溢出,可以看出漏桶算法能强⾏限制数据的传输速率。 221 | 222 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p1.pstatp.com/large/pgc-image/a32eed6ba1ad40d6928bdacace20103e) 223 | 224 | 225 | 226 | **3.令牌桶算法:**除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。**如图所示,令牌桶算法的原理是系统会以⼀个恒定的速度往桶⾥放⼊令牌,⽽如果请求需要被处理,则需要先从桶⾥获取⼀个令牌,当桶⾥没有令牌可取时,则拒绝服务。** 227 | 228 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p3.pstatp.com/large/pgc-image/7692c1da8cdc417b99d6ee0004de20b2) 229 | 230 | 231 | 232 | ### **4、redis计数器限流;** 233 | 234 | ## 13. springcloud核⼼组件及其作⽤,以及springcloud⼯作原理: 235 | 236 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p1.pstatp.com/large/pgc-image/3da84a69bc9d40f1834aa7d61d968ac1) 237 | 238 | 239 | 240 | **springcloud由以下⼏个核⼼组件构成:** 241 | 242 | - **Eureka:**各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取注册表,从⽽知道其他服务在哪⾥ 243 | - **Ribbon:**服务间发起请求的时候,基于Ribbon做负载均衡,从⼀个服务的多台机器中选择⼀台 244 | - **Feign:**基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求 245 | - **Hystrix:**发起请求是通过Hystrix的线程池来⾛的,不同的服务⾛不同的线程池,实现了不同服务调⽤的隔离,避免了服务雪崩的问题 246 | - **Zuul:**如果前端、移动端要调⽤后端系统,统⼀从Zuul⽹关进⼊,由Zuul⽹关转发请求给对应的服务 247 | 248 | ## 14. eureka的缺点: 249 | 250 | 某个服务不可⽤时,各个Eureka Client不能及时的知道,需要1~3个⼼跳周期才能感知,但是,由于基于Netflix的服务调⽤端都会使⽤Hystrix来容错和降级,当服务调⽤不可⽤时Hystrix也能及时感知到,通过熔断机制来降级服务调⽤,因此弥补了基于客户端服务发现的时效性的缺点。 251 | 252 | ## 15. eureka缓存机制: 253 | 254 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p1.pstatp.com/large/pgc-image/f47b18e721984745b47cbbada0be18fa) 255 | 256 | 257 | 258 | - **第⼀层缓存:**readOnlyCacheMap,本质上是ConcurrentHashMap:这是⼀个JVM的CurrentHashMap只读缓存,这个主要是为了供客户端获取注册信息时使⽤,其缓存更新,依赖于定时器的更新,通过和readWriteCacheMap 的值做对⽐,如果数据不⼀致,则以readWriteCacheMap 的数据为准。readOnlyCacheMap 缓存更新的定时器时间间隔,默认为30秒 259 | - **第⼆层缓存:**readWriteCacheMap,本质上是Guava缓存:此处存放的是最终的缓存, 当服务下线,过期,注册,状态变更,都会来清除这个缓存⾥⾯的数据。 然后通过CacheLoader进⾏缓存加载,在进⾏readWriteCacheMap.get(key)的时候,⾸先看这个缓存⾥⾯有没有该数据,如果没有则通过CacheLoader的load⽅法去加载,加载成功之后将数据放⼊缓存,同时返回数据。 readWriteCacheMap 缓存过期时间,默认为 180 秒 。 260 | - **缓存机制:**设置了⼀个每30秒执⾏⼀次的定时任务,定时去服务端获取注册信息。获取之后,存⼊本地内存。 261 | 262 | ## 16. 熔断的原理,以及如何恢复? 263 | 264 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p9.pstatp.com/large/pgc-image/d82552c82c9344cf849d52a219f318fc) 265 | 266 | 267 | 268 | **a. 服务的健康状况 = 请求失败数 / 请求总数.** 269 | 270 | 熔断器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值⽐较决定的. 271 | 272 | - 当熔断器开关关闭时, 请求被允许通过熔断器. 如果当前健康状况⾼于设定阈值, 开关继续保持关闭. 如果当前健康状况低于设定阈值, 开关则切换为打开状态. 273 | - 当熔断器开关打开时, 请求被禁⽌通过. 274 | - 当熔断器开关处于打开状态, 经过⼀段时间后, 熔断器会⾃动进⼊半开状态, 这时熔断器只允许⼀个请求通过. 当该请求调⽤成功时, 熔断器恢复到关闭状态. 若该请求失败, 熔断器继续保持打开状态, 接下来的请求被禁⽌通过. 275 | 276 | 熔断器的开关能保证服务调⽤者在调⽤异常服务时, 快速返回结果, 避免⼤量的同步等待. 并且熔断器能在⼀段时间后继续侦测请求执⾏结果, 提供恢复服务调⽤的可能。 277 | 278 | ## 17. 服务雪崩? 279 | 280 | **简介:**服务雪崩效应是⼀种因服务提供者的不可⽤导致服务调⽤者的不可⽤,并将不可⽤逐渐放⼤的过程. 281 | 282 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p1.pstatp.com/large/pgc-image/2d33f78547644117814d7f82167f386f) 283 | 284 | 285 | 286 | **形成原因:** 287 | 288 | - 服务提供者不可 289 | - 重试加⼤流量 290 | - 服务调⽤者不可⽤ 291 | 292 | **采⽤策略:** 293 | 294 | - 流量控制 295 | - 改进缓存模式 296 | - 服务⾃动扩容 297 | - 服务调⽤者降级服务 298 | 299 | ## 18. 什么是 Hystrix 断路器?我们需要它吗? 300 | 301 | 由于某些原因,employee-consumer 公开服务会引发异常。在这种情况下使用 Hystrix 我们定义了一个回退方法。如果在公开服务中发生异常,则回退方法返回一些默认值 302 | 303 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p3.pstatp.com/large/pgc-image/1295bb52dce241f1895e4d2a44eca754) 304 | 305 | 306 | 307 | 中断,并且员工使用者将一起跳过 firtsPage 方法,并直接调用回退方法。 断路器的目的是给第一页方法或第一页方法可能调用的其他方法留出时间,并导致异常恢复。可能发生的情况是,在负载较小的情况下,导致异常的问题有更好的恢复机会 。 308 | 309 | ![2020备战阿里Java岗,这20道SpringCloud面试解析不懂,等通知吧](http://p1.pstatp.com/large/pgc-image/d176634898da400fbe7e9dd6f1084e48) 310 | 311 | 312 | 313 | ## 19. 多个消费者调⽤同⼀接⼝,eruka默认的分配⽅式是什么? 314 | 315 | - RoundRobinRule:轮询策略,Ribbon以轮询的⽅式选择服务器,这个是默认值。所以示例中所启动的两个服务会被循环访问; 316 | - RandomRule:随机选择,也就是说Ribbon会随机从服务器列表中选择⼀个进⾏访问; 317 | - BestAvailableRule:最⼤可⽤策略,即先过滤出故障服务器后,选择⼀个当前并发请求数最⼩的; 318 | - WeightedResponseTimeRule:带有加权的轮询策略,对各个服务器响应时间进⾏加权处理,然后在采⽤轮询的⽅式来获取相应的服务器; 319 | - AvailabilityFilteringRule:可⽤过滤策略,先过滤出故障的或并发请求⼤于阈值⼀部分服务实例,然后再以线性轮询的⽅式从过滤后的实例清单中选出⼀个; 320 | - ZoneAvoidanceRule:区域感知策略,先使⽤主过滤条件(区域负载器,选择最优区域)对所有实例过滤并返回过滤后的实例清单,依次使⽤次过滤条件列表中的过滤条件对主过滤条件的结果进⾏过滤,判断最⼩过滤数(默认1)和最⼩过滤百分⽐(默认0),最后对满⾜条件的服务器则使⽤RoundRobinRule(轮询⽅式)选择⼀个服务器实例。 321 | 322 | ## **20. 接⼝限流⽅法?** 323 | 324 | - 限制 总并发数(⽐如 数据库连接池、线程池) 325 | 326 | 1. 限制 瞬时并发数(如 nginx 的 limit_conn 模块,⽤来限制 瞬时并发连接数) 327 | 2. 限制 时间窗⼝内的平均速率(如 Guava 的 RateLimiter、nginx 的 limit_req模块,限制每秒的平均速率) 328 | 3. 限制 远程接⼝ 调⽤速率 329 | 4. 限制 MQ 的消费速率 330 | 5. 可以根据⽹络连接数、⽹络流量、CPU或内存负载等来限流 331 | 332 | -------------------------------------------------------------------------------- /Java核心面试知识集—SpringBoot面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ## 概述 26 | 27 | ### 什么是 Spring Boot? 28 | 29 | Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。 30 | 31 | ### Spring Boot 有哪些优点? 32 | 33 | Spring Boot 主要有如下优点: 34 | 35 | 1. 容易上手,提升开发效率,为 Spring 开发提供一个更快、更广泛的入门体验。 36 | 2. 开箱即用,远离繁琐的配置。 37 | 3. 提供了一系列大型项目通用的非业务性功能,例如:内嵌服务器、安全管理、运行数据监控、运行状况检查和外部化配置等。 38 | 4. 没有代码生成,也不需要XML配置。 39 | 5. 避免大量的 Maven 导入和各种版本冲突。 40 | 41 | ### Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的? 42 | 43 | 启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解: 44 | 45 | @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。 46 | 47 | @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。 48 | 49 | @ComponentScan:Spring组件扫描。 50 | 51 | ## 配置 52 | 53 | ### 什么是 JavaConfig? 54 | 55 | Spring JavaConfig 是 Spring 社区的产品,它提供了配置 Spring IoC 容器的纯Java 方法。因此它有助于避免使用 XML 配置。使用 JavaConfig 的优点在于: 56 | 57 | (1)面向对象的配置。由于配置被定义为 JavaConfig 中的类,因此用户可以充分利用 Java 中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean 方法等。 58 | 59 | (2)减少或消除 XML 配置。基于依赖注入原则的外化配置的好处已被证明。但是,许多开发人员不希望在 XML 和 Java 之间来回切换。JavaConfig 为开发人员提供了一种纯 Java 方法来配置与 XML 配置概念相似的 Spring 容器。从技术角度来讲,只使用 JavaConfig 配置类来配置容器是可行的,但实际上很多人认为将JavaConfig 与 XML 混合匹配是理想的。 60 | 61 | (3)类型安全和重构友好。JavaConfig 提供了一种类型安全的方法来配置 Spring容器。由于 Java 5.0 对泛型的支持,现在可以按类型而不是按名称检索 bean,不需要任何强制转换或基于字符串的查找。 62 | 63 | ### Spring Boot 自动配置原理是什么? 64 | 65 | 注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自动配置的核心, 66 | 67 | @EnableAutoConfiguration 给容器导入META-INF/spring.factories 里定义的自动配置类。 68 | 69 | 筛选有效的自动配置类。 70 | 71 | 每一个自动配置类结合对应的 xxxProperties.java 读取配置文件进行自动配置功能 72 | 73 | ### 你如何理解 Spring Boot 配置加载顺序? 74 | 75 | 在 Spring Boot 里面,可以使用以下几种方式来加载配置。 76 | 77 | 1)properties文件; 78 | 79 | 2)YAML文件; 80 | 81 | 3)系统环境变量; 82 | 83 | 4)命令行参数; 84 | 85 | 等等…… 86 | 87 | ### 什么是 YAML? 88 | 89 | YAML 是一种人类可读的数据序列化语言。它通常用于配置文件。与属性文件相比,如果我们想要在配置文件中添加复杂的属性,YAML 文件就更加结构化,而且更少混淆。可以看出 YAML 具有分层配置数据。 90 | 91 | ### YAML 配置的优势在哪里 ? 92 | 93 | YAML 现在可以算是非常流行的一种配置文件格式了,无论是前端还是后端,都可以见到 YAML 配置。那么 YAML 配置和传统的 properties 配置相比到底有哪些优势呢? 94 | 95 | 1. 配置有序,在一些特殊的场景下,配置有序很关键 96 | 2. 支持数组,数组中的元素可以是基本数据类型也可以是对象 97 | 3. 简洁 98 | 99 | 相比 properties 配置文件,YAML 还有一个缺点,就是不支持 @PropertySource 注解导入自定义的 YAML 配置。 100 | 101 | ### Spring Boot 是否可以使用 XML 配置 ? 102 | 103 | Spring Boot 推荐使用 Java 配置而非 XML 配置,但是 Spring Boot 中也可以使用 XML 配置,通过 @ImportResource 注解可以引入一个 XML 配置。 104 | 105 | ### spring boot 核心配置文件是什么?bootstrap.properties 和 application.properties 有何区别 ? 106 | 107 | 单纯做 Spring Boot 开发,可能不太容易遇到 bootstrap.properties 配置文件,但是在结合 Spring Cloud 时,这个配置就会经常遇到了,特别是在需要加载一些远程配置文件的时侯。 108 | 109 | spring boot 核心的两个配置文件: 110 | 111 | - bootstrap (. yml 或者 . properties):boostrap 由父 ApplicationContext 加载的,比 applicaton 优先加载,配置在应用程序上下文的引导阶段生效。一般来说我们在 Spring Cloud Config 或者 Nacos 中会用到它。且 boostrap 里面的属性不能被覆盖; 112 | - application (. yml 或者 . properties): 由ApplicatonContext 加载,用于 spring boot 项目的自动化配置。 113 | 114 | ### 什么是 Spring Profiles? 115 | 116 | Spring Profiles 允许用户根据配置文件(dev,test,prod 等)来注册 bean。因此,当应用程序在开发中运行时,只有某些 bean 可以加载,而在 PRODUCTION中,某些其他 bean 可以加载。假设我们的要求是 Swagger 文档仅适用于 QA 环境,并且禁用所有其他文档。这可以使用配置文件来完成。Spring Boot 使得使用配置文件非常简单。 117 | 118 | ### 如何在自定义端口上运行 Spring Boot 应用程序? 119 | 120 | 为了在自定义端口上运行 Spring Boot 应用程序,您可以在application.properties 中指定端口。server.port = 8090 121 | 122 | ## 安全 123 | 124 | ### 如何实现 Spring Boot 应用程序的安全性? 125 | 126 | 为了实现 Spring Boot 的安全性,我们使用 spring-boot-starter-security 依赖项,并且必须添加安全配置。它只需要很少的代码。配置类将必须扩展WebSecurityConfigurerAdapter 并覆盖其方法。 127 | 128 | ### 比较一下 Spring Security 和 Shiro 各自的优缺点 ? 129 | 130 | 由于 Spring Boot 官方提供了大量的非常方便的开箱即用的 Starter ,包括 Spring Security 的 Starter ,使得在 Spring Boot 中使用 Spring Security 变得更加容易,甚至只需要添加一个依赖就可以保护所有的接口,所以,如果是 Spring Boot 项目,一般选择 Spring Security 。当然这只是一个建议的组合,单纯从技术上来说,无论怎么组合,都是没有问题的。Shiro 和 Spring Security 相比,主要有如下一些特点: 131 | 132 | 1. Spring Security 是一个重量级的安全管理框架;Shiro 则是一个轻量级的安全管理框架 133 | 2. Spring Security 概念复杂,配置繁琐;Shiro 概念简单、配置简单 134 | 3. Spring Security 功能强大;Shiro 功能简单 135 | 136 | ### Spring Boot 中如何解决跨域问题 ? 137 | 138 | 跨域可以在前端通过 JSONP 来解决,但是 JSONP 只可以发送 GET 请求,无法发送其他类型的请求,在 RESTful 风格的应用中,就显得非常鸡肋,因此我们推荐在后端通过 (CORS,Cross-origin resource sharing) 来解决跨域问题。这种解决方案并非 Spring Boot 特有的,在传统的 SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。 139 | 140 | ```java 141 | @Configuration 142 | public class CorsConfig implements WebMvcConfigurer { 143 | 144 | @Override 145 | public void addCorsMappings(CorsRegistry registry) { 146 | registry.addMapping("/**") 147 | .allowedOrigins("*") 148 | .allowCredentials(true) 149 | .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") 150 | .maxAge(3600); 151 | } 152 | 153 | } 154 | ``` 155 | 156 | 项目中前后端分离部署,所以需要解决跨域的问题。 157 | 我们使用cookie存放用户登录的信息,在spring拦截器进行权限控制,当权限不符合时,直接返回给用户固定的json结果。 158 | 当用户登录以后,正常使用;当用户退出登录状态时或者token过期时,由于拦截器和跨域的顺序有问题,出现了跨域的现象。 159 | 我们知道一个http请求,先走filter,到达servlet后才进行拦截器的处理,如果我们把cors放在filter里,就可以优先于权限拦截器执行。 160 | 161 | ```java 162 | @Configuration 163 | public class CorsConfig { 164 | 165 | @Bean 166 | public CorsFilter corsFilter() { 167 | CorsConfiguration corsConfiguration = new CorsConfiguration(); 168 | corsConfiguration.addAllowedOrigin("*"); 169 | corsConfiguration.addAllowedHeader("*"); 170 | corsConfiguration.addAllowedMethod("*"); 171 | corsConfiguration.setAllowCredentials(true); 172 | UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource(); 173 | urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration); 174 | return new CorsFilter(urlBasedCorsConfigurationSource); 175 | } 176 | 177 | } 178 | ``` 179 | 180 | ### 什么是 CSRF 攻击? 181 | 182 | CSRF 代表跨站请求伪造。这是一种攻击,迫使最终用户在当前通过身份验证的Web 应用程序上执行不需要的操作。CSRF 攻击专门针对状态改变请求,而不是数据窃取,因为攻击者无法查看对伪造请求的响应。 183 | 184 | ## 监视器 185 | 186 | ### Spring Boot 中的监视器是什么? 187 | 188 | Spring boot actuator 是 spring 启动框架中的重要功能之一。Spring boot 监视器可帮助您访问生产环境中正在运行的应用程序的当前状态。有几个指标必须在生产环境中进行检查和监控。即使一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器模块公开了一组可直接作为 HTTP URL 访问的REST 端点来检查状态。 189 | 190 | ### 如何在 Spring Boot 中禁用 Actuator 端点安全性? 191 | 192 | 默认情况下,所有敏感的 HTTP 端点都是安全的,只有具有 ACTUATOR 角色的用户才能访问它们。安全性是使用标准的 HttpServletRequest.isUserInRole 方法实施的。 我们可以使用来禁用安全性。只有在执行机构端点在防火墙后访问时,才建议禁用安全性。 193 | 194 | ### 我们如何监视所有 Spring Boot 微服务? 195 | 196 | Spring Boot 提供监视器端点以监控各个微服务的度量。这些端点对于获取有关应用程序的信息(如它们是否已启动)以及它们的组件(如数据库等)是否正常运行很有帮助。但是,使用监视器的一个主要缺点或困难是,我们必须单独打开应用程序的知识点以了解其状态或健康状况。想象一下涉及 50 个应用程序的微服务,管理员将不得不击中所有 50 个应用程序的执行终端。为了帮助我们处理这种情况,我们将使用位于的开源项目。 它建立在 Spring Boot Actuator 之上,它提供了一个 Web UI,使我们能够可视化多个应用程序的度量。 197 | 198 | ## 整合第三方项目 199 | 200 | ### 什么是 WebSockets? 201 | 202 | WebSocket 是一种计算机通信协议,通过单个 TCP 连接提供全双工通信信道。 203 | 204 | 1、WebSocket 是双向的 -使用 WebSocket 客户端或服务器可以发起消息发送。 205 | 206 | 2、WebSocket 是全双工的 -客户端和服务器通信是相互独立的。 207 | 208 | 3、单个 TCP 连接 -初始连接使用 HTTP,然后将此连接升级到基于套接字的连接。然后这个单一连接用于所有未来的通信 209 | 210 | 4、Light -与 http 相比,WebSocket 消息数据交换要轻得多。 211 | 212 | ### 什么是 Spring Data ? 213 | 214 | Spring Data 是 Spring 的一个子项目。用于简化数据库访问,支持NoSQL 和 关系数据存储。其主要目标是使数据库的访问变得方便快捷。Spring Data 具有如下特点: 215 | 216 | SpringData 项目支持 NoSQL 存储: 217 | 218 | 1. MongoDB (文档数据库) 219 | 2. Neo4j(图形数据库) 220 | 3. Redis(键/值存储) 221 | 4. Hbase(列族数据库) 222 | 223 | SpringData 项目所支持的关系数据存储技术: 224 | 225 | 1. JDBC 226 | 2. JPA 227 | 228 | Spring Data Jpa 致力于减少数据访问层 (DAO) 的开发量. 开发者唯一要做的,就是声明持久层的接口,其他都交给 Spring Data JPA 来帮你完成!Spring Data JPA 通过规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。 229 | 230 | ### 什么是 Spring Batch? 231 | 232 | 。 233 | 234 | ### 什么是 FreeMarker 模板? 235 | 236 | FreeMarker 是一个基于 Java 的模板引擎,最初专注于使用 MVC 软件架构进行动态网页生成。使用 Freemarker 的主要优点是表示层和业务层的完全分离。程序员可以处理应用程序代码,而设计人员可以处理 html 页面设计。最后使用freemarker 可以将这些结合起来,给出最终的输出页面。 237 | 238 | ### 如何集成 Spring Boot 和 ActiveMQ? 239 | 240 | 241 | 242 | ### 什么是 Apache Kafka? 243 | 244 | Apache Kafka 是一个分布式发布 - 订阅消息系统。它是一个可扩展的,容错的发布 - 订阅消息系统,它使我们能够构建分布式应用程序。这是一个 Apache 顶级项目。Kafka 适合离线和在线消息消费。 245 | 246 | ### 什么是 Swagger?你用 Spring Boot 实现了它吗? 247 | 248 | Swagger 广泛用于可视化 API,使用 Swagger UI 为前端开发人员提供在线沙箱。Swagger 是用于生成 RESTful Web 服务的可视化表示的工具,规范和完整框架实现。它使文档能够以与服务器相同的速度更新。当通过 Swagger 正确定义时,消费者可以使用最少量的实现逻辑来理解远程服务并与其进行交互。因此,Swagger消除了调用服务时的猜测。 249 | 250 | ### 前后端分离,如何维护接口文档 ? 251 | 252 | 前后端分离开发日益流行,大部分情况下,我们都是通过 Spring Boot 做前后端分离开发,前后端分离一定会有接口文档,不然会前后端会深深陷入到扯皮中。一个比较笨的方法就是使用 word 或者 md 来维护接口文档,但是效率太低,接口一变,所有人手上的文档都得变。在 Spring Boot 中,这个问题常见的解决方案是 Swagger ,使用 Swagger 我们可以快速生成一个接口文档网站,接口一旦发生变化,文档就会自动更新,所有开发工程师访问这一个在线网站就可以获取到最新的接口文档,非常方便。 253 | 254 | ## 其他 255 | 256 | ### 如何重新加载 Spring Boot 上的更改,而无需重新启动服务器?Spring Boot项目如何热部署? 257 | 258 | 这可以使用 DEV 工具来实现。通过这种依赖关系,您可以节省任何更改,嵌入式tomcat 将重新启动。Spring Boot 有一个开发工具(DevTools)模块,它有助于提高开发人员的生产力。Java 开发人员面临的一个主要挑战是将文件更改自动部署到服务器并自动重启服务器。开发人员可以重新加载 Spring Boot 上的更改,而无需重新启动服务器。这将消除每次手动部署更改的需要。Spring Boot 在发布它的第一个版本时没有这个功能。这是开发人员最需要的功能。DevTools 模块完全满足开发人员的需求。该模块将在生产环境中被禁用。它还提供 H2 数据库控制台以更好地测试应用程序。 259 | 260 | ```xml 261 | 262 | org.springframework.boot 263 | spring-boot-devtools 264 | 265 | ``` 266 | 267 | ### 您使用了哪些 starter maven 依赖项? 268 | 269 | 使用了下面的一些依赖项 270 | 271 | spring-boot-starter-activemq 272 | 273 | spring-boot-starter-security 274 | 275 | 这有助于增加更少的依赖关系,并减少版本的冲突。 276 | 277 | ### Spring Boot 中的 starter 到底是什么 ? 278 | 279 | 首先,这个 Starter 并非什么新的技术点,基本上还是基于 Spring 已有功能来实现的。首先它提供了一个自动化配置类,一般命名为 `XXXAutoConfiguration` ,在这个配置类中通过条件注解来决定一个配置是否生效(条件注解就是 Spring 中原本就有的),然后它还会提供一系列的默认配置,也允许开发者根据实际情况自定义相关配置,然后通过类型安全的属性注入将这些配置属性注入进来,新注入的属性会代替掉默认属性。正因为如此,很多第三方框架,我们只需要引入依赖就可以直接使用了。当然,开发者也可以自定义 Starter 280 | 281 | ### spring-boot-starter-parent 有什么用 ? 282 | 283 | 我们都知道,新创建一个 Spring Boot 项目,默认都是有 parent 的,这个 parent 就是 spring-boot-starter-parent ,spring-boot-starter-parent 主要有如下作用: 284 | 285 | 1. 定义了 Java 编译版本为 1.8 。 286 | 2. 使用 UTF-8 格式编码。 287 | 3. 继承自 spring-boot-dependencies,这个里边定义了依赖的版本,也正是因为继承了这个依赖,所以我们在写依赖时才不需要写版本号。 288 | 4. 执行打包操作的配置。 289 | 5. 自动化的资源过滤。 290 | 6. 自动化的插件配置。 291 | 7. 针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml。 292 | 293 | ### Spring Boot 打成的 jar 和普通的 jar 有什么区别 ? 294 | 295 | Spring Boot 项目最终打包成的 jar 是可执行 jar ,这种 jar 可以直接通过 `java -jar xxx.jar` 命令来运行,这种 jar 不可以作为普通的 jar 被其他项目依赖,即使依赖了也无法使用其中的类。 296 | 297 | Spring Boot 的 jar 无法被其他项目依赖,主要还是他和普通 jar 的结构不同。普通的 jar 包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 `\BOOT-INF\classes` 目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置,将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。 298 | 299 | ### 运行 Spring Boot 有哪几种方式? 300 | 301 | 1)打包用命令或者放到容器中运行 302 | 303 | 2)用 Maven/ Gradle 插件运行 304 | 305 | 3)直接执行 main 方法运行 306 | 307 | ### Spring Boot 需要独立的容器运行吗? 308 | 309 | 可以不需要,内置了 Tomcat/ Jetty 等容器。 310 | 311 | ### 开启 Spring Boot 特性有哪几种方式? 312 | 313 | 1)继承spring-boot-starter-parent项目 314 | 315 | 2)导入spring-boot-dependencies项目依赖 316 | 317 | ### 如何使用 Spring Boot 实现异常处理? 318 | 319 | Spring 提供了一种使用 ControllerAdvice 处理异常的非常有用的方法。 我们通过实现一个 ControlerAdvice 类,来处理控制器类抛出的所有异常。 320 | 321 | ### 如何使用 Spring Boot 实现分页和排序? 322 | 323 | 使用 Spring Boot 实现分页非常简单。使用 Spring Data-JPA 可以实现将可分页的传递给存储库方法。 324 | 325 | ### 微服务中如何实现 session 共享 ? 326 | 327 | 在微服务中,一个完整的项目被拆分成多个不相同的独立的服务,各个服务独立部署在不同的服务器上,各自的 session 被从物理空间上隔离开了,但是经常,我们需要在不同微服务之间共享 session ,常见的方案就是 Spring Session + Redis 来实现 session 共享。将所有微服务的 session 统一保存在 Redis 上,当各个微服务对 session 有相关的读写操作时,都去操作 Redis 上的 session 。这样就实现了 session 共享,Spring Session 基于 Spring 中的代理过滤器实现,使得 session 的同步操作对开发人员而言是透明的,非常简便。 328 | 329 | ### Spring Boot 中如何实现定时任务 ? 330 | 331 | 定时任务也是一个常见的需求,Spring Boot 中对于定时任务的支持主要还是来自 Spring 框架。 332 | 333 | 在 Spring Boot 中使用定时任务主要有两种不同的方式,一个就是使用 Spring 中的 @Scheduled 注解,另一个则是使用第三方框架 Quartz。 334 | 335 | 使用 Spring 中的 @Scheduled 的方式主要通过 @Scheduled 注解来实现。 336 | 337 | 使用 Quartz ,则按照 Quartz 的方式,定义 Job 和 Trigger 即可。 338 | 339 | -------------------------------------------------------------------------------- /Java核心面试知识集—SpringMVC面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ## 概述 26 | 27 | ### 什么是Spring MVC?简单介绍下你对Spring MVC的理解? 28 | 29 | Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把模型-视图-控制器分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。 30 | 31 | ### Spring MVC的优点 32 | 33 | (1)可以支持各种视图技术,而不仅仅局限于JSP; 34 | 35 | (2)与Spring框架集成(如IoC容器、AOP等); 36 | 37 | (3)清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。 38 | 39 | (4) 支持各种请求资源的映射策略。 40 | 41 | ## 核心组件 42 | 43 | ### Spring MVC的主要组件? 44 | 45 | (1)前端控制器 DispatcherServlet(不需要程序员开发) 46 | 47 | 作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。 48 | 49 | (2)处理器映射器HandlerMapping(不需要程序员开发) 50 | 51 | 作用:根据请求的URL来查找Handler 52 | 53 | (3)处理器适配器HandlerAdapter 54 | 55 | 注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。 56 | 57 | (4)处理器Handler(需要程序员开发) 58 | 59 | (5)视图解析器 ViewResolver(不需要程序员开发) 60 | 61 | 作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view) 62 | 63 | (6)视图View(需要程序员开发jsp) 64 | 65 | View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等) 66 | 67 | ### 什么是DispatcherServlet 68 | 69 | Spring的MVC框架是围绕DispatcherServlet来设计的,它用来处理所有的HTTP请求和响应。 70 | 71 | ### 什么是Spring MVC框架的控制器? 72 | 73 | 控制器提供一个访问应用程序的行为,此行为通常通过服务接口实现。控制器解析用户输入并将其转换为一个由视图呈现给用户的模型。Spring用一个非常抽象的方式实现了一个控制层,允许用户创建多种用途的控制器。 74 | 75 | ### Spring MVC的控制器是不是单例模式,如果是,有什么问题,怎么解决? 76 | 77 | 答:是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写字段。 78 | 79 | ## 工作原理 80 | 81 | ### 请描述Spring MVC的工作流程?描述一下 DispatcherServlet 的工作流程? 82 | 83 | (1)用户发送请求至前端控制器DispatcherServlet; 84 | (2) DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle; 85 | (3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet; 86 | (4)DispatcherServlet 调用 HandlerAdapter处理器适配器; 87 | (5)HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器); 88 | (6)Handler执行完成返回ModelAndView; 89 | (7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet; 90 | (8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析; 91 | (9)ViewResolver解析后返回具体View; 92 | (10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中) 93 | (11)DispatcherServlet响应用户。 94 | 95 | ![img](https://img-blog.csdnimg.cn/20200208211439106.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 96 | 97 | ## MVC框架 98 | 99 | ### MVC是什么?MVC设计模式的好处有哪些 100 | 101 | mvc是一种设计模式(设计模式就是日常开发中编写代码的一种好的方法和经验的总结)。模型(model)-视图(view)-控制器(controller),三层架构的设计模式。用于实现前端页面的展现与后端业务数据处理的分离。 102 | 103 | mvc设计模式的好处 104 | 105 | 1.分层设计,实现了业务系统各个组件之间的解耦,有利于业务系统的可扩展性,可维护性。 106 | 107 | 2.有利于系统的并行开发,提升开发效率。 108 | 109 | ## 常用注解 110 | 111 | ### 注解原理是什么 112 | 113 | 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。 114 | 115 | ### Spring MVC常用的注解有哪些? 116 | 117 | @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。 118 | 119 | @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。 120 | 121 | @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。 122 | 123 | ### SpingMvc中的控制器的注解一般用哪个,有没有别的注解可以替代? 124 | 125 | 答:一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替。 126 | 127 | ### @Controller注解的作用 128 | 129 | 在Spring MVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Model 返回给对应的View 进行展示。在Spring MVC 中提供了一个非常简便的定义Controller 的方法,你无需继承特定的类或实现特定的接口,只需使用@Controller 标记一个类是Controller ,然后使用@RequestMapping 和@RequestParam 等一些注解用以定义URL 请求和Controller 方法之间的映射,这样的Controller 就能被外界访问到。此外Controller 不会直接依赖于HttpServletRequest 和HttpServletResponse 等HttpServlet 对象,它们可以通过Controller 的方法参数灵活的获取到。 130 | 131 | @Controller 用于标记在一个类上,使用它标记的类就是一个Spring MVC Controller 对象。分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping 注解。@Controller 只是定义了一个控制器类,而使用@RequestMapping 注解的方法才是真正处理请求的处理器。单单使用@Controller 标记在一个类上还不能真正意义上的说它就是Spring MVC 的一个控制器类,因为这个时候Spring 还不认识它。那么要如何做Spring 才能认识它呢?这个时候就需要我们把这个控制器类交给Spring 来管理。有两种方式: 132 | 133 | - 在Spring MVC 的配置文件中定义MyController 的bean 对象。 134 | - 在Spring MVC 的配置文件中告诉Spring 该到哪里去找标记为@Controller 的Controller 控制器。 135 | 136 | ### @RequestMapping注解的作用 137 | 138 | RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。 139 | 140 | RequestMapping注解有六个属性,下面我们把她分成三类进行说明(下面有相应示例)。 141 | 142 | **value, method** 143 | 144 | value: 指定请求的实际地址,指定的地址可以是URI Template 模式(后面将会说明); 145 | 146 | method: 指定请求的method类型, GET、POST、PUT、DELETE等; 147 | 148 | **consumes,produces** 149 | 150 | consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html; 151 | 152 | produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回; 153 | 154 | **params,headers** 155 | 156 | params: 指定request中必须包含某些参数值是,才让该方法处理。 157 | 158 | headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求。 159 | 160 | ### @ResponseBody注解的作用 161 | 162 | 作用: 该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。 163 | 164 | 使用时机:返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用; 165 | 166 | ### @PathVariable和@RequestParam的区别 167 | 168 | 请求路径上有个id的变量值,可以通过@PathVariable来获取 @RequestMapping(value = “/page/{id}”, method = RequestMethod.GET) 169 | 170 | @RequestParam用来获得静态的URL请求入参 spring注解时action里用到。 171 | 172 | ## 其他 173 | 174 | ### Spring MVC与Struts2区别 175 | 176 | 相同点 177 | 178 | 都是基于mvc的表现层框架,都用于web项目的开发。 179 | 180 | 不同点 181 | 182 | 1.前端控制器不一样。Spring MVC的前端控制器是servlet:DispatcherServlet。struts2的前端控制器是filter:StrutsPreparedAndExcutorFilter。 183 | 184 | 2.请求参数的接收方式不一样。Spring MVC是使用方法的形参接收请求的参数,基于方法的开发,线程安全,可以设计为单例或者多例的开发,推荐使用单例模式的开发(执行效率更高),默认就是单例开发模式。struts2是通过类的成员变量接收请求的参数,是基于类的开发,线程不安全,只能设计为多例的开发。 185 | 186 | 3.Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,Spring MVC通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。 187 | 188 | 4.与spring整合不一样。Spring MVC是spring框架的一部分,不需要整合。在企业项目中,Spring MVC使用更多一些。 189 | 190 | ### Spring MVC怎么样设定重定向和转发的? 191 | 192 | (1)转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4" 193 | 194 | (2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com" 195 | 196 | ### Spring MVC怎么和AJAX相互调用的? 197 | 198 | 通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 : 199 | 200 | (1)加入Jackson.jar 201 | 202 | (2)在配置文件中配置json的映射 203 | 204 | (3)在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody注解。 205 | 206 | ### 如何解决POST请求中文乱码问题,GET的又如何处理呢? 207 | 208 | (1)解决post请求乱码问题: 209 | 210 | 在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8; 211 | 212 | ```xml 213 | 214 | CharacterEncodingFilter 215 | org.springframework.web.filter.CharacterEncodingFilter 216 | 217 | 218 | encoding 219 | utf-8 220 | 221 | 222 | 223 | 224 | CharacterEncodingFilter 225 | /* 226 | 227 | ``` 228 | 229 | (2)get请求中文参数出现乱码解决方法有两个: 230 | 231 | ①修改tomcat配置文件添加编码与工程编码一致,如下: 232 | 233 | ```xml 234 | 235 | ``` 236 | 237 | ②另外一种方法对参数进行重新编码: 238 | 239 | String userName = new String(request.getParamter(“userName”).getBytes(“ISO8859-1”),“utf-8”) 240 | 241 | ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。 242 | 243 | ### Spring MVC的异常处理? 244 | 245 | 答:可以将异常抛给Spring框架,由Spring框架来处理;我们只需要配置简单的异常处理器,在异常处理器中添视图页面即可。 246 | 247 | ### 如果在拦截请求中,我想拦截get方式提交的方法,怎么配置 248 | 249 | 答:可以在@RequestMapping注解里面加上method=RequestMethod.GET。 250 | 251 | ### 怎样在方法里面得到Request,或者Session? 252 | 253 | 答:直接在方法的形参中声明request,Spring MVC就自动把request对象传入。 254 | 255 | ### 如果想在拦截的方法里面得到从前台传入的参数,怎么得到? 256 | 257 | 答:直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。 258 | 259 | ### 如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象? 260 | 261 | 答:直接在方法中声明这个对象,Spring MVC就自动会把属性赋值到这个对象里面。 262 | 263 | ### Spring MVC中函数的返回值是什么? 264 | 265 | 答:返回值可以有很多类型,有String, ModelAndView。ModelAndView类把视图和数据都合并的一起的,但一般用String比较好。 266 | 267 | ### Spring MVC用什么对象从后台向前台传递数据的? 268 | 269 | 答:通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以通过el表达式拿到。 270 | 271 | ### 怎么样把ModelMap里面的数据放入Session里面? 272 | 273 | 答:可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。 274 | 275 | ### Spring MVC里面拦截器是怎么写的 276 | 277 | 有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在Spring MVC的配置文件中配置拦截器即可: 278 | 279 | ```xml 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | ``` 291 | 292 | ### 介绍一下 WebApplicationContext 293 | 294 | WebApplicationContext 继承了ApplicationContext 并增加了一些WEB应用必备的特有功能,它不同于一般的ApplicationContext ,因为它能处理主题,并找到被关联的servlet。 295 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /Java核心面试知识集—Spring面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ## Spring概述(10) 26 | 27 | ### 什么是spring? 28 | 29 | Spring是**一个轻量级Java开发框架**,最早有**Rod Johnson**创建,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEE full-stack(一站式)轻量级开源框架,为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此Java开发者可以专注于应用程序的开发。 30 | 31 | Spring最根本的使命是**解决企业级应用开发的复杂性,即简化Java开发**。 32 | 33 | Spring可以做很多事情,它为企业级开发提供给了丰富的功能,但是这些功能的底层都依赖于它的两个核心特性,也就是**依赖注入(dependency injection,DI)和面向切面编程(aspect-oriented programming,AOP)**。 34 | 35 | 为了降低Java开发的复杂性,Spring采取了以下4种关键策略 36 | 37 | - 基于POJO的轻量级和最小侵入性编程; 38 | - 通过依赖注入和面向接口实现松耦合; 39 | - 基于切面和惯例进行声明式编程; 40 | - 通过切面和模板减少样板式代码。 41 | 42 | ### Spring框架的设计目标,设计理念,和核心是什么 43 | 44 | **Spring设计目标**:Spring为开发者提供一个一站式轻量级应用开发平台; 45 | 46 | **Spring设计理念**:在JavaEE开发中,支持POJO和JavaBean开发方式,使应用面向接口开发,充分支持OO(面向对象)设计方法;Spring通过IoC容器实现对象耦合关系的管理,并实现依赖反转,将对象之间的依赖关系交给IoC容器,实现解耦; 47 | 48 | **Spring框架的核心**:IoC容器和AOP模块。通过IoC容器管理POJO对象以及他们之间的耦合关系;通过AOP以动态非侵入的方式增强服务。 49 | 50 | IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。 51 | 52 | ### Spring的优缺点是什么? 53 | 54 | 优点 55 | 56 | - 方便解耦,简化开发 57 | 58 | Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。 59 | 60 | - AOP编程的支持 61 | 62 | Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。 63 | 64 | - 声明式事务的支持 65 | 66 | 只需要通过配置就可以完成对事务的管理,而无需手动编程。 67 | 68 | - 方便程序的测试 69 | 70 | Spring对Junit4支持,可以通过注解方便的测试Spring程序。 71 | 72 | - 方便集成各种优秀框架 73 | 74 | Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。 75 | 76 | - 降低JavaEE API的使用难度 77 | 78 | Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。 79 | 80 | 缺点 81 | 82 | - Spring明明一个很轻量级的框架,却给人感觉大而全 83 | - Spring依赖反射,反射影响性能 84 | - 使用门槛升高,入门Spring需要较长时间 85 | 86 | ### Spring有哪些应用场景 87 | 88 | **应用场景**:JavaEE企业应用开发,包括SSH、SSM等 89 | 90 | **Spring价值**: 91 | 92 | - Spring是非侵入式的框架,目标是使应用程序代码对框架依赖最小化; 93 | - Spring提供一个一致的编程模型,使应用直接使用POJO开发,与运行环境隔离开来; 94 | - Spring推动应用设计风格向面向对象和面向接口开发转变,提高了代码的重用性和可测试性; 95 | 96 | ### Spring由哪些模块组成? 97 | 98 | Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在`核心容器(Core Container)` 、 `AOP(Aspect Oriented Programming)和设备支持(Instrmentation)` 、`数据访问与集成(Data Access/Integeration)` 、 `Web`、 `消息(Messaging)` 、 `Test`等 6 个模块中。 以下是 Spring 5 的模块结构图: 99 | 100 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/2019102923475419.png) 101 | 102 | - spring core:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。 103 | - spring beans:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。 104 | - spring context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。 105 | - spring jdbc:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。 106 | - spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。 107 | - spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。 108 | - spring test:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。 109 | 110 | ### Spring 框架中都用到了哪些设计模式? 111 | 112 | 1. 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例; 113 | 2. 单例模式:Bean默认为单例模式。 114 | 3. 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术; 115 | 4. 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。 116 | 5. 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。 117 | 118 | ### 详细讲解一下核心容器(spring context应用上下文) 模块 119 | 120 | 这是基本的Spring模块,提供spring 框架的基础功能,BeanFactory 是 任何以spring为基础的应用的核心。Spring 框架建立在此模块之上,它使Spring成为一个容器。 121 | 122 | Bean 工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从真正的应用代码中分离。最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory ,它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。 123 | 124 | ### Spring框架中有哪些不同类型的事件 125 | 126 | Spring 提供了以下5种标准的事件: 127 | 128 | 1. 上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。 129 | 2. 上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。 130 | 3. 上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。 131 | 4. 上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。 132 | 5. 请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。 133 | 134 | ### Spring 应用程序有哪些不同组件? 135 | 136 | Spring 应用一般有以下组件: 137 | 138 | - 接口 - 定义功能。 139 | - Bean 类 - 它包含属性,setter 和 getter 方法,函数等。 140 | - Bean 配置文件 - 包含类的信息以及如何配置它们。 141 | - Spring 面向切面编程(AOP) - 提供面向切面编程的功能。 142 | - 用户程序 - 它使用接口。 143 | 144 | ### 使用 Spring 有哪些方式? 145 | 146 | 使用 Spring 有以下方式: 147 | 148 | - 作为一个成熟的 Spring Web 应用程序。 149 | - 作为第三方 Web 框架,使用 Spring Frameworks 中间层。 150 | - 作为企业级 Java Bean,它可以包装现有的 POJO(Plain Old Java Objects)。 151 | - 用于远程使用。 152 | 153 | ## Spring控制反转(IOC)(13) 154 | 155 | ### 什么是Spring IOC 容器? 156 | 157 | 控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。 158 | 159 | Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。 160 | 161 | ### 控制反转(IoC)有什么作用 162 | 163 | - 管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要程序猿来维护的话,那是相当头疼的 164 | - 解耦,由容器去维护具体的对象 165 | - 托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的 166 | 167 | ### IOC的优点是什么? 168 | 169 | - IOC 或 依赖注入把应用的代码量降到最低。 170 | - 它使应用容易测试,单元测试不再需要单例和JNDI查找机制。 171 | - 最小的代价和最小的侵入性使松散耦合得以实现。 172 | - IOC容器支持加载服务时的饿汉式初始化和懒加载。 173 | 174 | ### Spring IoC 的实现机制 175 | 176 | Spring 中的 IoC 的实现原理就是工厂模式加反射机制。 177 | 178 | 示例: 179 | 180 | ```java 181 | interface Fruit { 182 | public abstract void eat(); 183 | } 184 | 185 | class Apple implements Fruit { 186 | public void eat(){ 187 | System.out.println("Apple"); 188 | } 189 | } 190 | 191 | class Orange implements Fruit { 192 | public void eat(){ 193 | System.out.println("Orange"); 194 | } 195 | } 196 | 197 | class Factory { 198 | public static Fruit getInstance(String ClassName) { 199 | Fruit f=null; 200 | try { 201 | f=(Fruit)Class.forName(ClassName).newInstance(); 202 | } catch (Exception e) { 203 | e.printStackTrace(); 204 | } 205 | return f; 206 | } 207 | } 208 | 209 | class Client { 210 | public static void main(String[] a) { 211 | Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple"); 212 | if(f!=null){ 213 | f.eat(); 214 | } 215 | } 216 | } 217 | ``` 218 | 219 | ### Spring 的 IoC支持哪些功能 220 | 221 | Spring 的 IoC 设计支持以下功能: 222 | 223 | - 依赖注入 224 | - 依赖检查 225 | - 自动装配 226 | - 支持集合 227 | - 指定初始化方法和销毁方法 228 | - 支持回调某些方法(但是需要实现 Spring 接口,略有侵入) 229 | 230 | 其中,最重要的就是依赖注入,从 XML 的配置上说,即 ref 标签。对应 Spring RuntimeBeanReference 对象。 231 | 232 | 对于 IoC 来说,最重要的就是容器。容器管理着 Bean 的生命周期,控制着 Bean 的依赖注入。 233 | 234 | ### BeanFactory 和 ApplicationContext有什么区别? 235 | 236 | BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。 237 | 238 | 依赖关系 239 | 240 | BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。 241 | 242 | ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能: 243 | 244 | - 继承MessageSource,因此支持国际化。 245 | - 统一的资源文件访问方式。 246 | - 提供在监听器中注册bean的事件。 247 | - 同时加载多个配置文件。 248 | - 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。 249 | 250 | 加载方式 251 | 252 | BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。 253 | 254 | ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。 255 | 256 | 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。 257 | 258 | 创建方式 259 | 260 | BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。 261 | 262 | 注册方式 263 | 264 | BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。 265 | 266 | ### Spring 如何设计容器的,BeanFactory和ApplicationContext的关系详解 267 | 268 | Spring 作者 Rod Johnson 设计了两个接口用以表示容器。 269 | 270 | - BeanFactory 271 | - ApplicationContext 272 | 273 | BeanFactory 简单粗暴,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 **“低级容器”**。 274 | 275 | ApplicationContext 可以称之为 **“高级容器”**。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的 bean。 276 | 277 | 当然,除了这两个大接口,还有其他的辅助接口,这里就不介绍他们了。 278 | 279 | BeanFactory和ApplicationContext的关系 280 | 281 | 为了更直观的展示 “低级容器” 和 “高级容器” 的关系,这里通过常用的 ClassPathXmlApplicationContext 类来展示整个容器的层级 UML 关系。 282 | 283 | ![img](https://img-blog.csdnimg.cn/20191105111441363.png) 284 | 285 | 有点复杂? 先不要慌,我来解释一下。 286 | 287 | 最上面的是 BeanFactory,下面的 3 个绿色的,都是功能扩展接口,这里就不展开讲。 288 | 289 | 看下面的隶属 ApplicationContext 粉红色的 “高级容器”,依赖着 “低级容器”,这里说的是依赖,不是继承哦。他依赖着 “低级容器” 的 getBean 功能。而高级容器有更多的功能:支持不同的信息源头,可以访问文件资源,支持应用事件(Observer 模式)。 290 | 291 | 通常用户看到的就是 “高级容器”。 但 BeanFactory 也非常够用啦! 292 | 293 | 左边灰色区域的是 “低级容器”, 只负载加载 Bean,获取 Bean。容器其他的高级功能是没有的。例如上图画的 refresh 刷新 Bean 工厂所有配置,生命周期事件回调等。 294 | 295 | 小结 296 | 297 | 说了这么多,不知道你有没有理解Spring IoC? 这里小结一下:IoC 在 Spring 里,只需要低级容器就可以实现,2 个步骤: 298 | 299 | 1. 加载配置文件,解析成 BeanDefinition 放在 Map 里。 300 | 2. 调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。 301 | 302 | 上面就是 Spring 低级容器(BeanFactory)的 IoC。 303 | 304 | 至于高级容器 ApplicationContext,他包含了低级容器的功能,当他执行 refresh 模板方法的时候,将刷新整个容器的 Bean。同时其作为高级容器,包含了太多的功能。一句话,他不仅仅是 IoC。他支持不同信息源头,支持 BeanFactory 工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等等。 305 | 306 | ### ApplicationContext通常的实现是什么? 307 | 308 | **FileSystemXmlApplicationContext** :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。 309 | 310 | **ClassPathXmlApplicationContext**:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。 311 | 312 | **WebXmlApplicationContext**:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。 313 | 314 | ### 什么是Spring的依赖注入? 315 | 316 | 控制反转IoC是一个很大的概念,可以用不同的方式来实现。其主要实现方式有两种:依赖注入和依赖查找 317 | 318 | 依赖注入:相对于IoC而言,依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。 319 | 320 | ### 依赖注入的基本原则 321 | 322 | 依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter)或者是构造器传递给需要的对象。 323 | 324 | ### 依赖注入有什么优势 325 | 326 | 依赖注入之所以更流行是因为它是一种更可取的方式:让容器全权负责依赖查询,受管组件只需要暴露JavaBean的setter方法或者带参数的构造器或者接口,使容器可以在初始化时组装对象的依赖关系。其与依赖查找方式相比,主要优势为: 327 | 328 | - 查找定位操作与应用代码完全无关。 329 | - 不依赖于容器的API,可以很容易地在任何容器以外使用应用对象。 330 | - 不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器。 331 | 332 | ### 有哪些不同类型的依赖注入实现方式? 333 | 334 | 依赖注入是时下最流行的IoC实现方式,依赖注入分为接口注入(Interface Injection),Setter方法注入(Setter Injection)和构造器注入(Constructor Injection)三种方式。其中接口注入由于在灵活性和易用性比较差,现在从Spring4开始已被废弃。 335 | 336 | **构造器依赖注入**:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。 337 | 338 | **Setter方法注入**:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。 339 | 340 | ### 构造器依赖注入和 Setter方法注入的区别 341 | 342 | | **构造函数注入** | **setter** **注入** | 343 | | -------------------------- | -------------------------- | 344 | | 没有部分注入 | 有部分注入 | 345 | | 不会覆盖 setter 属性 | 会覆盖 setter 属性 | 346 | | 任意修改都会创建一个新实例 | 任意修改不会创建一个新实例 | 347 | | 适用于设置很多属性 | 适用于设置少量属性 | 348 | 349 | 两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。 350 | 351 | ## Spring Beans(19) 352 | 353 | ### 什么是Spring beans? 354 | 355 | Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中 的形式定义。 356 | 357 | ### 一个 Spring Bean 定义 包含什么? 358 | 359 | 一个Spring Bean 的定义包含容器必知的所有配置元数据,包括如何创建一个bean,它的生命周期详情及它的依赖。 360 | 361 | ### 如何给Spring 容器提供配置元数据?Spring有几种配置方式 362 | 363 | 这里有三种重要的方法给Spring 容器提供配置元数据。 364 | 365 | - XML配置文件。 366 | - 基于注解的配置。 367 | - 基于java的配置。 368 | 369 | ### Spring配置文件包含了哪些信息 370 | 371 | Spring配置文件是个XML 文件,这个文件包含了类信息,描述了如何配置它们,以及如何相互调用。 372 | 373 | ### Spring基于xml注入bean的几种方式 374 | 375 | 1. Set方法注入; 376 | 2. 构造器注入:①通过index设置参数的位置;②通过type设置参数类型; 377 | 3. 静态工厂注入; 378 | 4. 实例工厂; 379 | 380 | ### 你怎样定义类的作用域? 381 | 382 | 当定义一个 在Spring里,我们还能给这个bean声明一个作用域。它可以通过bean 定义中的scope属性来定义。如,当Spring要在需要的时候每次生产一个新的bean实例,bean的scope属性被指定为prototype。另一方面,一个bean每次使用的时候必须返回同一个实例,这个bean的scope 属性 必须设为 singleton。 383 | 384 | ### 解释Spring支持的几种bean的作用域 385 | 386 | Spring框架支持以下五种bean的作用域: 387 | 388 | - **singleton :** bean在每个Spring ioc 容器中只有一个实例。 389 | - **prototype**:一个bean的定义可以有多个实例。 390 | - **request**:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。 391 | - **session**:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。 392 | - **global-session**:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。 393 | 394 | **注意:** 缺省的Spring bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。 395 | 396 | ### Spring框架中的单例bean是线程安全的吗? 397 | 398 | 不是,Spring框架中的单例bean不是线程安全的。 399 | 400 | spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。 401 | 402 | 实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。 403 | 404 | - 有状态就是有数据存储功能。 405 | - 无状态就是不会保存数据。 406 | 407 | ### Spring如何处理线程并发问题? 408 | 409 | 在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。 410 | 411 | ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。 412 | 413 | ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。 414 | 415 | ### 解释Spring框架中bean的生命周期 416 | 417 | 在传统的Java应用中,bean的生命周期很简单。使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。相比之下,Spring容器中的bean的生命周期就显得相对复杂多了。正确理解Spring bean的生命周期非常重要,因为你或许要利用Spring提供的扩展点来自定义bean的创建过程。下图展示了bean装载到Spring应用上下文中的一个典型的生命周期过程。 418 | 419 | ![img](https://img-blog.csdnimg.cn/201911012343410.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 420 | 421 | bean在Spring容器中从创建到销毁经历了若干阶段,每一阶段都可以针对Spring如何管理bean进行个性化定制。 422 | 423 | 正如你所见,在bean准备就绪之前,bean工厂执行了若干启动步骤。 424 | 425 | 我们对上图进行详细描述: 426 | 427 | Spring对bean进行实例化; 428 | 429 | Spring将值和bean的引用注入到bean对应的属性中; 430 | 431 | 如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法; 432 | 433 | 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入; 434 | 435 | 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来; 436 | 437 | 如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法; 438 | 439 | 如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用; 440 | 441 | 如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法; 442 | 443 | 此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁; 444 | 445 | 如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。 446 | 447 | 现在你已经了解了如何创建和加载一个Spring容器。但是一个空的容器并没有太大的价值,在你把东西放进去之前,它里面什么都没有。为了从Spring的DI(依赖注入)中受益,我们必须将应用对象装配进Spring容器中。 448 | 449 | ### 哪些是重要的bean生命周期方法? 你能重载它们吗? 450 | 451 | 有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。 452 | 453 | bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。 454 | 455 | ### 什么是Spring的内部bean?什么是Spring inner beans? 456 | 457 | 在Spring框架中,当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean。内部bean可以用setter注入“属性”和构造方法注入“构造参数”的方式来实现,内部bean通常是匿名的,它们的Scope一般是prototype。 458 | 459 | ### 在 Spring中如何注入一个java集合? 460 | 461 | Spring提供以下几种集合的配置元素: 462 | 463 | 类型用于注入一列值,允许有相同的值。 464 | 465 | 类型用于注入一组值,不允许有相同的值。 466 | 467 | 类型用于注入一组键值对,键和值都可以为任意类型。 468 | 469 | 类型用于注入一组键值对,键和值都只能为String类型。 470 | 471 | ### 什么是bean装配? 472 | 473 | 装配,或bean 装配是指在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,如何通过依赖注入来把它们装配到一起。 474 | 475 | ### 什么是bean的自动装配? 476 | 477 | 在Spring框架中,在配置文件中设定bean的依赖关系是一个很好的机制,Spring 容器能够自动装配相互合作的bean,这意味着容器不需要和配置,能通过Bean工厂自动处理bean之间的协作。这意味着 Spring可以通过向Bean Factory中注入的方式自动搞定bean之间的依赖关系。自动装配可以设置在每个bean上,也可以设定在特定的bean上。 478 | 479 | ### 解释不同方式的自动装配,spring 自动装配 bean 有哪些方式? 480 | 481 | 在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。 482 | 483 | 在Spring框架xml配置中共有5种自动装配: 484 | 485 | - no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。 486 | - byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。 487 | - byType:通过参数的数据类型进行自动装配。 488 | - constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。 489 | - autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。 490 | 491 | ### 使用@Autowired注解自动装配的过程是怎样的? 492 | 493 | 使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,。 494 | 495 | 在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean: 496 | 497 | - 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据; 498 | - 如果查询的结果不止一个,那么@Autowired会根据名称来查找; 499 | - 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。 500 | 501 | ### 自动装配有哪些局限性? 502 | 503 | 自动装配的局限性是: 504 | 505 | **重写**:你仍需用 和 配置来定义依赖,意味着总要重写自动装配。 506 | 507 | **基本数据类型**:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。 508 | 509 | **模糊特性**:自动装配不如显式装配精确,如果有可能,建议使用显式装配。 510 | 511 | ### 你可以在Spring中注入一个null 和一个空字符串吗? 512 | 513 | 可以。 514 | 515 | ## Spring注解(8) 516 | 517 | ### 什么是基于Java的Spring注解配置? 给一些注解的例子 518 | 519 | 基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。 520 | 521 | 以@Configuration 注解为例,它用来标记类可以当做一个bean的定义,被Spring IOC容器使用。 522 | 523 | 另一个例子是@Bean注解,它表示此方法将要返回一个对象,作为一个bean注册进Spring应用上下文。 524 | 525 | ```java 526 | @Configuration 527 | public class StudentConfig { 528 | @Bean 529 | public StudentBean myStudent() { 530 | return new StudentBean(); 531 | } 532 | } 533 | ``` 534 | 535 | ### 怎样开启注解装配? 536 | 537 | 注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在Spring配置文件中配置 ``元素。 538 | 539 | ### @Component, @Controller, @Repository, @Service 有何区别? 540 | 541 | @Component:这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。 542 | 543 | @Controller:这将一个类标记为 Spring Web MVC 控制器。标有它的 Bean 会自动导入到 IoC 容器中。 544 | 545 | @Service:此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。您可以在服务层类中使用 @Service 而不是 @Component,因为它以更好的方式指定了意图。 546 | 547 | @Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException。 548 | 549 | ### @Required 注解有什么作用 550 | 551 | 这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出BeanInitializationException。示例: 552 | 553 | ```java 554 | public class Employee { 555 | private String name; 556 | @Required 557 | public void setName(String name){ 558 | this.name=name; 559 | } 560 | public string getName(){ 561 | return name; 562 | } 563 | } 564 | ``` 565 | 566 | ### @Autowired 注解有什么作用 567 | 568 | @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。@Autowired 注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和@Required一样,修饰setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。 569 | 570 | ```java 571 | public class Employee { 572 | private String name; 573 | @Autowired 574 | public void setName(String name) { 575 | this.name=name; 576 | } 577 | public string getName(){ 578 | return name; 579 | } 580 | } 581 | ``` 582 | 583 | ### @Autowired和@Resource之间的区别 584 | 585 | @Autowired可用于:构造函数、成员变量、Setter方法 586 | 587 | @Autowired和@Resource之间的区别 588 | 589 | - @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。 590 | - @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。 591 | 592 | ### @Qualifier 注解有什么作用 593 | 594 | 当您创建多个相同类型的 bean 并希望仅使用属性装配其中一个 bean 时,您可以使用@Qualifier 注解和 @Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。 595 | 596 | ### @RequestMapping 注解有什么用? 597 | 598 | @RequestMapping 注解用于将特定 HTTP 请求方法映射到将处理相应请求的控制器中的特定类/方法。此注释可应用于两个级别: 599 | 600 | - 类级别:映射请求的 URL 601 | - 方法级别:映射 URL 以及 HTTP 请求方法 602 | 603 | ## Spring数据访问(14) 604 | 605 | ### 解释对象/关系映射集成模块 606 | 607 | Spring 通过提供ORM模块,支持我们在直接JDBC之上使用一个对象/关系映射映射(ORM)工具,Spring 支持集成主流的ORM框架,如Hiberate,JDO和 iBATIS,JPA,TopLink,JDO,OJB 。Spring的事务管理同样支持以上所有ORM框架及JDBC。 608 | 609 | ### 在Spring框架中如何更有效地使用JDBC? 610 | 611 | 使用Spring JDBC 框架,资源管理和错误处理的代价都会被减轻。所以开发者只需写statements 和 queries从数据存取数据,JDBC也可以在Spring框架提供的模板类的帮助下更有效地被使用,这个模板叫JdbcTemplate 612 | 613 | ### 解释JDBC抽象和DAO模块 614 | 615 | 通过使用JDBC抽象和DAO模块,保证数据库代码的简洁,并能避免数据库资源错误关闭导致的问题,它在各种不同的数据库的错误信息之上,提供了一个统一的异常访问层。它还利用Spring的AOP 模块给Spring应用中的对象提供事务管理服务。 616 | 617 | ### spring DAO 有什么用? 618 | 619 | Spring DAO(数据访问对象) 使得 JDBC,Hibernate 或 JDO 这样的数据访问技术更容易以一种统一的方式工作。这使得用户容易在持久性技术之间切换。它还允许您在编写代码时,无需考虑捕获每种技术不同的异常。 620 | 621 | ### spring JDBC API 中存在哪些类? 622 | 623 | JdbcTemplate 624 | 625 | SimpleJdbcTemplate 626 | 627 | NamedParameterJdbcTemplate 628 | 629 | SimpleJdbcInsert 630 | 631 | SimpleJdbcCall 632 | 633 | ### JdbcTemplate是什么 634 | 635 | JdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。 636 | 637 | ### 使用Spring通过什么方式访问Hibernate?使用 Spring 访问 Hibernate 的方法有哪些? 638 | 639 | 在Spring中有两种方式访问Hibernate: 640 | 641 | - 使用 Hibernate 模板和回调进行控制反转 642 | - 扩展 HibernateDAOSupport 并应用 AOP 拦截器节点 643 | 644 | ### 如何通过HibernateDaoSupport将Spring和Hibernate结合起来? 645 | 646 | 用Spring的 SessionFactory 调用 LocalSessionFactory。集成过程分三步: 647 | 648 | - 配置the Hibernate SessionFactory 649 | - 继承HibernateDaoSupport实现一个DAO 650 | - 在AOP支持的事务中装配 651 | 652 | ### Spring支持的事务管理类型, spring 事务实现方式有哪些? 653 | 654 | Spring支持两种类型的事务管理: 655 | 656 | **编程式事务管理**:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。 657 | 658 | **声明式事务管理**:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。 659 | 660 | ### Spring事务的实现方式和实现原理 661 | 662 | Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。 663 | 664 | ### 说一下Spring的事务传播行为 665 | 666 | spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。 667 | 668 | > ① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。 669 | > 670 | > ② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。 671 | > 672 | > ③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。 673 | > 674 | > ④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。 675 | > 676 | > ⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 677 | > 678 | > ⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。 679 | > 680 | > ⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。 681 | 682 | ### 说一下 spring 的事务隔离? 683 | 684 | spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致: 685 | 686 | 1. ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么; 687 | 2. ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读); 688 | 3. ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别; 689 | 4. ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别; 690 | 5. ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。 691 | 692 | **脏读** :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。 693 | 694 | **不可重复读** :是指在一个事务内,多次读同一数据。 695 | 696 | **幻读** :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。 697 | 698 | ### Spring框架的事务管理有哪些优点? 699 | 700 | - 为不同的事务API 如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式。 701 | - 为编程式事务管理提供了一套简单的API而不是一些复杂的事务API 702 | - 支持声明式事务管理。 703 | - 和Spring各种数据访问抽象层很好得集成。 704 | 705 | ### 你更倾向用那种事务管理类型? 706 | 707 | 大多数Spring框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。 708 | 709 | ## Spring面向切面编程(AOP)(13) 710 | 711 | ### 什么是AOP 712 | 713 | OOP(Object-Oriented Programming)面向对象编程,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。 714 | 715 | AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。 716 | 717 | ### Spring AOP and AspectJ AOP 有什么区别?AOP 有哪些实现方式? 718 | 719 | AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。 720 | 721 | (1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。 722 | 723 | (2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。 724 | 725 | ### JDK动态代理和CGLIB动态代理的区别 726 | 727 | Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理: 728 | 729 | - JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。 730 | - 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。 731 | 732 | 静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。 733 | 734 | > InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。 735 | 736 | ### 如何理解 Spring 中的代理? 737 | 738 | 将 Advice 应用于目标对象后创建的对象称为代理。在客户端对象的情况下,目标对象和代理对象是相同的。 739 | 740 | Advice + Target Object = Proxy 741 | 742 | ### 解释一下Spring AOP里面的几个名词 743 | 744 | (1)切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。 745 | 746 | (2)连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。 747 | 748 | (3)通知(Advice):在AOP术语中,切面的工作被称为通知。 749 | 750 | (4)切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。 751 | 752 | (5)引入(Introduction):引入允许我们向现有类添加新方法或属性。 753 | 754 | (6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。 755 | 756 | (7)织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入: 757 | 758 | - 编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。 759 | - 类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。 760 | - 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。 761 | 762 | ### Spring在运行时通知对象 763 | 764 | 通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。 765 | 766 | 直到应用需要被代理的bean时,Spring才创建代理对象。如果使用的是ApplicationContext的话,在ApplicationContext从BeanFactory中加载所有bean的时候,Spring才会创建被代理的对象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入SpringAOP的切面。 767 | 768 | ### Spring只支持方法级别的连接点 769 | 770 | 因为Spring基于动态代理,所以Spring只支持方法连接点。Spring缺少对字段连接点的支持,而且它不支持构造器连接点。方法之外的连接点拦截功能,我们可以利用Aspect来补充。 771 | 772 | ### 在Spring AOP 中,关注点和横切关注的区别是什么?在 spring aop 中 concern 和 cross-cutting concern 的不同之处 773 | 774 | 关注点(concern)是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。 775 | 776 | 横切关注点(cross-cutting concern)是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。 777 | 778 | ### Spring通知有哪些类型? 779 | 780 | 在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过SpringAOP框架触发的代码段。 781 | 782 | Spring切面可以应用5种类型的通知: 783 | 784 | 1. 前置通知(Before):在目标方法被调用之前调用通知功能; 785 | 2. 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么; 786 | 3. 返回通知(After-returning ):在目标方法成功执行之后调用通知; 787 | 4. 异常通知(After-throwing):在目标方法抛出异常后调用通知; 788 | 5. 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。 789 | 790 | > 同一个aspect,不同advice的执行顺序: 791 | > 792 | > ①没有异常情况下的执行顺序: 793 | > 794 | > around before advice 795 | > before advice 796 | > target method 执行 797 | > around after advice 798 | > after advice 799 | > afterReturning 800 | > 801 | > ②有异常情况下的执行顺序: 802 | > 803 | > around before advice 804 | > before advice 805 | > target method 执行 806 | > around after advice 807 | > after advice 808 | > afterThrowing:异常发生 809 | > java.lang.RuntimeException: 异常发生 810 | 811 | ### 什么是切面 Aspect? 812 | 813 | aspect 由 pointcount 和 advice 组成,切面是通知和切点的结合。 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP 就是负责实施切面的框架, 它将切面所定义的横切逻辑编织到切面所指定的连接点中. 814 | AOP 的工作重心在于如何将增强编织目标对象的连接点上, 这里包含两个工作: 815 | 816 | - 如何通过 pointcut 和 advice 定位到特定的 joinpoint 上 817 | - 如何在 advice 中编写切面代码. 818 | 819 | 可以简单地认为, 使用 @Aspect 注解的类就是切面. 820 | 821 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020021212264438.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 822 | 823 | ### 解释基于XML Schema方式的切面实现 824 | 825 | 在这种情况下,切面由常规类以及基于XML的配置实现。 826 | 827 | ### 解释基于注解的切面实现 828 | 829 | 在这种情况下(基于@AspectJ的实现),涉及到的切面声明的风格与带有java5标注的普通java类一致。 830 | 831 | ### 有几种不同类型的自动代理? 832 | 833 | BeanNameAutoProxyCreator 834 | 835 | DefaultAdvisorAutoProxyCreator 836 | 837 | Metadata autoproxying 838 | 839 | -------------------------------------------------------------------------------- /Java核心面试知识集—Tomcat面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ## Tomcat是什么? 26 | 27 | Tomcat 服务器Apache软件基金会项目中的一个核心项目,是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。 28 | 29 | ## Tomcat的缺省端口是多少,怎么修改 30 | 31 | 1. 找到Tomcat目录下的conf文件夹 32 | 2. 进入conf文件夹里面找到server.xml文件 33 | 3. 打开server.xml文件 34 | 4. 在server.xml文件里面找到下列信息 35 | 5. 把Connector标签的8080端口改成你想要的端口 36 | 37 | ```xml 38 | 39 | 42 | ``` 43 | 44 | ## tomcat 有哪几种Connector 运行模式(优化)? 45 | 46 | 下面,我们先大致了解Tomcat Connector的三种运行模式。 47 | 48 | - **BIO:同步并阻塞** 一个线程处理一个请求。缺点:并发量高时,线程数较多,浪费资源。Tomcat7或以下,在Linux系统中默认使用这种方式。 49 | 50 | **配制项**:protocol=”HTTP/1.1” 51 | 52 | - NIO:同步非阻塞IO 53 | 54 | 利用Java的异步IO处理,可以通过少量的线程处理大量的请求,可以复用同一个线程处理多个connection(多路复用)。 55 | 56 | Tomcat8在Linux系统中默认使用这种方式。 57 | 58 | Tomcat7必须修改Connector配置来启动。 59 | 60 | **配制项**:protocol=”org.apache.coyote.http11.Http11NioProtocol” 61 | 62 | **备注**:我们常用的Jetty,Mina,ZooKeeper等都是基于java nio实现. 63 | 64 | - APR:即Apache Portable Runtime,从操作系统层面解决io阻塞问题。**AIO方式,****异步非阻塞IO**(Java NIO2又叫AIO) 主要与NIO的区别主要是操作系统的底层区别.可以做个比喻:比作快递,NIO就是网购后要自己到官网查下快递是否已经到了(可能是多次),然后自己去取快递;AIO就是快递员送货上门了(不用关注快递进度)。 65 | 66 | **配制项**:protocol=”org.apache.coyote.http11.Http11AprProtocol” 67 | 68 | **备注**:需在本地服务器安装APR库。Tomcat7或Tomcat8在Win7或以上的系统中启动默认使用这种方式。Linux如果安装了apr和native,Tomcat直接启动就支持apr。 69 | 70 | ## Tomcat有几种部署方式? 71 | 72 | **在Tomcat中部署Web应用的方式主要有如下几种:** 73 | 74 | 1. 利用Tomcat的自动部署。 75 | 76 | 把web应用拷贝到webapps目录。Tomcat在启动时会加载目录下的应用,并将编译后的结果放入work目录下。 77 | 78 | 2. 使用Manager App控制台部署。 79 | 80 | 在tomcat主页点击“Manager App” 进入应用管理控制台,可以指定一个web应用的路径或war文件。 81 | 82 | 3. 修改conf/server.xml文件部署。 83 | 84 | 修改conf/server.xml文件,增加Context节点可以部署应用。 85 | 86 | 4. 增加自定义的Web部署文件。 87 | 88 | 在conf/Catalina/localhost/ 路径下增加 xyz.xml文件,内容是Context节点,可以部署应用。 89 | 90 | ## tomcat容器是如何创建servlet类实例?用到了什么原理? 91 | 92 | 1. 当容器启动时,会读取在webapps目录下所有的web应用中的web.xml文件,然后对 **xml文件进行解析,并读取servlet注册信息**。然后,将每个应用中注册的servlet类都进行加载,并通过 **反射的方式实例化**。(有时候也是在第一次请求时实例化) 93 | 2. 在servlet注册时加上1如果为正数,则在一开始就实例化,如果不写或为负数,则第一次请求实例化。 94 | 95 | ## Tomcat工作模式 96 | 97 | Tomcat作为servlet容器,有三种工作模式: 98 | 99 | - 1、独立的servlet容器,servlet容器是web服务器的一部分; 100 | - 2、进程内的servlet容器,servlet容器是作为web服务器的插件和java容器的实现,web服务器插件在内部地址空间打开一个jvm使得java容器在内部得以运行。反应速度快但伸缩性不足; 101 | - 3、进程外的servlet容器,servlet容器运行于web服务器之外的地址空间,并作为web服务器的插件和java容器实现的结合。反应时间不如进程内但伸缩性和稳定性比进程内优; 102 | 103 | 进入Tomcat的请求可以根据Tomcat的工作模式分为如下两类: 104 | 105 | - Tomcat作为应用程序服务器:请求来自于前端的web服务器,这可能是Apache, IIS, Nginx等; 106 | - Tomcat作为独立服务器:请求来自于web浏览器; 107 | 108 | 面试时问到Tomcat相关问题的几率并不高,正式因为如此,很多人忽略了对Tomcat相关技能的掌握,下面这一篇文章整理了Tomcat相关的系统架构,介绍了Server、Service、Connector、Container之间的关系,各个模块的功能,可以说把这几个掌握住了,Tomcat相关的面试题你就不会有任何问题了!另外,在面试的时候你还要有意识无意识的往Tomcat这个地方引,就比如说常见的Spring MVC的执行流程,一个URL的完整调用链路,这些相关的题目你是可以往Tomcat处理请求的这个过程去说的!掌握了Tomcat这些技能,面试官一定会佩服你的! 109 | 110 | 学了本章之后你应该明白的是: 111 | 112 | - Server、Service、Connector、Container四大组件之间的关系和联系,以及他们的主要功能点; 113 | - Tomcat执行的整体架构,请求是如何被一步步处理的; 114 | - Engine、Host、Context、Wrapper相关的概念关系; 115 | - Container是如何处理请求的; 116 | - Tomcat用到的相关设计模式; 117 | 118 | ## Tomcat顶层架构 119 | 120 | 俗话说,站在巨人的肩膀上看世界,一般学习的时候也是先总览一下整体,然后逐个部分个个击破,最后形成思路,了解具体细节,Tomcat的结构很复杂,但是 Tomcat 非常的模块化,找到了 Tomcat 最核心的模块,问题才可以游刃而解,了解了 Tomcat 的整体架构对以后深入了解 Tomcat 来说至关重要! 121 | 122 | 先上一张Tomcat的顶层结构图(图A),如下: 123 | 124 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191021215330153.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 125 | 126 | Tomcat中最顶层的容器是Server,代表着整个服务器,从上图中可以看出,一个Server可以包含至少一个Service,即可以包含多个Service,用于具体提供服务。 127 | 128 | Service主要包含两个部分:Connector和Container。从上图中可以看出 Tomcat 的心脏就是这两个组件,他们的作用如下: 129 | 130 | - Connector用于处理连接相关的事情,并提供Socket与Request请求和Response响应相关的转化; 131 | - Container用于封装和管理Servlet,以及具体处理Request请求; 132 | 133 | 一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但是可以有多个Connectors,这是因为一个服务可以有多个连接,如同时提供Http和Https链接,也可以提供向相同协议不同端口的连接,示意图如下(Engine、Host、Context下面会说到): 134 | 135 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191021215344811.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 136 | 137 | 多个 Connector 和一个 Container 就形成了一个 Service,有了 Service 就可以对外提供服务了,但是 Service 还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非 Server 莫属了!所以整个 Tomcat 的生命周期由 Server 控制。 138 | 139 | 另外,上述的包含关系或者说是父子关系,都可以在tomcat的conf目录下的server.xml配置文件中看出,下图是删除了注释内容之后的一个完整的server.xml配置文件(Tomcat版本为8.0) 140 | 141 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191021215355649.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 142 | 143 | 详细的配置文件内容可以到Tomcat官网查看:[Tomcat配置文件](http://tomcat.apache.org/tomcat-8.0-doc/index.html) 144 | 145 | 上边的配置文件,还可以通过下边的一张结构图更清楚的理解: 146 | 147 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/2019102121541531.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 148 | 149 | Server标签设置的端口号为8005,shutdown=”SHUTDOWN” ,表示在8005端口监听“SHUTDOWN”命令,如果接收到了就会关闭Tomcat。一个Server有一个Service,当然还可以进行配置,一个Service有多个Connector,Service左边的内容都属于Container的,Service下边是Connector。 150 | 151 | ### Tomcat顶层架构小结 152 | 153 | 1. Tomcat中只有一个Server,一个Server可以有多个Service,一个Service可以有多个Connector和一个Container; 154 | 2. Server掌管着整个Tomcat的生死大权; 155 | 3. Service 是对外提供服务的; 156 | 4. Connector用于接受请求并将请求封装成Request和Response来具体处理; 157 | 5. Container用于封装和管理Servlet,以及具体处理request请求; 158 | 159 | 知道了整个Tomcat顶层的分层架构和各个组件之间的关系以及作用,对于绝大多数的开发人员来说Server和Service对我们来说确实很远,而我们开发中绝大部分进行配置的内容是属于Connector和Container的,所以接下来介绍一下Connector和Container。 160 | 161 | ## Connector和Container的微妙关系 162 | 163 | 由上述内容我们大致可以知道一个请求发送到Tomcat之后,首先经过Service然后会交给我们的Connector,Connector用于接收请求并将接收的请求封装为Request和Response来具体处理,Request和Response封装完之后再交由Container进行处理,Container处理完请求之后再返回给Connector,最后在由Connector通过Socket将处理的结果返回给客户端,这样整个请求的就处理完了! 164 | 165 | Connector最底层使用的是Socket来进行连接的,Request和Response是按照HTTP协议来封装的,所以Connector同时需要实现TCP/IP协议和HTTP协议! 166 | 167 | Tomcat既然需要处理请求,那么肯定需要先接收到这个请求,接收请求这个东西我们首先就需要看一下Connector! 168 | 169 | Connector架构分析 170 | 171 | Connector用于接受请求并将请求封装成Request和Response,然后交给Container进行处理,Container处理完之后在交给Connector返回给客户端。 172 | 173 | 因此,我们可以把Connector分为四个方面进行理解: 174 | 175 | 1. Connector如何接受请求的? 176 | 2. 如何将请求封装成Request和Response的? 177 | 3. 封装完之后的Request和Response如何交给Container进行处理的? 178 | 4. Container处理完之后如何交给Connector并返回给客户端的? 179 | 180 | 首先看一下Connector的结构图(图B),如下所示: 181 | 182 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191021215430677.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 183 | 184 | Connector就是使用ProtocolHandler来处理请求的,不同的ProtocolHandler代表不同的连接类型,比如:Http11Protocol使用的是普通Socket来连接的,Http11NioProtocol使用的是NioSocket来连接的。 185 | 186 | 其中ProtocolHandler由包含了三个部件:Endpoint、Processor、Adapter。 187 | 188 | 1. Endpoint用来处理底层Socket的网络连接,Processor用于将Endpoint接收到的Socket封装成Request,Adapter用于将Request交给Container进行具体的处理。 189 | 2. Endpoint由于是处理底层的Socket网络连接,因此Endpoint是用来实现TCP/IP协议的,而Processor用来实现HTTP协议的,Adapter将请求适配到Servlet容器进行具体的处理。 190 | 3. Endpoint的抽象实现AbstractEndpoint里面定义的Acceptor和AsyncTimeout两个内部类和一个Handler接口。Acceptor用于监听请求,AsyncTimeout用于检查异步Request的超时,Handler用于处理接收到的Socket,在内部调用Processor进行处理。 191 | 192 | 至此,我们应该很轻松的回答1,2,3的问题了,但是4还是不知道,那么我们就来看一下Container是如何进行处理的以及处理完之后是如何将处理完的结果返回给Connector的? 193 | 194 | ## Container架构分析 195 | 196 | Container用于封装和管理Servlet,以及具体处理Request请求,在Container内部包含了4个子容器,结构图如下(图C): 197 | 198 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191021215443306.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 199 | 200 | 4个子容器的作用分别是: 201 | 202 | 1. Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine; 203 | 2. Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点; 204 | 3. Context:代表一个应用程序,对应着平时开发的一套程序,或者一个WEB-INF目录以及下面的web.xml文件; 205 | 4. Wrapper:每一Wrapper封装着一个Servlet; 206 | 207 | 下面找一个Tomcat的文件目录对照一下,如下图所示: 208 | 209 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191021215455991.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 210 | 211 | Context和Host的区别是Context表示一个应用,我们的Tomcat中默认的配置下webapps下的每一个文件夹目录都是一个Context,其中ROOT目录中存放着主应用,其他目录存放着子应用,而整个webapps就是一个Host站点。 212 | 213 | 我们访问应用Context的时候,如果是ROOT下的则直接使用域名就可以访问,例如:www.baidu.com,如果是Host(webapps)下的其他应用,则可以使用www.baidu.com/docs进行访问,当然默认指定的根应用(ROOT)是可以进行设定的,只不过Host站点下默认的主应用是ROOT目录下的。 214 | 215 | 看到这里我们知道Container是什么,但是还是不知道Container是如何进行请求处理的以及处理完之后是如何将处理完的结果返回给Connector的?别急!下边就开始探讨一下Container是如何进行处理的! 216 | 217 | ### Container如何处理请求的 218 | 219 | Container处理请求是使用Pipeline-Valve管道来处理的!(Valve是阀门之意) 220 | 221 | Pipeline-Valve是**责任链模式**,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将处理后的结果返回,再让下一个处理者继续处理。 222 | 223 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191021215507725.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 224 | 225 | 但是!Pipeline-Valve使用的责任链模式和普通的责任链模式有些不同!区别主要有以下两点: 226 | 227 | - 每个Pipeline都有特定的Valve,而且是在管道的最后一个执行,这个Valve叫做BaseValve,BaseValve是不可删除的; 228 | - 在上层容器的管道的BaseValve中会调用下层容器的管道。 229 | 230 | 我们知道Container包含四个子容器,而这四个子容器对应的BaseValve分别在:StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve。 231 | 232 | Pipeline的处理流程图如下(图D): 233 | 234 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191021215519408.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 235 | 236 | - Connector在接收到请求后会首先调用最顶层容器的Pipeline来处理,这里的最顶层容器的Pipeline就是EnginePipeline(Engine的管道); 237 | - 在Engine的管道中依次会执行EngineValve1、EngineValve2等等,最后会执行StandardEngineValve,在StandardEngineValve中会调用Host管道,然后再依次执行Host的HostValve1、HostValve2等,最后在执行StandardHostValve,然后再依次调用Context的管道和Wrapper的管道,最后执行到StandardWrapperValve。 238 | - 当执行到StandardWrapperValve的时候,会在StandardWrapperValve中创建FilterChain,并调用其doFilter方法来处理请求,这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet,其doFilter方法会依次调用所有的Filter的doFilter方法和Servlet的service方法,这样请求就得到了处理! 239 | - 当所有的Pipeline-Valve都执行完之后,并且处理完了具体的请求,这个时候就可以将返回的结果交给Connector了,Connector在通过Socket的方式将结果返回给客户端。 240 | 241 | ## 总结 242 | 243 | 至此,我们已经对Tomcat的整体架构有了大致的了解,从图A、B、C、D可以看出来每一个组件的基本要素和作用。我们在脑海里应该有一个大概的轮廓了!如果你面试的时候,让你简单的聊一下Tomcat,上面的内容你能脱口而出吗?当你能够脱口而出的时候,面试官一定会对你刮目相看的! 244 | 245 | -------------------------------------------------------------------------------- /Java核心面试知识集—zookeeper面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ------ 26 | 27 | ## 1.ZooKeeper是什么? 28 | 29 | ZooKeeper是一个**分布式的,开放源码的分布式**应用程序协调服务,是Google的Chubby一个开源的实现,它是**集群的管理者,**监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。 30 | 客户端的**读请求可以被集群中的**任意一台机器处理,如果读请求在节点上注册了监听器,这个监听器也是由所连接的zookeeper机器来处理。对于**写请求,这些请求会同**时发给其他zookeeper机器并且达成一致后,请求才会返回成功。因此,随着**zookeeper的集群机器增多,读请求的吞吐会提高但是写请求的吞吐会下降。 31 | 有序性是zookeeper中非常重要的一个特性,所有的**更新都是全局有序的,每个更新都有一个**唯一的时间戳,这个时间戳称为**zxid(Zookeeper Transaction Id)。而**读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个**zookeeper最新的zxid。 32 | 33 | ## *2.ZooKeeper提供了什么?* 34 | 35 | 1、**文件系统 36 | 2、**通知机制 37 | 38 | ## *3.Zookeeper文件系统* 39 | 40 | Zookeeper提供一个多层级的节点命名空间(节点称为znode)。与文件系统不同的是,这些节点**都可以设置关联的数据,而文件系统中只有文件节点可以存放数据而目录节点不行。Zookeeper为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得Zookeeper**不能用于存放大量的数据,每个节点的存放数据上限为**1M。 41 | 42 | ## *4.四种类型的znode* 43 | 44 | 1、**PERSISTENT-持久化目录节点 45 | 客户端与zookeeper断开连接后,该节点依旧存在 46 | 2、**PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点 47 | 客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号 48 | 3、**EPHEMERAL-临时目录节点 49 | 客户端与zookeeper断开连接后,该节点被删除 50 | 4、**EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点 51 | 客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号 52 | ![](https://upload-images.jianshu.io/upload_images/11474088-e097f8e6af153e76.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 53 | 54 | ## *5.Zookeeper通知机制* 55 | 56 | client端会对某个znode建立一个**watcher事件,当该znode发生变化时,这些client会收到zk的通知,然后client可以根据znode变化来做出业务上的改变等。 57 | 58 | ## *6.Zookeeper做了什么?* 59 | 60 | 1、命名服务 61 | 2、配置管理 62 | 3、集群管理 63 | 4、分布式锁 64 | 5、队列管理 65 | 66 | ## *7.zk的命名服务(文件系统)* 67 | 68 | 命名服务是指通过指定的名字来**获取资源或者**服务的地址,利用zk创建一个全局的路径,即是**唯一的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。 69 | 70 | ## *8.zk的配置管理(文件系统、通知机制)* 71 | 72 | 程序分布式的部署在不同的机器上,将程序的配置信息放在zk的**znode下,当有配置发生改变时,也就是znode发生变化时,可以通过改变zk中某个目录节点的内容,利用**watcher通知给各个客户端,从而更改配置。 73 | 74 | ## *9.Zookeeper集群管理(文件系统、通知机制)* 75 | 76 | 所谓集群管理无在乎两点:**是否有机器退出和加入、选举master。 77 | 对于第一点,所有机器约定在父目录下**创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper的连接断开,其所创建的临时目录节点被删除,**所有其他机器都收到通知:某个兄弟目录被删除,于是,所有人都知道:它上船了。 78 | 新机器加入也是类似,**所有机器收到通知:新兄弟目录加入,highcount又有了,对于第二点,我们稍微改变一下,**所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为master就好。 79 | 80 | ## *10.Zookeeper分布式锁(文件系统、通知机制)* 81 | 82 | 有了zookeeper的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是**保持独占,另一个是**控制时序。 83 | 对于第一类,我们将zookeeper上的一个**znode看作是一把锁,通过createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。用完删除掉自己创建的distribute_lock 节点就释放出锁。 84 | 对于第二类, /distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,**编号最小的获得锁,用完删除,依次方便。 85 | 86 | ## *11.获取分布式锁的流程* 87 | 88 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-3684ad409420a3fc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 89 | 在获取分布式锁的时候在locker节点下创建临时顺序节点,释放锁的时候删除该临时节点。客户端调用createNode方法在locker下创建临时顺序节点, 90 | 然后调用getChildren(“locker”)来获取locker下面的所有子节点,注意此时不用设置任何Watcher。客户端获取到所有的子节点path之后,如果发现自己创建的节点在所有创建的子节点序号最小,那么就认为该客户端获取到了锁。如果发现自己创建的节点并非locker所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到**比自己小的那个节点,然后对其调用**exist()方法,同时对其注册事件监听器。之后,让这个被关注的节点删除,则客户端的Watcher会收到相应通知,此时再次判断自己创建的节点是否是locker子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤继续获取到比自己小的一个节点并注册监听。当前这个过程中还需要许多的逻辑判断。 91 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-a746a00310a0390d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 92 | 93 | 代码的实现主要是基于互斥锁,获取分布式锁的重点逻辑在于**BaseDistributedLock,实现了基于Zookeeper实现分布式锁的细节。 94 | 95 | ## *12.Zookeeper队列管理(文件系统、通知机制)* 96 | 97 | 两种类型的队列: 98 | 1、同步队列,当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。 99 | 2、队列按照 FIFO 方式进行入队和出队操作。 100 | 第一类,在约定目录下创建临时目录节点,监听节点数目是否是我们要求的数目。 101 | 第二类,和分布式锁服务中的控制时序场景基本原理一致,入列有编号,出列按编号。在特定的目录下创建**PERSISTENT_SEQUENTIAL节点,创建成功时**Watcher通知等待的队列,队列删除**序列号最小的节点用以消费。此场景下Zookeeper的znode用于消息存储,znode存储的数据就是消息队列中的消息内容,SEQUENTIAL序列号就是消息的编号,按序取出即可。由于创建的节点是持久化的,所以**不必担心队列消息的丢失问题。 102 | 103 | ## *13.Zookeeper数据复制* 104 | 105 | Zookeeper作为一个集群提供一致的数据服务,自然,它要在**所有机器间做数据复制。数据复制的好处: 106 | 1、容错:一个节点出错,不致于让整个系统停止工作,别的节点可以接管它的工作; 107 | 2、提高系统的扩展能力 :把负载分布到多个节点上,或者增加节点来提高系统的负载能力; 108 | 3、提高性能:让**客户端本地访问就近的节点,提高用户访问速度。 109 | 110 | 从客户端读写访问的透明度来看,数据复制集群系统分下面两种: 111 | 1、**写主(WriteMaster) :对数据的**修改提交给指定的节点。读无此限制,可以读取任何一个节点。这种情况下客户端需要对读与写进行区别,俗称**读写分离; 112 | 2、**写任意(Write Any):对数据的**修改可提交给任意的节点,跟读一样。这种情况下,客户端对集群节点的角色与变化透明。 113 | 114 | 对zookeeper来说,它采用的方式是**写任意。通过增加机器,它的读吞吐能力和响应能力扩展性非常好,而写,随着机器的增多吞吐能力肯定下降(这也是它建立observer的原因),而响应能力则取决于具体实现方式,是**延迟复制保持最终一致性,还是**立即复制快速响应。 115 | 116 | ## *14.Zookeeper工作原理* 117 | 118 | Zookeeper 的核心是**原子广播,这个机制保证了**各个Server之间的同步。实现这个机制的协议叫做**Zab协议。Zab协议有两种模式,它们分别是**恢复模式(选主)和**广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。 119 | 120 | ## *15.zookeeper是如何保证事务的顺序一致性的?* 121 | 122 | zookeeper采用了**递增的事务Id来标识,所有的proposal(提议)都在被提出的时候加上了zxid,zxid实际上是一个64位的数字,高32位是epoch(时期; 纪元; 世; 新时代)用来标识leader是否发生改变,如果有新的leader产生出来,epoch会自增,**低32位用来递增计数。当新产生proposal的时候,会依据数据库的两阶段过程,首先会向其他的server发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行。 123 | 124 | ## *16.Zookeeper 下 Server工作状态* 125 | 126 | 每个Server在工作过程中有三种状态: 127 | LOOKING:当前Server**不知道leader是谁,正在搜寻 128 | LEADING:当前Server即为选举出来的leader 129 | FOLLOWING:leader已经选举出来,当前Server与之同步 130 | 131 | ## *17.zookeeper是如何选取主leader的?* 132 | 133 | 当leader崩溃或者leader失去大多数的follower,这时zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为**fast paxos。 134 | 135 | 1、Zookeeper选主流程(basic paxos) 136 | (1)选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server; 137 | (2)选举线程首先向所有Server发起一次询问(包括自己); 138 | (3)选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中; 139 | (4)收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server; 140 | (5)线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2 + 1的Server票数,设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。 通过流程分析我们可以得出:要使Leader获得多数Server的支持,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于n+1. 每个Server启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的server还会从磁盘快照中恢复数据和会话信息,zk会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。 141 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-f8830053b796fae8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 142 | 143 | 2、Zookeeper选主流程(basic paxos) 144 | fast paxos流程是在选举过程中,某Server首先向所有Server提议自己要成为leader,当其它Server收到提议以后,解决epoch和 zxid的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息,重复这个流程,最后一定能选举出Leader。 145 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-5d3bad804a654026.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 146 | 147 | ## *18.Zookeeper同步流程* 148 | 149 | 选完Leader以后,zk就进入状态同步过程。 150 | 1、Leader等待server连接; 151 | 2、Follower连接leader,将最大的zxid发送给leader; 152 | 3、Leader根据follower的zxid确定同步点; 153 | 4、完成同步后通知follower 已经成为uptodate状态; 154 | 5、Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。 155 | ![image.png](https://upload-images.jianshu.io/upload_images/11474088-6c579f972403bc1b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 156 | 157 | ## *19.分布式通知和协调* 158 | 159 | 对于系统调度来说:操作人员发送通知实际是通过控制台**改变某个节点的状态,**然后zk将这些变化发送给注册了这个节点的watcher的所有客户端。 160 | 对于执行情况汇报:每个工作进程都在某个目录下**创建一个临时节点。**并携带工作的进度数据,这样**汇总的进程可以监控目录子节点的变化获得工作进度的实时的全局情况。 161 | 162 | ## *20.机器中为什么会有leader?* 163 | 164 | 在分布式环境中,有些业务逻辑只需要集群中的某一台机器进行执行,**其他的机器可以共享这个结果,这样可以大大**减少重复计算,**提高性能,于是就需要进行leader选举。 165 | 166 | ## *21.zk节点宕机如何处理?* 167 | 168 | Zookeeper本身也是集群,推荐配置不少于3个服务器。Zookeeper自身也要保证当一个节点宕机时,其他节点会继续提供服务。 169 | 如果是一个Follower宕机,还有2台服务器提供访问,因为Zookeeper上的数据是有多个副本的,数据并不会丢失; 170 | 如果是一个Leader宕机,Zookeeper会选举出新的Leader。 171 | ZK集群的机制是只要超过半数的节点正常,集群就能正常提供服务。只有在ZK节点挂得太多,只剩一半或不到一半节点能工作,集群才失效。 172 | 所以 173 | 3个节点的cluster可以挂掉1个节点(leader可以得到2票>1.5) 174 | 2个节点的cluster就不能挂掉任何1个节点了(leader可以得到1票<=1) 175 | 176 | ## *22.zookeeper负载均衡和nginx负载均衡区别* 177 | 178 | zk的负载均衡是可以调控,nginx只是能调权重,其他需要可控的都需要自己写插件;但是nginx的吞吐量比zk大很多,应该说按业务选择用哪种方式。 179 | 180 | ## *23.zookeeper watch机制* 181 | 182 | Watch机制官方声明:一个Watch事件是一个一次性的触发器,当被设置了Watch的数据发生了改变的时候,则服务器将这个改变发送给设置了Watch的客户端,以便通知它们。 183 | Zookeeper机制的特点: 184 | 1、一次性触发数据发生改变时,一个watcher event会被发送到client,但是client**只会收到一次这样的信息。 185 | 2、watcher event异步发送watcher的通知事件从server发送到client是**异步的,这就存在一个问题,不同的客户端和服务器之间通过socket进行通信,由于**网络延迟或其他因素导致客户端在不通的时刻监听到事件,由于Zookeeper本身提供了**ordering guarantee,即客户端监听事件后,才会感知它所监视znode发生了变化。所以我们使用Zookeeper不能期望能够监控到节点每次的变化。Zookeeper**只能保证最终的一致性,而无法保证强一致性。 186 | 3、数据监视Zookeeper有数据监视和子数据监视getdata() and exists()设置数据监视,getchildren()设置了子节点监视。 187 | 4、注册watcher **getData、exists、getChildren 188 | 5、触发watcher **create、delete、setData 189 | 6、**setData()会触发znode上设置的data watch(如果set成功的话)。一个成功的**create() 操作会触发被创建的znode上的数据watch,以及其父节点上的child watch。而一个成功的**delete()操作将会同时触发一个znode的data watch和child watch(因为这样就没有子节点了),同时也会触发其父节点的child watch。 190 | 7、当一个客户端**连接到一个新的服务器上时,watch将会被以任意会话事件触发。当**与一个服务器失去连接的时候,是无法接收到watch的。而当client**重新连接时,如果需要的话,所有先前注册过的watch,都会被重新注册。通常这是完全透明的。只有在一个特殊情况下,**watch可能会丢失:对于一个未创建的znode的exist watch,如果在客户端断开连接期间被创建了,并且随后在客户端连接上之前又删除了,这种情况下,这个watch事件可能会被丢失。 191 | 8、Watch是轻量级的,其实就是本地JVM的**Callback,服务器端只是存了是否有设置了Watcher的布尔类型 192 | 193 | -------------------------------------------------------------------------------- /Java核心面试知识集—大厂数据库面试题.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ------ 26 | 27 | 28 | 29 | ### 事务四大特性(ACID)原子性、一致性、隔离性、持久性? 30 | 31 | **原子性(Atomicity)** 32 | 33 | - **原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚**,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。 34 | 35 | **一致性(Consistency)** 36 | 37 | - **事务开始前和结束后,数据库的完整性约束没有被破坏。比如 A 向 B 转账,不可能 A 扣了钱,B 却没收到**。 38 | 39 | **隔离性(Isolation)** 40 | 41 | - **隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离**。 42 | 43 | 同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如 A 正在从一张银行卡中取钱,在 A 取钱的过程结束前,B 不能向这张卡转账。 44 | 45 | **关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到**。   **持久性(Durability)** 46 | 47 | - **持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作**。 48 | 49 | ### 事务的并发?事务隔离级别,每个级别会引发什么问题,MySQL 默认是哪个级别? 50 | 51 | 从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题,然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行, **在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行, 事务的隔离级别可以通过隔离事务属性指定**。 52 | 53 | #### 事务的并发问题 54 | 55 | 1、**脏读**:事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据 56 | 57 | 2、**不可重复读**:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果因此本事务先后两次读到的数据结果会不一致。 58 | 59 | 3、**幻读**:幻读解决了不重复读,保证了同一个事务里,查询的结果都是事务开始时的状态(一致性)。 60 | 61 | 例如:事务 T1 对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作 这时事务 T2 又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。 而操作事务 T1 的用户如果再查看刚刚修改的数据,会发现还有跟没有修改一样,其实这行是从事务 T2 中添加的,就好像产生幻觉一样,这就是发生了幻读。 62 | 63 | **小结**:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。    64 | 65 | #### 事务的隔离级别 66 | 67 | | 事务隔离级别 | 脏读 | 不可重复读 | 幻读 | 68 | | :------------------------ | :--- | :--------- | :--- | 69 | | 读未提交 read-uncommitted | 是 | 是 | 是 | 70 | | 不可重复读 read-committed | 否 | 是 | 是 | 71 | | 可重复读 repeatable-read | 否 | 否 | 是 | 72 | | 串行化 serializable | 否 | 否 | 否 | 73 | 74 | - **读未提交**:另一个事务修改了数据,但尚未提交,而本事务中的 SELECT 会读到这些未被提交的数据**脏读** 75 | - **不可重复读**:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果因此本事务先后两次读到的数据结果会不一致。 76 | - **可重复读**:在同一个事务里,SELECT 的结果是事务开始时时间点的状态,因此,同样的 SELECT 操作读到的结果会是一致的。但是,会有**幻读**现象 77 | - **串行化**:最高的隔离级别,在这个隔离级别下,不会产生任何异常。并发的事务,就像事务是在一个个按照顺序执行一样 78 | 79 | **MySQL 默认的事务隔离级别为 repeatable-read** 80 | 81 | - **MySQL 支持 4 中事务隔离级别**. 82 | - 事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持. 83 | - Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE 84 | 85 | #### 补充: 86 | 87 | 1. SQL 规范所规定的标准,不同的数据库具体的实现可能会有些差异 88 | 2. MySQL 中默认事务隔离级别是“可重复读”时并不会锁住读取到的行 89 | 90 | - **事务隔离级别**:**未提交读时**,写数据只会锁住相应的行。 91 | - **事务隔离级别为**:**可重复读时**,写数据会锁住整张表。 92 | - **事务隔离级别为**:**串行化时**,读写数据都会锁住整张表。 93 | 94 | **隔离级别越高**,**越能保证数据的完整性和一致性**,但是对并发性能的影响也越大,鱼和熊掌不可兼得啊。**对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为 Read Committed,它能够避免脏读取,而且具有较好的并发性能**。尽管它会导致不可重复读、幻读这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。 95 | 96 | ### MySQL 常见的三种存储引擎(InnoDB、MyISAM、MEMORY)的区别? 97 | 98 | #### MySQL 存储引擎 MyISAM 与 InnoDB 如何选择 99 | 100 | MySQL 有多种存储引擎,每种存储引擎有各自的优缺点,可以择优选择使用:`MyISAM、InnoDB、MERGE、MEMORY(HEAP)、BDB(BerkeleyDB)、EXAMPLE、FEDERATED、ARCHIVE、CSV、BLACKHOLE`。 101 | 102 | **虽然 MySQL 里的存储引擎不只是 MyISAM 与 InnoDB 这两个,但常用的就是两个**。 103 | 104 | **两种存储引擎的大致区别表现在**: 105 | 106 | - **InnoDB 支持事务,MyISAM 不支持**,这一点是非常之重要。事务是一种高级的处理方式,如在一些列增删改中只要哪个出错还可以回滚还原,而 MyISAM 就不可以了。 107 | - **MyISAM 适合查询以及插入为主的应用**。 108 | - **InnoDB 适合频繁修改以及涉及到安全性较高的应用**。 109 | - InnoDB 支持外键,MyISAM 不支持。 110 | - **从 MySQL5.5.5 以后,InnoDB 是默认引擎**。 111 | - InnoDB 不支持 FULLTEXT 类型的索引。 112 | - **InnoDB 中不保存表的行数**,如`select count(*) from table`时,InnoDB 需要扫描一遍整个表来计算有多少行,但是 MyISAM 只要简单的读出保存好的行数即可。注意的是,当 count(*)语句包含 where 条件时 MyISAM 也需要扫描整个表。 113 | - 对于自增长的字段,InnoDB 中必须包含只有该字段的索引,但是在 MyISAM 表中可以和其他字段一起建立联合索引。 114 | - `DELETE FROM table`时,**InnoDB 不会重新建立表,而是一行一行的 删除,效率非常慢**。**MyISAM 则会重建表**。 115 | - InnoDB 支持行锁(某些情况下还是锁整表,如 `update table set a=1 where user like '%lee%'`。 116 | 117 | #### 关于 MySQL 数据库提供的两种存储引擎,MyISAM 与 InnoDB 选择使用: 118 | 119 | - **INNODB 会支持一些关系数据库的高级功能**,**如事务功能和行级锁,MyISAM 不支持**。 120 | - **MyISAM 的性能更优,占用的存储空间少**,所以,选择何种存储引擎,视具体应用而定。 121 | - **如果你的应用程序一定要使用事务,毫无疑问你要选择 INNODB 引擎**。但要注意,INNODB 的行级锁是有条件的。在 where 条件没有使用主键时,照样会锁全表。比如 DELETE FROM mytable 这样的删除语句。 122 | - **如果你的应用程序对查询性能要求较高,就要使用 MyISAM 了**。**MyISAM 索引和数据是分开的,而且其索引是压缩的,可以更好地利用内存**。所以它的**查询性能明显优于 INNODB**。压缩后的索引也能节约一些磁盘空间。**MyISAM 拥有全文索引的功能,这可以极大地优化 LIKE 查询的效率**。 123 | 124 | 有人说 MyISAM 只能用于小型应用,其实这只是一种偏见。如果数据量比较大,这是需要通过升级架构来解决,比如分表分库,而不是单纯地依赖存储引擎。 125 | 126 | **现在一般都是选用 innodb 了,主要是 MyISAM 的全表锁,读写串行问题,并发效率锁表,效率低**,MyISAM 对于读写密集型应用一般是不会去选用的。 127 | 128 | ### MEMORY 存储引擎 129 | 130 | **MEMORY 是 MySQL 中一类特殊的存储引擎。它使用存储在内存中的内容来创建表,而且数据全部放在内存中**。这些特性与前面的两个很不同。 131 | 132 | 每个基于 MEMORY 存储引擎的表实际对应一个磁盘文件。该文件的文件名与表名相同,类型为 frm 类型。该文件中只存储表的结构。而其数据文件,都是存储在内存中,这样有利于数据的快速处理,提高整个表的效率。值得注意的是,服务器需要有足够的内存来维持 MEMORY 存储引擎的表的使用。如果不需要了,可以释放内存,甚至删除不需要的表。 133 | 134 | MEMORY 默认使用哈希索引。速度比使用 B 型树索引快。当然如果你想用 B 型树索引,可以在创建索引时指定。 135 | 136 | 注意,**MEMORY 用到的很少,因为它是把数据存到内存中,如果内存出现异常就会影响数据。如果重启或者关机,所有数据都会消失**。因此,基于**MEMORY 的表的生命周期很短,一般是一次性的**。 137 | 138 | ### MySQL 的 MyISAM 与 InnoDB 两种存储引擎在,事务、锁级别,各自的适用场景? 139 | 140 | **事务处理上方面** 141 | 142 | - **MyISAM**:**强调的是性能**,每次查询具有原子性,其执行数度比 InnoDB 类型更快,但是**不提供事务支持**。 143 | - **InnoDB**:**提供事务支持事务,外部键等高级数据库功能**。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。 144 | 145 | **锁级别** 146 | 147 | - **MyISAM**:**只支持表级锁**,用户在操作 MyISAM 表时,select,update,delete,insert 语句都会给表自动加锁,如果加锁以后的表满足 insert 并发的情况下,可以在表的尾部插入新的数据。 148 | - **InnoDB**:支持事务和行级锁,是 innodb 的最大特色。行锁大幅度提高了多用户并发操作的新能。但是 InnoDB 的行锁,只是在 WHERE 的主键是有效的,非主键的 WHERE 都会锁全表的。 149 | 150 | ### 查询语句不同元素(where、jion、limit、group by、having 等等)执行先后顺序? 151 | 152 | 1. 查询中用到的关键词主要包含六个,并且他们的顺序依次为 `select--from--where--group by--having--order by` 153 | 154 | 其中 select 和 from 是必须的,其他关键词是可选的,这六个关键词的执行顺序 与 sql 语句的书写顺序并不是一样的,而是按照下面的顺序来执行 155 | 156 | - from:需要从哪个数据表检索数据 157 | - where:过滤表中数据的条件 158 | - group by:如何将上面过滤出的数据分组 159 | - having:对上面已经分组的数据进行过滤的条件 160 | - select:查看结果集中的哪个列,或列的计算结果 161 | - order by :按照什么样的顺序来查看返回的数据 162 | 163 | 1. from 后面的表关联,是自右向左解析 而 where 条件的解析顺序是自下而上的。 164 | 165 | 也就是说,在写 SQL 文的时候,尽量把数据量小的表放在最右边来进行关联(用小表去匹配大表),而把能筛选出小量数据的条件放在 where 语句的最左边 (用小表去匹配大表) 166 | 167 | ### 什么是临时表,临时表什么时候删除? 168 | 169 | 时表可以手动删除: 170 | 171 | ``` 172 | DROP TEMPORARY TABLE IF EXISTS temp_tb; 173 | ``` 174 | 175 | **临时表只在当前连接可见,当关闭连接时,MySQL 会自动删除表并释放所有空间。因此在不同的连接中可以创建同名的临时表,并且操作属于本连接的临时表**。 176 | 177 | 创建临时表的语法与创建表语法类似,不同之处是**增加关键字 TEMPORARY**,如: 178 | 179 | ``` 180 | CREATE TEMPORARY TABLE tmp_table ( 181 | NAME VARCHAR (10) NOT NULL, 182 | time date NOT NULL 183 | ); 184 | 185 | select * from tmp_table; 186 | ``` 187 | 188 | ### MySQL B+Tree 索引和 Hash 索引的区别? 189 | 190 | - Hash 索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位; 191 | - B+树索引需要从根节点到枝节点,最后才能访问到页节点这样多次的 IO 访问; 192 | 193 | 那为什么大家不都用 Hash 索引而还要使用 B+树索引呢? 194 | 195 | #### Hash 索引 196 | 197 | 1. Hash 索引仅仅能满足"=","IN"和"<=>"查询,不能使用范围查询,因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系,并不能保证和 Hash 运算前完全一样; 198 | 2. Hash 索引无法被用来避免数据的排序操作,因为 Hash 值的大小关系并不一定和 Hash 运算前的键值完全一样; 199 | 3. Hash 索引不能利用部分索引键查询,对于组合索引,Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash 值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也无法被利用; 200 | 4. Hash 索引在任何时候都不能避免表扫描,由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要回表查询数据; 201 | 5. Hash 索引遇到大量 Hash 值相等的情况后性能并不一定就会比 B+树索引高。 202 | 203 | #### B+Tree 索引 204 | 205 | **MySQL 中,只有 HEAP/MEMORY 引擎才显示支持 Hash 索引**。 206 | 207 | **常用的 InnoDB 引擎中默认使用的是 B+树索引**,它会实时监控表上索引的使用情况,如果认为建立哈希索引可以提高查询效率,则自动在内存中的“自适应哈希索引缓冲区”建立哈希索引(**在 InnoDB 中默认开启自适应哈希索引**),通过观察搜索模式,MySQL 会利用 index key 的前缀建立哈希索引,如果一个表几乎大部分都在缓冲池中,那么建立一个哈希索引能够加快等值查询。 208 | 209 | #### B+树索引和哈希索引的明显区别是: 210 | 211 | **如果是等值查询,那么哈希索引明显有绝对优势**,**因为只需要经过一次算法即可找到相应的键值;当然了,这个前提是,键值都是唯一的。如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,直到找到相应的数据**; 212 | 213 | **如果是范围查询检索,这时候哈希索引就毫无用武之地了**,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索; 214 | 215 | 同理,**哈希索引没办法利用索引完成排序**,以及 like ‘xxx%’ 这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询); 216 | 217 | **哈希索引也不支持多列联合索引的最左匹配规则**; 218 | 219 | B+树索引的关键字检索效率比较平均,不像 B 树那样波动幅度大,**在有大量重复键值情况下,哈希索引的效率也是极低的,因为存在所谓的哈希碰撞问题**。 220 | 221 | **在大多数场景下,都会有范围查询、排序、分组等查询特征,用 B+树索引就可以了**。 222 | 223 | ### sql 查询语句确定创建哪种类型的索引,如何优化查询 224 | 225 | - **性能优化过程中,选择在哪个列上创建索引是最重要的步骤之一**,可以考虑使用索引的主要有两种类型的列:在 where 子句中出现的列,在 join 子句中出现的列。 226 | - 考虑列中值的分布,索引的列的基数越大,索引的效果越好。 227 | - 使用短索引,如果**对字符串列进行索引,应该指定一个前缀长度**,可节省大量索引空间,提升查询速度。 228 | - **利用最左前缀**,顾名思义,就是最左优先,在多列索引,有体现:(ALTER TABLE people ADD INDEX lname*fname*age (lame,fname,age);),所谓最左前缀原则就是先要看第一列,在第一列满足的条件下再看左边第二列,以此类推 229 | - **不要过度建索引,只保持所需的索引**。每个额外的索引都要占用额外的磁盘空间,并**降低写操作的性能**。 230 | - 在修改表的内容时,索引必须进行更新,有时可能需要重构,因此,**索引越多,所花的时间越长**。 231 | - **MySQL 只对一下操作符才使用索引**:`<,<=,=,>,>=,between,in` 232 | - **以及某些时候的 like**(不以通配符%或_开头的情形)。 233 | 234 | ### 聚集索引和非聚集索引区别? 235 | 236 | **聚合索引(clustered index) / 非聚合索引(nonclustered index)** 237 | 238 | **根本区别** 239 | 240 | 聚集索引和非聚集索引的根本区别是**表记录的排列顺序和与索引的排列顺序是否一致**。 241 | 242 | **聚集索引** 243 | 244 | **聚集索引表记录的排列顺序和索引的排列顺序一致,所以查询效率快,只要找到第一个索引值记录,其余就连续性的记录在物理也一样连续存放。聚集索引对应的缺点就是修改慢,因为为了保证表中记录的物理和索引顺序一致,在记录插入的时候,会对数据页重新排序**。 245 | 246 | 聚集索引类似于新华字典中用拼音去查找汉字,拼音检索表于书记顺序都是按照 a~z 排列的,就像相同的逻辑顺序于物理顺序一样,当你需要查找 a,ai 两个读音的字,或是想一次寻找多个傻(sha)的同音字时,也许向后翻几页,或紧接着下一行就得到结果了。 247 | 248 | **非聚集索引** 249 | 250 | **非聚集索引制定了表中记录的逻辑顺序,但是记录的物理和索引不一定一致,两种索引都采用 B+树结构,非聚集索引的叶子层并不和实际数据页相重叠,而采用叶子层包含一个指向表中的记录在数据页中的指针方式。非聚集索引层次多,不会造成数据重排**。 251 | 252 | 非聚集索引类似在新华字典上通过偏旁部首来查询汉字,检索表也许是按照横、竖、撇来排列的,但是由于正文中是 a~z 的拼音顺序,所以就类似于逻辑地址于物理地址的不对应。同时适用的情况就在于分组,大数目的不同值,频繁更新的列中,这些情况即不适合聚集索引。 253 | 254 | ### 有哪些锁(乐观锁悲观锁),select 时怎么加排它锁? 255 | 256 | #### 悲观锁(Pessimistic Lock) 257 | 258 | **悲观锁的特点是先获取锁,再进行业务操作**,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。**通常所说的“一锁二查三更新”即指的是使用悲观锁。通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的 select … for update 操作来实现悲观锁**。当数据库执行 select for update 时会获取被 select 中的数据行的行锁,因此其他并发执行的 select for update 如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update 获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。 259 | 260 | 这里需要注意的一点是不同的数据库对 select for update 的实现和支持都是有所区别的,例如 oracle 支持 select for update no wait,表示如果拿不到锁立刻报错,而不是等待,MySQL 就没有 no wait 这个选项。另外**MySQL 还有个问题是 select for update 语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此如果在 MySQL 中用悲观锁务必要确定走了索引,而不是全表扫描**。 261 | 262 | #### 乐观锁(Optimistic Lock) 263 | 264 | **乐观锁,也叫乐观并发控制,它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,那么当前正在提交的事务会进行回滚**。 265 | 266 | 乐观锁的特点先进行业务操作,不到万不得已不去拿锁。即“乐观”的认为拿锁多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。 267 | 268 | **乐观锁在数据库上的实现完全是逻辑的,不需要数据库提供特殊的支持**。**一般的做法是在需要锁的数据上增加一个版本号,或者时间戳**,然后按照如下方式实现: 269 | 270 | **乐观锁(给表加一个版本号字段) 这个并不是乐观锁的定义,给表加版本号,是数据库实现乐观锁的一种方式**。 271 | 272 | ``` 273 | 1\. SELECT data AS old_data, version AS old_version FROM …; 274 | 2\. 根据获取的数据进行业务操作,得到 new_data 和 new_version 275 | 3\. UPDATE SET data = new_data, version = new_version WHERE version = old_version 276 | if (updated row > 0) { 277 | // 乐观锁获取成功,操作完成 278 | } else { 279 | // 乐观锁获取失败,回滚并重试 280 | } 281 | ``` 282 | 283 | **乐观锁在不发生取锁失败的情况下开销比悲观锁小**,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能 284 | 285 | **乐观锁还适用于一些比较特殊的场景,例如在业务操作过程中无法和数据库保持连接等悲观锁无法适用的地方**。 286 | 287 | #### 总结 288 | 289 | **悲观锁和乐观锁是数据库用来保证数据并发安全防止更新丢失的两种方法**,例子在`select ... for update`前加个事务就可以防止更新丢失。悲观锁和乐观锁大部分场景下差异不大,一些独特场景下有一些差别,一般我们可以从如下几个方面来判断。 290 | 291 | - **响应速度**:如果需要非常高的响应速度,**建议采用乐观锁方案**,成功就执行,不成功就失败,不需要等待其他并发去释放锁。 292 | - **冲突频率**:如果冲突频率非常高,**建议采用悲观锁**,保证成功率,如果冲突频率大,乐观锁会需要多次重试才能成功,代价比较大。 293 | - **重试代价**:如果重试代价大,建议采用悲观锁。 294 | 295 | ### 非关系型数据库和关系型数据库区别,优势比较? 296 | 297 | #### 非关系型数据库的优势 298 | 299 | **1. 性能** 300 | 301 | NOSQL 是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过 SQL 层的解析,所以性能非常高。 302 | 303 | **2. 可扩展性** 304 | 305 | 同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。 306 | 307 | #### 关系型数据库的优势 308 | 309 | **1. 复杂查询** 310 | 311 | 可以用 SQL 语句方便的在一个表以及多个表之间做非常复杂的数据查询。 312 | 313 | **2. 事务支持** 314 | 315 | 使得对于安全性能很高的数据访问要求得以实现。 316 | 317 | #### 总结 318 | 319 | **对于这两类数据库,对方的优势就是自己的弱势,反之亦然**。 320 | 321 | NOSQL 数据库慢慢开始具备 SQL 数据库的一些复杂查询功能,比如 MongoDB。 322 | 323 | 对于事务的支持也可以用一些系统级的原子操作来实现例如乐观锁之类的方法来曲线救国,比如 Redis set nx。 324 | 325 | ### 数据库三范式,根据某个场景设计数据表? 326 | 327 | - 所有字段值都是不可分解的原子值。 328 | - 在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。 329 | - 数据表中的每一列数据都和主键直接相关,而不能间接相关。 330 | 331 | #### 第一范式(确保每列保持原子性) 332 | 333 | **第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式**。 334 | 335 | 第一范式的合理遵循需要根据系统的实际需求来定。比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分,那么就非要将“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储,这样在对地址中某一部分操作的时候将非常方便。这样设计才算满足了数据库的第一范式,如下表所示。 336 | 337 | 上表所示的用户信息遵循了第一范式的要求,这样在对用户使用城市进行分类的时候就非常方便,也提高了数据库的性能。 338 | 339 | #### 第二范式(确保表中的每列都和主键相关) 340 | 341 | 第二范式在第一范式的基础之上更进一层。第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。**也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中**。 342 | 343 | **比如要设计一个订单信息表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键**。 344 | 345 | #### 第三范式(确保每列都和主键列直接相关,而不是间接相关) 346 | 347 | 第三范式需要确保数据表中的**每一列数据都和主键直接相关,而不能间接相关**。 348 | 349 | 比如在设计一个订单数据表的时候,可以将客户编号作为一个外键和订单表建立相应的关系。而不可以在订单表中添加关于客户其它信息(比如姓名、所属公司等)的字段。 350 | 351 | ### 数据库的读写分离、主从复制,主从复制分析的 7 个问题? 352 | 353 | #### 主从复制的几种方式 354 | 355 | **同步复制** 356 | 357 | - 所谓的同步复制,意思是 master 的变化,必须等待 slave-1,slave-2,…,slave-n 完成后才能返回。 这样,显然不可取,也不是 MySQL 复制的默认设置。比如,在 WEB 前端页面上,用户增加了条记录,需要等待很长时间。 358 | 359 | **异步复制** 360 | 361 | - 如同 AJAX 请求一样。master 只需要完成自己的数据库操作即可。至于 slaves 是否收到二进制日志,是否完成操作,不用关心,MySQL 的默认设置。 362 | 363 | **半同步复制** 364 | 365 | - master 只保证 slaves 中的一个操作成功,就返回,其他 slave 不管。 这个功能,是由 google 为 MySQL 引入的。 366 | 367 | #### 主从复制分析的 7 个问题 368 | 369 | **问题 1:master 的写操作,slaves 被动的进行一样的操作,保持数据一致性,那么 slave 是否可以主动的进行写操作?** 370 | 371 | 假设 slave 可以主动的进行写操作,slave 又无法通知 master,这样就导致了 master 和 slave 数据不一致了。因此**slave 不应该进行写操作**,至少是 slave 上涉及到复制的数据库不可以写。实际上,**这里已经揭示了读写分离的概念**。 372 | 373 | **问题 2:主从复制中,可以有 N 个 slave,可是这些 slave 又不能进行写操作,要他们干嘛?** 374 | 375 | **以实现数据备份**。 376 | 377 | **类似于高可用的功能,一旦 master 挂了,可以让 slave 顶上去,同时 slave 提升为 master**。 378 | 379 | 异地容灾,比如 master 在北京,地震挂了,那么在上海的 slave 还可以继续。 380 | 381 | 主要用于实现 scale out,**分担负载,可以将读的任务分散到 slaves 上**。 382 | 383 | 【**很可能的情况是,一个系统的读操作远远多于写操作,因此写操作发向 master,读操作发向 slaves 进行操作**】 384 | 385 | **问题 3:主从复制中有 master,slave1,slave2,…等等这么多 MySQL 数据库,那比如一个 JAVA WEB 应用到底应该连接哪个数据库?** 386 | 387 | 当 然,我们在应用程序中可以这样,`insert/delete/update`这些更新数据库的操作,用`connection(for master)`进行操作,`select 用 connection(for slaves)`进行操作。那我们的应用程序还要完成怎么从 slaves 选择一个来执行 select,例如**使用简单的轮循算法**。 388 | 389 | 这样的话,相当于应用程序完成了 SQL 语句的路由,而且与 MySQL 的主从复制架构非常关联,一旦 master 挂了,某些 slave 挂了,那么应用程序就要修改了。能不能让应用程序与 MySQL 的主从复制架构没有什么太多关系呢? 390 | 391 | **找一个组件**,**application program 只需要与它打交道,用它来完成 MySQL 的代理,实现 SQL 语句的路由**。 392 | 393 | MySQL proxy 并不负责,怎么从众多的 slaves 挑一个?可以交给另一个组件(比如 haproxy)来完成。 394 | 395 | 这就是所谓的`MySQL READ WRITE SPLITE,MySQL`的读写分离。 396 | 397 | **问题 4:如果 MySQL proxy , direct , master 他们中的某些挂了怎么办?** 398 | 399 | 总统一般都会弄个副总统,以防不测。同样的,可以给这些关键的节点来个备份。 400 | 401 | **问题 5:当 master 的二进制日志每产生一个事件,都需要发往 slave,如果我们有 N 个 slave,那是发 N 次,还是只发一次?** 402 | 403 | 如果只发一次,发给了 slave-1,那 slave-2,slave-3,…它们怎么办? 404 | 405 | 显 然,应该发 N 次。实际上,**在 MySQL master 内部,维护 N 个线程,每一个线程负责将二进制日志文件发往对应的 slave**。master 既要负责写操作,还的维护 N 个线程,负担会很重。**可以这样,slave-1 是 master 的从,slave-1 又是 slave-2,slave-3,…的主**,同时 slave-1 不再负责 select。 **slave-1 将 master 的复制线程的负担,转移到自己的身上**。**这就是所谓的多级复制的概念**。 406 | 407 | **问题 6:当一个 select 发往 MySQL proxy,可能这次由 slave-2 响应,下次由 slave-3 响应,这样的话,就无法利用查询缓存了。** 408 | 409 | 应该找一个共享式的缓存,比如 memcache 来解决。将 slave-2,slave-3,…这些查询的结果都缓存至 mamcache 中。 410 | 411 | **问题 7:随着应用的日益增长,读操作很多,我们可以扩展 slave,但是如果 master 满足不了写操作了,怎么办呢?** 412 | 413 | scale on ?更好的服务器? 没有最好的,只有更好的,太贵了。。。 414 | 415 | scale out ? 主从复制架构已经满足不了。 416 | 417 | 可以分库【垂直拆分】,分表【水平拆分】。 418 | 419 | ### 使用 explain 优化 sql 和索引? 420 | 421 | 对于复杂、效率低的 sql 语句,我们通常是使用 explain sql 来分析 sql 语句,这个语句可以打印出,语句的执行。这样方便我们分析,进行优化 422 | 423 | - **table**:显示这一行的数据是关于哪张表的 424 | - **type**:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为`const、eq_reg、ref、range、index`和`ALL` 425 | - **all**: full table scan ;MySQL 将遍历全表以找到匹配的行; 426 | - **index** : index scan; index 和 all 的区别在于 index 类型只遍历索引; 427 | - **range**:索引范围扫描,对索引的扫描开始于某一点,返回匹配值的行,常见与`between ,< ,>`等查询; 428 | - **ref**:非唯一性索引扫描,返回匹配某个单独值的所有行,常见于使用非唯一索引即唯一索引的非唯一前缀进行查找; 429 | - **eq_ref**:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常用于主键或者唯一索引扫描; 430 | - **const**,system:当 MySQL 对某查询某部分进行优化,并转为一个常量时,使用这些访问类型。如果将主键置于 where 列表中,MySQL 就能将该查询转化为一个常量。 431 | - **possible_keys**:显示可能应用在这张表中的索引。如果为空,没有可能的索引。可以为相关的域从 WHERE 语句中选择一个合适的语句 432 | - **key**: **实际使用的索引。如果为 NULL,则没有使用索引**。很少的情况下,MySQL 会选择优化不足的索引。这种情况下,可以在 SELECT 语句中使用 USE INDEX(indexname)来强制使用一个索引或者用 IGNORE INDEX(indexname)来强制 MySQL 忽略索引 433 | - **key_len**:**使用的索引的长度。在不损失精确性的情况下,长度越短越好** 434 | - **ref**:显示索引的哪一列被使用了,如果可能的话,是一个常数 435 | - **rows**:MySQL 认为必须检查的用来返回请求数据的行数 436 | - **Extra**:关于 MySQL 如何解析查询的额外信息。将在表 4.3 中讨论,但这里可以看到的坏的例子是 Using temporary 和 Using filesort,意思 MySQL 根本不能使用索引,结果是检索会很慢。 437 | 438 | ### MySQL 慢查询怎么解决? 439 | 440 | - **slow\*query\*log** 慢查询开启状态。 441 | - **slow\*query\*log_file** 慢查询日志存放的位置(这个目录需要 MySQL 的运行帐号的可写权限,一般设置为 MySQL 的数据存放目录)。 442 | - **long\*query\*time** 查询超过多少秒才记录。 443 | 444 | ### 什么是 内连接、外连接、交叉连接、笛卡尔积等? 445 | 446 | #### 内连接 447 | 448 | 内连接查询操作列出与连接条件匹配的数据行,它使用比较运算符比较被连接列的 列值。 449 | 450 | **内连接分三种**: 451 | 452 | 1. **等值连接**:在连接条件中使用等于号`(=)`运算符比较被连接列的列值,其查询结 果中列出被连接表中的所有列,包括其中的重复列。 453 | 454 | 例,下面使用等值连接列出 authors 和 publishers 表中位于同一城市的作者和出版社: 455 | 456 | ``` 457 | SELECT * FROM authors AS a INNER JOIN publishers AS p ON a.city=p.city 458 | ``` 459 | 460 | 1. **不等连接**: 在连接条件使用除等于运算符以外的其它比较运算符比较被连接的 列的列值。这些运算符包括`>、>=、<=、<、!>、!<`和`<>`。 461 | 2. **自然连接**:在连接条件中使用等于(=)运算符比较被连接列的列值,但它使用选 择列表指出查询结果集合中所包括的列,并删除连接表中的重复列。 462 | 463 | 例,在选择列表中删除 authors 和 publishers 表中重复列(city 和 state): 464 | 465 | ``` 466 | SELECT a.*,p.pub_id,p.pub_name,p.country FROM authors AS a INNER JOIN publishers AS p ON a.city=p.city 467 | ``` 468 | 469 | #### 外连接 470 | 471 | 外连接,返回到查询结果集合中的不仅包含符合连接条件的行,**而且还包括左表**(**左外连接或左连接**)、右表(**右外连接或右连接**)或两个边接表(全外连接)中的所有数据行。    472 | 473 | - **left join**(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录。 474 | - **right join**(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录。 475 | 476 | **例如 1**: 477 | 478 | ``` 479 | SELECT a.*,b.* FROM luntan LEFT JOIN usertable as b ON a.username=b.username 480 | ``` 481 | 482 | **例如 2**: 483 | 484 | ``` 485 | SELECT a.*,b.* FROM city as a FULL OUTER JOIN user as b ON a.username=b.username 486 | ``` 487 | 488 | #### 交叉连接 489 | 490 | **交叉连接不带 `WHERE` 子句**,**它返回被连接的两个表所有数据行的“笛卡尔积”**,返回到结果集合中的数据行数等于第一个表中符合查询条件的数据行数乘以第二个表中符合查询条件的数据行数。 491 | 492 | 例,titles 表中有 6 类图书,而 publishers 表中有 8 家出版社,则下 列交叉连接检索到的记录数将等于 6*8=48 行。    493 | 494 | 例如: 495 | 496 | ``` 497 | SELECT type,pub_name FROM titles CROSS JOIN publishers ORDER BY type 498 | ``` 499 | 500 | #### 笛卡尔积 501 | 502 | 笛卡尔积是两个表每一个字段相互匹配,去掉`where` 或者`inner join`的等值 得出的结果就是笛卡尔积。**笛卡尔积也等同于交叉连接**。 503 | 504 | #### 总结 505 | 506 | - 内连接: 只连接匹配的行。 507 | - 左外连接: 包含左边表的全部行(不管右边的表中是否存在与它们匹配的行),以及右边表中全部匹配的行。 508 | - 右外连接: 包含右边表的全部行(不管左边的表中是否存在与它们匹配的行),以及左边表中全部匹配的行。 509 | - 全外连接: 包含左、右两个表的全部行,不管另外一边的表中是否存在与它们匹配的行。 510 | - 交叉连接 生成笛卡尔积-它不使用任何匹配或者选取条件,而是直接将一个数据源中的每个行与另一个数据源的每个行都一一匹配。 511 | 512 | ### MySQL 都有什么锁,死锁判定原理和具体场景,死锁怎么解决? 513 | 514 | #### MySQL 都有什么锁 515 | 516 | MySQL 有三种锁的级别:**页级、表级、行级**。 517 | 518 | - **表级锁**:开销小,加锁快;**不会出现死锁**;锁定粒度大,发生锁冲突的概率最高,并发度最低。 519 | - **行级锁**:开销大,加锁慢;**会出现死锁**;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 520 | - **页面锁**:开销和加锁时间界于表锁和行锁之间;**会出现死锁**;锁定粒度界于表锁和行锁之间,并发度一般 521 | 522 | #### 什么情况下会造成死锁 523 | 524 | - 所谓死锁: 是指两个或两个以上的进程在执行过程中。 525 | - 因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。 526 | - 此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等竺的进程称为死锁进程。 527 | - 表级锁不会产生死锁.所以解决死锁主要还是针对于最常用的 InnoDB。 528 | 529 | 死锁的关键在于:两个(或以上)的 Session 加锁的顺序不一致。 530 | 531 | 那么对应的**解决死锁问题的关键就是**:让不同的 session**加锁有次序**。 532 | 533 | #### 死锁的解决办法 534 | 535 | - 查出的线程杀死 kill 536 | 537 | ``` 538 | SELECT trx_MySQL_thread_id FROM information_schema.INNODB_TRX; 539 | ``` 540 | 541 | - 设置锁的超时时间 542 | 543 | Innodb 行锁的等待时间,单位秒。可在会话级别设置,RDS 实例该参数的默认值为 50(秒)。 544 | 545 | 生产环境不推荐使用过大的 `innodb_lock_wait_timeout`参数值 546 | 547 | 该参数支持在会话级别修改,方便应用在会话级别单独设置某些特殊操作的行锁等待超时时间,如下: 548 | 549 | ``` 550 | set innodb_lock_wait_timeout=1000; —设置当前会话 Innodb 行锁等待超时时间,单位秒。 551 | ``` 552 | 553 | ### varchar 和 char 的使用场景? 554 | 555 | **char 的长度是不可变的,而 varchar 的长度是可变的**。 556 | 557 | 定义一个 char[10]和 varchar[10]。 558 | 559 | **如果存进去的是‘csdn’,那么 char 所占的长度依然为 10,除了字符‘csdn’外,后面跟六个空格,varchar 就立马把长度变为 4 了,取数据的时候,char 类型的要用 trim()去掉多余的空格,而 varchar 是不需要的**。 560 | 561 | char 的存取数度还是要比 varchar 要快得多,因为其长度固定,方便程序的存储与查找。 562 | 563 | char 也为此付出的是空间的代价,因为其长度固定,所以难免会有多余的空格占位符占据空间,**可谓是以空间换取时间效**率。 564 | 565 | varchar 是**以空间效率为首位**。 566 | 567 | **char 的存储方式是**:对英文字符(ASCII)占用 1 个字节,对一个汉字占用两个字节。 568 | 569 | **varchar 的存储方式是**:对每个英文字符占用 2 个字节,汉字也占用 2 个字节。 570 | 571 | 两者的存储数据都非 unicode 的字符数据。 572 | 573 | # 19.MySQL 高并发环境解决方案? 574 | 575 | MySQL 高并发环境解决方案 分库 分表 分布式 增加二级缓存。。。。。 576 | 577 | **需求分析**:互联网单位 每天大量数据读取,写入,并发性高。 578 | 579 | - **现有解决方式**:水平分库分表,由单点分布到多点数据库中,从而降低单点数据库压力。 580 | - **集群方案**:解决 DB 宕机带来的单点 DB 不能访问问题。 581 | - **读写分离策略**:极大限度提高了应用中 Read 数据的速度和并发量。无法解决高写入压力。 582 | 583 | ### 数据库崩溃时事务的恢复机制(REDO 日志和 UNDO 日志)? 584 | 585 | #### Undo Log 586 | 587 | **Undo Log 是为了实现事务的原子性**,在 MySQL 数据库 InnoDB 存储引擎中,还用了 Undo Log 来实现多版本并发控制(简称:MVCC)。 588 | 589 | - **事务的原子性**(Atomicity)事务中的所有操作,要么全部完成,要么不做任何操作,不能只做部分操作。如果在执行的过程中发生了错误,要回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过。 590 | - **原理**Undo Log 的原理很简单,**为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为 UndoLog)**。然后进行数据的修改。如果出现了错误或者用户执行了 ROLLBACK 语句,**系统可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态**。 591 | 592 | **之所以能同时保证原子性和持久化,是因为以下特点**: 593 | 594 | - 更新数据前记录 Undo log。 595 | - 为了保证持久性,必须将数据在事务提交前写到磁盘。只要事务成功提交,数据必然已经持久化。 596 | - **Undo log 必须先于数据持久化到磁盘**。如果在 G,H 之间系统崩溃,**undo log 是完整的, 可以用来回滚事务**。 597 | - 如果在 A-F 之间系统崩溃,因为数据没有持久化到磁盘。所以磁盘上的数据还是保持在事务开始前的状态。 598 | 599 | **缺陷**:**每个事务提交前将数据和 Undo Log 写入磁盘,这样会导致大量的磁盘 IO,因此性能很低**。 600 | 601 | 如果能够将数据缓存一段时间,就能减少 IO 提高性能。但是这样就会丧失事务的持久性。因此引入了另外一种机制来实现持久化,即 Redo Log。 602 | 603 | #### Redo Log 604 | 605 | - **原理和 Undo Log 相反**,**Redo Log 记录的是新数据的备份**。**在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可以根据 Redo Log 的内容,将所有数据恢复到最新的状态**。 606 | 607 | -------------------------------------------------------------------------------- /Java核心面试知识集—计算机网络基础.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | > PS: 20 | > 21 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 22 | 23 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ## 计算机网络体系结构 26 | 27 | 在计算机网络的基本概念中,分层次的体系结构是最基本的。计算机网络体系结构的抽象概念较多,在学习时要多思考。这些概念对后面的学习很有帮助。 28 | 29 | ### 网络协议是什么? 30 | 31 | 在计算机网络要做到有条不紊地交换数据,就必须遵守一些事先约定好的规则,比如交换数据的格式、是否需要发送一个应答信息。这些规则被称为网络协议。 32 | 33 | ### 为什么要对网络协议分层? 34 | 35 | - 简化问题难度和复杂度。由于各层之间独立,我们可以分割大问题为小问题。 36 | - 灵活性好。当其中一层的技术变化时,只要层间接口关系保持不变,其他层不受影响。 37 | - 易于实现和维护。 38 | - 促进标准化工作。分开后,每层功能可以相对简单地被描述。 39 | 40 | 网络协议分层的缺点: 功能可能出现在多个层里,产生了额外开销。 41 | 42 | 为了使不同体系结构的计算机网络都能互联,国际标准化组织 ISO 于1977年提出了一个试图使各种计算机在世界范围内互联成网的标准框架,即著名的开放系统互联基本参考模型 OSI/RM,简称为OSI。 43 | 44 | OSI 的七层协议体系结构的概念清楚,理论也较完整,但它既复杂又不实用,TCP/IP 体系结构则不同,但它现在却得到了非常广泛的应用。TCP/IP 是一个四层体系结构,它包含应用层,运输层,网际层和网络接口层(用网际层这个名字是强调这一层是为了解决不同网络的互连问题),不过从实质上讲,TCP/IP 只有最上面的三层,因为最下面的网络接口层并没有什么具体内容,因此在学习计算机网络的原理时往往采用折中的办法,即综合 OSI 和 TCP/IP 的优点,采用一种只有五层协议的体系结构,这样既简洁又能将概念阐述清楚,有时为了方便,也可把最底下两层称为网络接口层。 45 | 46 | 四层协议,五层协议和七层协议的关系如下: 47 | 48 | - TCP/IP是一个四层的体系结构,主要包括:应用层、运输层、网际层和网络接口层。 49 | - 五层协议的体系结构主要包括:应用层、运输层、网络层,数据链路层和物理层。 50 | - OSI七层协议模型主要包括是:应用层(Application)、表示层(Presentation)、会话层(Session)、运输层(Transport)、网络层(Network)、数据链路层(Data Link)、物理层(Physical)。 51 | 52 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200316173310511.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoaW5rV29u,size_16,color_FFFFFF,t_70) 53 | 54 | 注:五层协议的体系结构只是为了介绍网络原理而设计的,实际应用还是 TCP/IP 四层体系结构。 55 | 56 | ## TCP/IP 协议族 57 | 58 | ### 应用层 59 | 60 | 应用层( application-layer )的任务是通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程(进程:主机中正在运行的程序)间的通信和交互的规则。 61 | 62 | 对于不同的网络应用需要不同的应用层协议。在互联网中应用层协议很多,如域名系统 DNS,支持万维网应用的 HTTP 协议,支持电子邮件的 SMTP 协议等等。 63 | 64 | ### 运输层 65 | 66 | 运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的**数据传输服务**。应用进程利用该服务传送应用层报文。 67 | 68 | 运输层主要使用一下两种协议 69 | 70 | 1. 传输控制协议-TCP:提供面向连接的,可靠的数据传输服务。 71 | 2. 用户数据协议-UDP:提供无连接的,尽最大努力的数据传输服务(不保证数据传输的可靠性)。 72 | 73 | | | UDP | TCP | 74 | | ------------ | ------------------------------------------ | -------------------------------------- | 75 | | 是否连接 | 无连接 | 面向连接 | 76 | | 是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输,使用流量控制和拥塞控制 | 77 | | 连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 | 78 | | 传输方式 | 面向报文 | 面向字节流 | 79 | | 首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 | 80 | | 场景 | 适用于实时应用(IP电话、视频会议、直播等) | 适用于要求可靠传输的应用,例如文件传输 | 81 | 82 | **每一个应用层(TCP/IP参考模型的最高层)协议一般都会使用到两个传输层协议之一:** 83 | 84 | 运行在`TCP协议`上的协议: 85 | 86 | - `HTTP(Hypertext Transfer Protocol,超文本传输协议)`,主要用于普通浏览。 87 | - `HTTPS(HTTP over SSL,安全超文本传输协议)`,`HTTP`协议的安全版本。 88 | - `FTP(File Transfer Protocol,文件传输协议)`,用于文件传输。 89 | - `POP3(Post Office Protocol, version 3,邮局协议)`,收邮件用。 90 | - `SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)`,用来发送电子邮件。 91 | - `TELNET(Teletype over the Network,网络电传)`,通过一个`终端(terminal)`登陆到网络。 92 | - `SSH(Secure Shell,用于替代安全性差的TELNET)`,用于加密安全登陆用。 93 | 94 | 运行在`UDP协议`上的协议: 95 | 96 | - `BOOTP(Boot Protocol,启动协议)`,应用于无盘设备。 97 | - `NTP(Network Time Protocol,网络时间协议)`,用于网络同步。 98 | - `DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)`,动态配置IP地址。 99 | 100 | 运行在`TCP`和`UDP`协议上: 101 | 102 | - `DNS(Domain Name Service,域名服务)`,用于完成地址查找,邮件转发等工作。 103 | 104 | ### 网络层 105 | 106 | 网络层的任务就是选择合适的网间路由和交换结点,确保计算机通信的数据及时传送。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 IP 协议,因此分组也叫 IP 数据报 ,简称数据报。 107 | 108 | 互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Intert Prococol)和许多路由选择协议,因此互联网的网络层也叫做网际层或 IP 层。 109 | 110 | ### 数据链路层 111 | 112 | 数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。 113 | 114 | 在两个相邻节点之间传送数据时,数据链路层将网络层交下来的 IP 数据报组装成帧,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。 115 | 116 | 在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。 117 | 118 | 一般的web应用的通信传输流是这样的: 119 | 120 | ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAxOS81LzkvMTZhOWM5Y2Q1MjNlMDU5OQ?x-oss-process=image/format,png) 121 | 122 | 发送端在层与层之间传输数据时,每经过一层时会被打上一个该层所属的首部信息。反之,接收端在层与层之间传输数据时,每经过一层时会把对应的首部信息去除。 123 | 124 | ### 物理层 125 | 126 | 在物理层上所传送的数据单位是比特。 物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。 127 | 128 | ### TCP/IP 协议族 129 | 130 | 在互联网使用的各种协议中最重要和最著名的就是 TCP/IP 两个协议。现在人们经常提到的 TCP/IP 并不一定是单指 TCP 和 IP 这两个具体的协议,而往往是表示互联网所使用的整个 TCP/IP 协议族。 131 | 132 | ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAxOS80LzcvMTY5ZjY5NjZjMjRhZjM0NQ?x-oss-process=image/format,png) 133 | 134 | > 互联网协议套件(英语:Internet Protocol Suite,缩写`IPS`)是一个网络通讯模型,以及一整个网络传输协议家族,为网际网络的基础通讯架构。它常被通称为TCP/IP协议族(英语:`TCP/IP Protocol Suite`,或`TCP/IP Protocols`),简称`TCP/IP`。因为该协定家族的两个核心协定:`TCP(传输控制协议)和IP(网际协议)`,为该家族中最早通过的标准。 135 | 136 | 划重点: 137 | 138 | ``` 139 | TCP(传输控制协议)和IP(网际协议)` 是最先定义的两个核心协议,所以才统称为`TCP/IP协议族 140 | ``` 141 | 142 | ## TCP的三次握手四次挥手 143 | 144 | TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,在发送数据前,通信双方必须在彼此间建立一条连接。所谓的“连接”,其实是客户端和服务端保存的一份关于对方的信息,如ip地址、端口号等。 145 | 146 | TCP可以看成是一种字节流,它会处理IP层或以下的层的丢包、重复以及错误问题。在连接的建立过程中,双方需要交换一些连接的参数。这些参数可以放在TCP头部。 147 | 148 | 一个TCP连接由一个4元组构成,分别是两个IP地址和两个端口号。一个TCP连接通常分为三个阶段:连接、数据传输、退出(关闭)。**通过三次握手建立一个链接,通过四次挥手来关闭一个连接**。 149 | 150 | **当一个连接被建立或被终止时,交换的报文段只包含TCP头部,而没有数据**。 151 | 152 | ### TCP报文的头部结构 153 | 154 | 在了解TCP连接之前先来了解一下TCP报文的头部结构。 155 | 156 | ![TCPHeader.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC8xLzcvMTZmN2UwM2IxOWU2YzEzNA?x-oss-process=image/format,png) 157 | 158 | 上图中有几个字段需要重点介绍下: 159 | 160 | (1)序号:seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。 161 | 162 | (2)确认序号:ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,ack=seq+1。 163 | 164 | (3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下: 165 | 166 | - ACK:确认序号有效。 167 | - FIN:释放一个连接。 168 | - PSH:接收方应该尽快将这个报文交给应用层。 169 | - RST:重置连接。 170 | - SYN:发起一个新连接。 171 | - URG:紧急指针(urgent pointer)有效。 172 | 173 | 需要注意的是: 174 | 175 | - 不要将确认序号ack与标志位中的ACK搞混了。 176 | - 确认方ack=发起方seq+1,两端配对。 177 | 178 | ### 三次握手 179 | 180 | > 三次握手的本质是确认通信双方收发数据的能力 181 | 182 | 首先,我让信使运输一份信件给对方,**对方收到了,那么他就知道了我的发件能力和他的收件能力是可以的**。 183 | 184 | 于是他给我回信,**我若收到了,我便知我的发件能力和他的收件能力是可以的,并且他的发件能力和我的收件能力是可以**。 185 | 186 | 然而此时他还不知道他的发件能力和我的收件能力到底可不可以,于是我最后回馈一次,**他若收到了,他便清楚了他的发件能力和我的收件能力是可以的**。 187 | 188 | 这,就是三次握手,这样说,你理解了吗? 189 | 190 | ![三次握手.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC8xLzcvMTZmN2UwM2IxZWE1MDdlOA?x-oss-process=image/format,png) 191 | 192 | - `第一次握手`:客户端要向服务端发起连接请求,首先客户端随机生成一个起始序列号ISN(比如是100),那客户端向服务端发送的报文段包含SYN标志位(也就是SYN=1),序列号seq=100。 193 | - `第二次握手`:服务端收到客户端发过来的报文后,发现SYN=1,知道这是一个连接请求,于是将客户端的起始序列号100存起来,并且随机生成一个服务端的起始序列号(比如是300)。然后给客户端回复一段报文,回复报文包含SYN和ACK标志(也就是SYN=1,ACK=1)、序列号seq=300、确认号ack=101(客户端发过来的序列号+1)。 194 | - `第三次握手`:客户端收到服务端的回复后发现ACK=1并且ack=101,于是知道服务端已经收到了序列号为100的那段报文;同时发现SYN=1,知道了服务端同意了这次连接,于是就将服务端的序列号300给存下来。然后客户端再回复一段报文给服务端,报文包含ACK标志位(ACK=1)、ack=301(服务端序列号+1)、seq=101(第一次握手时发送报文是占据一个序列号的,所以这次seq就从101开始,需要注意的是不携带数据的ACK报文是不占据序列号的,所以后面第一次正式发送数据时seq还是101)。当服务端收到报文后发现ACK=1并且ack=301,就知道客户端收到序列号为300的报文了,就这样客户端和服务端通过TCP建立了连接。 195 | 196 | ### 四次挥手 197 | 198 | > 四次挥手的目的是关闭一个连接 199 | 200 | ![四次挥手.jpeg](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC8xLzcvMTZmN2UwM2IyMWEwN2YwYw?x-oss-process=image/format,png) 201 | 202 | 比如客户端初始化的序列号ISA=100,服务端初始化的序列号ISA=300。TCP连接成功后客户端总共发送了1000个字节的数据,服务端在客户端发FIN报文前总共回复了2000个字节的数据。 203 | 204 | - `第一次挥手`:当客户端的数据都传输完成后,客户端向服务端发出连接释放报文(当然数据没发完时也可以发送连接释放报文并停止发送数据),释放连接报文包含FIN标志位(FIN=1)、序列号seq=1101(100+1+1000,其中的1是建立连接时占的一个序列号)。需要注意的是客户端发出FIN报文段后只是不能发数据了,但是还可以正常收数据;另外FIN报文段即使不携带数据也要占据一个序列号。 205 | - `第二次挥手`:服务端收到客户端发的FIN报文后给客户端回复确认报文,确认报文包含ACK标志位(ACK=1)、确认号ack=1102(客户端FIN报文序列号1101+1)、序列号seq=2300(300+2000)。此时服务端处于关闭等待状态,而不是立马给客户端发FIN报文,这个状态还要持续一段时间,因为服务端可能还有数据没发完。 206 | - `第三次挥手`:服务端将最后数据(比如50个字节)发送完毕后就向客户端发出连接释放报文,报文包含FIN和ACK标志位(FIN=1,ACK=1)、确认号和第二次挥手一样ack=1102、序列号seq=2350(2300+50)。 207 | - `第四次挥手`:客户端收到服务端发的FIN报文后,向服务端发出确认报文,确认报文包含ACK标志位(ACK=1)、确认号ack=2351、序列号seq=1102。注意客户端发出确认报文后不是立马释放TCP连接,而是要经过2MSL(最长报文段寿命的2倍时长)后才释放TCP连接。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接,所以服务端结束TCP连接的时间要比客户端早一些。 208 | 209 | ## 常见面试题 210 | 211 | ### 为什么TCP连接的时候是3次?2次不可以吗? 212 | 213 | 因为需要考虑连接时丢包的问题,如果只握手2次,第二次握手时如果服务端发给客户端的确认报文段丢失,此时服务端已经准备好了收发数(可以理解服务端已经连接成功)据,而客户端一直没收到服务端的确认报文,所以客户端就不知道服务端是否已经准备好了(可以理解为客户端未连接成功),这种情况下客户端不会给服务端发数据,也会忽略服务端发过来的数据。 214 | 215 | 如果是三次握手,即便发生丢包也不会有问题,比如如果第三次握手客户端发的确认ack报文丢失,服务端在一段时间内没有收到确认ack报文的话就会重新进行第二次握手,也就是服务端会重发SYN报文段,客户端收到重发的报文段后会再次给服务端发送确认ack报文。 216 | 217 | ### 为什么TCP连接的时候是3次,关闭的时候却是4次? 218 | 219 | 因为只有在客户端和服务端都没有数据要发送的时候才能断开TCP。而客户端发出FIN报文时只能保证客户端没有数据发了,服务端还有没有数据发客户端是不知道的。而服务端收到客户端的FIN报文后只能先回复客户端一个确认报文来告诉客户端我服务端已经收到你的FIN报文了,但我服务端还有一些数据没发完,等这些数据发完了服务端才能给客户端发FIN报文(所以不能一次性将确认报文和FIN报文发给客户端,就是这里多出来了一次)。 220 | 221 | ### 为什么客户端发出第四次挥手的确认报文后要等2MSL的时间才能释放TCP连接? 222 | 223 | 这里同样是要考虑丢包的问题,如果第四次挥手的报文丢失,服务端没收到确认ack报文就会重发第三次挥手的报文,这样报文一去一回最长时间就是2MSL,所以需要等这么长时间来确认服务端确实已经收到了。 224 | 225 | ### 如果已经建立了连接,但是客户端突然出现故障了怎么办? 226 | 227 | TCP设有一个保活计时器,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。 228 | 229 | ### 什么是HTTP,HTTP 与 HTTPS 的区别 230 | 231 | HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范 232 | 233 | | 区别 | HTTP | HTTPS | 234 | | -------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 235 | | 协议 | 运行在 TCP 之上,明文传输,**客户端与服务器端都无法验证对方的身份** | 身披 SSL( Secure Socket Layer )外壳的 HTTP,运行于 SSL 上,SSL 运行于 TCP 之上, **是添加了加密和认证机制的 HTTP**。 | 236 | | 端口 | 80 | 443 | 237 | | 资源消耗 | 较少 | 由于加解密处理,会消耗更多的 CPU 和内存资源 | 238 | | 开销 | 无需证书 | 需要证书,而证书一般需要向认证机构购买 | 239 | | 加密机制 | 无 | 共享密钥加密和公开密钥加密并用的混合加密机制 | 240 | | 安全性 | 弱 | 由于加密机制,安全性强 | 241 | 242 | ### 常用HTTP状态码 243 | 244 | HTTP状态码表示客户端HTTP请求的返回结果、标识服务器处理是否正常、表明请求出现的错误等。 245 | 246 | 状态码的类别: 247 | 248 | | 类别 | 原因短语 | 249 | | ---- | ------------------------------------------------------ | 250 | | 1XX | Informational(信息性状态码) 接受的请求正在处理 | 251 | | 2XX | Success(成功状态码) 请求正常处理完毕 | 252 | | 3XX | Redirection(重定向状态码) 需要进行附加操作以完成请求 | 253 | | 4XX | Client Error(客户端错误状态码) 服务器无法处理请求 | 254 | | 5XX | Server Error(服务器错误状态码) 服务器处理请求出错 | 255 | 256 | 常用HTTP状态码: 257 | 258 | | 2XX | 成功(这系列表明请求被正常处理了) | 259 | | ---- | ------------------------------------------------------ | 260 | | 200 | OK,表示从客户端发来的请求在服务器端被正确处理 | 261 | | 204 | No content,表示请求成功,但响应报文不含实体的主体部分 | 262 | | 206 | Partial Content,进行范围请求成功 | 263 | 264 | | 3XX | 重定向(表明浏览器要执行特殊处理) | 265 | | ---- | ------------------------------------------------------------ | 266 | | 301 | moved permanently,永久性重定向,表示资源已被分配了新的 URL | 267 | | 302 | found,临时性重定向,表示资源临时被分配了新的 URL | 268 | | 303 | see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源(对于301/302/303响应,几乎所有浏览器都会删除报文主体并自动用GET重新请求) | 269 | | 304 | not modified,表示服务器允许访问资源,但请求未满足条件的情况(与重定向无关) | 270 | | 307 | temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求 | 271 | 272 | | 4XX | 客户端错误 | 273 | | ---- | ------------------------------------------------------------ | 274 | | 400 | bad request,请求报文存在语法错误 | 275 | | 401 | unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息 | 276 | | 403 | forbidden,表示对请求资源的访问被服务器拒绝,可在实体主体部分返回原因描述 | 277 | | 404 | not found,表示在服务器上没有找到请求的资源 | 278 | 279 | | 5XX | 服务器错误 | 280 | | ---- | ------------------------------------------------------------ | 281 | | 500 | internal sever error,表示服务器端在执行请求时发生了错误 | 282 | | 501 | Not Implemented,表示服务器不支持当前请求所需要的某个功能 | 283 | | 503 | service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求 | 284 | 285 | ### GET和POST区别 286 | 287 | 说道GET和POST,就不得不提HTTP协议,因为浏览器和服务器的交互是通过HTTP协议执行的,而GET和POST也是HTTP协议中的两种方法。 288 | 289 | HTTP全称为Hyper Text Transfer Protocol,中文翻译为超文本传输协议,目的是保证浏览器与服务器之间的通信。HTTP的工作方式是客户端与服务器之间的请求-应答协议。 290 | 291 | HTTP协议中定义了浏览器和服务器进行交互的不同方法,基本方法有4种,分别是GET,POST,PUT,DELETE。这四种方法可以理解为,对服务器资源的查,改,增,删。 292 | 293 | - GET:从服务器上获取数据,也就是所谓的查,仅仅是获取服务器资源,不进行修改。 294 | - POST:向服务器提交数据,这就涉及到了数据的更新,也就是更改服务器的数据。 295 | - PUT:英文含义是放置,也就是向服务器新添加数据,就是所谓的增。 296 | - DELETE:从字面意思也能看出,这种方式就是删除服务器数据的过程。 297 | 298 | **GET和POST区别** 299 | 300 | 1. Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。 但是这种做法也不时绝对的,大部分人的做法也是按照上面的说法来的,但是也可以在get请求加上 request body,给 post请求带上 URL 参数。 301 | 302 | 2. Get请求提交的url中的数据最多只能是2048字节,这个限制是浏览器或者服务器给添加的,http协议并没有对url长度进行限制,目的是为了保证服务器和浏览器能够正常运行,防止有人恶意发送请求。Post请求则没有大小限制。 303 | 304 | 3. Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。 305 | 306 | 4. Get执行效率却比Post方法好。Get是form提交的默认方法。 307 | 308 | 5. GET产生一个TCP数据包;POST产生两个TCP数据包。 309 | 310 | 对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据); 311 | 312 | 而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。 313 | 314 | ### 什么是对称加密与非对称加密 315 | 316 | 对称密钥加密是指加密和解密使用同一个密钥的方式,**这种方式存在的最大问题就是密钥发送问题,即如何安全地将密钥发给对方;** 317 | 318 | 而非对称加密是指使用一对非对称密钥,即公钥和私钥,公钥可以随意发布,但私钥只有自己知道。发送密文的一方使用对方的公钥进行加密处理,对方接收到加密信息后,使用自己的私钥进行解密。 319 | 由于非对称加密的方式不需要发送用来解密的私钥,所以可以保证安全性;但是和对称加密比起来,非常的慢 320 | 321 | ### 什么是HTTP2 322 | 323 | HTTP2 可以提高了网页的性能。 324 | 325 | 在 HTTP1 中浏览器限制了同一个域名下的请求数量(Chrome 下一般是六个),当在请求很多资源的时候,由于队头阻塞当浏览器达到最大请求数量时,剩余的资源需等待当前的六个请求完成后才能发起请求。 326 | 327 | HTTP2 中引入了多路复用的技术,这个技术可以只通过一个 TCP 连接就可以传输所有的请求数据。多路复用可以绕过浏览器限制同一个域名下的请求数量的问题,进而提高了网页的性能。 328 | 329 | ### Session、Cookie和Token的主要区别 330 | 331 | HTTP协议本身是无状态的。什么是无状态呢,即服务器无法判断用户身份。 332 | 333 | **什么是cookie** 334 | 335 | cookie是由Web服务器保存在用户浏览器上的小文件(key-value格式),包含用户相关的信息。客户端向服务器发起请求,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户身份。 336 | 337 | **什么是session** 338 | 339 | session是依赖Cookie实现的。session是服务器端对象 340 | 341 | session 是浏览器和服务器会话过程中,服务器分配的一块储存空间。服务器默认为浏览器在cookie中设置 sessionid,浏览器在向服务器请求过程中传输 cookie 包含 sessionid ,服务器根据 sessionid 获取出会话中存储的信息,然后确定会话的身份信息。 342 | 343 | **cookie与session区别** 344 | 345 | - 存储位置与安全性:cookie数据存放在客户端上,安全性较差,session数据放在服务器上,安全性相对更高; 346 | - 存储空间:单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,session无此限制 347 | - 占用服务器资源:session一定时间内保存在服务器上,当访问增多,占用服务器性能,考虑到服务器性能方面,应当使用cookie。 348 | 349 | **什么是Token** 350 | 351 | Token的引入:Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。 352 | 353 | Token的定义:Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。 354 | 355 | 使用Token的目的:Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。 356 | 357 | Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位 358 | 359 | **session与token区别** 360 | 361 | - session机制存在服务器压力增大,CSRF跨站伪造请求攻击,扩展性不强等问题; 362 | - session存储在服务器端,token存储在客户端 363 | - token提供认证和授权功能,作为身份认证,token安全性比session好; 364 | - session这种会话存储方式方式只适用于客户端代码和服务端代码运行在同一台服务器上,token适用于项目级的前后端分离(前后端代码运行在不同的服务器下) 365 | 366 | ### Servlet是线程安全的吗 367 | 368 | 369 | 370 | ### Servlet接口中有哪些方法及Servlet生命周期探秘 371 | 372 | 373 | 374 | ### 如果客户端禁止 cookie 能实现 session 还能用吗? 375 | 376 | 377 | 378 | -------------------------------------------------------------------------------- /Java核心面试知识集—设计模式.md: -------------------------------------------------------------------------------- 1 | # Contanct Me 2 | 3 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取 4 | 5 | >本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了 6 | > 7 | >> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 8 | 9 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 10 | 11 | ## QQ群 12 | 13 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 14 | 15 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | 20 | >PS: 21 | > 22 | >>平常很忙,找miffy小姐姐领取就好了,免费获取的! 23 | 24 | ![image.png](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 25 | 26 | # 设计模式知识点笔记汇总 27 | 28 | ## 1.单例模式(Singleton Pattern) 29 | 30 | ## 2.工厂模式 31 | 32 | ## 3.抽象工厂模式(Abstract Factory Pattern) 33 | 34 | ## 4.模板方法模式(Template Method Pattern) 35 | 36 | ## 5.建造者模式(Builder Pattern) 37 | 38 | ## 6.代理模式(Proxy Pattern) 39 | 40 | ## 7.原型模式(Prototype Pattern) 41 | 42 | ## 8.中介者模式 43 | 44 | ## 9.命令模式 45 | 46 | ## 10.责任链模式 47 | 48 | ## 11.装饰模式(Decorator Pattern) 49 | 50 | ## 12.策略模式(Strategy Pattern) 51 | 52 | ## 13.适配器模式(Adapter Pattern) 53 | 54 | 这一块后面会更新的,有已经整理的解析在这份【Java核心面试知识集】知识点笔记文档里了,**有需要的朋友可以加交流Q群:578486082 管理员处免费领取!** 55 | 56 | ![](https://upload-images.jianshu.io/upload_images/11474088-a6f003ad297d6e6d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 57 | 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java-mianshi-note 2 | 【一线互联网大厂Java核心面试题库】Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等... 3 | 4 | 2020年初开始,陆陆续续一直在收集整理高频面试点,于是乎就有了这份【Java核心面试知识集】。废这么大心思的初衷也是为了希望给准备面试或者学习的你提供一丁点的帮助,至少能节约一些时间再去搜集资料复习或学习。 5 | 6 | 在此之前有分享过这份知识点笔记的初稿,现在又对知识点笔记进行了一定的优化。于是有了现在的V2.0版本的面试手册。当然除了在线版还有本地文档版本 7 | 8 | > 部分内容收集整理于网络,在此也再次感谢所有内容产出者的贡献! 9 | > 10 | > > **如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料、面试资料,进阶、架构资料,都可以加上[【QQ群】](https://jq.qq.com/?_wv=1027&k=oE5kCnMu)领取。祝愿每一位有追求的Java开发同胞都能进大厂拿高薪!** 11 | 12 | 13 | 14 | ## QQ群 15 | 16 | > 本群由我创立,目前已将群主权限交由合作方便于进行日常管理,介意的朋友们在GitHub上看最新版就好了> 这份笔记资料是会免费提供的,特地向你们保证…毕竟还是要恰饭的嘛… 17 | 18 | 祝愿每一位有追求的Java开发同胞都能进大厂拿高薪! 19 | 20 | Java架构交流QQ群:**578486082** (备注一下GitHub,免得被认成打无良广告的) 21 | 22 | 快捷加群方式:[点击此处加入群聊:java高级程序猿①](https://jq.qq.com/?_wv=1027&k=oE5kCnMu) 23 | 24 | ![image](https://upload-images.jianshu.io/upload_images/24613101-931262091ba7ed2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 25 | 26 | > PS: 27 | > 28 | > > 平常很忙,找miffy小姐姐领取就好了,免费获取的! 29 | 30 | ![image](https://upload-images.jianshu.io/upload_images/24613101-4b0507ab7ef34106.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 31 | 32 | 33 | 34 | # Java-review-Gudie 35 | 36 | 【Java核心面试知识集】:Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等。包含了作为一个Java工程师在面试中需要用到或者可能用到的绝大部分知识。欢迎大家阅读,持续更新中… 37 | 38 | | 序号 | 内容 | 链接地址 | 39 | | ---- | ------------------ | ------------------------------------------------------------ | 40 | | 1 | **Java基础** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—Java基础知识面试题.md) | 41 | | 2 | **Java集合容器** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—Java集合容器面试题.md) | 42 | | 3 | **Java异常** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—Java异常面试题.md) | 43 | | 4 | **并发编程** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—Java并发编程面试题.md) | 44 | | 5 | **JVM** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—JVM面试题.md) | 45 | | 6 | **Spring** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—Spring面试题.md) | 46 | | 7 | **Spring MVC** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—SpringMVC面试题.md) | 47 | | 8 | **Spring Boot** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—SpringBoot面试题.md) | 48 | | 9 | **Spring Cloud** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—Spring%20Cloud面试题.md) | 49 | | 10 | **MyBatis** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—MyBatis面试题.md) | 50 | | 11 | **Redis** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—Redis面试题.md) | 51 | | 12 | **MySQL** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—MySQL面试题.md) | 52 | | 13 | **RabbitMQ** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—RabbitMQ面试题.md) | 53 | | 14 | **Dubbo** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—Dubbo面试题.md) | 54 | | 15 | **Linux** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—Linux面试题.md) | 55 | | 16 | **Tomcat** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—Tomcat面试题.md) | 56 | | 17 | **ZooKeeper** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—zookeeper面试题.md) | 57 | | 18 | **Netty** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—Netty面试题.md) | 58 | | 19 | **架构设计** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—架构设计%26分布式%26数据结构与算法面试题.md) | 59 | | 20 | **大厂数据库** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—大厂数据库面试题.md) | 60 | | 21 | **设计模式** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—设计模式.md) | 61 | | 22 | **计算机网络基础** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—计算机网络基础.md) | 62 | | 23 | **常见面试算法题** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—常见面试算法题.md) | 63 | | 24 | **Kafka** | [:mag:点击直达](https://github.com/minfei-miffy/Java-mianshi-note/blob/master/Java核心面试知识集—Kafka面试题.md) | 64 | | 25 | **完整离线版** | [:mag:点击直达](#完整离线版) | 65 | 66 | # 完整离线版 67 | 68 | ![](https://upload-images.jianshu.io/upload_images/11474088-47be2144bb66cd11.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 69 | 70 | ------ 71 | 72 | --------------------------------------------------------------------------------