以下目录参考下篇
5. RocketMQ 消息中间件、RabbitMQ、ActiveMQ
【√】5.1 RocketMQ
6. Kafka 大数据量消息中间件、ElasticSearch、ZooKeeper
【√】6.1 Kafka
【√】6.2 ElasticSearch
7. 分布式、研发提效、高并发、线程安全
【×】7.1 分布式与集群
【√】7.2 高并发、线程安全
【×】7.3 研发提效
【√】7.4 设计模式
8. Linux 运维、Tomcat、Nginx、Docker、K8s、Kuboard
【×】8.1 Docker 容器管理
9. Maven 版本管理、Git、SVN
【×】9.1 项目版本管理 Git
10. 安全规约
1. Java 基础
【√】1.1 数据结构:集合 Set Map List Array Tree
Collection
├List
│├LinkedList 链表集合
│├ArrayList 数组集合
│└Vector 线程安全数组,基于虚拟机并发控制 synchronized
│ └Stack 后进先出堆栈
└Set 不允许重复元素
├HashSet
└TreeSet 排序后的 set 按照升序排列
Map
├Hashtable 线程安全集合,基于虚拟机并发控制 synchronized
├HashMap
└WeakHashMap 对键值弱引用,键值不再被外部引用,允许被 GC 回收
任何数据结构的构造或初始化,都应指定大小
集合类 | Key | Value | Super | 说明 |
---|---|---|---|---|
TreeMap | 不允许为 null | 允许为 null | AbstractMap | 线程不安全 |
HashMap | 允许为 null | 允许为 null | AbstractMap | 线程不安全 |
ConcurrentHashMap | 不允许为 null | 不允许为 null | AbstractMap | 线程安全、锁分段技术(JDK8:CAS) |
Hashtable | 不允许为 null | 不允许为 null | Dictionary | 线程安全、重量级锁synchronized |
Map 存储键值对,允许键值都为 null
// HashMap
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
ConcurrentHashMap为什么键值不允许为null的二义性问题
当使用containskey(),如果允许键值为空,就会获取到结果为null,但是此时我们不知道值是否真的存在于集合中。在并发场景下,一个线程读取到真实存在的null,移除元素后,然后另一个线程再去读取,无法判断集合是否是包含该对象。
从源码角度,put方法内部,当键值为null直接就会抛异常。
(1)值没有在集合中,所以返回的结果就是 null (空);
(2)值就是 null(空),所以返回的结果就是它原本的 null(空) 值
HashMap为很忙允许键值为null
因为put内部求hash没有用Object.hashCod()方法实现。也没有做判空处理,允许键值为空,但仅允许一个键为null,可允许多个value为null,因为键唯一。
List 底层是数组
Set 不允许元素重复,允许包含值为null的元素,但最多只能有一个null元素
// HashSet
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
Map 的存储实现
Tree 树结构,树包含父节点、左子树、右子树;父节点的数据小于左子树,左子树的数据小于右子树。
// Hash 冲突的解决:开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)、再哈希法、链地址法;此处使用链地址法
// 存储时:
int hash = key.hashCode(); // 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值
int index = hash % Entry[].length;
Entry[index] = value; <br/>
// 取值时:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];
HashMap 死链问题
HashMap JDK1.7
使用 Hash + 数组 + 链表的结构存储键对元素;旧数组采用头插法维护新元素,新链表元素会逆序排序在并发下链存在环的风险,即死链。
HashMap JDK1.8
使用 Hash + 数组 + 链表 + 红黑树的结构存储键值对元素;采用尾插法维护新元素
死链出现的原因:扩容方法,当加入的元素超过一个阈值,会引发数组结构的扩容操作。会新创建一个2倍的数组,将原来的元素添加到新的数据里,但是在解析数组绑定的链表的时候,为了提高效率,会从链表头开始循环,从而导致数据被反向绑定到新数组里
JUC 包中 atomic 包使用底层操作类 sun.misc.Unsafe 基于硬件算法 CAS 实现并发控制;
Unsafe API 主要分类
此处基于 CAS 实现并发控制,具体体现在本地方法 native compareAndSwapInt()
本地方法即 Java 调用非 Java 程序写的方法,本地方法通常为 C 或 C++ 编写
(1)Info相关。主要返回某些低级别的内存信息:addressSize(), pageSize()
(2)Objects相关。主要提供Object和它的域操纵方法:allocateInstance(),objectFieldOffset()
(3)Class相关。主要提供Class和它的静态域操纵方法:staticFieldOffset(),defineClass(),defineAnonymousClass(),ensureClassnitialized()
(4)Arrays相关。数组操纵方法:arrayBaseOffset(),arrayIndexScale()
(5)Synchronization相关。主要提供低级别同步原语(如基于CPU的CAS(Compare-And-Swap)原语):monitorEnter(),tryMonitorEnter(),monitorExit(),compareAndSwapInt(),putOrderedInt()
(6)Memory相关。直接内存访问方法(绕过JVM堆直接操纵本地内存):allocateMemory(),copyMemory(),freeMemory(),getAddress(),getInt(),putInt()
JUC 包中 locks 包使用
提供读写锁 ReenWriteLock、ReentrantLock、ReentrantReadWriteLock、StampedLock
TODO
ReentrantReadWriteLock 在沒有任何读写锁时,才可以取得写入锁。
StampedLock是比ReentrantReadWriteLock更快的一种锁,支持乐观读、悲观读锁和写锁
(1)synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定;
(2)ReentrantLock、ReentrantReadWriteLock,、StampedLock都是对象层面的锁定,要保证锁定一定会被释放,就必须将unLock()放到finally{}中;
(3)StampedLock 对吞吐量有巨大的改进,特别是在读线程越来越多的场景下;
(4)StampedLock有一个复杂的API,对于加锁操作,很容易误用其他方法;
(5)当只有少量竞争者的时候,synchronized是一个很好的通用的锁实现;
(6)当线程增长能够预估,ReentrantLock是一个很好的通用的锁实现;
JUC 中的 ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentSkipListMap、CopyOnWriteArrayList、CountDownLatch、CyclicBarrier、Executors、ForkJoinTask、ForkJoinWorkerThread、FutureTask、LinkedBlockingQueue、ThreadPoolExecutor、Semaphore、ArrayBlockingQueue、CompletableFuture
TODO
ConcurrentHashMap 分段锁存储;
CountDownLatch 闭锁,一组线程等待其他线程执行完成后再执行。CountDownLatch 初始化时会给定一个计数,然后每次调用 countDown() 计数减1。异步变同步。
CyclicBarrier 它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point) 也就是阻塞在调用 cyclicBarrier.await() 的地方。和 CountDownLatch 的区别是,计数允许被重置。
Semaphore 信号量,维护了一个许可集,每次使用时执行 acquire() 从Semaphore 获取许可,如果没有则会阻塞,每次使用完执行 release() 释放许可。Semaphore 对用于对资源的控制,比如数据连接有限,使用 Semaphore 限制访问数据库的线程数。
Exchanger 用于两个线程间的数据交换,它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。两个线程相互等待处理结果并进行数据传递。
ThreadLocal
将线程作为key,将对象作为value;线程变量共享,但是线程作为key,要及时回收变量,否则会内存泄漏;
这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量;每个线程一个实例T,ThreadLocal 变量共享;必须回收自定义的 ThreadLocal 变量,多线程场景下,线程复用,不及时回收存在内存泄漏的风险。
public final class AopContext {
private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal("Current AOP proxy");
private AopContext() {
}
public static Object currentProxy() throws IllegalStateException {
Object proxy = currentProxy.get();
if (proxy == null) {
throw new IllegalStateException("Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
} else {
return proxy;
}
}
@Nullable
static Object setCurrentProxy(@Nullable Object proxy) {
Object old = currentProxy.get();
if (proxy != null) {
currentProxy.set(proxy);
} else {
currentProxy.remove();
}
return old;
}
}
树、图、链表
图和链表都有环,对于链表校验环,即校验后置节点是否在当前节点前置链路出现过即可。
二叉树:拥有一组节点组合而成具有层次关系的数据结构,没有子节点的称为叶子节点。普通的二叉查找树在极端情况下可退化成链表,此时的增删查效率都会比较低下。
满二叉树:所有父节点都有左子树和右子树,叶子节点且都为最底层树。
完全二叉树:前m个节点数据和满二叉树的前m个节点完全一致,称为完全二叉树
平衡多路查找树 B Tree:所有叶子节点到根节点的距离完全一致;该结构用作查询时,能减少查询次数,即索引结构上能减少磁盘 IO 操作的次数
自平衡的二叉查找树 红黑树 B+ Tree 相对于平衡查找树拥有更高的性能。红黑树具有良好的效率,它可在 O(logN) 时间内完成查找、增加、删除等操作
红黑树根据以下性质维持自平衡
节点是红色或黑色。
根节点是黑色。
所有叶子都是黑色(叶子是NIL节点)。
每个红色节点必须有两个黑色的子节点。
从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点(简称黑高)。
红黑树性质顺口溜:一头一脚黑, 黑连红不连。 插入看叔伯, 删除看兄弟
增删查动作下的左旋右旋
3 个一组,左插左旋,右插右旋,中间晋升;
一维列表,分组递归,代表排序,逐级上升;
通过性质发现: 将节点设置为红色在插入时对红黑树造成的影响是小的,而黑色是最大的
将红黑树的节点默认颜色设置为红色,是为尽可能减少在插入新节点对红黑树造成的影响。
一个 m 阶的 B+ 树具有如下几个特征:
有 k 个子树的中间节点包含有 k 个元素(B 树中是 k-1 个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
B+ 树与 B 树最大的不同就是其非叶节点不保存数据,数据都在叶子节点保存,并且叶子节点按照顺序依次相连。
聚簇索引(Clustered Index)中,叶子节点包含完整的记录。而非聚簇索引中,叶子节点仅包含指向完整记录的指针,因此到了叶子节点后还需要做一次磁盘 IO。
【√】1.2 基础算法:排序算法、二分算法、银行家算法、最短路径算法、最少使用算法、一致性哈希算法
排序算法
/* 冒泡排序 */
void BubbleSort(int arr[], int length)
for (int i = 0; i < length; i++)
for (int j = 0; j < length - i - 1; j++)
if (arr[j] > arr[j + 1]) {
int temp;
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
public class MergeSort {
// 归并排序
public static int[] mergeSort(int[] arr, int left, int right) {
// 如果 left == right,表示数组只有一个元素,则不用递归排序
if (left < right) {
// 把大的数组分隔成两个数组
int mid = (left + right) / 2;
// 对左半部分进行排序
arr = mergeSort(arr, left, mid);
// 对右半部分进行排序
arr = mergeSort(arr, mid + 1, right);
//进行合并
merge(arr, left, mid, right);
}
return arr;
}
// 合并函数,把两个有序的数组合并起来
// arr[left..mif]表示一个数组,arr[mid+1 .. right]表示一个数组
private static void merge(int[] arr, int left, int mid, int right) {
//先用一个临时数组把他们合并汇总起来
int[] a = new int[right - left + 1];
int i = left;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= right) {
if (arr[i] < arr[j]) {
a[k++] = arr[i++];
} else {
a[k++] = arr[j++];
}
}
while(i <= mid) a[k++] = arr[i++];
while(j <= right) a[k++] = arr[j++];
// 把临时数组复制到原数组
for (i = 0; i < k; i++) {
arr[left++] = a[i];
}
}
}
二分算法,集合是有序的
思想
令L=1, R=8, mid=(L+R)/2=4
第一步:比较arr[mid]=6>4,说明要在arr[L]….arr[mid-1]中找,R=mid-1=3
第二步:更新mid=(L+R)/2=2,比较arr[mid]=3<4,说明要在arr[mid+1]…arr[R]中找,L=mid+1=3
第三步:更新mid=(L+R)/2=3,比较arr[mid]=4,4==key,查找结束!返回结果为3
#include<iostream>
using namespace std;
const int N=1e6;
int n,key,arr[N];
int binarySearch(int arr[],int n,int x)
{
int l = 1;
int r = n;
int ans = -1;
while(l <= r)
{
int m = (l + r) / 2;
if(arr[m] == x)
{
ans = m;
break;
}
if(arr[m] < x)
l = m + 1;
else
r = m - 1;
}
return ans;
}
int main()
{
cin >> n >> key;
for(int i = 1;i <= n;i++)
cin >> arr[i];
int ans = binarySearch(arr,n,key);
cout << ans;
return 0;
}
银行家算法
当一个进程申请使用资源的时候,银行家算法通过先试探分配给该进程资源,然后通过安全性算法判断分配后的系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待。
最短路径算法
TODO一致性哈希算法
用于Nginx、memcached负载均衡策略、Redis 动态扩容
-
哈希算法 hash(数据)% N
传统hash算法,对 N 台服务器进行取模运算,对应的打到不同的服务器上,对于这种线性结构,不便于扩展,增加或减少一台服务器,都会影响整个数据。新数据走哈希算法后进入的服务器与扩展前的错误错乱了。需要清除原来所有的数据,重新哈希计算,成本很高。 -
一致性哈希算法 hash(数据)% 2^32 相对于哈希算法,支持动态扩容
原理:
哈希环:圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、5、6……直到 2^32-1,也就是说0点左侧的第一个点代表 2^32-1。我们把这个由2的32次方个点组成的圆环称为hash环。
负载均衡:服务器和数据(字符)经过md5散列之后,就是一串随机的字符串,根据哈希算法取模,在虚拟的哈希环上依次按模打在不同的节点上,实现负载均衡。此时,扩展服务器,会出现部分数据失效。有更好的扩展性
一致性哈希就是构建环状结构代替传统哈希线状结构。
整个Hash空间被构建成一个首尾相接的环,使用一致性Hash时需要进行两次映射。
第一次,给每个节点(集群)计算Hash,然后记录它们的Hash值,这就是它们在环上的位置。
第二次,给每个Key计算Hash,然后沿着顺时针的方向找到环上的第一个节点,就是该Key储存对应的集群。
分析一下节点增加和删除时对负载均衡的影响,如下图:
可以看到,当节点被删除时,其余节点在环上的映射不会发生改变,只是原来打在对应节点上的Key现在会转移到顺时针方向的下一个节点上去。增加一个节点也是同样的,最终都只有少部分的Key发生了失效。不过发生节点变动后,整体系统的压力已经不是均衡的了
- hash环的偏斜及虚拟节点
真实服务器存在,hash环的偏斜情况,非理想状态下,真实服务器哈希取模后,可能分布在环上相邻的点;导致大量数据还是会分布在某个节点,少量数据分布在其他节点;
通过引入虚拟节点去支持更好的复杂均衡,一个真实服务器可以复制多个虚拟节点;虚拟节点越多,负载均衡分布效果越好。
【√】1.3 Thread
volatile 线程内存可见性,解决一写多读场景的并发问题;
线程的实现方式
new Thread();
public class Thread implements Runnable {}
Callable 和 Runnable 的区别,在一个task里面,Runnable没有返回值,Callable有返回值且支持抛出异常;
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
【×】1.4 代理、反射、流操作、Netty
java.lang.reflect.* 通过字节码文件 .class 获取实体类的方法及属性。
AOP 就是基于代理实现的
动态代理 | 优点 | 缺点 |
---|---|---|
JDK 动态代理 | 仅支持代理实现了接口的类 | 未实现接口的类不支持 JDK 动态代理 |
CGlib | 支持代理类文件,原理对指定目标类生成一个子类,覆盖其中的方法实现增强 | 通过继承方式,子类增强,所以不能对 final 类进行代理 |
Netty
Netty 是一个高性能、异步事件驱动的 NIO (异步通讯 non-blocking IO)框架,基于 JAVA NIO 提供的 API 实现。
提供了对 TCP、UDP 和文件传输的支持,作为一个异步 NIO 框架,Netty 的所有 IO 操作都是异步非阻塞的,通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。
Java NIO 同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理;
Java BIO 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时,服务端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销;
Java AIO(NIO.2):异步非阻塞,AIO引入异步通道的概念,采用了Proactor模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用;
BIO方式适用于连接数比较小且固定的架构,这种方式对服务器资源要求比较高,并不局限于应用中,JDK1.4以前的唯一选择,但程序简单易理解;
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间通讯等。编程比较复杂,JDK1.4开始支持。
AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
三次握手四次握手
1)序号(sequence number):Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
2)确认号(acknowledgement number):Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
3)标志位(Flags):共6个,即URG、ACK、PSH、RST、SYN、FIN等。具体含义如下:
URG:紧急指针(urgent pointer)有效。
ACK:确认序号有效。
PSH:接收方应该尽快将这个报文交给应用层。
RST:重置连接。
SYN:发起一个新连接。
FIN:释放一个连接。
三次握手
建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认;
服务器收到syn包,必须确认客户端的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手;
四次挥手
Client发送⼀个FIN,⽤来关闭Client到Server的数据传送,Client进⼊FIN_WAIT_1状态;
Server收到FIN后,发送⼀个ACK给Client,确认序号为收到序号+1,Server进⼊CLOSE_WAIT状态;
Server发送⼀个FIN,⽤来关闭Server到Client的数据传送,Server进⼊LAST_ACK状态;
Client收到FIN后,Client进⼊TIME_WAIT状态,接着发送⼀个ACK给Server,确认序号为收到序号+1,Server进⼊CLOSED状态,完成四次挥⼿;
【√】1.5 JVM 原理
生命周期(加载-验证-准备-解析-初始化-使用-卸载)
内存模型(程序计数器、虚拟机栈、本地方法栈、堆、方法区)
JVM类加载机制(双亲委派模型)
垃圾收集机制(标记-清除、标记-整理、复制算法)
类字节码实现机制
JVM调优案例
GC日志详解等
解释与编译共存
Java程序要经过先编译,后解释两个步骤;
由Java编写的程序需要先经过编译步骤,生成字节码(后缀名是.class的文件),这种字节码必须由Java解释器来解释执行。内存模型
堆 | 非堆 | 虚拟机栈 | 本地方法栈 | 程序计数器 | |
---|---|---|---|---|---|
英文名称 | Heap | Non-Heap | JVM Stack | Native Method Stack | Program Counter Register |
说明 | 包含新生代与老年代 | 又称永久代或方法区 | 为 Java 方法执行服务 | 为 Native 方法执行服务 | 字节码执行时的行号计数器 |
线程安全 | 多线程共享 | 多线程共享 | 线程私有 | 线程私有 | 线程私有 |
存储数据 | 对象实例 | 常量、静态变量、已被JVM加载的类 | 栈帧 | 栈帧 | 字节码执行时的行号计数器 |
垃圾回收算法
垃圾回收器
串行回收器:Serial、Serial old
并行回收器:ParNew、Parallel Scavenge、Parallel old、CMS、G1(Garbage First)
Serial,首先串行适合单核,基本上是在JDK1.3之前的版本会默认使用 Serial,通过复制算法、串行回收和"stop-the-World"机制的方式执行内存回收;
ParNew,目前多核都不推荐串行垃圾回收,推荐并行或并发回收;ParNew 就是 Serial 的多线程版本,也是复制算法、"stop-the-World"机制。
Parallel,吞吐量优先
CMS,低延迟
Garbage First,是一个并行收集器,将堆划分为多个区域 Region,G1 GC有计划地避免在整个Java堆中进行全区域的垃圾收集。针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征。年轻代GC Young GC、老年代并发标记过程 Concurrent Marking、混合回收 Mixed GC
如果你想要最小化地使用内存和并行开销,请选Serial GC;
如果你想要最大化应用程序的吞吐量,请选Parallel GC;
如果你想要最小化GC的中断或停顿时间,请选CMs GC。
垃圾回收器 | 内存大小 |
---|---|
Serial+SerianlOld | 十M |
PS+PO | 百M-G |
PN+CMS | 十G |
G1 | 百G |
ZGC | T |
类加载
加载阶段主要是使用类加载器,根据类的全限定名,获取类的二进制字节流;
验证阶段对文本格式、元数据、字节码、符号引用进行验证;
准备阶段在非堆为静态变量分配内存,设置初始值零值;
解析阶段将常量池内的符号引用替换为直接引用;
初始化阶段开始执行字节码,初始化类变量和其他资源,对静态变量进行赋值等。
类加载器
类别 | 程序可直接引用 | 支持加载的类库 |
---|---|---|
启动类加载器 Bootstrap ClassLoader | 否 |
JAVA_HOME\lib 目录下的类库 和参数 -Xbootclasspath 指定路径中的类库
|
扩展类加载器 Extension ClassLoader | 是 |
JAVA_HOME\lib\ext 目录下的类库 和系统变量 java.ext.dirs 指定路径中的类库
|
系统类加载器 Application ClassLoader | 是 | 系统变量 classpath 指定路径中的类库 |
自定义类加载器 | 是 |
双亲委派模型
一个类加载器收到类加载的请求,会把请求委派给父-类加载器去完成,如果父-类加载器没有找到所需的类无法完成加载时,子-类加载器开始尝试类加载。
JVM调优
垃圾回收器配置 -XX:+UseSerialGC
内存配置:垃圾回收频繁时,调整内存大小,新生代老年代占比,老年代年龄大小
调整GC触发时机
调整 JVM本地内存大小 XX:MaxDirectMemorySize
//设置Serial垃圾收集器(新生代)
开启:-XX:+UseSerialGC
//设置PS+PO,新生代使用功能Parallel Scavenge 老年代将会使用Parallel Old收集器
开启 -XX:+UseParallelOldGC
//CMS垃圾收集器(老年代)
开启 -XX:+UseConcMarkSweepGC
//设置G1垃圾收集器
开启 -XX:+UseG1GC
//设置堆初始值
指令1:-Xms2g
指令2:-XX:InitialHeapSize=2048m
//设置堆区最大值
指令1:`-Xmx2g`
指令2: -XX:MaxHeapSize=2048m
//新生代内存配置
指令1:-Xmn512m
指令2:-XX:MaxNewSize=512m
//survivor区和Eden区大小比率
指令:-XX:SurvivorRatio=6 //S区和Eden区占新生代比率为1:6,两个S区2:6
//新生代和老年代的占比
-XX:NewRatio=4 //表示新生代:老年代 = 1:4 即老年代占整个堆的4/5;默认值=2
//进入老年代最小的GC年龄,年轻代对象转换为老年代对象最小年龄值,默认值7
-XX:InitialTenuringThreshol=7
//使用多少比例的老年代后开始CMS收集,默认是68%,如果频繁发生SerialOld卡顿,应该调小
-XX:CMSInitiatingOccupancyFraction
//G1混合垃圾回收周期中要包括的旧区域设置占用率阈值。默认占用率为 65%
-XX:G1MixedGCLiveThresholdPercent=65
// JVM除了堆内存之外还有一块堆外内存,这片内存也叫本地内存,可是这块内存区域不足了并不会主动触发GC,只有在堆内存区域触发的时候顺带会把本地内存回收了,而一旦本地内存分配不足就会直接报OOM异常。
-XX:MaxDirectMemorySize
调优案例
TOP
jmap
jstat -gc
visualVM
后台导出数据引发的OOM
问题描述:公司的后台系统,偶发性的引发OOM异常,堆内存溢出。
1、因为是偶发性的,所以第一次简单的认为就是堆内存不足导致,所以单方面的加大了堆内存从4G调整到8G。
2、但是问题依然没有解决,只能从堆内存信息下手,通过开启了-XX:+HeapDumpOnOutOfMemoryError参数 获得堆内存的dump文件。
3、VisualVM 对 堆dump文件进行分析,通过VisualVM查看到占用内存最大的对象是String对象,本来想跟踪着String对象找到其引用的地方,但dump文件太大,跟踪进去的时候总是卡死,而String对象占用比较多也比较正常,最开始也没有认定就是这里的问题,于是就从线程信息里面找突破点。
4、通过线程进行分析,先找到了几个正在运行的业务线程,然后逐一跟进业务线程看了下代码,发现有个引起我注意的方法,导出订单信息。
5、因为订单信息导出这个方法可能会有几万的数据量,首先要从数据库里面查询出来订单信息,然后把订单信息生成excel,这个过程会产生大量的String对象。
6、为了验证自己的猜想,于是准备登录后台去测试下,结果在测试的过程中发现到处订单的按钮前端居然没有做点击后按钮置灰交互事件,结果按钮可以一直点,因为导出订单数据本来就非常慢,使用的人员可能发现点击后很久后页面都没反应,结果就一直点,结果就大量的请求进入到后台,堆内存产生了大量的订单对象和EXCEL对象,而且方法执行非常慢,导致这一段时间内这些对象都无法被回收,所以最终导致内存溢出。
7、知道了问题就容易解决了,最终没有调整任何JVM参数,只是在前端的导出订单按钮上加上了置灰状态,等后端响应之后按钮才可以进行点击,然后减少了查询订单信息的非必要字段来减少生成对象的体积,然后问题就解决了。
单个缓存数据过大导致的系统CPU飚高
1、系统发布后发现CPU一直飚高到600%,发现这个问题后首先要做的是定位到是哪个应用占用CPU高,通过top 找到了对应的一个java应用占用CPU资源600%。
2、如果是应用的CPU飚高,那么基本上可以定位可能是锁资源竞争,或者是频繁GC造成的。
3、所以准备首先从GC的情况排查,如果GC正常的话再从线程的角度排查,首先使用jstat -gc PID 指令打印出GC的信息,结果得到得到的GC 统计信息有明显的异常,应用在运行了才几分钟的情况下GC的时间就占用了482秒,那么问这很明显就是频繁GC导致的CPU飚高。
4、定位到了是GC的问题,那么下一步就是找到频繁GC的原因了,所以可以从两方面定位了,可能是哪个地方频繁创建对象,或者就是有内存泄露导致内存回收不掉。
5、根据这个思路决定把堆内存信息dump下来看一下,使用jmap -dump 指令把堆内存信息dump下来(堆内存空间大的慎用这个指令否则容易导致会影响应用,因为我们的堆内存空间才2G所以也就没考虑这个问题了)。
6、把堆内存信息dump下来后,就使用visualVM进行离线分析了,首先从占用内存最多的对象中查找,结果排名第三看到一个业务VO占用堆内存约10%的空间,很明显这个对象是有问题的。
7、通过业务对象找到了对应的业务代码,通过代码的分析找到了一个可疑之处,这个业务对象是查看新闻资讯信息生成的对象,由于想提升查询的效率,所以把新闻资讯保存到了redis缓存里面,每次调用资讯接口都是从缓存里面获取。
8、把新闻保存到redis缓存里面这个方式是没有问题的,有问题的是新闻的50000多条数据都是保存在一个key里面,这样就导致每次调用查询新闻接口都会从redis里面把50000多条数据都拿出来,再做筛选分页拿出10条返回给前端。50000多条数据也就意味着会产生50000多个对象,每个对象280个字节左右,50000个对象就有13.3M,这就意味着只要查看一次新闻信息就会产生至少13.3M的对象,那么并发请求量只要到10,那么每秒钟都会产生133M的对象,而这种大对象会被直接分配到老年代,这样的话一个2G大小的老年代内存,只需要几秒就会塞满,从而触发GC。
9、知道了问题所在后那么就容易解决了,问题是因为单个缓存过大造成的,那么只需要把缓存减小就行了,这里只需要把缓存以页的粒度进行缓存就行了,每个key缓存10条作为返回给前端1页的数据,这样的话每次查询新闻信息只会从缓存拿出10条数据,就避免了此问题的 产生。
CPU经常100% 问题定位
问题分析:CPU高一定是某个程序长期占用了CPU资源。
1、所以先需要找出那个进行占用CPU高。
top 列出系统各个进程的资源占用情况。
2、然后根据找到对应进行里哪个线程占用CPU高。
top -Hp 进程ID 列出对应进程里面的线程占用资源情况
3、找到对应线程ID后,再打印出对应线程的堆栈信息
printf “%x\n” PID 把线程ID转换为16进制。
jstack PID 打印出进程的所有线程信息,从打印出来的线程信息中找到上一步转换为16进制的线程ID对应的线程信息。
4、最后根据线程的堆栈信息定位到具体业务方法,从代码逻辑中找到问题所在。
查看是否有线程长时间的watting 或blocked
如果线程长期处于watting状态下, 关注watting on xxxxxx,说明线程在等待这把锁,然后根据锁的地址找到持有锁的线程
数据分析平台系统频繁 Full GC
平台主要对用户在 App 中行为进行定时分析统计,并支持报表导出,使用 CMS GC 算法。
数据分析师在使用中发现系统页面打开经常卡顿,通过 jstat 命令发现系统每次 Young GC 后大约有 10% 的存活对象进入老年代。
原来是因为 Survivor 区空间设置过小,每次 Young GC 后存活对象在 Survivor 区域放不下,提前进入老年代。
通过调大 Survivor 区,使得 Survivor 区可以容纳 Young GC 后存活对象,对象在 Survivor 区经历多次 Young GC 达到年龄阈值才进入老年代。
调整之后每次 Young GC 后进入老年代的存活对象稳定运行时仅几百 Kb,Full GC 频率大大降低。
业务对接网关 OOM
网关主要消费 Kafka 数据,进行数据处理计算然后转发到另外的 Kafka 队列,系统运行几个小时候出现 OOM,重启系统几个小时之后又 OOM。
通过 jmap 导出堆内存,在 eclipse MAT 工具分析才找出原因:代码中将某个业务 Kafka 的 topic 数据进行日志异步打印,该业务数据量较大,大量对象堆积在内存中等待被打印,导致 OOM。
【√】1.6 基础编程规约
import * 本质上是无影响的,因为在生成字节码文件时,class 文件还是只会包含引用的包,其他包不导入;不过依然推荐使用import 具体的包,可读性好
foreach 遍历中强制不使用remove方法,不安全
Map、Iterator 迭代器遍历时不推荐遍历k,推荐遍历元素k-v;keySet本质上遍历两次,先遍历元素,后取key组成集合,推荐entrySet();JDK8 推荐使用 Map.forEach;
ThreadLocal 线程池场景下线程共享,要及时回收remove ThreadLocal变量,存在内存泄漏风险
推荐使用 ThreadPoolExecutor 的方式创建线程池,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险;Executors 返回的线程池对象,允许创建大量的线程或处理大量的求,存在资源浪费资源耗尽的风险
推荐构造器注入,避免空指针异常、避免循环依赖;缺点是代码冗余不易读。setter注入方式按需加载,不是初始化阶段把所有对象注入,不支持final;field注入方式不符合javabean规范,存在空指针异常风险,不支持final修饰。
byte、boolean、char、short、int、float、long、double
2. MySQL 数据库、Oracle、Redis、NoSQL、ShardingSphere
【√】2.1 MySQL
数据库引擎(InnDB 支持事务行锁、MyISAM)
SQL执行原理(解析器、优化器、执行器)
索引底层机制
SQL执行计划分析(explain 分析)
Mysql锁机制(悲观锁:行锁、表锁;乐观锁)
Mysql事务隔离(ACID、隔离级别、传播特性)
SQL优化实践(索引失效、大SQL拆分、file_sort)等
MySQL 数据量瓶颈
单表超出 1000 W推荐分库分表;
分页查询瓶颈,返回总页数或者确定聚簇索引后根据聚簇索引查询,避免查询 offset+n 条数据
推荐 count(*),避免 select *
对于 Mybattis 使用#{}避免 SQL依赖注入,预编译阶段会解析为占位符从而避免 SQL 注入;而 ${} 占位符预编译阶段解析时会将值直接替换,存在 SQL 注入风险。
-- 连续的ID
SELECT a.* FROM 表 1 a where a.id > 100000 limit 20
-- 不连续的ID
SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
数据库引擎
数据库引擎 | Innodb | Myisam | Memory | Merge |
---|---|---|---|---|
事务 | 支持 | 不支持 | ||
全文索引 | 不支持 | 支持 | ||
事务 | 支持 | 不支持 | ||
外键 | 支持 | 不支持 | ||
表锁 | 支持 | 支持 | ||
行锁 | 支持 | 不支持 | ||
优点 | 支持行级锁,事务,并发,内存是 Myisam的2.5倍 | 不支持 | 全表锁 | 一组Myisam表的组合 |
索引
聚簇索引:聚簇索引不是单独的一种索引类型,而是一种数据存储方式。这种存储方式是依靠B+树来实现的,根据表的主键构造一棵B+树且B+树叶子节点存放的都是索引 和 表行记录时,可称主键索引为聚簇索引。聚簇索引也可理解为将数据与索引存放在一起,找到了索引也就找到了数据。(聚簇索引中,叶子节点包含完整的记录。而非聚簇索引中,叶子节点仅包含指向完整记录的指针,因此到了叶子节点后还需要做一次磁盘 IO)
非聚簇索引:数据与索引是分开存放的,B+树叶子节点存放的不是数据表行记录,而是主键值或者数据表行记录的物理地址
索引使用 B+ Tree 的优点
- 更少的磁盘 IO 次数。由于 B+ 树的中间节点不携带完整数据,所以每个节点(磁盘页)可以存放更多元素,使得树的深度更小,更加 “扁平”,从而减少磁盘 IO 次数。
- 性能更稳定。B 树的匹配结束位置既可能在中间节点,也可能在叶子节点,而 B+ 树一定在叶子节点,因此每次查找的时间都是相近的。
- 范围查找更方便。在 B 树中,需要先找到范围的下限,然后通过中序遍历查找到上限,而 B+ 树由于叶子节点是有序链表,所以找到了起点之后只需要顺序遍历到终点即可。
sql 执行过程
查询过程
SQL执行语句前要先连接数据库,通过解析器,优化器,执行器,去查询缓存,但是在一个表上有更新的时候,跟这个表有关的查询缓存会失效;(在mysql8中没有缓存这个逻辑)
sql 接口处理收到的 SQL 语句,解析器将语句转换为数据库语言,优化器对语句优化,执行器根据优化器生成一套执行计划,去调用存储引擎接口完成语句执行计划。
Explain 语句执行计划分析
- select_type 项
SIMPLE: 简单SELECT,不使用UNION或子查询等
PRIMARY: 子查询中最外层查询,查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY
UNION: UNION中的第二个或后面的SELECT语句
DEPENDENT UNION: UNION中的第二个或后面的SELECT语句,取决于外面的查询
UNION RESULT: UNION的结果,union语句中第二个select开始后面所有select
SUBQUERY: 子查询中的第一个SELECT,结果不依赖于外部查询
DEPENDENT SUBQUERY: 子查询中的第一个SELECT,依赖于外部查询
DERIVED: 派生表的SELECT, FROM子句的子查询
UNCACHEABLE SUBQUERY: 一个子查询的结果不能被缓存,必须重新评估外链接的第一行- type 项:全表查询 ALL 性能差,NULL性能最好
ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
index: Full Index Scan,index与ALL区别为index类型只遍历索引树
range: 只检索给定范围的行,使用一个索引来选择行
ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
const、system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system
NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。- possible_keys 与 key
possible_keys 包含 key,是 key 的超集;查询可能会使用到的索引项,key是查询使用到的索引项- key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度;不损失精确性的情况下,长度越短越好
(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)- rows
列与索引的比较,表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值- extra
Using where:不用读取表中所有信息,仅通过索引就可以获取所需数据,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤
Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询,常见 group by ; order by
Using filesort:当Query中包含 order by 操作,而且无法利用索引完成的排序操作称为“文件排序”
Using join buffer:改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。
Impossible where:这个值强调了where语句会导致没有符合条件的行(通过收集统计信息不可能存在结果)。
Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行
No tables used:Query语句中使用from dual 或不含任何from子句
Not exists:MySQL能够对查询执行 LEFT JOIN 优化,并且在找到与 LEFT JOIN 条件匹配的一行后,不会在上一行组合中检查此表中的更多行。
索引失效的情况
左模糊或全模糊查询时,根据 B+ 树结构可知,无法左模糊查询,索引失效。
索引列上使用范围查询 >、<、between 时索引失效。
条件查询存在 or 保留字时索引失效,建议使用 union 代替。
条件查询存在字段为 varchar 时,查询条件未加引号导致索引失效。
查询的结果集超过全表的 30% 时索引失效。不建议对枚举类型字段创建索引,比如常见的 status,type 字段。
查询条件使用函数在索引列上时索引失效。
字符串条件查询时,应单引号括起来,否则由于隐式转换会导致索引失效。
对于索引列进行逻辑运算,包括 +、-、* 、/、<>、! 等运算会索引失效,所以 != 在条件查询中会导致索引失效;这种情况下可以创建函数索引来处理。
对于组合索引,根据最左前缀原则不使用左边的字段,索引失效。
in、not in、exists、not exists 保留字的使用会索引失效。
B-tree索引 is null、is not null 不会走索引,位图索引 is null,is not null 都会走。
当变量采用的是times变量,而表的字段采用的是date变量时,或相反情况时索引失效。
语句的执行阶段
FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMITBinlog
配置 log-bin 开启日志文件记录
log-bin=C:\ProgramData\MySQL\BinlogData
# binlog 日志格式
binlog_format=ROW
SQL优化
检查SQL,解释执行优化语句
优先使用聚簇主键索引;
建议建立合适的单索引或聚合索引;
避免索引失效;
大SQL可以考虑通过业务去实现,不要都通过SQL去处理业务;
MySQL 锁机制
以下演示以 sys_config 表为例
CREATE TABLE `sys_config` (
`config_id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '参数主键',
`config_name` VARCHAR(100) NULL DEFAULT '' COMMENT '参数名称' COLLATE 'utf8_general_ci',
`config_key` VARCHAR(100) NULL DEFAULT '' COMMENT '参数键名' COLLATE 'utf8_general_ci',
`config_value` VARCHAR(500) NULL DEFAULT '' COMMENT '参数键值' COLLATE 'utf8_general_ci',
`config_type` CHAR(1) NULL DEFAULT 'N' COMMENT '系统内置(Y是 N否)' COLLATE 'utf8_general_ci',
`create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NULL DEFAULT NULL COMMENT '更新时间',
`remark` VARCHAR(500) NULL DEFAULT NULL COMMENT '备注' COLLATE 'utf8_general_ci',
PRIMARY KEY (`config_id`) USING BTREE
) COMMENT='参数配置表' COLLATE='utf8_general_ci' ENGINE=InnoDB;
- 表锁:共享(读)锁、排他(写)锁
表的读锁,多个事务可以同时读取,不可写,又称为共享锁
表的写锁,多个事务不可同时写,需阻塞,又称为排他锁
-- 查看表锁情况;解除表锁;对表 sys_config 加读锁,加写锁
SHOW OPEN TABLES
UNLOCK TABLES
LOCK TABLE sys_config READ
LOCK TABLE sys_config WRITE
-- 加了表锁之后,无法执行写操作
UPDATE sys_config SET remark = '备注事务一' WHERE config_id = 1;
/* SQL错误(1099):Table 'sys_config' was locked with a READ lock and can't be updated */
- 行锁,偏写锁,对于存在索引的表支持行锁
对于表 sys_config,不设置表锁时,两个事务同时对主键 config_id = 1 的记录执行更新语句。其中一个事务不提交,另一个事务会进入阻塞。当地一个事务提交后,其他事务才可执行。
使用 for update 可对记录主动加行锁,如果不是索引列则升级为表锁;
UPDATE sys_config SET remark = '备注事务一' WHERE config_id = 1;
/* SQL错误(1205):Lock wait timeout exceeded; try restarting transaction */
锁定某一行,其对该行的其他事务对其操作会被阻塞,直至锁定行的会话结束commit;
select * from sys_config where config_type = 1 for update;
commit;
悲观锁、乐观锁
拿到数据后就上锁,其他事务需等待,否不可写;行锁,表锁都是悲观锁;
乐观锁,通过版本号的形式,拿到数据时认为不存在并发问题,认为其他事务的修改不会影响数据,在更新数据时通过版本号校验是否被其他事务修改了。
主从同步延迟
读写分离提高吞吐量和高可用,主从同步通过binlog日志进行异步复制实现;
主从同步延迟解决方案:
多主库,增大主库写吞吐量;
sync_binlog在slave端设置为0;sync_binlog=1,表示MySQL不控制binlog刷新到磁盘。
innodb_flush_log_at_trx_commit
MySQL 高级用法
-- 查询是否存在表
SELECT * from information_schema.tables WHERE TABLE_SCHEMA = 'niaonao' and TABLE_NAME IN ('position_history20220531','position_history20220530');
drop table if exists position_history20220531, position_history20220530;
-- 备份数据到csv
select * from position_history20220531 into OUTFILE './history20220531.csv' character set gbk fields terminated by ',' enclosed BY '\"';
-- 存在则更新;注意低版本 sharding 不支持 ON DUPLICATE KEY UPDATE
<insert id="batchInsert" >
insert into region_history
(region_id,region_name,map_name,floor,tag_id,user_name,statistics_date,status)
values
<foreach collection="list" item="item" separator="," >
(#{item.regionId},#{item.regionName},#{item.mapName},#{item.floor},#{item.tagId},#{item.userName},#{item.statisticsDate},#{item.status})
</foreach>
ON DUPLICATE KEY UPDATE region_name = values(region_name)
</insert>
慢 SQL 定位
-- 通过开启打印慢查询日志,一般线上不推荐这种方式
-- 开启慢查询、设置慢SQL时间
show variables LIKE '%SLOW%';
set global slow_query_log=on;
show variables LIKE 'long_query_time';
set long_query_time=on;
-- 分析慢SQL日志
pt-query-digest mysql-slow.log
-- 查看 SQL 的执行频率,判断高频插入、高频提交等操作
SHOW GLOBAL STATUS;
-- 查看那些线程在运行,定位低效率的 SQL
-- 查看 MYSQL 正在运行的线程、包括线程状态是否锁表等,info 也包含了具体的 SQL 语句
show PROCESSLIST;
-- 观察大事务、长事务、锁等待等状态
show engine innodb status
-- 查询最近15条SQL,及执行耗时情况
show profiles
-- 查询某条 SQL 耗时细项
show profile for query 2
【×】2.2 Oracle
TODO
【√】2.3 Redis
Key-Value类型的内存数据库
基本数据类型(String、Set、ZSet、List、Hash)
持久化方式(AOF、RDB)
缓存更新内存一致性(先更新sql后删除key)
缓存穿透、击穿、雪崩(布隆过滤器,过期时间分散与续期避免同个时刻失效,互斥锁应对并发请求进行阻塞)
缓存降级(自动降级、人工降级)
Redis 集群(Sentinel哨兵、Redis Cluster)
主从复制、主从一致、主从延迟
淘汰策略(不淘汰、过期时间、保留热点数据删除最近最少使用的数据、随机删除)
缓存提高性能基本数据类型
:String List Set Zset Hash持久化方式
:AOF、RDB(配置文件conf修改配置)缓存穿透、缓存雪崩、缓存击穿
类型 | 说明 | 解决方案 |
---|---|---|
缓存穿透 | 访问的数据在数据库中不存在,在缓存中也不存在,导致每次都会穿透缓存访问数据库 | 布隆过滤器;非法请求参数业务校验避免无意义的请求参数;设置一个值为 null 的热点数据到缓存,并设置过期时间 |
缓存击穿 | 热点数据的缓存某个时刻失效,大量的请求直接击穿缓存访问到数据库,数据库压力骤增 | 互斥锁,大量请求只能有一个能拿到锁资源并阻塞,查到数据后放入缓存,后续请求会走缓存;设置合适的缓存过期时间或续期策略 |
缓存雪崩 | 大量的热点数据在某段时刻失效,对应的大量请求引起缓存雪崩,都会直接访问数据库,数据库压力骤增 | 热点数据过期时间避免在都在某个时间段;互斥锁(行锁、表锁等);合适的缓存过期时间或续期策略 |
缓存降级 | 缓存失效或者服务器挂掉的情况不去访问数据库 | 根据访问超时时间自动降级或可人工配置降级,数据库访问量超出最大阈值人工降级,不去访问缓存和数据库,直接返回默认结果 |
布隆过滤器
布隆过滤器解决了 Hash 碰撞的问题,类似 HashMap 是存在两个元素经过 Hash 取模后存放的位置是相同的位置,这个问题就是 Hash 碰撞问题。
布隆过滤器是基于bitmap和若干个hash算法实现的;在一个存在一定数量的集合中过滤一个对应的元素,判断该元素是否一定不在集合中或者可能在集合中;有一定的错误识别率,可配置误差范围参数,配置误差越小时查询效率越低,误差大查询效率高;
将所有热点数据的 key 存放到布隆过滤器,请求过来时校验布隆过滤器是否存在热点数据的 key,不存在则直接返回,存在再访问缓存,访问数据库。
为什么使用布隆过滤器不使用 HashMap,如果用HashSet或Hashmap存储的话,每一个用户ID都要存成int,占4个字节即32bit。而一个用户在bitmap中只需要1个bit,内存节省了32倍。
我们会把热点数据放入redis,同时面临的问题都是穿过 redis 访问数据库,给数据库带来压力;
并发一致性问题
主从库延迟,延迟双删问题
缓存和数据库一致性问题
读写,写一定是写数据库,读可以读缓存;
设置缓存失效时间,保证热数据留下,无用的数据都不放在缓存中,且能有效保证数据一致性。
对于更新缓存和写入数据库是两步操作,存在并发问题;
删除缓存的方案,保证每次都是取数据库,然后放入缓存;
缓存并发一致性问题解决方案
先更新数据库,再更新缓存;
先更新缓存,再更新数据库;前两者都存在,数据库与缓存不一致的情况;
先删除缓存,再读取数据库,并发情况下,会读取到更新前的数据;
最优的方案是:先写数据库,再删除缓存;因为写数据库有锁,写的时间比读时间长。
为防止第一步成功,第二步失败,可以引入MQ或消息订阅,异步重试操作保证,第二步执行成功。
缓存一致性问题总结:
为了提高性能,引入缓存;
考虑缓存一致性问题,引入更新数据库+更新缓存;更新数据库+删除缓存;
更新数据库+删除缓存在并发下存在一致性问题,推荐使用先更新数据库+后删除缓存;
引入MQ消息不丢失,异步重试操作方式多步骤操作中后续步骤失败。
Redis 删除策略
当内存用完时,会按照删除策略删除热点数据
- noeviction 不删除策略, 达到最大内存限制时,继续 set 缓存数据会报错
- allkeys-lru 全部数据删除最少使用策略;优先删除最近最少使用(less recently used ,LRU) 的 key。
- volatile-lru 设置过期时间数据删除最少使用策略;
- allkeys-random 全部数据随机删除策略;
- volatile-random 设置过期时间的数据随机删除策略;
- volatile-ttl 设置过期时间的数据,优先删除剩余时间最短的策略;优先删除剩余时间(time to live,TTL) 短的key。
Redis 集群
Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
集群的缺点:批量删除,因为key存放在不同的插槽上。
cluster-enabled yes 打开集群模式
cluster-config-file nodes-6379.conf 设定节点配置文件名
cluster-node-timeout 15000 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换。
启动集群
redis-cli --cluster create --cluster-replicas 1 192.168.11.101:6379 192.168.11.101:6380 192.168.11.101:6381 192.168.11.101:6389 192.168.11.101:6390 192.168.11.101:6391
Proxy+Replication+Sentinel
工作原理
前端使用Twemproxy+KeepAlived做代理,将其后端的多台Redis实例分片进行统一管理与分配
每一个分片节点的Slave都是Master的副本且只读
Sentinel持续不断的监控每个分片节点的Master,当Master出现故障且不可用状态时,Sentinel会通知/启动自动故障转移等动作
Sentinel 可以在发生故障转移动作后触发相应脚本(通过 client-reconfig-script 参数配置 ),脚本获取到最新的Master来修改Twemproxy配置
Sentinel 哨兵模式 监控、通知、故障转移
哨兵监控各个节点服务,当主节点宕机,会选一个从节点升为主节点,保证服务的高可用;监控服务状态是否正常运行,监测到服务不可用,会通过API 脚本通知管理员
Redis Cluster 集群
客户端与Redis节点直连,不需要中间Proxy层,直接连接任意一个Master节点。根据公式HASH_SLOT=CRC16(key) mod 16384,计算出映射到哪个分片上,然后Redis会去相应的节点进行操作。
类似 Hash 环,Redis Cluster采用HashSlot来实现Key值的均匀分布和实例的增删管理。首先默认分配了16384个Slot,每个Slot相当于一致性Hash环上的一个节点。集群的所有实例将均匀地占有这些Slot,而最终当我们Set一个Key时,使用CRC16(Key) % 16384来计算出这个Key属于哪个Slot,并最终映射到对应的实例上去。集群扩展性,要增加一个节点D,RedisCluster的做法是将之前每台机器上的一部分Slot移动到D上,删除某个节点,就是将节点上Solt归还给源节点
1)无需Sentinel哨兵监控,如果Master挂了,Redis Cluster内部自动将Slave切换Master
2)可以进行水平扩容
3)支持自动化迁移,当出现某个Slave宕机了,集群自动把多余的Slave迁移到没有Slave的Master 中
主从复制、主从同步、主从延迟仍然是同步刷新和异步刷新两种方式;一个保证效率一个保证数据一致性;
Redis事务不支持回滚,弃用;推荐分布式事务 SEATA
Redis 分布式锁
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放;
如果 SETNX 返回1,说明该进程获得锁;返回 0 说明其他线程持有锁资源。
set 命令本身就支持复杂的参数,以支持 nx,ex,px操作。
EX seconds : 将键的过期时间设置为 seconds 秒。 执行 SET key value EX seconds 的效果等同于执行 SETEX key seconds value 。
PX milliseconds : 将键的过期时间设置为 milliseconds 毫秒。 执行 SET key value PX milliseconds 的效果等同于执行 PSETEX key milliseconds value 。
NX : 只在键不存在时, 才对键进行设置操作。 执行 SET key value NX 的效果等同于执行 SETNX key value 。
无阻塞取值 scan、keys
假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
一般人会说:使用keys指令可以扫出指定模式的key列表。
如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
因为redis的单线程的,keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。·
命令
# dbsize 返回当前数据库 key 的数量
# info 返回当前 Redis 服务器状态和一些统计信息
# monitor 实时监听并返回Redis服务器接收到的所有请求信息
# shutdown 把数据同步保存到磁盘上,并关闭Redis服务
# config get parameter 获取一个 Redis 配置参数信息
# config set parameter value 设置一个 Redis 配置参数信息
# config resetstat 重置 info 命令的统计信息
# debug segfault 制造一次服务器当机
# flushdb 删除当前数据库中所有 key
# flushall 删除全部数据库中所有 key
Redis 持久化扩容与缓存扩容
如果Redis被当做缓存使用,使用一致性哈希算法实现动态扩容缩容。
如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。
【√】2.4 ShardingSphere
分库分表解决方案 ShardingSphere
解决问题:主从库集群(mode.type: Cluster)、主动同步(binlog)、数据分片(逻辑表、真实表)、分布式事务(SEATA)、读写分离(rules.readwrite-splitting)、负责均衡(ROUND_ROBIN、RANDOM、WEIGHT)、数据加密
旨在建立多模数据库上层的标准和生态;站在数据库的上层,关注数据库之间的协作;
ShardingSphere-JDBC、ShardingSphere-Proxy 均提供标准化的数据水平扩展、分布式事务和分布式治理等功能;
ShardingSphere-JDBC作为增强版 jdbc驱动,完美支持 jdbc 和所有 ORM 框架,适用于 Java 开发的高性能的轻量级;
ShardingSphere-Proxy 作为数据库代理端,更适合运维人员,对分片数据库进行管理和运维的场景;
ShardingSphere-JDBC 支持所有实现JDBC的数据库(MySQL、Oracle、PostgreSQL)、所有的数据库连接池(C3P0、DBCP)、支持所有的 ORM (Object Relational Mapping 对象关系映射)框架(JPA、Hibernate、Mybatis)
分片-水平分片
水平分片:根据某个字段(分片键)或某种规则将数据分散至多个库或表;按行数据拆分,多张表的行数据不一样;
分片是一种水平分区模式,将一个表的行为拆分为多张表(分区)的实践。
- 根据键值分片
根据键值基于哈希进行分片,进行哈希值计算的是分片键(表的某一列)- 根据范围分片
基于给定值的范围进行分片- 基于目录分片
需要建立一个使用分片键的查找表,以跟踪那个分片保存哪些数据
分片-垂直分片
垂直分片:按业务拆库拆表,专库专用;
分库分表除了逻辑分片,也可以业务分表;比如按时间分表,某一段时间的数据放入一张表;新数据入新表;按活动类型分表,某一个活动类型的表入一张表,新活动类型入新的表;
其他公司解决方案
技术 | ShardingSphere | TSharding | Cobar | MyCAT |
---|---|---|---|---|
公司 | 当当 | 蘑菇街 | 阿里巴巴 | Cobar |
熔断限流
某个 ShardingSphere 节点超出负载时,停止该节点对数据库的访问;
对超负荷的请求进行限流,保护部分请求得到及时的响应。分片键
用于将数据库(表)水平拆分的数据库字段。根据分片键打散各个数据到各个数据节点(分片);分片算法
用于将数据分片的算法,支持 =、>=、<=、>、<、BETWEEN 和 IN 进行分片。
分片算法可由开发者自行实现,也可使用 Apache ShardingSphere 内置的分片算法语法糖,灵活度非常高。
内置分片算法:
- 取模分片算法 MOD
- 哈希取模分片算法 HASH_MOD
- 基于分片容量的范围分片算法 VOLUME_RANGE
- 基于分片边界的范围分片算法 BOUNDARY_RANGE
- 自动时间段分片算法 AUTO_INTERVAL
分片策略
分片键+分片算法逻辑表、真实表、单表
数据库中进行分片拆分的表就是逻辑表;不进行分片拆分的表是单表;
对于数据库中逻辑表 t_order 进行分片拆分后真实存在的表 t_order1、t_order2 是真实表负责均衡算法
- WEIGHT 权重算法
- ROUND_ROBIN 轮询算法
- RANDOM 随机算法
支持单机和集群模式,集群环境需要通过独立部署的注册中心来存储元数据和协调节点状态。
数据库兼容性问题
shardingsphere5.0.0之前的版本不支持 MySQL 插入或更新语法 ON DUPLICATE KEY UPDATE
不支持 MySQL 本地克隆 CLONE LOCAL DATA DIRECTORY
脱敏字段无法支持计算操作,比如>、<、sum()、avg()等
分布式事务
数据库事务满足ACID(原子性、一致性、隔离性、持久性)
本地事务 | 两(三)阶段事务 | 柔性事务 | |
---|---|---|---|
业务改造 | 无 | 无 | 实现相关接口 |
一致性 | 不支持 | 支持 | 最终一致 |
隔离性 | 不支持 | 支持 | 业务方保证 |
并发性能 | 无影响 | 严重衰退 | 略微衰退 |
适合场景 | 业务方处理不一致 | 短事务 & 低并发 | 长事务 & 高并发 |
支持项 | 支持非跨库事务;支持因逻辑异常导致的跨库事务 | 支持嵌套事务;支持分片跨库事务;保证原子操作和数据强一致性;服务宕机事务自动恢复 | 支持分片跨库事务;支持读提交隔离级别;服务宕机自动恢复提交中的事务 |
柔性事务:
提倡采用最终一致性放宽对强一致性的要求,以达到事务处理并发度的提升。
ShadingSphehe 集成 SEATA 作为柔性事务的使用方案,SEATA 实现了 SQL 反向操作的自动生成。
读写分离、主从同步
分库分表中一般有主从库,一主多从;
读写分离原理:根据SQL语义分析,读操作和写操作分别路由至主库和从库;提高了系统的吞吐量和高可用。
主库:写操作 insert、update、delete
从库:读操作 select
主从同步:将主库的数据异步的同步到从库的操作。 由于主从同步的异步性,从库与主库的数据会短时间内不一致;通过binlog日志实现主从同步
spring:
shardingsphere:
# 数据源配置
datasource:
# 规则配置
rules:
readwrite-splitting:
data-sources:
ds0: #主库从库逻辑数据源定义 ds0 为 user_db
write-data-source-name: master
read-data-source-names: slave
load-balancer-name: round-robin
load-balancers:
round-robin: # 负载均衡算法
type: ROUND_ROBIN # 轮询
模式配置
mode: # 不配置则默认内存模式
type: Cluster # 运行模式类型。可选配置:Memory、Standalone、Cluster
repository:
type: # 持久化仓库类型
props: # 持久化仓库所需属性
namespace: # 注册中心命名空间
server-lists: # 注册中心连接地址
foo_key: foo_value
bar_key: bar_value
overwrite: # 是否使用本地配置覆盖持久化配置
数据源配置
dataSources:
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/ds_1
username: root
password:
ds_2:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/ds_2
username: root
password:
规则配置
数据分片规则
rules:
- !SHARDING
tables: # 数据分片规则配置
<logic-table-name> (+): # 逻辑表名称
actualDataNodes (?): # 由数据源名 + 表名组成(参考 Inline 语法规则)
databaseStrategy (?): # 分库策略,缺省表示使用默认分库策略,以下的分片策略只能选其一
standard: # 用于单分片键的标准分片场景
shardingColumn: # 分片列名称
shardingAlgorithmName: # 分片算法名称
complex: # 用于多分片键的复合分片场景
shardingColumns: # 分片列名称,多个列以逗号分隔
shardingAlgorithmName: # 分片算法名称
hint: # Hint 分片策略
shardingAlgorithmName: # 分片算法名称
none: # 不分片
tableStrategy: # 分表策略,同分库策略
keyGenerateStrategy: # 分布式序列策略
column: # 自增列名称,缺省表示不使用自增主键生成器
keyGeneratorName: # 分布式序列算法名称
autoTables: # 自动分片表规则配置
t_order_auto: # 逻辑表名称
actualDataSources (?): # 数据源名称
shardingStrategy: # 切分策略
standard: # 用于单分片键的标准分片场景
shardingColumn: # 分片列名称
shardingAlgorithmName: # 自动分片算法名称
bindingTables (+): # 绑定表规则列表
- <logic_table_name_1, logic_table_name_2, ...>
- <logic_table_name_1, logic_table_name_2, ...>
broadcastTables (+): # 广播表规则列表
- <table-name>
- <table-name>
defaultDatabaseStrategy: # 默认数据库分片策略
defaultTableStrategy: # 默认表分片策略
defaultKeyGenerateStrategy: # 默认的分布式序列策略
defaultShardingColumn: # 默认分片列名称
# 分片算法配置
shardingAlgorithms:
<sharding-algorithm-name> (+): # 分片算法名称
type: # 分片算法类型
props: # 分片算法属性配置
# ...
# 分布式序列算法配置
keyGenerators:
<key-generate-algorithm-name> (+): # 分布式序列算法名称
type: # 分布式序列算法类型
props: # 分布式序列算法属性配置
# ...
读写分离规则
rules:
- !READWRITE_SPLITTING
dataSources:
<data-source-name> (+): # 读写分离逻辑数据源名称
type: # 读写分离类型,比如:Static,Dynamic
props:
auto-aware-data-source-name: # 自动发现数据源名称(与数据库发现配合使用)
write-data-source-name: # 写库数据源名称
read-data-source-names: # 读库数据源名称,多个从数据源用逗号分隔
loadBalancerName: # 负载均衡算法名称
# 负载均衡算法配置
loadBalancers:
<load-balancer-name> (+): # 负载均衡算法名称
type: # 负载均衡算法类型
props: # 负载均衡算法属性配置
# ...
3. Spring 框架、SpringBoot、SpringCLoud、Dubbo
【√】3.1 Spring
spring IOC(容器管理,bean依赖注入控制反转)
spring AOP原理(基于代理面前切面编程,应用于日志打印,全局校验,访问拦截等)
spring 5
springMVC
事务管理(ACID、传播特性、隔离级别)
循环依赖
spring设计模式等(单例、工厂、代理、责任链)
事务管理
事务特性 ACID 原子性、一致性、隔离性、持久性
隔离级别 读提交、读未提交、重复读、序列化;Spring 事务还有个默认级别,数据库配置什么,Spring就是什么。
传播行为 required required_new supports not_supported mandatory never nested
- mandatory 当前存在事务,则加入事务,不存在则抛异常
- never 当前存在事务,则抛异常,不存在则以非事务运行
- nested 当前存在事务,则嵌套事务,不存在则新建事务
- supports 当前存在事务,则加入事务,不存在则以非事务运行
- not_supported 当前存在事务,则挂起事务,不存在则以非事务运行
Spring 事务
声明式事务 @Transaction 或 xml 配置,编程式事务 beginTransaction()、commit()、rollback();
声明式事务失效场景
@Transactional仅支持public方法
@Transactional(propagation=Propagation.NEVER) 传播特性设置不支持事务时,抛异常事务失效,以及其他不支持事务的特性Propagation.NOT_SUPPORTED
抛出的异常被try catch时,无法回滚事务
事务不支持嵌套;@Transactional,方法嵌套调用失效
// AbstractFallbackTransactionAttributeSource.class
// 声明式事务的方法必须为public方法
@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
if (this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
...
}
// 抛出的异常被try catch时,无法回滚事务;默认只回滚 RuntimeException 异常,所以建议指定异常以兼容更多场景
@Transactional(rollbackFor=Exception.class)
public void add() throws Exception {
try {
...
} catch (Exception e) {}
}
// 对于方法 test1 test2
// test1 有事务,test2 无事务,事务生效
// test1 有事务,test2 有事务,事务生效
// test1 无事务,test2 有事务,事务不生效
// test1 无事务,test2 无事务,事务不生效
Spring 常用注解
@Component:泛指各种组件
@Controller、@Service、@Repository都可以称为@Component。
@Controller:控制层
@Service:业务层
@Repository:数据访问层
@Value 获取指定名称的配置
@Bean
@Import
注入注解
@Autowired 存在多个实现时,可以配合 @Qualifier 指定名称注入
@Resource 按名称匹配
@Qualifier 强制指定选择
@Primary 默认首选,例如多个数据源配置时,可以指定主配置类
配置注解
@Configuration 声明当前类为配置类;
@Bean 注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式;
@ComponentScan 扫描可以供 IOC 容器管理的类
切面注解(在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持)
@Aspect 声明一个切面
@After 在方法执行之后执行(方法上)
@Before 在方法执行之前执行(方法上)
@Around 在方法执行之前与之后执行(方法上)
@PointCut 声明切点
异步注解
@EnableAsync 配置类中通过此注解开启对异步任务的支持;
@Async 在实际执行的bean方法使用该注解来声明其是一个异步任务(方法上或类上所有的方法都将异步,需要@EnableAsync开启异步任务)
定时任务注解
@EnableScheduling 在配置类上使用,开启计划任务的支持(类上)
@Scheduled 标识一个任务,包括cron,fixDelay,fixRate等类型
enable 注解,开启对xx的支持
@EnableAspectAutoProxy:开启对AspectJ自动代理的支持;
@EnableAsync:开启异步方法的支持;
@EnableScheduling:开启计划任务的支持;
@EnableWebMvc:开启web MVC的配置支持;
@EnableConfigurationProperties:开启对@ConfigurationProperties注解配置Bean的支持;
@EnableJpaRepositories:开启对SpringData JPA Repository的支持;
@EnableTransactionManagement:开启注解式事务的支持;
@EnableCaching:开启注解式的缓存支持;
SpringMVC 常用注解
@EnableWebMvc
@Controller
@RequestMapping
@ResponseBody
@RequestBody
@PathVariable
@RestController
@ControllerAdvice 全局数据处理
@ExceptionHandler 用于全局处理控制器里的异常
@ModelAttribute
@Transactional
SpringBoot 常用注解
@SpringBootApplication 启动类,开启自动配置
@EnableAutoConfiguration 根据声明的依赖,自动装载配置 @ImportAutoConfiguration 导入指定配置类
@SpringBootConfiguration 代替 @Configuration
@ImportResource
@Scope 指定 bean 的作用域
@Profile 在那个环境下被激活
@RequestParam
@PostMapping @GetMapping @PutMapping @DeleteMapping
@Service 标记当前类是一个service类,自动装载到 IOC,不需要xml配置bean
@Mapper @MapperScan 不需要 xml 配置就可管理 mapper 映射
@Component @ComponentScan 自动扫描加载 bean 组件到 IOC 容器
@Entity @Table @Column @Setter @Getter标识实体类映射数据表
@Builder 标识当前类可以通过建造者创建对象
@EnableTransactionManagement 启用事务管理,不需要 xml 配置bean
@Slf4j 简单日志门面代替 private final Logger logger = LoggerFactory.getLogger(当前类名.class);
SpringCloud 常用注解
@SpringCloudApplication 包含pringBoot注解、注册服务中心注解、断路器注解 @SpringBootApplication、@EnableDiscovertyClient、@EnableCircuitBreaker
@EnableEurekaServer @EnableDiscoveryClient 服务注册发现
@LoadBalanced 开启负载均衡能力;
@EnableCircuitBreaker 用在启动类上,开启断路器功能;
@HystrixCommand(fallbackMethod=”backMethod”) 用在方法上,fallbackMethod指定断路回调方法;
@EnableConfigServer 用在启动类上,表示这是一个配置中心,开启Config Server;
@EnableZuulProxy 开启zuul路由,用在启动类上;
【√】3.2 SpringMVC
SpringMVC 原理
DispatcherServlet 转发 Handler 处理器处理给具体的 Servlet 然后处理请求,返回指定的 ModeAndView
【√】3.3 SpringBoot
特征
致力于快速开发,避免繁杂的配置,引入自动装配
直接嵌入 Tomcat、Jetty 或 Undertow
提供 boot-start 依赖,比如 jdbc,druid,redis,pageHelper,test
尽可能自动配置 Spring 和 3rd 方库
提供生产就绪功能,例如指标、健康检查和外部化配置
自动装配 Spring,无需 XML 配置,提供 yml 风格
集成中间件
springboot 与 redis 数据库集成操作 综合案例 分布式缓存
springboot 与 ElasticSearch 分布式搜索引擎 技术 综合案例
springboot 与 MongoDB nosql 综合案例
springboot 与 MQ(RabbitMQ)集成 综合案例
SpringBoot 自动装载原理
org.springframework.boot.autoconfigure.SpringBootApplication.class
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class}), @Filter(type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class})})
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration.class
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.class
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
...
}
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration.class
@Configuration
@EnableConfigurationProperties({HttpProperties.class})
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty(prefix = "spring.http.encoding",value = {"enabled"},matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
...
}
org.springframework.boot.autoconfigure.http.HttpProperties.class
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
private boolean logRequestDetails;
private final HttpProperties.Encoding encoding = new HttpProperties.Encoding();
public HttpProperties() {
}
...
}
1>SpringBoot启动的时候加载主配置类@SpringBootApplication,开启了自动配置功能 @EnableAutoConfiguration
2>EnableAutoConfiguration 自动装配注解通过 AutoConfigurationImportSelector 实现给容器自动导入组件;然后将类路径下 META-INF/spring.factories 里面配置的所有AutoConfiguration的值加入到了容器中;(org.springframework.boot.autoconfigure.EnableAutoConfiguration 的属性值即待导入的组件)
3>每一个自动配置类进行自动配置功能;
4>以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;一但这个配置类生效,这个配置类就会给容器中添加各种组件,这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
a、@Configuration //表示这是一个配置类,也可以给容器中添加组件
b、@EnableConfigurationProperties//启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpProperties绑定起来;并把HttpProperties加入到ioc容器中
c、@ConditionalOnWebApplication //Spring底层@Conditional注解:根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效
d、@ConditionalOnClass(CharacterEncodingFilter.class) //判断当前项目有没有CharacterEncodingFilter这个类;CharacterEncodingFilter类是SpringMVC中进行乱码解决的过滤器;
e、@ConditionalOnProperty(prefix = “spring.http.encoding”, value = “enabled”, matchIfMissing=true) //判断配置文件中是否存在 spring.http.encoding.enabled这个配置;如果不存在,判断也是成立的
【√】3.4 SpringCloud
微服务面临问题
服务治理:解决小服务之间的管理问题;服务注册与发现 SpringCloud Eureka、SpringCloud Alibaba Nacos
服务调用:解决小服务之间的通讯问题;Feign是一种声明式、模板化的HTTP客户端。
服务网关:解决微服务客户端的访问问题;网关路由管理 Netflix Zuul、SpringCloud GateWay
服务容错:解决微服务出现问题的解决;负载均衡 Netflix Ribbon,熔断器 Netflix Hystrix
链路追踪:解决微服务出错的定位问题 SpringCloud Bus
SpringCloud 核心组件
- SpringCloud Netflix:核心组件,可以对多个Netflix OSS开源套件进行整合,包括以下几个组件:
Eureka:服务治理组件,包含服务注册与发现
Hystrix:容错管理组件,实现了熔断器
Ribbon:客户端负载均衡的服务调用组件
Feign:基于Ribbon和Hystrix的声明式服务调用组件
Zuul:网关组件,提供智能路由、访问过滤、频控限制等功能
Archaius:外部化配置组件- SpringCloud Config:配置中心,支持客户端配置信息刷新、加密/解密配置内容等。
- SpringCloud Bus:事件、消息总线,用于传播集群中的状态变化或事件,以及触发后续的处理
- SpringCloud Security:基于spring security的安全工具包,为我们的应用程序添加安全控制
- SpringCloud Consul:封装了Consul操作,Consul是一个服务发现与配置工具(与Eureka作用类似),与Docker容器可以无缝集成
Ribbon 负载均衡
提供负责均衡、容错、异步和多协议(HTTP,TCP,UDP)支持、缓存、批处理功能
- 原理:
服务消费者发起请求
LoadBalancerInterceptor拦截器拦截请求
RibbonLoadBanlancerClient根据请求中的uri获取到服务提供者的id
通过DynamicServerListLoadBalancer根据id从eureka服务端拉去服务列表
通过Ribbon负载均衡规则选择某个服务- 负载均衡策略:
RoundRobinRule 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule 对以下两种服务器进行忽略
WeightedResponseTimeRule 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个权重值会影响服务器的选择。
ZoneAvoidanceRule 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,而后再对Zone内的多个服务做轮询。
BestAvailableRule 忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule 随机选择一个可用的服务器。
RetryRule 重试机制的选择逻辑- 注解
@LoadBalanced 在RestTemplate的bean上再添加一个@LoadBalanced注解即可。
Feign 声明性 rest 客户端
@EnableFeignClients
@FeignClient
Feign是一个声明式的Web服务客户端。这使得Web服务客户端的写入更加方便 要使用Feign创建一个界面并对其进行注释。它具有可插拔注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud添加了对Spring MVC注释的支持,并在Spring Web中使用默认使用的HttpMessageConverters。Spring Cloud集成Ribbon和Eureka以在使用Feign时提供负载均衡的http客户端。
Zuul/GateWay 服务网关
@EnableZuulProxy
@EnableZuulServer
Zuul是Netflix的基于JVM的路由器和服务器端负载均衡器。
API网关可以提供一个单独且统一的API入口用于访问内部一个或多个API。简单来说嘛就是一个统一入口,比如现在的支付宝或者微信的相关api服务一样,都有一个统一的api地址,统一的请求参数,统一的鉴权。
服务网关 | Zuul | GateWay |
---|---|---|
实现 | 基于Servlet | |
异步 | 不支持 | 支持 |
扩展性 | 扩展性低 | 扩展性高,实现了限流、负载均衡 |
身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符的请求。
审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生产视图。
动态路由:动态地将请求路由到不同的后端集群。
压力测试:逐渐增加指向集群的流量,以了解性能。
负载分配:为每一种负载类型分配对应容量,并启用超出限定值的请求。
静态响应处理:在边缘位置直接建立部分相应,从而避免其转发到内部集群。
Hystrix 断路器
应对服务雪崩
熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。
在一个分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,如何能够保证在一个依赖出问题的情况下,不会导致整体服务失败,这个就是Hystrix需要做的事情。Hystrix提供了熔断、隔离、Fallback、cache、监控等功能,能够在一个、或多个依赖同时出现问题时保证系统依然可用
熔断策略
当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务.
断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回情况,如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN).
熔断降级
Fallback 当请求后端服务异常时,返回默认的响应结果
Cache 对于同样的请求,把缓存直接返回
监控接口状态
SpringCloud Config 配置中心
@EnableConfigServer
SpringCloud Eureka 服务注册发现
当客户端注册Eureka时,它提供有关自身的元数据,例如主机和端口,运行状况指示符URL,主页等。Eureka从属于服务的每个实例接收心跳消息。如果心跳失败超过可配置的时间表,则通常将该实例从注册表中删除
高可用
Eureka服务器没有后端存储,但注册表中的服务实例都必须发送心跳以保持其注册更新(因此可以在内存中完成)。客户端还具有eureka注册的内存缓存
SpringCloud Bus 消息总线
Spring Cloud Bus 将分布式系统的节点与轻量级消息代理链接起来。然后可以使用此代理来广播状态更改(例如配置更改)或其他管理指令。一个关键的想法是,总线就像一个分布式执行器,用于横向扩展的 Spring Boot 应用程序。但是,它也可以用作应用程序之间的通信渠道。该项目为 AMQP 代理或 Kafka 提供启动器作为传输
总线事件(的子类RemoteApplicationEvent)可以通过设置来跟踪 spring.cloud.bus.trace.enabled=true。如果这样做,Spring Boot TraceRepository (如果存在)会显示每个发送的事件以及来自每个服务实例的所有确认
Spring Security 安全
【√】3.4 Dubbo
IPC 进程过程调用
LPC 本地过程调用
RPC 远程过程调用,Remote Procedure Call,分布式应用架构
RPC
通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想;目标是像调用本地服务一样调用远程服务,具体实现客户端是以调用本地服务的方式调用服务端 API
客户端与服务端建立 TCP 链接,相比于 HTTP 通信协议少去了应用层的东西
寻址问题:通过服务注册实现,在调用对应的方法也就是自动通过 TCP 链接交互对应的地址
Dubbo
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
- Provider
暴露服务方称之为“服务提供者”。- Consumer
调用远程服务方称之为“服务消费者”。- Registry
服务注册与发现的中心目录服务称之为“服务注册中心”。- Monitor
统计服务的调用次数和调用时间的日志服务称之为“服务监控中心”。
主要特性
1.1 服务注册/发现
1.2 健康检查
1.3 多协议支持
1.4 序列化服务
1.5 服务集群
1.6 失败执行策略
1.7 负载均衡策略
1.8 服务治理
1.9 服务监控
1.10 服务运行容器
1.11 直连提供者
1.12 服务代理
1.13 版本/分组管理
1.14 泛化调用
1.15 延迟暴露
1.16 本地存根
1.17 服务降级
1.18 路由规则配置
RPC | HTTP | |
---|---|---|
场景 | 内部服务调用,传输性能高,服务治理方便 | 浏览器接口调用,开放平台接口开放 |
协议 | TCP | HTTP |
传输效率 | 请求报文小,效率高 | 序列化时耗性能,效率低 |
负载均衡 | 支持 | 需要通过 nginx 或 HAProxy 实现 |
服务治理 | 支持,自动治理,通知上下游 | 需要实现通知,上下游都知道后再修改 nginx 配置 |
4. Mybatis 持久层框架、JFinal、Heibernate
ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。
一般的JDBC本身要频繁的创建数据库连接和关闭连接,占用资源;ORM 持久层框架也会引入数据库连接池,对连接进行管理,避免资源浪费。
框架 | MyBatis | MyBatis |
---|---|---|
原理 | config.xml加载配置,mapper.xmlSQL解析,resultMap结果映射 | |
优点 | 将SQL与程序解耦;支持动态SQL;兼容主流数据库 | |
缺点 | xml 编写量大;移植性差,要求编写SQL; |
【√】4.1 MyBatis
MyBatis SQL 防注入(#{})、二级缓存(SqlSession和SqlSessionFactory)、分页原理(内置分页插件接口拦截SQL物理分页)
原理
配置文件加载mapper映射文件,根据id映射具体的mapper具体的实体方法;构造SqlSessionFactory、SqlSession 去执行器执行 MapperStatementt,解析paramType和resultMap封装返回结果
1)读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了MyBatis 的运行环境等信息,例如数据库连接信息。
2)加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。
SQL 注入
对于 Mybattis 使用#{}避免 SQL依赖注入,预编译阶段会解析为占位符从而避免 SQL 注入;而 KaTeX parse error: Expected 'EOF', got '#' at position 36: …换,存在 SQL 注入风险。 #̲{}是占位符,预编译处理;{}是拼接符,字符串替换,没有预编译处理。
//sqlMap 中如下的 sql 语句
select * from user where name = #{name};
//解析成为预编译语句
select * from user where name = ?;
select * from user where name = '${name}'
//传递的参数为 "ruhua" 时,解析为如下,然后发送数据库服务器进行编译。
select * from user where name = "niaonao";
分库分表历史表清除方案
动态删除分表、历史表CSV备份
<resultMap id="tableNameResult" type="java.lang.String" >
<result column="table_name" property="tableName" jdbcType="VARCHAR" />
</resultMap>
<!-- 查询历史表名称 -->
<select id="findExpireTableNameList" parameterType="list" resultMap="tableNameResult">
SELECT `table_name` from information_schema.tables
WHERE TABLE_SCHEMA = 'niaonao' and TABLE_NAME IN
(<foreach collection="list" item="item" separator=",">
#{item}
</foreach>)
</select>
<!-- 文件导出csv并清除历史表 -->
<select id="historyCsvOutFile" parameterType="list" >
select * from #{tableName} into OUTFILE #{csvDir} character set gbk fields terminated by ',' enclosed BY '\"';
drop table if exists #{tableName};
</select>
service 调用
String dir = csvPath + tableName + ".csv";
File f = new File(dir);
if (f.exists()) {
f.delete();
}
locPositionHistoryMapper.historyCsvOutFile(tableName, dir);
多 SQL 执行方案 allowMultiQueries=true
<!-- url: jdbc:mysql://localhost:3306/niaonao?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
allowMultiQueries 支持多条SQL语句执行通过 ; 分开
-->
<!-- 历史数据文件导出csv,并清除历史记录 -->
<delete id="historyCsvOutFileAndClear" parameterType="String">
select * from loc_warn where warn_time < #{expirationDate} into OUTFILE #{csvDir} character set gbk fields terminated by ',' enclosed BY '\"';
delete from loc_warn where warn_time < #{expirationDate};
</delete>
二级缓存
一级缓存是SqlSession 级别的缓存,事务提交 Flush、commit 缓存就被清除了;一级缓存存在时,再次查询相同条件,直接返回缓存数据,且存储的是对象,两次结果完全相同;
二级缓存是SqlSessionFactofy 级别的缓存,全局缓存,发送写操作时清除缓存;存储的是数据,通过数据创建新对象返回。
一对一、一对多关系 association
一对多关系用 association 标识
<resultMap type="Stuinfo" id="stuAndMajor">
<id property="sno" column="sno"/>
<result property="sname" column="sname"/>
<association property="major" javaType="Major" select="getMajor" column="majorId"/>
</resultMap>
<select id="getStuAndMajor" resultMap="stuAndMajor" parameterType="String">
select * from stuinfo where sno=#{sno}
</select>
<select id="getMajor" resultType="Major" parameterType="int">
select * from major where id=#{majorId}
</select>
Mybatis是如何分页
1)内置分页插件
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
2)自定义分页语句
【×】4.2 JFinal
TODO JFinal
【√】4.3 Heibernate
原理
1)读取并解析配置文件
2)读取并解析映射信息,创建SessionFactory
3)打开Sesssion
4)创建事务Transation
5)持久化操作
6)提交事务
7)关闭Session
8)关闭SesstionFactory
延迟加载 lazy
通过设置 lazy 属性支持延迟加载
当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。
状态转换 瞬时状态、游离状态、持久状态
当对象由瞬时状态(Transient)一save()时,就变成了持久化状态;
当我们在Session里存储对象的时候,实际是在Session的Map里存了一份, 也就是它的缓存里放了一份,然后,又到数据库里存了一份,在缓存里这一份叫持久对象(Persistent)。 Session 一 Close()了,它的缓存也都关闭了,整个Session也就失效了,这个时候,这个对象变成了游离状态(Detached),但数据库中还是存在的。
当游离状态(Detached)update()时,又变为了持久状态(Persistent)。
当持久状态(Persistent)delete()时,又变为了瞬时状态(Transient), 此时,数据库中没有与之对应的记录
一对一、一对多关系 many-to-one、one-to-many、many-to-many
它们通过配置文件中的many-to-one、one-to-many、many-to-many来实现类之间的关联关系的。
检索策略 立即检索、延迟检索、迫切左外连接检索
优点 | 缺点 | |
---|---|---|
立即检索 | 对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便的从一个对象导航到与它关联的对象 | select语句太多;可能会加载应用程序不需要访问的对象白白浪费许多内存空间 |
延迟检索 | 由应用程序决定需要加载哪些对象,可以避免可执行多余的select语句,以及避免加载应用程序不需要访问的对象。因此能提高检索性能,并且能节省内存空间 | 应用程序如果希望访问游离状态代理类实例,必须保证他在持久化状态时已经被初始化 |
迫切左外连接检索 | 对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便地冲一个对象导航到与它关联的对象。2使用了外连接,select语句数目少 | 可能会加载应用程序不需要访问的对象,白白浪费许多内存空间;2复杂的数据库表连接也会影响检索性能 |
缓存机制 一级缓存、二级缓存
Hibernate一级缓存又称为Session的缓存。由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存。第一级缓存是必需的,不允许而且事实上也无法卸除。在第一级缓存中,持久化类的每个实例都具有唯一的OID。
Hibernate二级缓存又称为SessionFactory的缓存,由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。第二级缓存是可选的,是一个可配置的插件,在默认情况下,SessionFactory不会启用这个插件。
缓存策略
文章来源:https://www.toymoban.com/news/detail-606815.html
策略 | 说明 |
---|---|
Read-only | 适用于那些频繁读取却不会更新的数据 |
Read/write | 适用于需要被更新的数据,比read-only更耗费资源 |
Nonstrict read/write | 不保障两个同时进行的事务会修改同一块数据,这种策略适用于那些经常读取但是极少更新的数据 |
Transactional | 完全事务化得缓存策略 |
参考网上较多博客,感谢各大博主,不一一列举
ElasticSearch基本查询
ElasticSearch读写底层原理及性能调优
一致性Hash在负载均衡中的应用
Power By niaonao, The End文章来源地址https://www.toymoban.com/news/detail-606815.html
到了这里,关于【技术面试】Java八股文业余选手-上篇(持续更新)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!