回馈社区,第100篇,纪念秋招!
本篇笔记为笔者备战暑期实习生和秋招期间做的知识点总结与汇总。在 Java 大后端学习过程中有很多易难记的知识点,本篇笔记就是用来记录和区分这些知识点的;
本篇笔记面向的读者是准备面试的同学,问题源于平时积累与一些真实面试问题,因此不可能面面俱到,如有错误,欢迎指正!
如果您的时间足够,建议以本篇笔记的目录为索引,在网上找一些课程或书籍打好基础。打好基础很重要!打好基础很重要!打好基础很重要!如果您的时间不足,建议先阅读《Guide 哥的 Java 面试突击》里面对一些常见的高频面试问题做了很好的汇总;然后自己做笔记或参照这篇,对一些难记、易混淆的知识点做进一步巩固记忆;
最后,祝大家都能如愿收获 Offer!
比较项 | String | StringBuffer | StringBuilder |
---|---|---|---|
安全 | final,线程安全 | 同步锁 | 非线程安全,性能较高 |
适用 | 少量数据 | 多线程,字符串缓冲区 | 单线程,字符串缓冲区 |
父类 | Object | AbstractStringBuilder | 同 AbstractStringBuilder |
比较项 | 接口 | 抽象类 |
---|---|---|
修饰符 | static、final | 没要求 |
访问修饰符 | 方法默认 public | 不能用 private |
设计角度 | 行为规范 | 模板设计 |
比较项 | 成员变量 | 局部变量 |
---|---|---|
归属 | 类 | 方法 |
修饰符语法 | 无限制 | 不能有访问修饰符与 static |
存储方式 | static 修饰类->方法区,修饰实例->堆 | 基本类型->栈,引用类型->堆内存的引用或指向常量池的地址 |
生命周期 | 对象 | 方法调用 |
赋初值 | 默认值 | 不会自动赋值 |
== | equals |
---|---|
基本类型比较值,引用类型比较地址 | 未覆盖时比较地址,覆盖按自定义 |
修饰 | 说明 |
---|---|
变量 | (一般静态变量 final + static)基本类型值不变。引用类型指向不变 |
类 | 不能继承。方法默认 final |
方法 | 不能修改。早期转为内嵌调用 |
比较项 | 重载 | 重写 |
---|---|---|
通俗来说 | 根据输入数据的不同,做出不同的处理 | 覆盖父类方法 |
发生范围 | 同一个类 | 子类 |
时期 | 编译期 | 运行期 |
返回值、异常 | 可改 | 相同或更小(子类) |
方法名 | 同 | 同 |
参数 | 必改 | 不可改 |
不想被序列化:transient
关键字。序列化时阻止,反序列化时不恢复;
IO 流:InputStream 字节输入流。OutputStream 字节输出流。Reader 字符输入流。Writer 字符输出流;
继承调用优先级:this.show(O)
、super.show(O)
、this.show((super)O)
、super.show((super)O)
;
反射:获取类对象:对象.getClass
[已有对象]、类.class
[需要导包]、Class.forName(类路径)
[常用]
protect:在同一个包下、存在继承关系时可以访问;
this.show(O)
、super.show(O)
、this.show((super)O)
、super.show((super)O)
对象.getClass
[已有对象]、类.class
[需要导包]、Class.forName(类路径)
[常用];clazz.getConstructors()
[所有公有构造方法]、clazz.getDeclaredConstructors()
[所有的构造方法]、clazz.getConstructor(null)
[获取公有、无参的构造方法]、clazz.getDeclaredConstructor(char.class)
[获取私有构造方法,并调用]clazz.getConstructor().newInstance()
clazz.getFields()
[获取所有公有的字段]、clazz.getDeclaredFields()
[获取所有的字段(包括私有、受保护、默认的)]、clazz.getField("xx")
[获取公有字段xx并调用]、clazz.getDeclaredField("xx")
[获取私有字段xx并调用——追加 .setAccessible(true)
可以暴力反射,解除私有限定]stuClass.getMethods()
clazz.getMethod("main", String[].class)
[第一个参数:方法名称,第二个参数:方法形参的类型];Integer i = Integer.valueOf(10)
;int n = i.intValue()
;共同点:有序、可重复、实现List接口、类似数组;
比较项 | ArrayList | LinkedList | Vector |
---|---|---|---|
线程安全 | 不安全 | 不安全 | 安全 |
底层 | Object[] | JDK1.6 前为循环链表,后为双向链表 | Object[],所有方法同步处理 |
修改操作 | 受元素位置影响 | 不影响 | |
快速随机访问 | 支持,实现 RandomAccess 接口 | 不支持 | |
内存 | 预留空间 | 每个元素空间较大 |
在调用 add()
方法时,会调用 ensureCapacityInternal()
方法扩容,其中形参为最小扩容量minCapacity。当修改后的数组容量大于当前的数组长度
,调用grow()
扩容。有三种情况:
1. 当前数组由默认构造方法
生成,并且第一次添加数据:将数组容量由0扩容为10(最小扩容量为10),后续1.5倍扩容;
2. 当前数组由自定义初始容量构造方法
生成:前4次+1(最小扩容量为1)扩容,后续1.5倍扩容;
3. 扩容量(newCapacity)大于Integer的最大值时:抛OutOfMemoryError异常;
共同点:键值对、Key 不可重复的(无序的)、value 可重复的(无序的);
比较项 | HashMap | HashTable | ConcurrentHashMap | LinkedHashMap | TreeMap |
---|---|---|---|---|---|
线程安全 | 不安全,多线程下数据丢失 | 安全 | 安全 | 不安全 | 不安全 |
安全机制 | JDK1.8 前超过容量 rehash,可能形成环形链表 | synchronized 全局锁 | JDK1.8 前分段锁,后头节点锁(synchronized) | ||
底层 | JDK1.8 前数组+链表,后数组+链表/红黑树 | 数组+链表 | JDK1.8 前分段数组+链表,后数组+链表/红黑树 | 跟 HashMap 类似,不过引入双向链表保证有序 | 红黑树 |
效率 | 较高 | 较低,淘汰 | 较高 | ||
Null key/value | key允许1个,value多个 | 不允许 | 不允许 | 允许 | key不允许,value允许 |
初始大小与扩容(无初始值) | 初始 16,扩容 2 倍 | 初始 11,扩容 2n+1 | |||
初始大小与扩容(有初始值) | 2的幂次方 | 按初始值 |
假设 2 个线程同时进行 rehash,将数组大小由2扩展为4。线程1的 node.val 指向 Key(3),node.next 指向 Key(7)。这时线程2率先完成 rehash,让链表里 Key(7) 指向 Key(3)。这时线程1工作,让 key(3) 指向 Key(7),由此成环;
这里建议结合源码理解;
链表转红黑树:hashcode 碰撞次数与泊松分布有关,hash 发生 8 次碰撞时概率降到了 0.00000006,几乎为不可能事件。所以在一般情况下都是为链表,只有在极端情况下才会使用红黑树提高性能;
红黑树转链表:如果也将该阈值设置于 8,那么当 hash 碰撞在 8 时,会反生链表和红黑树的不停相互激荡转换,白白浪费资源。中间有个差值 7 可以防止链表和树之间的频繁转换;
共同点:不可重复、唯一值、(无序);
比较项 | HashSet | LinkedHashSet | TreeSet |
---|---|---|---|
特点 | 线程不安全的,可以存储 null 值 | 按照添加的顺序遍历 | 按照添加元素的顺序进行遍历,排序的方式有自然排序和定制排序 |
底层 | HashMap | LinkedHashMap | 红黑树 |
注意:在 Java 中没有线程安全的 ConcurrentHashSet,但可以根据 ConcurrentHashMap 创建支持线程安全的 ConcurrentHashSet;
线程:初始、运行、阻塞、等待、超时等待、终止;
进程:创建、就绪、运行、阻塞、结束;
共同点:暂停线程执行;
比较项 | sleep() | wait() |
---|---|---|
锁 | 不释放(synchronized里使用该方法) | 释放 |
作用 | 暂停执行 | 线程间交互 |
苏醒 | 自动 | notify() 或者 notifyAll() |
比较项 | start() | run() |
---|---|---|
含义 | 启动线程进入就绪状态,分配到时间片后开始运行——调用run()方法 | 当成一个 main 线程下的普通方法去执行 |
由谁决定调用 | 线程调度器 | Java虚拟机在运行相应线程时直接调用 |
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
volatile 保证指令不重排:分配内存空间
、初始化对象
、对象引用指向堆内存
;
共同点:保证数据可见性、有序性(指令重排);
比较项 | synchronized | volatile |
---|---|---|
级别 | 早期重量级,现在做了优化 | 轻量级 |
作用 | 方法与代码块 | 变量 |
数据可见性 | 保证 | 保证 |
原子性 | 保证(使用锁操作共享数据) | 不保证 |
解决 | 多线程间访问资源同步性 | 变量在多线程间可见性 |
其他作用 | 同步块里可能指令重排 | 禁止指令重排序(双重校验锁实现对象单例)、不能保证原子性 |
Java虚拟机使用一种Java内存模型来屏蔽硬件与操作系统的差异。JDK1.2 前从共享内存(主存)读取变量。当前把变量保存在本地内存(寄存器),可能一个线程在修改主存,另一个线程修改本地内存,导致数据不一致。可以使用 volatile 保证数据可见性;
实现每一个线程都有自己的专属本地变量。底层是 ThreadLocalMap
类型的变量,通过 set 或 get 方法修改专属本地变量。其中 key 为弱引用
,value 为强引用
。无外部强应用情况下,垃圾回收导致 key 被清理,value 无法被回收,导致内存泄露。解决方法:使用完 ThreadLocal 后调用 remove()
方法;
共同点:都可以作为任务去执行、接口、可编写多线程程序、采用Thread.start()启动线程;
比较项 | Runnable | Callable |
---|---|---|
时间 | JDK1.0 | JDK1.5 |
异常处理 | 不会返回结果或抛出检查异常 | 会 |
需要实现的方法 | run() | call() |
返回值 | 无 | 返回泛型,与Future、FutureTask配合可以用来获取异步执行的结果 |
实例创建 | 需要Thread包装启动 | 先通过FutureTask包装再通过Thread包装执行 |
共同点:可向线程池中提交一个任务、可接受Runnable类型的任务、可从ExecutorService接口中调用;
比较项 | execute() | submit() |
---|---|---|
适用 | 用于提交不需要返回值的任务 | 用于提交需要返回 值的任务 |
方法所在接口 | Executor接口 | ExecutorService接口 |
异常处理 | 异常信息会被打印到控制台 | 需要通过Future.get方法来获取异常 |
返回值-能否判断成功与否 | 无法 | 返回一个 Future 类型的对象 |
构造方法
指定:最小可以同时运行的线程数量、任务队列容量、核心线程数;FixedThreadPool
(固定线程数量的线程池)、SingleThreadExecutor
(只有一个线程的线程池)、CachedThreadPool
(可根据实际情况调整线程数量的线程池);当前同时运行的线程数达到最大线程数量并且队列满。抛出异常、调用自己的线程执行任务、丢弃不处理、丢弃最早未处理任务;
基本类型 | 数组类型 | 引用类型 | 对象的属性修改类型 |
---|---|---|---|
AtomicInteger:整形原子类 | AtomicIntegerArray:整形数组原子类 | AtomicReference:引用类型原子类 | AtomicIntegerFieldUpdater:原子更新整形字段的更新器 |
AtomicLong:长整型原子类 | AtomicLongArray:长整形数组原子类 | AtomicStampedReference:原子更新带有版本号的引用类型 | AtomicLongFieldUpdater:原子更新长整形字段的更新器 |
AtomicBoolean:布尔型原子类 | AtomicReferenceArray:引用类型数组原子类 | AtomicMarkableReference :原子更新带有标记位的引用类型 | AtomicReferenceFieldUpdater:原子更新引用类型字段的更新器 |
用来构建锁和同步器的框架。核心思想:如果被请求的共享资源空闲
,则将当前请求资源的线程设置为有效的工作线程
,并且将共享资源设置为锁定状态
。如果被请求的共享资源被占用
,那么就使用一套线程阻塞等待
以及被唤醒时锁分配
的机制。使用 CLH 队列实现。使用一个 volatile 修饰的 state 共享变量表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。公平锁:先到先得,非公平锁:谁抢到给谁;
使用 CAS 算法,过多线程同时竞争同一个变量,大量线程会竞争失败,处于自旋状态消耗性能。LongAdder 内部维护一个 Cells 数组,把一个变量分解为多个变量,避免大量线程同时失效;
使用Cells数组分解变量、获取资源失败时尝试获取其他原子变量的锁而不是自旋 CAS 重试、Cells 数组占用内存较大-使用惰性加载;
在不使用锁的情况下实现多线程安全,是一种非阻塞算法实现,是一种乐观锁技术。使用三个参数:当前内存值V
、旧的预期值A
、即将更新的值B
,当且仅当预期值A和内存值V相同时,将内存值修改为B;(缺点:循环时间长、开销很大、只能保证一个共享变量的原子操作、存在ABA问题);
3. 运行时数据区:
数据区 | 内容 | 二级内容 |
---|---|---|
程序计数器(私) | 虚拟机字节码指令的地址/Undefined(本地方法) | |
虚拟机栈(私) | 栈帧 | 局部变量表(编译期可知的基本类型,对象引用)、操作数栈、动态链接、方法出口、附加信息 |
本地方法栈(私) | 类似虚拟机栈, Native 方法 | |
堆 | 对象实例和数组 | |
方法区 | JDK8 后为元空间,(运行时常量池) | (对象类型)、类信息、常量、静态变量、即时编译器编译后的代码 |
直接内存 |
4. Java 对象创建:类加载检查、分配内存(碰撞指针与空闲列表)、初始化零值、设置对象头(哈希码,GC 分代年龄,锁状态标志,线程持有的锁)、执行 init 方法;
5. GC Roots:虚拟机栈中引用的对象、方法区中类静态引用的对象、方法区中常量引用的对象;
6. 引用:强引用(不回收)、软引用(空间不足回收)、弱引用(发现就回收)、虚引用(任何时候都可以回收);
7. 方法区回收:废弃常量(没有引用)、无用的类(实例回收、ClassLoader 回收、无反射);
8. 垃圾回收算法:引用计数、标记清除(老年代)、标记整理(老年代)、复制(新生代)、分代收集;
9. 垃圾收集器:Serial(单线程,复制算法)、ParNew(多线程,复制算法)、Parallel Scavenge(吞吐量,多线程,复制)、Serial Old(单线程,标记整理)、Parallnel old(多线程,标记整理)、CMS(最短回收停顿时间,多线程,标记清除)、G1(多线程,标记整理+复制);
10. 必须进行初始化的5种时机:new关键字、反射、父类还没初始化、主启动类、静态方法句柄对应的类没有初始化;
11. 类加载过程:加载(class文件读入内存)、连接(验证-魔数,准备-static分配变量,解析-常量池的符号引用替换为直接引用)、初始化(init赋值)、使用、卸载;
12. 几种类加载器:启动类加载器(加载 Java 的核心类,C++实现)、扩展类加载器、系统类加载器、自定义类加载器;
13. 确定被调用的方法:解析符号引用(静态解析-编译期,动态解析-运行期)、分派确定目标方法(静态分派-重载,动态分派-重写);
OSI 七层:物理层、数据链路层、网络层、运输层、会话层、表示层、应用层;
TCP/IP:网络接口层、网际层、运输层、应用层;
五层协议:物理层(IEEE 802.)、数据链路层(ARP,RARP,PPTP)、网络层(ip,icmp)、运输层(TCP,UDP)、应用层(DNS,HTTP,HTTP);
TCP 协议保证可靠传输:数据分块排序、校验和、流量控制(发送窗口)、拥塞控制(拥塞窗口+拥塞算法)、ARQ协议(停止等待ARQ协议-分组,连续ARQ协议-窗口)、超时重传;
拥塞算法:慢开始 、 拥塞避免 、快重传与快恢复;
URl-过程:1.解析域名
(浏览器缓存,操作系统缓存-hosts文件,(开始递归查询),本地域名服务器,根域名服务器,主域名服务器,网站注册的域名服务器)、2.TCP连接
(三次握手)、3.发送HTTP请求
(发送Cookies)、4.服务器处理请求
(Controller,Service,Mapper)、5.服务器返回HTML页面
(80端口,response,信息头,html)、6.浏览器显示HTML
(html-dom树-dom元素与属性-根是docunment对象,css,js)、7.连接结束
;
类型 | 原因短语 | |
---|---|---|
1XX | 信息性状态码 | 接收的请求正在处理 |
2XX | 成功状态码 | 请求正常处理 |
3XX | 重定向状态码 | 需要其他附加操作完成请求 |
4XX | 客户端错误状态码 | 服务器无法处理请求 |
5XX | 服务器错误状态码 | 服务器处理请求错误 |
比较项 | HTTP/1.0 | HTTP/1.1 |
---|---|---|
长连接 | 短连接 | 长连接 |
错误状态响应码 | 增加24个 | |
缓存处理 | 使用header里的If-Modified-Since,Expires来做为缓存判断的标准 | 引入更多标准 |
带宽优化及网络连接的使用 | 存在带宽浪费 | 引入range头域,方便了开发者自由的选择 |
HTTPS 建立过程:(SSL 建立)
一次握手
:
二次握手
:
三次握手
:
会话密钥
,使用证书的公钥加密会话密钥
。传输会话密钥;四次握手
:
会话密钥
,与客户端相同;会话密钥
加密一条信息,服务端能接收说明 SSL 层建立成功;cilent 端解析证书:
解决问题:
HTTP | HTTPS |
---|---|
无状态、无连接、基于请求和响应、通信使用明文 | 内容加密、验证身份、保护数据完整性、混合加密、数字摘要、数字签名技术 |
使用原因:HTTP 协议是一种无状态的协议;
关系:可以理解成 Cookie 是的 Session 的 Id;
相同点:用来跟踪浏览器用户身份的会话方式;
服务端如何下发 Cookie:服务器第一次接收到请求时,开辟了一块 Session 空间(创建了 Session 对象),同时生成一个 Session id,通过响应头的 Set-Cookie:“JSESSIONID=XXXXXXX” 命令,向客户端发送要求设置 Cookie 的响应。客户端收到响应后,在本机客户端设置了一个 JSESSIONID=XXXXXXX 的 Cookie 信息,该 Cookie 的过期时间为浏览器会话结束;
比较项 | cookie | session |
---|---|---|
所在端 | 客户端 | 服务端 |
原理 | 户端访问服务器,服务器会生成一份 Cookie 传输给客户端,客户端保存 Cookie,以后每次访问服务器带上 Cookie | 客户端再次访问服务器只需要从该 Session 中查找该客户的状态 |
特点 | 简单易懂 | 安全 |
缺点 | 容易被篡改、保存的数据量有限 | 占用服务器性能 |
1. 进程通信方式:匿名管道、有名管道、信号、消息队列、信号量、共享内存、套接字;
2. 线程间同步方式:互斥量、信号量、事件;
3. 进程调度算法:先到先服务(FCFS)、短作业优先(SJF)、时间片轮转、多级反馈队列调度、优先级;
4. 死锁条件:互斥条件、请求与保持条件、不剥夺条件、循环等待条件;
5. 内存管理机制:块式管理、页式管理、段式管理、段页式管理机制;
6. 快表与多级页表:虚拟地址到物理地址的转换要快。解决虚拟地址空间大,页表也会很大的问题;
7. 虚拟内存技术实现:请求分页存储管理、请求分段存储管理、请求段页式存储管理;
8. 页面置换算法:OPT 最佳页面置换算法、FIFO 先进先出页面置换算法、LRU 最近最久未使用页面置换算法、LFU 最少使用页面置换算法;
9. 线程状态:初始、运行、阻塞、等待、超时等待、终止;
10. 进程状态:创建、就绪、运行、阻塞、结束;
11. 系统调用:用户态
(user mode) : 用户态运行的进程可以直接读取用户程序的数据。系统态
(kernel mode):系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制(设备管理、文件管理、进程管理、内存管理);
深度优先搜索(DFS):
广度优先搜索(BFS):
比较项 | MyISAM | InnoDB |
---|---|---|
版本 | 5.5之前 | 5.5之后 |
锁级别 | 表级锁(共享锁、排他锁) | 表级锁、行级锁(共享锁、排他锁) |
事务 | 不支持 | 支持(通过 redo log) |
崩溃后安全恢复 | 有崩溃修复能力 | |
外键 | 不支持 | 支持 |
MVCC | 不支持 | 支持 |
索引 | 非聚簇索引。索引与数据分离(不支持hash索引) | 聚簇索引。数据文件本身就是索引 |
存储结构 | 三个文件(表格定义、数据文件、索引文件) | 数据文件 |
数据检索 | 叶节点的data域存放的是数据记录的地址。需要根据地址读取数据 | 叶节点data域保存了完整的数据记录 |
存储顺序 | 按记录插入顺序保存 | 按主键大小有序插入 |
默认隔离级别 | 可重复读(使用 Next-Key Lock 锁算法可避免幻读) | |
应用范围 | SELECT密集型 | INSERT和UPDATE密集型 |
插入缓冲
(要插入的索引页不在内存中时放进 change buffer)、二次写
(两次操作 redo log 表)、自适应哈希索引
(自动根据访问频率和模式建立 hash 索引)、预读
(异步将磁盘页读取到缓存池中);
MVCC | |
---|---|
是什么 | 多版本并发控制 |
目的 | 为了实现读-写冲突时不加锁的并发控制,是一个抽象概念 |
实现方式 | 快照读 (当前读-读取时加悲观锁) |
实现原理 | 依赖3个隐式字段 (最近修改事务ID,回滚指针,隐式自增ID)+undo日志 (链表,记录历史数据)+read view (做可见性判断,记录某个事务快照读时其他事务活跃状态) |
具体过程 | 事务A对某行数据执行快照读,数据库对该行数据生成read view读视图,其中有 up_limit_id (活跃事务ID列表里最小值)、活跃事务 ID 列表 、low_limit_id (当前尚未分配的下一个事务ID),判断最近修改事务ID是否在read view读视图范围内,不在时说明符合可见性,可以并发修改 |
应用 | MVCC (解决读写冲突)+乐/悲观锁 (解决写写冲突)提高并发性能 |
对比项 | 共享锁 | 排它锁 |
---|---|---|
说明 | 用户要进行数据的读 取时,对数据加上共享锁 |
用户要进行数据的写 入时,对数据加上排他锁 |
比较项 | 乐观锁 | 悲观锁 |
---|---|---|
说明 | 假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性(修改数据时上锁) | 假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作(查询完数据就锁起来) |
适用 | 多读 场景 |
多写 场景 |
原子性、一致性(事务前后数据一致)、隔离性(事务不被其他事务干扰)、持久性(事务对数据库修改是持久的);
并发问题:脏读(读取到未提交的事务)、丢失修改(修改了未提交的事务)、不可重复读(读取到已修改的数据)、幻读(读取到已增删的数据);
隔离级别:读未提交、读已提交(解决脏读)、可重复读(默认-解决脏读、不可重复读)、可串行化(全解决);
对数据库中的两张或两张以上表进行连接操作。分为内连接、外连接(左外连接、右外连接、全外连接)
限定查询数据范围、加缓存redis、读/写分离、垂直拆分(缺点是出现冗余)、水平拆分(缺点是可能造成分布式事务)、引擎(MyISAM适合SELECT密集型的,InnoDB适合INSERT和UPDATE密集型的表);
事务支持(分布式事务)、跨库join、跨节点的count,order by,group by以及聚合函数问题、数据迁移,容量规划,扩容等问题、ID问题(雪花算法);
序号 | 原因 | 解决 |
---|---|---|
1 | 加载了额外的数据列 | 使用limit |
2 | 索引没有命中 | 重写SQL,避免在 where 子句使用 !=、<>、or操作符 |
3 | 数据量太大 | 大表优化 |
4 | 总是返回全部列 | 避免使用SELECT * |
5 | 重复查询相同的数据 | 加缓存redis |
6 | 存在锁竞争 | 执行单个查询 |
7 | 统计所有列数使用count(*)-忽略所有的列 |
where、order by(没有时:分批从硬盘读取数据后排序合并,有时:索引本身有序)、join(对 on 涉及的字段建立索引);
主键索引、唯一索引、普通索引、全文索引;
最左前缀匹配原则(MySQL匹配到范围查询就停止匹配)、频繁查的字段建立索引、更新频繁的字段不建立索引、区分度低的数据不建立索引、有外键的数据列必须建立索引、重复值多的列不建立索引、text/image/bit类型的列不建立索引;
PRIMARY KEY(主键索引)、UNIQUE(唯一索引) 、INDEX(普通索引)、FULLTEXT(全文索引) 、多列索引 ;
联合索引 | |
---|---|
含义 | 对表上的两个或两个以上的列字段进行索引 |
形式 | index (a,b,c),可以支持 a | a,b | a,b,c 3种组合进行查找(最左前缀原则) |
创建语法 | create index 索引名 on 表名 (字段名1,字段名2,…) |
删除索引 | alter table 表名 drop index 索引名 |
查询:连接器(权限校验)、(查询缓存)、分析器(词法、语法分析)、优化器、执行器、引擎;
更新:连接器(权限校验)、(查询缓存)、分析器(词法、语法分析)、执行器、引擎、redo log prepare
(重做日志)、binlog
(归档日志)、redo log commit
;
一开始 MySQL没有使用 InnoDB(redo log 是 InnoDB 引擎特有的,用来支持事务);
先 redo log 直接提交,然后写 binlog:完 redo log 后,机器挂了,binlog 日志没有被写入。机器重启时会通过 redo log 恢复数据。导致主从数据库数据不一致;
先 binlog,后 redo log:写完 binlog 后,机器重启,由于没有 redo log,无法恢复数据;
statement(记录每条会修改数据的sql,节省IO提高性能)、row(记录每行改动,性能较差)、mixed(折中方案)
比较项 | redo log | undo log | binlog | error log | slow query log | general log | relay log |
---|---|---|---|---|---|---|---|
中文名 | 重做日志 | 回滚日志 | 归档日志 | 错误日志 | 慢查询日志 | 普通查询日志 | 中继日志 |
记录内容 | 记录了xxx页做了xx修改的信息 | 发生修改数据的补偿sql(相反操作,比如删除与添加) | 记录MySQL中增删改时的记录日志 | MySQL在启动、关闭或者运行过程中的错误信息 | 记录执行时间超过指定阈值的SQL语句 | 客户端连接信息以及执行的SQL语句信息 | |
使用目的 | 用于宕机后数据恢复 | 用来回滚到某一个版本 | 进行主从复制,以及数据库的恢复 | 获取到错误日志的位置等信息 | 找出慢的原因 | ||
储存与格式 | 以块为单位的一种内存格式 | 二进制、三种格式(修改数据的sql、每行改动、折中) | |||||
日志类型 | 物理日志 | 逻辑日志 | 逻辑日志 | ||||
用途 | 事务。更新的数据先放在redo log,然后刷新到磁盘 | 主从复制、提供回滚、MVCC |
事务:开启事务 -> 查询待更新的记录到内存,并加 X 锁 -> 记录 undo log 到内存 buffer -> 记录 redo log 到内存 buffer -> 更改内存中的数据记录 -> 提交事务,触发 redo log 刷盘 -> 记录 bin log -> 事务结束;
崩溃恢复:redo log和binlog有个共有数据字段,XID -> 按顺序扫描 redo log -> 既有prepare、又有commit的redo log直接提交 -> 只有prepare、没有commit的redo log,就拿着XID去binlog找对应的事务,找到就提交,否则回滚;(为什么不使用binlog:需要重做事务,可能涉及多个事务);
列不可再分、属性完全依赖主键、属性不依赖其他非主属性;
数据库在刷新脏页(redolog写满导致数据库全身心执行数据同步,内存不够换页频繁)、拿不到锁(show processlist命令查看);
字段没有索引、数据库选错索引;
比较项 | Record lock | Gap lock | Next-key lock |
---|---|---|---|
锁算法 | 单个行记录上的锁 | 间隙锁,锁定一个范围,不包括记录本身 | record+gap 锁定一个范围,包含记录本身 |
应用 | 查询的索引含有唯一属性时 | 为了阻止多个事务将记录插入到同一范围内 | 行的查询,解决幻读 |
列不可再分、属性完全依赖主键、属性不依赖其他非主属性;
user
权限表(用户账号)、db
权限表(账号对应操作权限)、table_priv
(表的操作权限)、columns_priv
(列的权限)、host
(更细致的一种);
比较项 | B 树 | B+ 树 | hash 索引 |
---|---|---|---|
内部节点 | 键和值 | 键 | \ |
叶子节点 | 键和值 | 键和值,有链连接 | \ |
好处 | 将重复查询是数据靠近根节点,提高效率 | 内部节点内存小,一次读取可以获取更多键,缩小查找范围。通过叶子节点的链遍历数据提高效率。IO次数少 | 等值查询更快(但是不稳定、性能不可预测-hash碰撞) |
缺点 | 不稳定 | 不支持:范围查询(无序)、模糊查询、最左前缀匹配 | |
适用 | 随机检索 | 随机检索、顺序检索 |
与操作系统的磁盘操作相关。我们知道一次磁盘读/写操作需要的时间 = 寻道时间
(操作系统可控) + 延迟时间
+ 传输时间
。延迟时间和传输时间与磁盘转速相关,操作系统不可控。因此我们每一次 IO 的时间大致是确定、稳定的。B+ 树的稳定性体现在所有数据查询的路径长度相同,每一个数据的查询效率相当。IO 时间与 B+ 树查找时间稳定,系统就相对比较稳定;
红黑树特点
:保证深度一致、数据量大时深度大。B+树特点:节点存数据、每层节点多、节点小,层数少。MySQL通过减少磁盘IO提高效率,B+树每层节点多,层数少,节点小,能在较少IO下加载更多key进内存。AVL和红黑树更多是存储在内存,红黑树可能导致树的深度过大造成磁盘IO读写过于频繁;
*补充:操作系统按页读取硬盘中的数据,一次读取一页,如果要读取的数据量超过一页的大小,就会触发多次 IO 操作。因此B+树每层节点不是越多越好;
首先,数据查询需要经过两步:1. 将索引页加载到缓存中,然后从缓存中找到数据页;2. 将数据页数据加载到缓存,然后将数据返回出去;
B+ 树索引降低了 IO 次数:B+ 树的索引页中全部是都是索引,这样一个数据页中能查询到很多索引降低了下一次去磁盘再拿索引页的可能性;(B 树在非叶子节点存储数据了,这样在一个索引页上有数据又有索引,效率肯定低了)
首先我们要知道,B+ 树索引并不能直接找到行,只是找到行所在的页,通过把整页读入内存,再在内存中查找;B+ 树高度一般为 2-4 层,查找记录时最多只需要 2-4 次 IO;
假设现在有个 2 层的 B+ 树索引,首先通过一次 IO 加载非叶子节点
(非叶子节点不存储行数据,存储索引键,其中 data 域里存储的是地址)。在内存中使用二分法查找到数据项,获取 data 域的地址。然后根据地址通过二次 IO 加载叶子节点
(叶子节点存放行数据),在内存中进行二分查找找到数据;
*每个磁盘块大小为 4KB,能存储上百万的数据,3 层的 B+ 树能够表示上百万的数据,因此只需要 3 次 IO 就能在百万级数据中进行查找;
B 树支持随机查找:在单个节点内部支持随机查找;
B+ 树支持随机查找和顺序查找:在单个节点内部支持随机查找。B+ 树的叶子节点有指针相互连接,实现顺序查找(方便范围查询);
超键(唯一标识元组的属性集)、候选键(最小超键)、主键(唯一数据行)、外键(一个表存在另一个表的主键);
not null(非空)、unique(独一)、primary key(主键)、foreign key(外键)、check(控制值范围);
比较项 | int(10) | char(10) | varchar(10) |
---|---|---|---|
含义 | 10位数字长度 | 10位固定字符串 | 10位可变字符串 |
实际长度 | 占32个字节,int型4位 | 不足补空格(空格不算字符) | 不足补空格(空格算字符) |
比较项 | Delet | Truncate | Drop |
---|---|---|---|
类型 | DML | DDL | DDL |
回滚 | 可 | 不可 | 不可 |
删除内容 | 表结构在,删除全部或者一部分数据行 | 表结构在,删除所有数据 | 表结构不在 |
删除速度 | 速度慢,逐行删除 | 速度快 | 速度最快 |
比较项 | Memcached | Redis |
---|---|---|
数据类型 | 只有 key/value | 多种 |
持久化 | 内存 | RDB AOF |
容灾恢复 | 无 | 基于持久化 |
内存溢出 | 报错 | 持久化到硬盘 |
集群 | 无原生集群 | 支持 cluster 模式 |
线程模型 | 多线程非阻塞IO复用 | 单线程IO多路复用 |
高级功能 | 发布订阅模型、Lua 脚本、事务、支持更多的编程语言 | |
过期数据删除 | 惰性删除 | 惰性删除与定期删除 |
SDS、链表、字典、跳跃表、整数集合、压缩列表、对象;
多个 socket、IO 多路复用程序、文件事件分派器、事件处理器;
惰性删除(CPU友好)、定期删除(内存友好);
最近最少未使用(LRU)、即将过期、任意选择、最近最少使用、报错;
持久化
RDB(默认,存数据)、AOF(实时性好、更新频率高,存操作);
MULTI,EXEC,DISCARD 和 WATCH 等命令。不支持原子性,不支持回滚;
问题 | 描述 | 数据库层面解决 | 数据层面解决 | 业务层面解决 |
---|---|---|---|---|
缓存穿透 | 访问一个数据库和缓存都没有的key,导致每次请求都到数据库 | 缓存无效 key / 布隆过滤器(过滤不存在的key) | 前端参数校验 | |
缓存雪崩 | 大量key有相同过期时间,集体失效 / redis 大量宕机 | 集群+哨兵 | 延长数据过期时间 | 限流 |
缓存击穿 | 热点key过期导致短时间内大量访问落到数据库上,压垮数据库 | 数据库加锁,只允许一个客户访问 | 热数据不过期 | 热点限流 |
缓存预热 | 高频数据放缓存(秒杀系统) | 系统上线先加载部分数据 | 定时刷新缓存 | |
缓存降级 | 大量访问导致服务非关键部分不可用,保证关键部分可用 | 返回默认值 |
先更新数据库,然后再删除缓存;
设置主服务器的地址和端口、建立套接字连接、发送 PING 命令、身份验证、发送端口信息、同步、命令传播;
Sentinel 默认每 10 秒向主服务器发送 INFO 命令。Sentinel 默认每 2 秒向被监视的主从服务器发送命令。Sentinel 默认每秒向所有实例发送 PING 命令;
发送 PING 命令判断主观下线、询问其他 Sentinel 判断客观下线、协商选出领头 Sentinel(Raft 算法)、选新主服务器(状态良好、数据完整)、通过 SLAVEOF 复制新主服务器、将下线的主服务器设置为从服务器;
基于内存、非阻塞的IO多路复用机制、单线程避免上下文切换与竞争;
缓存、计数器、会话缓存、查找表(DNS记录)、分布式锁、消息队列(双向链表);
1. Spring 中的 bean 的作用域:singleton(单例)、prototype(多例)、request(一次请求一个)、session(一次请求一个)、global-session(全局);
2. @Component 和 @Bean:
比较项 | @Component | @Bean |
---|---|---|
作用对象 | 类 | 方法 |
加载类型 | 通过类路径扫描 | 在标有该注解的方法中定义 bean |
自定义 | 自定义性更强 |
3. Spring MVC:DispatcherServlet、HandlerMapping、HandlerAdapter(Handler,ModelAndView)、ViewResolver、View;
4. 设计模式:工厂(通过 BeanFactory/ApplicationContex 创建对象)、代理(Spring AOP)、单例(Bean 默认单例)、包装器(动态切换数据源)、观察者(事件驱动模型)、适配器(spring MVC);
5. AOP 代理方式:JDK动态代理(创建接口实现类)、CGLIB动态代理(创建子类的代理对象);
6. AOP 术语:连接点(增强哪些方法)、切入点(实际被真正增强的方法)、通知(增强的类型)、切面(动词,把通知应用到切入点的过程);
7. 事务传播行为:@Transactional。支持当前事务、不支持当前事务、其他;
1. 三个注解:@Configuration、@EnableAutoConfiguration、@ComponentScan;
2. 启动过程:1.始化准备 ApplicationContext
、告知服务启动、2.准备 Environment
、告知 Environment 环境准备好、(打印banner)、3.Environment设置进ApplicationContext
、4.初始化 ApplicationContext
、告知上下文环境准备好、5.加载 Config 配置到 ApplicationContext
、告知上下文环境加载完毕、6.refresh() 方法刷新应用上下文
、告知应用程序启动完毕;
1. 数据:结构化存储、由 Znode(key/value形式)组成、维护 stat 状态信息;
2. 节点类型:持久化节点、临时节点、有序节点、容器节点、TTL 节点;
1. 工作原理:第三方客户端向资源所有者(用户)申请认证请求、资源所有者同意请求返回许可、客户端根据许可向认证服务器申请认证令牌 Token、客户端根据认证令牌向资源服务器申请相关资源;
2. Oauth2.1:不建议隐式授权(授权码模式的简化版本,用户同意授权后直接返回访问令牌)。使用访问令牌时不应该通过 url 传递,而是http header或post body;
META-INF/spring.factories
包下包含 Spring Cloud 自动配置类的全类名;AutoServiceRegistrationAutoConfiguration
服务注册相关的配置类;AutoServiceRegistration
实例;NacosAutoServiceRegistration
间接实现了这个接口(中间隔了个 AbstractAutoServiceRegistration);EventListener
接口,说明 Nacos 通过事件监听机制注册进 Spring Cloud;this.bind ( event )
方法启用事件监听;NacosServiceRegistry.register()
方法进行服务注册;namingService.registerInstance()
完成服务的注册。具体来说会做这几件事:
BeatReactor.addBeatInfo()
创建心跳信息实现健康检测;NamingProxy.registerService()
,最终使用open API 或 SDK 方式发生HTTP请求给Nacos服务器;InstanceController
中使用接口 nacos/v1/ns/instance 接受请求;serviceName
(服务名)和 namespaceId
(命名空间Id);registerInstance
注册实例;ConcurrentHashMap
集合中(也就是 Nacos控制台的服务信息),并加到缓存;namespaceId
、serviceName
获得 Service 实例;subscribe()
方法来实现监听;HostReactor
类,实现服务的动态更新,基本原理是:
UpdateTask
线程,每 10s 发送一次 Pull
请求,获得服务端最新的地址列表;Push
消息给 Nacos 客户端,也就是服务端消费者;HostReactor
中提供的 processServiceJSON
解析消息,并更新本地服务地址列表;主程序类加载
时加载进来;Environment
环境后会使用事件监听机制通知 BootstrapApplicationListener
加载 classpath 路径下查找 META-INF/spring.factories 预定义的配置类。这些配置类是 Spring Cloud 提供的;刷新应用上下文
时会执行一些Spring Cloud非官方的操作,比如从 Nacos 服务器里加载配置文件等;(最终调用的是 NacosPropertySourceLocator.locate() 方法)
ConfigService
对象,按照顺序分别加载共享配置、扩展配置、应用名称对应的配置;NacosConfigService
对象;HttpAgent
、ClientWorker
;两个定时调度的线程池
,其中一个每隔 10s 检查一次配置是否发生变化。另一个用于实现客户端的定时长轮询功能。检查本地配置
,再检查服务端的配置
是否发生改变,发生改变就使用 HttpAgent 调用接口请求变更配置的 id 等信息;HttpResult
,里面有存在数据变更的 Data ID、Group、Tenant;getServerConfig()
调用 HttpAgent 的接口请求去 Nacos 服务器上读取具体的配置内容;ConfigController
类里的 /listener 接口接受请求,然后执行主要两个逻辑:获取客户端请求的变化的配置
,使用 MD5 值校验。和执行长轮询定时机制
;将客户端的长轮询请求封装成 ClientPolling
;ClientLongPolling 线程池
执行长轮询定时机制;allSubs 队列
中。在 29.5s 后拿出来执行任务。校验配置是否发生改变,发生改变则通过 response 返回给客户端;监听机制
实现的;LocalDataChangeEvent 事件
,该事件会被 LongPollingService 监听
;DataChangeTask 任务
,修改服务器上的配置;SentinelWebAutoConfiguration
配置类里有个 FilterRegistrationBean
;CommonFilter
;/*
规则拦截所有的请求,并且将这些请求设置为 Sentinel 的资源
;ProcessorSlot 调用链
进行工作,使用的是责任链模式
,链表元素是一个 Slot 槽
;FlowSlot(流控槽)
、StatisticSlot(统计槽)
、DegradeSlot(熔断槽)
;lookProcessChain()
方法从缓存中获取 slot 调用链,没有就创建一个;xxxSlot.entry()
方法,里面都是调用两个方法(除了统计槽外),checkXxx() 检查规则
和 fireEntry() 调用下一个Slot槽
;请求路径
、是否集群
、阈值类型
(QPS,并发线程数)、流控类型
(直接,关联,链路)、流控效果
(快速失败,排队等待等);流控类型(Strategy)
和 针对来源(limitApp)
用 if-else 的方式给我们实现了三种场景:
通过
;拒绝
;尝试转换状态为半开
;熔断策略
去配置,有:慢比例调用、异常比例、异常数;fireEntry()
方法执行后续槽,再进行统计;增加线程数
和 请求通过数
;ArrayMetric 指标数组
,里面有个 LeapArray 环形数组
, ArrayMetric 指标数组
里的所有方法都是操作 LeapArray 环形数组
的;LeapArray 环形数组
里的元素是 WindowWrap 窗口包装类
,也就是我们说的窗口,包装类包装的是 MetricBucket 指标桶
;MetricBucket 指标桶
统计的是单位时间内的请求成功、失败、异常、响应时长等指标数据,指标数据存储在 LongAdder[]
数组里;LongAdder
对象是 JDK1.8 新增的类,用于在高并发场景下代替 AtomicLong,以用空间换时间的方式降低了 CAS 失败的概率,从而提高性能;Tars是【基于名字服务】【使用Tars协议】的高性能【RPC】开发框架,同时配套一体化的【服务治理平台】,能够快速的以微服务的方式构建自己稳定可靠的分布式应用。Tars 的最大特点是多语言、基于二进制 Tars 协议。它是基于节点的 RPC 框架,其中包括服务节点与公共框架节点:服务节点是业务部分,有一个服务管理节点和若干个业务服务节点构成;公共框架节点实现一些基础功能,包括路由管理、包发布管理、配置中心、远程日志管理、统计管理、异常信息管理等;
术语 | 解释 |
---|---|
TC | 事务协调者 |
TM | 事务管理器 |
RM | 资源管理器(管理DB数据库) |
本地锁 | 可以理解成数据库自身的锁 |
全局锁 | 全局ID,在业务层实现 |
比较项 | 2PC | 3PC | TCC |
---|---|---|---|
含义 | 两阶段协议 | 三阶段协议 | 补偿事务 |
阶段 | 准备、提交/回滚 | 准备(CanCommit)、预提交(PreCommit)、提交/回滚(doCommit) | 准备(try)、提交(Confirm)、补偿(Cancel) |
锁 | 本地锁(数据库的锁) | 本地锁 | 全局ID |
锁释放时机 | 全程占用 | ||
层面 | DB数据库 | DB数据库 | 业务 |
缺点-改动 | 同步阻塞、单点故障问题、数据不一致、没办法超时只能不断重试 | 引入超时机制、预提交阶段 | |
超时响应 | 准备/预提交阶段超时失败,提交阶段超时默认提交 | ||
一阶段 | 准备:TC询问RM,RM响应成功与否,写本地的redo和undo日志 | 准备 | 准备 |
二阶段 | 预提交:TC询问RM,反馈成功就预执行:RM执行事务,将undo和redo信息记录到事务日志中。反之发送中断请求中断事务。 | 提交 | |
三阶段 | 提交/回滚 | 提交/回滚 | 补偿 |
比较项 | 固定窗口算法 | 滑动窗口算法 | 滑动日志算法 | 漏桶算法 | 令牌桶算法 | Redis 分布式限流 |
---|---|---|---|---|---|---|
概念 | 通过一个支持原子操作的计数器来累计 1 秒内的请求次数,当 1 秒内计数达到限流阈值时触发拒绝策略。每过 1 秒,计数器重置为 0 开始重新计数 | 与固定窗口原理类似 | 记录下所有的请求时间点,新请求到来时先判断最近指定时间范围内的请求数量是否超过指定阈值,由此来确定是否达到限流 | 用一个队列以先入先出的方式来进行请求处理 | 桶里放令牌,拿到令牌就处理 | 基于 incr 自增命令。使用 ZSET 有序集合来实现滑动窗口限流 |
优点 | 简单方便 | 窗口滑动的间隔越短,时间窗口的临界突变问题发生的概率也就越小 | 没有突变问题 | 恒定速度,保护系统 | ||
缺点 | 时间窗口的临界突变问题(第 1s 中的后 500 ms 和第 2s 的前 500ms ) | 可能发生时间窗口的临界突变问题 | 占用的内存较多 | 恒定速度,不能同时处理响应 | 需要开定时线程加令牌,有很多个是消耗资源(解决:在每次令牌获取时才进行计算令牌是否足够的) | |
适用范围 | 流量比较均匀时 | 流量比较均匀时 | 流量比较少 | 异步处理,不要求及时响应 | ||
实际应用 | Sentinel(但是是分布式限流) | Google 的 Java 开发工具包 Guava 中的限流工具类 RateLimiter |
比较项 | 窗口算法 | 桶算法 |
---|---|---|
优点 | 简单、直观的得到当前的 QPS 情况 | 速率恒定、 |
缺点 | 有时间窗口的临界突变问题、没有队列缓冲 | 不好统计 QPS、(漏桶)不能应对突发流量、(令牌)启动加载缓慢 |
比较项 | hash 算法 | 一致 hash 算法 | 带虚拟节点的一致 hash 算法 |
---|---|---|---|
概念 | 先取 hash 值,对机器总数取余 | 先取 hash 值,映射到一个环里 | 按比例增加虚拟节点,时 hash 环均匀 |
优点 | 简单 | 解决机器增减问题群 | 解决均匀问题 |
缺点 | 一台机器宕机后不能正确命中 hash,导致缓存雪崩问题 | hash 分布不均匀引起 | 增减节点时可能会有点麻烦 |
适用范围 | 机器数量稳定不变 | 机器数量比较多,能分布均匀 | 机器数量经常改变 |
实际应用 | Java 里用 TreeMap 实现(问题是 TreeMap 不是线程安全) |
签名字符串
、过期时间
。签名字符串
:通过对用户设定的加密字符串、请求路径。过期时间
:通过 MD5 哈希算法取哈希的方式获得Trace Id
(跟踪 ID)唯一标识,Span 标识发起的请求信息,各 Span 通过 Parent id
(跨度 ID)关联起来;