diff --git a/Algorithm.md b/Algorithm.md new file mode 100644 index 0000000..68c81e8 --- /dev/null +++ b/Algorithm.md @@ -0,0 +1,3443 @@ +
Algorithm
+ +Author:李茹钰(`echo`) + +Introduction:收纳技术相关的 `Data Structure`、`Algorithm`、`Design Pattern`等总结! + +[TOC] + +# Data Structure + +## 位运算 + +- **左移( << )**:操作数的非0位左移n位,低位补0 + +- **右移( >> )**:操作数的非0位右移n位,高位补0 + +- **无符号右移( >>> )**:正数右移,高位用0补,负数右移,高位用1补,当负数使用无符号右移时,用0进行补位 + +- **位非( ~ )**:操作数为1则为0,否则为1 + +- **位与( & )**:第一个数和第二个数,都为1则为1,否则为0 + +- **位或( | )**:第一个数和第二个数,有一个为1则为1,否则为0 + +- **位异或( ^ )**:第一个数和第二个数,有一个不相同则为1,否则为0 + + ```java + a^a=0; // 自己和自己异或等于0 + a^0=a; // 任何数字和0异或还等于他自己 + a^b^c=a^c^b; // 异或运算具有交换律 + ``` + + + + + +### 左移( << ) + +案例如下: + +`5<<2=20` + +首先会将5转为2进制表示形式(java中,整数默认就是int类型,也就是32位): +0000 0000 0000 0000 0000 0000 0000 0`101` 然后左移2位后,低位补0: +0000 0000 0000 0000 0000 0000 000`101` `00` 换算成10进制为20 + + + +### 右移( >> ) + +案例如下: + +`5>>2=1` + +还是先将5转为2进制表示形式: + +0000 0000 0000 0000 0000 0000 0000 0`101` 然后右移2位,高位补0 + +`00`00 0000 0000 0000 0000 0000 0000 000`1` + + + +### 无符号右移( >>> ) + +我们知道在Java中int类型占32位,可以表示一个正数,也可以表示一个负数。正数换算成二进制后的最高位为0,负数的二进制最高为为1。**正数右移,高位用0补,负数右移,高位用1补,当负数使用无符号右移时,用0进行补位**。 + +案例如下: + +`5>>3=1` + +`-5>>-1` + +`-5>>>536870911` + +我们来看看它的移位过程(可以通过其结果换算成二进制进行对比): + +5换算成二进制: 0000 0000 0000 0000 0000 0000 0000 0101 + +5右移3位后结果为0,0的二进制为: 0000 0000 0000 0000 0000 0000 0000 0000 // (用0进行补位) + +-5换算成二进制: 1111 1111 1111 1111 1111 1111 1111 1011 + +-5右移3位后结果为-1,-1的二进制为: 1111 1111 1111 1111 1111 1111 1111 1111 // (用1进行补位) + +-5无符号右移3位后的结果 536870911 换算成二进制: 0001 1111 1111 1111 1111 1111 1111 1111 // (用0进行补位) + + + +### 位非( ~ ) + +操作数的第n位为1,那么结果的第n位为0,反之。 + +案例如下: + +`~5=-6` + +5转换为二进制:0000 0000 0000 0000 0000 0000 0000 0101 + +-6转换为二进制:1111 1111 1111 1111 1111 1111 1111 1010 + + + +### 位与( & ) + +**都为1则为1,否则为0**。第一个操作数的第n位于第二个操作数的第n位如果都是1,那么结果的第n为也为1,否则为0。 + +案例如下: + +`5&3=1` + +将2个操作数和结果都转换为二进制进行比较: +5转换为二进制:0000 0000 0000 0000 0000 0000 0000 0`101` + +3转换为二进制:0000 0000 0000 0000 0000 0000 0000 0`011` + +1转换为二进制:0000 0000 0000 0000 0000 0000 0000 0`001` + +### 位或( | ) + +第一个操作数的的第n位于第二个操作数的第n位 只要有一个是1,那么结果的第n为也为1,否则为0。 + +案例如下: + +`5|3=7` + +5转换为二进制:0000 0000 0000 0000 0000 0000 0000 0101 + +3转换为二进制:0000 0000 0000 0000 0000 0000 0000 0011 + +7转换为二进制:0000 0000 0000 0000 0000 0000 0000 0111 + + + +### 位异或( ^ ) + +第一个操作数的的第n位于第二个操作数的第n位 相反,那么结果的第n为也为1,否则为0。 + +`a^0=a` + +案例如下: + +`5^3=6` + +5转换为二进制:0000 0000 0000 0000 0000 0000 0000 0101 + +3转换为二进制:0000 0000 0000 0000 0000 0000 0000 0011 + +6转换为二进制:0000 0000 0000 0000 0000 0000 0000 0110 + + + +### 衍生 + +由位运算操作符衍生而来的有: + +`&=` 按位与赋值 + +`|=` 按位或赋值 + +`^=` 按位非赋值 + +`>>=` 右移赋值 + +`>>=` 无符号右移赋值 + +`<<=` 赋值左移 + +和 += 一个概念而已。 + + + +## 常用数据结构 + +### 数组(Array) + +![数据结构-array](images/Algorithm/数据结构-array.png) + +**优点** + +- 构建非常简单 +- 能在 O(1) 的时间里根据数组的下标(index)查询某个元素 + +**缺点** + +- 构建时必须分配一段连续的空间 +- 查询某个元素是否存在时需要遍历整个数组,耗费 O(n) 的时间(其中,n 是元素的个数) +- 删除和添加某个元素时,同样需要耗费 O(n) 的时间 + +**基本操作** + +- **insert**:在某个索引处插入元素 +- **get**:读取某个索引处的元素 +- **delete**:删除某个索引处的元素 +- **size**:获取数组的长度 + + + +**案例一:翻转字符串“algorithm”** + +![翻转字符串algorithm](images/Algorithm/翻转字符串algorithm.gif) + +**解法**:用两个指针,一个指向字符串的第一个字符 a,一个指向它的最后一个字符 m,然后互相交换。交换之后,两个指针向中央一步步地靠拢并相互交换字符,直到两个指针相遇。这是一种比较快速和直观的方法。 + +**案例二:给定两个字符串s和t,编写一个函数来判断t是否是s的字母异位词。** + +说明:你可以假设字符串只包含小写字母。 + +**解题思路**:字母异位词,也就是两个字符串中的相同字符的数量要对应相等。 + +- 可以利用两个长度都为 26 的字符数组来统计每个字符串中小写字母出现的次数,然后再对比是否相等 +- 可以只利用一个长度为 26 的字符数组,将出现在字符串 s 里的字符个数加 1,而出现在字符串 t 里的字符个数减 1,最后判断每个小写字母的个数是否都为 0 + + + +### 链表(Linked List) + +链表(Linked List)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。 + +![链表(LinkedList)](images/Algorithm/链表(LinkedList).png) + +**优点** + +- 链表能灵活地分配内存空间 +- 能在 O(1) 时间内删除或者添加元素 + +**缺点** + +- 不像数组能通过下标迅速读取元素,每次都要从链表头开始一个一个读取 +- 查询第 k 个元素需要 O(k) 时间 + +**基本操作** + +- **insertAtEnd**:在链表结尾插入元素 +- **insertAtHead**:在链表开头插入元素 +- **delete** :删除链表的指定元素 +- **deleteAtHead** :删除链表第一个元素 +- **search**:在链表中查询指定元素 +- **isEmpty**:查询链表是否为空 + +**应用场景** + +- 如果要解决的问题里面**需要很多快速查询**,链表可能并不适合 +- 如果遇到的问题中,数据的元素个数不确定,而且需要**经常进行数据的添加和删除**,那么链表会比较合适 +- 如果数据元素大小确定,**删除和插入的操作并不多**,那么数组可能更适合 + + + +**链表实现数据结构** + +**① 用链表实现队列** + +![用链表实现栈](images/Algorithm/用链表实现栈.png) + +**② 用链表实现栈** + +![用链表实现队列](images/Algorithm/用链表实现队列.png) + + + +**链表翻转算法** + +**① 递归翻转** + +```java +/** + * 链表递归翻转模板 + */ +public Node reverseLinkedList(参数0) { + // Step1:终止条件 + if (终止条件) { + return; + } + + // Step2:逻辑处理:可能有,也可能没有,具体问题具体分析 + // Step3:递归调用 + Node reverse = reverseLinkedList(参数1); + // Step4:逻辑处理:可能有,也可能没有,具体问题具体分析 +} + +/** + * 链表递归翻转算法 + */ +public Node reverseLinkedList(Node head) { + // Step1:终止条件 + if (head == null || head.next == null) { + return head; + } + + // Step2:保存当前节点的下一个结点 + Node next = head.next; + // Step3:从当前节点的下一个结点开始递归调用 + Node reverse = reverseLinkedList(next); + // Step4:head挂到next节点的后面就完成了链表的反转 + next.next = head; + // 这里head相当于变成了尾结点,尾结点都是为空的,否则会构成环 + head.next = null; + return reverse; +} +``` + +**② 三指针翻转** + +```java +public static Node reverseLinkedList(Node head) { + // 单链表为空或只有一个节点,直接返回原单链表 + if (head == null || head.getNext() == null) { + return head; + } + + // 前一个节点指针 + Node preNode = null; + // 当前节点指针 + Node curNode = head; + // 下一个节点指针 + Node nextNode = null; + while (curNode != null) { + // nextNode 指向下一个节点 + nextNode = curNode.getNext(); + // 将当前节点next域指向前一个节点 + curNode.setNext(preNode); + // preNode 指针向后移动 + preNode = curNode; + // curNode指针向后移动 + curNode = nextNode; + } + + return preNode; +} +``` + +**③ 利用栈翻转** + +```java +public Node reverseLinkedList(Node node) { + Stack nodeStack = new Stack<>(); + // 存入栈中,模拟递归开始的栈状态 + while (node != null) { + nodeStack.push(node); + node = node.getNode(); + } + + // 特殊处理第一个栈顶元素:反转前的最后一个元素,因为它位于最后,不需要反转 + Node head = null; + if ((!nodeStack.isEmpty())) { + head = nodeStack.pop(); + } + + // 排除以后就可以快乐的循环 + while (!nodeStack.isEmpty()) { + Node tempNode = nodeStack.pop(); + tempNode.getNode().setNode(tempNode); + tempNode.setNode(null); + } + + return head; +} +``` + + + +#### 单向链表 + +![单向链表](images/Algorithm/单向链表.png) + +单向链表包含两个域: + +- **一个数据域**:用于存储数据 +- **一个指针域**:用于指向下一个节点(最后一个节点则指向一个空值): + +单链表的遍历方向单一,只能从链头一直遍历到链尾。它的缺点是当要查询某一个节点的前一个节点时,只能再次从头进行遍历查询,因此效率比较低,而双向链表的出现恰好解决了这个问题。单向链表代码如下: + +```java +public class Node { + private Eitem; + private Node next; +} +``` + + + +#### 双向链表 + +![双向链表](images/Algorithm/双向链表.png) + +双向链表的每个节点由三部分组成: + +- **prev指针**:指向前置节点 +- **item节点**:数据信息 +- **next指针**:指向后置节点 + +双向链表代码如下: + +```kotlin +public class Node { + private E item; + private Node next; + private Node prev; +} +``` + + + +#### 循环链表 + +循环链表又分为单循环链表和双循环链表,也就是将单向链表或双向链表的首尾节点进行连接。 + +- **单循环链表** + + ![单循环链表](images/Algorithm/单循环链表.png) + +- **双循环链表** + + ![双循环链表](images/Algorithm/双循环链表.png) + + + +### 栈(Stack) + +栈是一种**先进后出**(`FILO`,First in last out)或**后进先出**(`LIFO`,Last in first out)的数据结构。 + +![数据结构-stack](images/Algorithm/数据结构-stack.png) + +- **单向链表**:可以利用一个单链表来实现栈的数据结构。而且,因为我们都只针对栈顶元素进行操作,所以借用单链表的头就能让所有栈的操作在 O(1) 的时间内完成。 +- **Stack**:是Vector的子类,比Vector多了几个方法 + +```java +public class Stack extends Vector { + // 把元素压入栈顶 + public E push(E item) { + addElement(item); + return item; + } + + // 弹出栈顶元素 + public synchronized E pop() { + E obj; + int len = size(); + obj = peek(); + removeElementAt(len - 1); + return obj; + } + + // 访问当前栈顶元素,但是不拿走栈顶元素 + public synchronized E peek() { + int len = size(); + if (len == 0) + throw new EmptyStackException(); + return elementAt(len - 1); + } + + // 测试堆栈是否为空 + public boolean empty() { + return size() == 0; + } + + // 返回对象在堆栈中的位置,以1为基数 + public synchronized int search(Object o) { + int i = lastIndexOf(o); + if (i >= 0) { + return size() - i; + } + return -1; + } +} +``` + +**基本操作**(失败时:add/remove/element为抛异常,offer/poll/peek为返回false或null) + +- `E push(E)`:把元素压入栈 +- `E pop()`:把栈顶的元素弹出 +- `E peek()`:取栈顶元素但不弹出 +- `boolean empty()`:堆栈是否为空测试 +- `int search(o)`:返回对象在堆栈中的位置,以 1 为基数 + +**应用场景** + +在解决某个问题的时候,只要求关心最近一次的操作,并且在操作完成了之后,需要向前查找到更前一次的操作。 + +![Stack](images/Algorithm/Stack.png) + + + +**案例一:判断字符串是否有效** + +给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。有效字符串需满足: + +- 左括号必须用相同类型的右括号闭合 +- 左括号必须以正确的顺序闭合 +- 空字符串可被认为是有效字符串 + +**解题思路**:利用一个栈,不断地往里压左括号,一旦遇上了一个右括号,我们就把栈顶的左括号弹出来,表示这是一个合法的组合,以此类推,直到最后判断栈里还有没有左括号剩余。 + +![Stack判断字符串是否有效](images/Algorithm/Stack判断字符串是否有效.gif) + +**案例二:每日温度** + +根据每日气温列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。 + +**解题思路** + +- 思路 1:最直观的做法就是针对每个温度值向后进行依次搜索,找到比当前温度更高的值,这样的计算复杂度就是 O(n2)。 + +- 思路 2:可以运用一个堆栈 stack 来快速地知道需要经过多少天就能等到温度升高。从头到尾扫描一遍给定的数组 T,如果当天的温度比堆栈 stack 顶端所记录的那天温度还要高,那么就能得到结果。 + + ![Stack每日温度](images/Algorithm/Stack每日温度.gif) + + + +### 队列(Queue) + +队列(Queue)和栈不同,队列的最大特点是**先进先出**(`FIFO`),就好像按顺序排队一样。对于队列的数据来说,我们只允许在队尾查看和添加数据,在队头查看和删除数据。 + +- **栈**:采用**后进先出**(`LIFO`) +- **队列**:采用 **先进先出**(First in First Out,即`FIFO`) + +![数据结构-queue](images/Algorithm/数据结构-queue.png) + +**实现方式** + +可借助**双链表**来实现队列。双链表的头指针允许在队头查看和删除数据,而双链表的尾指针允许在队尾查看和添加数据。 + +**基本操作**(失败时:add/remove/element为抛异常,offer/poll/peek为返回false或null) + +- `int size()`:获取队列长度 +- `boolean add(E)`/`boolean offer(E)`:添加元素到队尾 +- `E remove()`/`E poll()`:获取队首元素并从队列中删除 +- `E element()`/`E peek()`:获取队首元素但并不从队列中删除 + +**应用场景** + +当需要按照一定的顺序来处理数据,而该数据的数据量在不断地变化的时候,则需要队列来处理。 + + + +### 双端队列(Deque) +双端队列和普通队列最大的不同在于,它允许我们在队列的头尾两端都能在 O(1) 的时间内进行数据的查看、添加和删除。 + +**实现方式** + +双端队列(Deque)与队列相似,可以利用一个**双链表实现**双端队列。 + +**应用场景** + +双端队列最常用的地方就是实现一个长度动态变化的窗口或者连续区间,而动态窗口这种数据结构在很多题目里都有运用。 + + + +**案例一:滑动窗口最大值** + +给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字,滑动窗口每次只向右移动一位,就返回当前滑动窗口中的最大值。 + +**解题思路** + +- 思路 1:移动窗口,扫描,获得最大值。假设数组里有 n 个元素,算法复杂度就是 O(n)。这是最直观的做法。 + +- 思路 2:利用一个双端队列来保存当前窗口中最大那个数在数组里的下标,双端队列新的头就是当前窗口中最大的那个数。通过该下标,可以很快知道新窗口是否仍包含原来那个最大的数。如果不再包含,就把旧的数从双端队列的头删除。 + + 因为双端队列能让上面的这两种操作都能在 O(1) 的时间里完成,所以整个算法的复杂度能控制在 O(n)。 + + ![Deque滑动窗口最大值](images/Algorithm/Deque滑动窗口最大值.gif) + + + +### 树(Tree) + +**树(Tree)**是一个分层的数据结构,由节点和连接节点的边组成。是一种特殊的图,它与图最大的区别是没有循环。树的结构十分直观,而树的很多概念定义都有一个相同的特点:递归。也就是说,一棵树要满足某种性质,往往要求每个节点都必须满足。而树的考题,无非就是要考查树的遍历以及序列化(serialization)。常见的树: + +- **普通二叉树** +- **平衡二叉树** +- **完全二叉树** +- **二叉搜索树** +- **四叉树(Quadtree)** +- **多叉树(N-ary Tree)** +- **红黑树(Red-Black Tree)** +- **自平衡二叉搜索树(AVL Tree)** + +#### 二叉树(Binary Tree) + +**二叉树(Binary Tree)**是n(n>=0)个结点的有限集合,该集合或者为空集(空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成(子树也为二叉树)。 + + + +**二叉树特点** + +- 每个结点最多有两棵子树,所以**二叉树中不存在度大于2的结点** +- 左子树和右子树是有顺序的,次序不能任意颠倒 +- 即使树中某结点只有一棵子树,也要区分它是左子树还是右子树 + + + +**二叉树性质** + +- **性质1**:在二叉树的第 i 层上**最多**有2^(i-1)个结点(i≥1) +- **性质2**:深度为k的二叉树**最多**有2^k-1个结点(k≥1) +- **性质3**:对任何一二叉树,如果其终端结点数(叶子节点数)为X,度为2的结点数为Y,则X = Y+1 +- **性质4**:n个结点的完全二叉树的深度为[log2 n ] + 1 +- **性质5**:对于任意一个完全二叉树来说,如果将含有的结点按照层次从左到右依次标号,对于任意一个结点 i ,完全二叉树还有以下3个结论成立: + - 当 i>1 时,父亲结点为结点 [i/2] 。(i=1 时,表示的是根结点,无父亲结点) + - 如果 2×i>n(总结点的个数) ,则结点 i 肯定没有左孩子(为叶子结点);否则其左孩子是结点 2×i 。 + - 如果 2×i+1>n ,则结点 i 肯定没有右孩子;否则右孩子是结点 2×i+1 + + + +**存储结构** + +其中data是数据域,lchild和rchild都是指针域,分别指向左孩子和右孩子。 + +```java +public class TreeNode { + public Object data; + public TreeNode leftChild; + public TreeNode rightChild; +} +``` + + + +#### 红黑树(Red Black Tree) + +红黑树全称是Red-Black Tree,一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。 + +![红黑树](images/Algorithm/红黑树.jpg) + +红黑树的特性: + +- **每个节点或者是黑色,或者是红色** +- **根节点是黑色** +- **每个叶子节点(NIL)是黑色。 注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点** +- **如果一个节点是红色的,则它的子节点必须是黑色的** +- **从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点** + +正是红黑树的这5条性质,使一棵n个结点的红黑树始终保持了logn的高度,从而也就解释了“红黑树的查找、插入、删除的时间复杂度最坏为O(log n)”这一结论成立的原因。 + + + +**① 左旋** + +逆时针旋转两个节点,让一个节点被其右子节点取代,而该节点成为右子节点的左子节点。 + +对x进行左旋,意味着"将x变成一个左节点"。 + +![左旋](images/Algorithm/左旋.jpg) + + + +**② 右旋** + +顺时针旋转两个节点,让一个节点被其左子节点取代,而该节点成为左子节点的右子节点。 + +对x进行左旋,意味着"将x变成一个左节点"。 + +![右旋](images/Algorithm/右旋.jpg) + + + +**③ 变色** + +变颜色条件:两个连续红色节点,并且叔叔节点是红色。 + + + +**④ 左旋条件** + +两个连续红节点,并且叔叔节点是黑色 , 下面的红色节点在右子树。 + +**情况1**:如果当前节点是右子树,并且父节点是左子树。`形如:(900是新插入节点)` + +![左旋条件情况1](images/Algorithm/左旋条件情况1.png) + +要根据父节点左旋【899】(根据谁左旋,谁就变成子节点): + +![左旋条件情况1流程](images/Algorithm/左旋条件情况1流程.gif) +【900】的左子树 挂到 【899】的右子树上 , 【899】变为子节点 , 【900】变为父节点` +`此时不变色。 + +**情况2**:如果当前节点是右子树,并且父节点是右子树。形如:(【100】是当前节点) + +![左旋条件情况2](images/Algorithm/左旋条件情况2.png) + +根据祖父节点左旋【56】(根据谁左旋,谁就变成子节点): + +![左旋条件情况2流程](images/Algorithm/左旋条件情况2流程.gif) + +【56】变为子节点,【89】变为父节点,【89】的左子树 挂到 【56】的右子树。同时: 父节点变黑(【89】变黑),祖父节点变红(【56】变红 )。 + + + +**⑤ 右旋条件** + +两个连续红节点,并且叔叔节点是黑色 , 下面的红色节点在左子树。 +**情况1**:如果当前节点是左子树,并且父节点也是右子树。形如:(【8000】是当前节点) + +![右旋条件情况1](images/Algorithm/右旋条件情况1.png) + +根据父节点右旋【9000】(根据谁右旋,谁就变成子节点): + +![右旋条件情况1流程](images/Algorithm/右旋条件情况1流程.gif) + +【9000】变为子节点,【8000】变为父节点,【8000】的右子树 挂到 【9000】的左子树,此时不变色。 + +**情况2**:如果当前节点是左子树,并且父节点也是左子树。形如:(【899】是当前节点) + +![右旋条件情况2](images/Algorithm/右旋条件情况2.png) + +根据祖父节点右旋【1000】(根据谁右旋,谁就变成子节点): + +![右旋条件情况2流程](images/Algorithm/右旋条件情况2流程.gif) + +【1000】变为子节点,【900】变为父节点,【900】的右子树 挂到 【1000】的左子树 +同时: 父节点变黑(【900】变黑),祖父节点变红(【1000】变红)。 + + + +**⑥ 旋转场景** + +无法通过变色而进行旋转的场景分为以下四种: + +**场景一:左左节点旋转** +这种情况下,父节点和插入的节点都是左节点,如下图(旋转原始图1)这种情况下,我们要插入节点 65。 + +规则如下:以祖父节点【右旋】,搭配【变色】。 + +![左左节点旋转](images/Algorithm/左左节点旋转.png) + +按照规则,步骤如下: + +![左左节点旋转步骤](images/Algorithm/左左节点旋转步骤.jpeg) + +**场景二:左右节点旋转** +这种情况下,父节点是左节点,插入的节点是右节点,在旋转原始图 1 中,我们要插入节点 67。 + +规则如下:先父节点【左旋】,然后祖父节点【右旋】,搭配【变色】。 + +按照规则,步骤如下: + +![左右节点旋转](images/Algorithm/左右节点旋转.png) + +**场景三:右左节点旋转** +这种情况下,父节点是右节点,插入的节点是左节点,如下图(旋转原始图 2)这种情况,我们要插入节点 68。 + +规则如下:先父节点【右旋】,然后祖父节点【左旋】,搭配【变色】。 + +![右左节点旋转](images/Algorithm/右左节点旋转.png) + +按照规则,步骤如下: + +![右左节点旋转步骤](images/Algorithm/右左节点旋转步骤.jpeg) + +**场景四:右右节点旋转** +这种情况下,父节点和插入的节点都是右节点,在旋转原始图 2 中,我们要插入节点 70。 + +规则如下:以祖父节点【左旋】,搭配【变色】。 + +按照规则,步骤如下: + +![右右节点旋转](images/Algorithm/右右节点旋转.png) + + + +#### 树的遍历 + +**① 前序遍历(Preorder Traversal)** + +**实现原理**:`先访问根节点,然后访问左子树,最后访问右子树`。在访问左、右子树的时候,同样,先访问子树的根节点,再访问子树根节点的左子树和右子树,这是一个不断递归的过程。 + +![前序遍历](images/Algorithm/前序遍历.gif) + +**应用场景**:运用最多的场合包括在树里进行搜索以及创建一棵新的树。 + + + +**② 中序遍历(Inorder Traversal)** + +**实现原理**:`先访问左子树,然后访问根节点,最后访问右子树`。在访问左、右子树的时候,同样,先访问子树的左边,再访问子树的根节点,最后再访问子树的右边。 + +![中序遍历](images/Algorithm/中序遍历.gif) + +**应用场景**:最常见的是二叉搜索树,由于二叉搜索树的性质就是左孩子小于根节点,根节点小于右孩子,对二叉搜索树进行中序遍历的时候,被访问到的节点大小是按顺序进行的。 + + + +**③ 后序遍历(Postorder Traversal)** + +**实现原理**:`先访问左子树,然后访问右子树,最后访问根节点`。 + +![后序遍历](images/Algorithm/后序遍历.gif) + +**应用场景**:在对某个节点进行分析的时候,需要来自左子树和右子树的信息。收集信息的操作是从树的底部不断地往上进行,好比你在修剪一棵树的叶子,修剪的方法是从外面不断地往根部将叶子一片片地修剪掉。 + + + +**案例一:二叉搜索中第K小的元素** + +给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。 + +**解题思路** + +这道题考察了两个知识点: + +- **二叉搜索树的性质**:对于每个节点来说,该节点的值比左孩子大,比右孩子小,而且一般来说,二叉搜索树里不出现重复的值。 + +- **二叉搜索树的遍历**:二叉搜索树的中序遍历是高频考察点,节点被遍历到的顺序是按照节点数值大小的顺序排列好的。即,中序遍历当中遇到的元素都是按照从小到大的顺序出现。 + +因此,我们只需要对这棵树进行中序遍历的操作,当访问到第 k 个元素的时候返回结果就好。 + +![二叉搜索中第K小的元素](images/Algorithm/二叉搜索中第K小的元素.gif) + + + +## 高级数据结构 + +### 优先队列(Priority Queue) + +能保证每次取出的元素都是队列中优先级别最高的。优先级别可以是自定义的,例如,数据的数值越大,优先级越高;或者数据的数值越小,优先级越高。优先级别甚至可以通过各种复杂的计算得到。 + +**实现方式** +优先队列的本质是一个二叉堆结构。堆在英文里叫 Binary Heap,它是利用一个数组结构来实现的完全二叉树。换句话说,优先队列的本质是一个数组,数组里的每个元素既有可能是其他元素的父节点,也有可能是其他元素的子节点,而且,每个父节点只能有两个子节点,很像一棵二叉树的结构。 + +**优先队列的性质** + +- 数组里的第一个元素 array[0] 拥有最高的优先级别 +- 给定一个下标 i,那么对于元素 array[i] 而言: + - 它的父节点所对应的元素下标是 (i-1)/2 + - 它的左孩子所对应的元素下标是 2×i + 1 + - 它的右孩子所对应的元素下标是 2×i + 2 +- 数组里每个元素的优先级别都要高于它两个孩子的优先级别 + +**应用场景**:从一堆杂乱无章的数据当中按照一定的顺序(或者优先级)逐步地筛选出部分乃至全部的数据。 + + + +**基本操作** + +**① 向上筛选(sift up/bubble up)** + +- 当有新的数据加入到优先队列中,新的数据首先被放置在二叉堆的底部。 +- 不断进行向上筛选的操作,即如果发现该数据的优先级别比父节点的优先级别还要高,那么就和父节点的元素相互交换,再接着往上进行比较,直到无法再继续交换为止。 + +![优先队列-向上筛选](images/Algorithm/优先队列-向上筛选.gif) + +**时间复杂度**:由于二叉堆是一棵完全二叉树,并假设堆的大小为 k,因此整个过程其实就是沿着树的高度往上爬,所以只需要 O(logk) 的时间。 + +**② 向下筛选(sift down/bubble down)** + +- 当堆顶的元素被取出时,要更新堆顶的元素来作为下一次按照优先级顺序被取出的对象,需要将堆底部的元素放置到堆顶,然后不断地对它执行向下筛选的操作。 +- 将该元素和它的两个孩子节点对比优先级,如果优先级最高的是其中一个孩子,就将该元素和那个孩子进行交换,然后反复进行下去,直到无法继续交换为止。 + +![优先队列-向下筛选](images/Algorithm/优先队列-向下筛选.gif) + +**时间复杂度**:整个过程就是沿着树的高度往下爬,所以时间复杂度也是 O(logk)。 + + + +**案例一:[前 K 个高频元素](https://leetcode-cn.com/problems/top-k-frequent-elements/)** + +给你一个整数数组 `nums` 和一个整数 `k` ,请你返回其中出现频率前 `k` 高的元素。你可以按 **任意顺序** 返回答案。 + +**最小堆解法**:题目最终需要返回的是前 k 个频率最大的元素,可以想到借助堆这种数据结构,对于 k 频率之后的元素不用再去处理,进一步优化时间复杂度。 + +![最小堆-前K个高频元素](images/Algorithm/最小堆-前K个高频元素.jpg) + +```java +public class Solution { + public List topKFrequent(int[] nums, int k) { + // 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值 + HashMap map = new HashMap(); + for(int num : nums){ + if (map.containsKey(num)) { + map.put(num, map.get(num) + 1); + } else { + map.put(num, 1); + } + } + + // 遍历map,用最小堆保存频率最大的k个元素 + PriorityQueue pq = new PriorityQueue<>(new Comparator() { + @Override + public int compare(Integer a, Integer b) { + return map.get(a) - map.get(b); + } + }); + for (Integer key : map.keySet()) { + if (pq.size() < k) { + pq.add(key); + } else if (map.get(key) > map.get(pq.peek())) { + pq.remove(); + pq.add(key); + } + } + + // 取出最小堆中的元素 + List res = new ArrayList<>(); + while (!pq.isEmpty()) { + res.add(pq.remove()); + } + return res; + } +} +``` + + + +### 图(Graph) + +**图(graph)**由多个**节点(vertex)**构成,节点之间阔以互相连接组成一个网络。(x, y)表示一条**边(edge)**,它表示节点 x 与 y 相连。边可能会有**权值(weight/cost)**。 + +![数据结构-graph](images/Algorithm/数据结构-graph.png) + + + +**常见图算法** + +- 图的遍历:深度优先、广度优先 +- 环的检测:有向图、无向图 +- 拓扑排序 +- 最短路径算法:Dijkstra、Bellman-Ford、Floyd Warshall +- 连通性相关算法:Kosaraju、Tarjan、求解孤岛的数量、判断是否为树 +- 图的着色、旅行商问题等 + + + +### 前缀树(Trie) + +假如有一个字典,字典里面有如下词:"A","to","tea","ted","ten","i","in","inn",每个单词还能有自己的一些权重值,那么用前缀树来构建这个字典将会是如下的样子: + +![前缀树](images/Algorithm/前缀树.png) + +**性质** + +- 每个节点至少包含两个基本属性 + + - children:数组或者集合,罗列出每个分支当中包含的所有字符 + - isEnd:布尔值,表示该节点是否为某字符串的结尾 + +- 前缀树的根节点是空的 + + 所谓空,即只利用到这个节点的 children 属性,即只关心在这个字典里,有哪些打头的字符 + +- 除了根节点,其他所有节点都有可能是单词的结尾,叶子节点一定都是单词的结尾 + + + +**实现** + +- 创建 + + - 遍历一遍输入的字符串,对每个字符串的字符进行遍历 + - 从前缀树的根节点开始,将每个字符加入到节点的 children 字符集当中 + - 如果字符集已经包含了这个字符,则跳过 + - 如果当前字符是字符串的最后一个,则把当前节点的 isEnd 标记为真。 + + 由上,创建的方法很直观。前缀树真正强大的地方在于,每个节点还能用来保存额外的信息,比如可以用来记录拥有相同前缀的所有字符串。因此,当用户输入某个前缀时,就能在 O(1) 的时间内给出对应的推荐字符串。 + +- 搜索 + + 与创建方法类似,从前缀树的根节点出发,逐个匹配输入的前缀字符,如果遇到了就继续往下一层搜索,如果没遇到,就立即返回。 + + + +### 线段树(Segment Tree) + +假设有一个数组 array[0 … n-1], 里面有 n 个元素,现在要经常对这个数组做两件事。 + +- 更新数组元素的数值 +- 求数组任意一段区间里元素的总和(或者平均值) + + + +**解法 1:遍历一遍数组** + +- 时间复杂度 O(n)。 + +**解法 2:线段树** + +- 线段树,就是一种按照二叉树的形式存储数据的结构,每个节点保存的都是数组里某一段的总和。 +- 适用于数据很多,而且需要频繁更新并求和的操作。 +- 时间复杂度 O(logn)。 + + + +**实现** +如数组是 [1, 3, 5, 7, 9, 11],那么它的线段树如下。 + +![线段树](images/Algorithm/线段树.png) + +根节点保存的是从下标 0 到下标 5 的所有元素的总和,即 36。左右两个子节点分别保存左右两半元素的总和。按照这样的逻辑不断地切分下去,最终的叶子节点保存的就是每个元素的数值。 + + + +### 树状数组(Fenwick Tree) + +树状数组(Fenwick Tree / Binary Indexed Tree)。 + +**举例**:假设有一个数组 array[0 … n-1], 里面有 n 个元素,现在要经常对这个数组做两件事。 + +- 更新数组元素的数值 +- 求数组前 k 个元素的总和(或者平均值) + + + +**解法 1:线段树** + +- 线段树能在 O(logn) 的时间里更新和求解前 k 个元素的总和 + +**解法 2:树状数** + +- 该问题只要求求解前 k 个元素的总和,并不要求任意一个区间 +- 树状数组可以在 O(logn) 的时间里完成上述的操作 +- 相对于线段树的实现,树状数组显得更简单 + + + +**树状数组特点** + +- 它是利用数组来表示多叉树的结构,在这一点上和优先队列有些类似,只不过,优先队列是用数组来表示完全二叉树,而树状数组是多叉树。 +- 树状数组的第一个元素是空节点。 +- 如果节点 tree[y] 是 tree[x] 的父节点,那么需要满足条件:y = x - (x & (-x))。 + + + +### 散列表(Hash) + +散列表又称为**哈希表**,是将某个对象变换为唯一标识符,该标识符通常用一个短的随机字母和数字组成的字符串来代表。哈希可以用来实现各种数据结构,其中最常用的就是**哈希表(hash table)**。哈希表通常由数组实现。 + +![数据结构-hash_table](images/Algorithm/数据结构-hash_table.png) + + + +### 二叉堆 + +**最大堆**:任何一个父节点的值,都 **大于** 或 **等于** 它左、右子节点的值,**堆顶** 是整个堆中的 **最大** 元素。 + +**最小堆**:任何一个父节点的值,都 **小于** 或 **等于** 它左、右孩子节点的值,**堆顶** 是整个堆中的 **最小** 元素。 + + + +# Algorithm + +## 复杂度 + +![SortAlgorithm](images/Algorithm/SortAlgorithm.png) + +**相关概念** + +- **稳定**:如果a原本在b前面,而a=b,排序之后a仍然在b的前面 +- **不稳定**:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面 +- **时间复杂度**:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律 +- **空间复杂度:**是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数 + + + +**时间复杂度与时间效率**:O(1) < O(log2N) < O(n) < O(N \* log2N) < O(N2) < O(N3) < 2N < 3N < N! + +一般来说,前四个效率比较高,中间两个差强人意,后三个比较差(只要N比较大,这个算法就动不了了)。 + + + +### 常数阶 + +```java +int sum = 0,n = 100; //执行一次 +sum = (1+n)*n/2; //执行一次 +System.out.println (sum); //执行一次 +``` + +上面算法的运行的次数的函数为 f(n)=3,根据推导大 O 阶的规则 1,我们需要将常数 3 改为 1,则这个算法的时间复杂度为 O(1)。如果 sum=(1+n)*n/2 这条语句再执行 10 遍,因为这与问题大小 n 的值并没有关系,所以这个算法的时间复杂度仍旧是 O(1),我们可以称之为常数阶。 + + + +### 线性阶 + +线性阶主要要分析循环结构的运行情况,如下所示: + +```java +for (int i = 0; i < n; i++) { + //时间复杂度为O(1)的算法 + ... +} +``` + +上面算法循环体中的代码执行了n次,因此时间复杂度为O(n)。 + + + +### 对数阶 + +接着看如下代码: + +```java +int number = 1; +while (number < n) { + number = number * 2; + //时间复杂度为O(1)的算法 + ... +} +``` + +可以看出上面的代码,随着 number 每次乘以 2 后,都会越来越接近 n,当 number 不小于 n 时就会退出循环。假设循环的次数为 X,则由 2^x=n 得出 x=log₂n,因此得出这个算法的时间复杂度为 O(logn)。 + + + +### 平方阶 + +下面的代码是循环嵌套: + +```java +for (int i = 0; i < n; i++) { for(int j = 0; j < n; i++) { //复杂度为O(1)的算法 ... }} +``` + +内层循环的时间复杂度在讲到线性阶时就已经得知是O(n),现在经过外层循环n次,那么这段算法的时间复杂度则为O(n²)。 + +## 算法思想 + +![算法思想](images/Algorithm/算法思想.jpg) + +### 分治(Divide and Conquer) + +分治算法思想很大程度上是基于递归的,也比较适合用递归来实现。顾名思义,分而治之。一般分为以下三个过程: + +- **分解**:将原问题分解成一系列子问题 +- **解决**:递归求解各个子问题,若子问题足够小,则直接求解 +- **合并**:将子问题的结果合并成原问题 + +比较经典的应用就是`归并排序 (Merge Sort)` 以及`快速排序 (Quick Sort)` 等。我们来从归并排序理解分治思想,归并排序就是将待排序数组不断二分为规模更小的子问题处理,再将处理好的子问题合并起来。 + + + +### 贪心(Greedy) + +`贪心算法`是`动态规划`算法的一个子集,可以更高效解决一部分更特殊的问题。实际上,用贪心算法解决问题的思路,并不总能给出最优解。因为它在每一步的决策中,选择目前最优策略,不考虑全局是不是最优。 + +**贪心算法+双指针求解** + +- 给一个孩子的饼干应当尽量小并且能满足孩子,大的留来满足胃口大的孩子 +- 因为胃口小的孩子最容易得到满足,所以优先满足胃口小的孩子需求 +- 按照从小到大的顺序使用饼干尝试是否可满足某个孩子 +- 当饼干 j >= 胃口 i 时,饼干满足胃口,更新满足的孩子数并移动指针 `i++ j++ res++` +- 当饼干 j < 胃口 i 时,饼干不能满足胃口,需要换大的 `j++` + + + +### 回溯(Backtracking) + +使用回溯法进行求解,回溯是一种通过穷举所有可能情况来找到所有解的算法。如果一个候选解最后被发现并不是可行解,回溯算法会舍弃它,并在前面的一些步骤做出一些修改,并重新尝试找到可行解。究其本质,其实就是枚举。 + +- 如果没有更多的数字需要被输入,说明当前的组合已经产生 +- 如果还有数字需要被输入: + - 遍历下一个数字所对应的所有映射的字母 + - 将当前的字母添加到组合最后,也就是 `str + tmp[r]` + + + +### 动态规划(Dynamic Programming) + +虽然动态规划的最终版本 (降维再去维) 大都不是递归,但解题的过程还是离开不递归的。新手可能会觉得动态规划思想接受起来比较难,确实,动态规划求解问题的过程不太符合人类常规的思维方式,我们需要切换成机器思维。使用动态规划思想解题,首先要明确动态规划的三要素。动态规划三要素: + +- `重叠子问题`:切换机器思维,自底向上思考 +- `最优子结构`:子问题的最优解能够推出原问题的优解 +- `状态转移方程`:dp[n] = dp[n-1] + dp[n-2] + + + +## 算法模板 + +### 递归模板 + +```java +public void recur(int level, int param) { + // terminator + if (level > MAX_LEVEL) { + // process result + return; + } + + // process current logic + process(level, param); + + // drill down + recur(level + 1, newParam); + + // restore current status +} +``` + +**List转树形结构** + +- **方案一:两层循环实现建树** +- **方案二:使用递归方法建树** + +```java +public class TreeNode { + private String id; + private String parentId; + private String name; + private List children; + + /** + * 方案一:两层循环实现建树 + * + * @param treeNodes 传入的树节点列表 + * @return + */ + public static List bulid(List treeNodes) { + List trees = new ArrayList<>(); + for (TreeNode treeNode : treeNodes) { + if ("0".equals(treeNode.getParentId())) { + trees.add(treeNode); + } + + for (TreeNode it : treeNodes) { + if (it.getParentId() == treeNode.getId()) { + if (treeNode.getChildren() == null) { + treeNode.setChildren(new ArrayList()); + } + treeNode.getChildren().add(it); + } + } + } + + return trees; + } + + /** + * 方案二:使用递归方法建树 + * + * @param treeNodes + * @return + */ + public static List buildByRecursive(List treeNodes) { + List trees = new ArrayList<>(); + for (TreeNode treeNode : treeNodes) { + if ("0".equals(treeNode.getParentId())) { + trees.add(findChildren(treeNode, treeNodes)); + } + } + + return trees; + } + + /** + * 递归查找子节点 + * + * @param treeNodes + * @return + */ + private static TreeNode findChildren(TreeNode treeNode, List treeNodes) { + for (TreeNode it : treeNodes) { + if (treeNode.getId().equals(it.getParentId())) { + if (treeNode.getChildren() == null) { + treeNode.setChildren(new ArrayList<>()); + } + treeNode.getChildren().add(findChildren(it, treeNodes)); + } + } + + return treeNode; + } +} +``` + + + +### 回溯模板 + + + +### DFS模板 + + + +### BFS模板 + + + +## 查找算法 + +### 顺序查找 + +就是一个一个依次查找。 + + + +### 二分查找 + +二分查找又叫折半查找,从有序列表的初始候选区`li[0:n]`开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半。如果待查值小于候选区中间值,则只需比较中间值左边的元素,减半查找范围。依次类推依次减半。 + +- 二分查找的前提:**列表有序** +- 二分查找的有点:**查找速度快** +- 二分查找的时间复杂度为:**O(logn)** + +![二分查找](images/Algorithm/二分查找.gif) + +JAVA代码如下: + +```java +/** + * 执行递归二分查找,返回第一次出现该值的位置 + * + * @param array 已排序的数组 + * @param start 开始位置,如:0 + * @param end 结束位置,如:array.length-1 + * @param findValue 需要找的值 + * @return 值在数组中的位置,从0开始。找不到返回-1 + */ +public static int searchRecursive(int[] array, int start, int end, int findValue) { + // 如果数组为空,直接返回-1,即查找失败 + if (array == null) { + return -1; + } + + if (start <= end) { + // 中间位置 + int middle = (start + end) / 1; + // 中值 + int middleValue = array[middle]; + if (findValue == middleValue) { + // 等于中值直接返回 + return middle; + } else if (findValue < middleValue) { + // 小于中值时在中值前面找 + return searchRecursive(array, start, middle - 1, findValue); + } else { + // 大于中值在中值后面找 + return searchRecursive(array, middle + 1, end, findValue); + } + } else { + // 返回-1,即查找失败 + return -1; + } +} + +/** + * 循环二分查找,返回第一次出现该值的位置 + * + * @param array 已排序的数组 + * @param findValue 需要找的值 + * @return 值在数组中的位置,从0开始。找不到返回-1 + */ +public static int searchLoop(int[] array, int findValue) { + // 如果数组为空,直接返回-1,即查找失败 + if (array == null) { + return -1; + } + + // 起始位置 + int start = 0; + // 结束位置 + int end = array.length - 1; + while (start <= end) { + // 中间位置 + int middle = (start + end) / 2; + // 中值 + int middleValue = array[middle]; + if (findValue == middleValue) { + // 等于中值直接返回 + return middle; + } else if (findValue < middleValue) { + // 小于中值时在中值前面找 + end = middle - 1; + } else { + // 大于中值在中值后面找 + start = middle + 1; + } + } + + // 返回-1,即查找失败 + return -1; +} +``` + + + +### 插值查找 + +插值查找算法类似于二分查找,不同的是插值查找每次从自适应mid处开始查找。将折半查找中的求mid索引的公式,low表示左边索引left,high表示右边索引right,key就是前面我们讲的findVal。 + +![二分查找mid](images/Algorithm/二分查找mid.png) **改为** ![插值查找mid](images/Algorithm/插值查找mid.png) + +**注意事项** + +- 对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找,速度较快 +- 关键字分布不均匀的情况下,该方法不一定比折半查找要好 + +```java +/** + * 插值查找 + * + * @param arr 已排序的数组 + * @param left 开始位置,如:0 + * @param right 结束位置,如:array.length-1 + * @param findValue + * @return + */ +public static int insertValueSearch(int[] arr, int left, int right, int findValue) { + //注意:findVal < arr[0] 和 findVal > arr[arr.length - 1] 必须需要, 否则我们得到的 mid 可能越界 + if (left > right || findValue < arr[0] || findValue > arr[arr.length - 1]) { + return -1; + } + + // 求出mid, 自适应 + int mid = left + (right - left) * (findValue - arr[left]) / (arr[right] - arr[left]); + int midValue = arr[mid]; + if (findValue > midValue) { + // 向右递归 + return insertValueSearch(arr, mid + 1, right, findValue); + } else if (findValue < midValue) { + // 向左递归 + return insertValueSearch(arr, left, mid - 1, findValue); + } else { + return mid; + } +} +``` + + + +### 斐波那契查找 + +黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近似值是0.618。由于按此比例设计的造型十分美丽,因此称为黄金分割,也称为中外比。这是一个神奇的数字,会带来意向不大的效果。斐波那契数列{1, 1,2, 3, 5, 8, 13,21, 34, 55 }发现斐波那契数列的两个相邻数的比例,无限接近黄金分割值0.618。 +斐波那契查找原理与前两种相似,仅仅改变了中间结点(mid)的位置,mid不再是中间或插值得到,而是位于黄金分割点附近,即mid=low+F(k-1)-1(F代表斐波那契数列),如下图所示: + +![斐波那契查找](images/Algorithm/斐波那契查找.png) + +JAVA代码如下: + +```java +/** + * 因为后面我们mid=low+F(k-1)-1,需要使用到斐波那契数列,因此我们需要先获取到一个斐波那契数列 + *

+ * 非递归方法得到一个斐波那契数列 + * + * @return + */ +private static int[] getFibonacci() { + int[] fibonacci = new int[20]; + fibonacci[0] = 1; + fibonacci[1] = 1; + for (int i = 2; i < fibonacci.length; i++) { + fibonacci[i] = fibonacci[i - 1] + fibonacci[i - 2]; + } + return fibonacci; +} + +/** + * 编写斐波那契查找算法 + *

+ * 使用非递归的方式编写算法 + * + * @param arr 数组 + * @param findValue 我们需要查找的关键码(值) + * @return 返回对应的下标,如果没有-1 + */ +public static int fibonacciSearch(int[] arr, int findValue) { + int low = 0; + int high = arr.length - 1; + int k = 0;// 表示斐波那契分割数值的下标 + int mid = 0;// 存放mid值 + int[] fibonacci = getFibonacci();// 获取到斐波那契数列 + // 获取到斐波那契分割数值的下标 + while (high > fibonacci[k] - 1) { + k++; + } + + // 因为 fibonacci[k] 值可能大于 arr 的 长度,因此我们需要使用Arrays类,构造一个新的数组 + int[] temp = Arrays.copyOf(arr, fibonacci[k]); + // 实际上需求使用arr数组最后的数填充 temp + for (int i = high + 1; i < temp.length; i++) { + temp[i] = arr[high]; + } + + // 使用while来循环处理,找到我们的数 findValue + while (low <= high) { + mid = low + fibonacci[k] - 1; + if (findValue < temp[mid]) { + high = mid - 1; + k--; + } else if (findValue > temp[mid]) { + low = mid + 1; + k++; + } else { + return Math.min(mid, high); + } + } + + return -1; +} +``` + + + +## 搜素算法 + +### 深度优先搜索(DFS) + +深度优先搜索(Depth-First Search / DFS)是一种**优先遍历子节点**而不是回溯的算法。 + +![深度优先搜索](images/Algorithm/深度优先搜索.jpg) + +**DFS解决的是连通性的问题**。即给定两个点,一个是起始点,一个是终点,判断是不是有一条路径能从起点连接到终点。起点和终点,也可以指的是某种起始状态和最终的状态。问题的要求并不在乎路径是长还是短,只在乎有还是没有。 + + + +**代码案例** + +```java +/** + * Depth-First Search(DFS) + *

+ * 从根节点出发,沿着左子树方向进行纵向遍历,直到找到叶子节点为止。然后回溯到前一个节点,进行右子树节点的遍历,直到遍历完所有可达节点为止。 + *

+ * 数据结构:栈 + * 父节点入栈,父节点出栈,先右子节点入栈,后左子节点入栈。递归遍历全部节点即可 + * + * @author lry + */ +public class DepthFirstSearch { + + /** + * 树节点 + * + * @param + */ + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class TreeNode { + private V value; + private List> childList; + + // 二叉树节点支持如下 + + public TreeNode getLeft() { + if (childList == null || childList.isEmpty()) { + return null; + } + + return childList.get(0); + + } + + public TreeNode getRight() { + if (childList == null || childList.isEmpty()) { + return null; + } + + return childList.get(1); + } + } + + /** + * 模型: + * .......A + * ...../ \ + * ....B C + * .../ \ / \ + * ..D E F G + * ./ \ / \ + * H I J K + */ + public static void main(String[] args) { + TreeNode treeNodeA = new TreeNode<>("A", new ArrayList<>()); + TreeNode treeNodeB = new TreeNode<>("B", new ArrayList<>()); + TreeNode treeNodeC = new TreeNode<>("C", new ArrayList<>()); + TreeNode treeNodeD = new TreeNode<>("D", new ArrayList<>()); + TreeNode treeNodeE = new TreeNode<>("E", new ArrayList<>()); + TreeNode treeNodeF = new TreeNode<>("F", new ArrayList<>()); + TreeNode treeNodeG = new TreeNode<>("G", new ArrayList<>()); + TreeNode treeNodeH = new TreeNode<>("H", new ArrayList<>()); + TreeNode treeNodeI = new TreeNode<>("I", new ArrayList<>()); + TreeNode treeNodeJ = new TreeNode<>("J", new ArrayList<>()); + TreeNode treeNodeK = new TreeNode<>("K", new ArrayList<>()); + // A->B,C + treeNodeA.getChildList().add(treeNodeB); + treeNodeA.getChildList().add(treeNodeC); + // B->D,E + treeNodeB.getChildList().add(treeNodeD); + treeNodeB.getChildList().add(treeNodeE); + // C->F,G + treeNodeC.getChildList().add(treeNodeF); + treeNodeC.getChildList().add(treeNodeG); + // D->H,I + treeNodeD.getChildList().add(treeNodeH); + treeNodeD.getChildList().add(treeNodeI); + // G->J,K + treeNodeG.getChildList().add(treeNodeJ); + treeNodeG.getChildList().add(treeNodeK); + + System.out.println("非递归方式"); + dfsNotRecursive(treeNodeA); + System.out.println(); + System.out.println("前续遍历"); + dfsPreOrderTraversal(treeNodeA, 0); + System.out.println(); + System.out.println("后续遍历"); + dfsPostOrderTraversal(treeNodeA, 0); + System.out.println(); + System.out.println("中续遍历"); + dfsInOrderTraversal(treeNodeA, 0); + } + + /** + * 非递归方式 + * + * @param tree + * @param + */ + public static void dfsNotRecursive(TreeNode tree) { + if (tree != null) { + // 次数之所以用 Map 只是为了保存节点的深度,如果没有这个需求可以改为 Stack> + Stack, Integer>> stack = new Stack<>(); + Map, Integer> root = new HashMap<>(); + root.put(tree, 0); + stack.push(root); + + while (!stack.isEmpty()) { + Map, Integer> item = stack.pop(); + TreeNode node = item.keySet().iterator().next(); + int depth = item.get(node); + + // 打印节点值以及深度 + System.out.print("-->[" + node.getValue().toString() + "," + depth + "]"); + + if (node.getChildList() != null && !node.getChildList().isEmpty()) { + for (TreeNode treeNode : node.getChildList()) { + Map, Integer> map = new HashMap<>(); + map.put(treeNode, depth + 1); + stack.push(map); + } + } + } + } + } + + /** + * 递归前序遍历方式 + *

+ * 前序遍历(Pre-Order Traversal) :指先访问根,然后访问子树的遍历方式,二叉树则为:根->左->右 + * + * @param tree + * @param depth + * @param + */ + public static void dfsPreOrderTraversal(TreeNode tree, int depth) { + if (tree != null) { + // 打印节点值以及深度 + System.out.print("-->[" + tree.getValue().toString() + "," + depth + "]"); + + if (tree.getChildList() != null && !tree.getChildList().isEmpty()) { + for (TreeNode item : tree.getChildList()) { + dfsPreOrderTraversal(item, depth + 1); + } + } + } + } + + /** + * 递归后序遍历方式 + *

+ * 后序遍历(Post-Order Traversal):指先访问子树,然后访问根的遍历方式,二叉树则为:左->右->根 + * + * @param tree + * @param depth + * @param + */ + public static void dfsPostOrderTraversal(TreeNode tree, int depth) { + if (tree != null) { + if (tree.getChildList() != null && !tree.getChildList().isEmpty()) { + for (TreeNode item : tree.getChildList()) { + dfsPostOrderTraversal(item, depth + 1); + } + } + + // 打印节点值以及深度 + System.out.print("-->[" + tree.getValue().toString() + "," + depth + "]"); + } + } + + /** + * 递归中序遍历方式 + *

+ * 中序遍历(In-Order Traversal):指先访问左(右)子树,然后访问根,最后访问右(左)子树的遍历方式,二叉树则为:左->根->右 + * + * @param tree + * @param depth + * @param + */ + public static void dfsInOrderTraversal(TreeNode tree, int depth) { + if (tree.getLeft() != null) { + dfsInOrderTraversal(tree.getLeft(), depth + 1); + } + + // 打印节点值以及深度 + System.out.print("-->[" + tree.getValue().toString() + "," + depth + "]"); + + if (tree.getRight() != null) { + dfsInOrderTraversal(tree.getRight(), depth + 1); + } + } + +} +``` + + + +### 广度优先搜索(BFS) + +广度优先搜索(Breadth-First Search / BFS)是**优先遍历邻居节点**而不是子节点的图遍历算法。 + +![广度优先搜索](images/Algorithm/广度优先搜索.jpg) + +**BFS一般用来解决最短路径的问题**。和深度优先搜索不同,广度优先的搜索是从起始点出发,一层一层地进行,每层当中的点距离起始点的步数都是相同的,当找到了目的地之后就可以立即结束。广度优先的搜索可以同时从起始点和终点开始进行,称之为双端 BFS。这种算法往往可以大大地提高搜索的效率。 + + + +**代码案例** + +```java +/** + * Breadth-First Search(BFS) + *

+ * 从根节点出发,在横向遍历二叉树层段节点的基础上纵向遍历二叉树的层次。 + *

+ * 数据结构:队列 + * 父节点入队,父节点出队列,先左子节点入队,后右子节点入队。递归遍历全部节点即可 + * + * @author lry + */ +public class BreadthFirstSearch { + + /** + * 树节点 + * + * @param + */ + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class TreeNode { + private V value; + private List> childList; + } + + /** + * 模型: + * .......A + * ...../ \ + * ....B C + * .../ \ / \ + * ..D E F G + * ./ \ / \ + * H I J K + */ + public static void main(String[] args) { + TreeNode treeNodeA = new TreeNode<>("A", new ArrayList<>()); + TreeNode treeNodeB = new TreeNode<>("B", new ArrayList<>()); + TreeNode treeNodeC = new TreeNode<>("C", new ArrayList<>()); + TreeNode treeNodeD = new TreeNode<>("D", new ArrayList<>()); + TreeNode treeNodeE = new TreeNode<>("E", new ArrayList<>()); + TreeNode treeNodeF = new TreeNode<>("F", new ArrayList<>()); + TreeNode treeNodeG = new TreeNode<>("G", new ArrayList<>()); + TreeNode treeNodeH = new TreeNode<>("H", new ArrayList<>()); + TreeNode treeNodeI = new TreeNode<>("I", new ArrayList<>()); + TreeNode treeNodeJ = new TreeNode<>("J", new ArrayList<>()); + TreeNode treeNodeK = new TreeNode<>("K", new ArrayList<>()); + // A->B,C + treeNodeA.getChildList().add(treeNodeB); + treeNodeA.getChildList().add(treeNodeC); + // B->D,E + treeNodeB.getChildList().add(treeNodeD); + treeNodeB.getChildList().add(treeNodeE); + // C->F,G + treeNodeC.getChildList().add(treeNodeF); + treeNodeC.getChildList().add(treeNodeG); + // D->H,I + treeNodeD.getChildList().add(treeNodeH); + treeNodeD.getChildList().add(treeNodeI); + // G->J,K + treeNodeG.getChildList().add(treeNodeJ); + treeNodeG.getChildList().add(treeNodeK); + + System.out.println("递归方式"); + bfsRecursive(Arrays.asList(treeNodeA), 0); + System.out.println(); + System.out.println("非递归方式"); + bfsNotRecursive(treeNodeA); + } + + /** + * 递归遍历 + * + * @param children + * @param depth + * @param + */ + public static void bfsRecursive(List> children, int depth) { + List> thisChildren, allChildren = new ArrayList<>(); + for (TreeNode child : children) { + // 打印节点值以及深度 + System.out.print("-->[" + child.getValue().toString() + "," + depth + "]"); + + thisChildren = child.getChildList(); + if (thisChildren != null && thisChildren.size() > 0) { + allChildren.addAll(thisChildren); + } + } + + if (allChildren.size() > 0) { + bfsRecursive(allChildren, depth + 1); + } + } + + /** + * 非递归遍历 + * + * @param tree + * @param + */ + public static void bfsNotRecursive(TreeNode tree) { + if (tree != null) { + // 跟上面一样,使用 Map 也只是为了保存树的深度,没这个需要可以不用 Map + Queue, Integer>> queue = new ArrayDeque<>(); + Map, Integer> root = new HashMap<>(); + root.put(tree, 0); + queue.offer(root); + + while (!queue.isEmpty()) { + Map, Integer> itemMap = queue.poll(); + TreeNode node = itemMap.keySet().iterator().next(); + int depth = itemMap.get(node); + + //打印节点值以及深度 + System.out.print("-->[" + node.getValue().toString() + "," + depth + "]"); + + if (node.getChildList() != null && !node.getChildList().isEmpty()) { + for (TreeNode child : node.getChildList()) { + Map, Integer> map = new HashMap<>(); + map.put(child, depth + 1); + queue.offer(map); + } + } + } + } + } + +} +``` + + + +### 迪杰斯特拉算法(Dijkstra) + +**迪杰斯特拉(Dijkstra)算法** 是典型最短路径算法,用于计算一个节点到其他节点的最短路径。它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。 + + + +**基本思想** + +通过Dijkstra计算图G中的最短路径时,需要指定起点s(即从顶点s开始计算)。 + +此外,引进两个集合S和U。S的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而U则是记录还未求出最短路径的顶点(以及该顶点到起点s的距离)。 + +初始时,S中只有起点s;U中是除s之外的顶点,并且U中顶点的路径是"起点s到该顶点的路径"。然后,从U中找出路径最短的顶点,并将其加入到S中;接着,更新U中的顶点和顶点对应的路径。 然后,再从U中找出路径最短的顶点,并将其加入到S中;接着,更新U中的顶点和顶点对应的路径。 ... 重复该操作,直到遍历完所有顶点。 + + +**操作步骤** + +- 初始时,S只包含起点s;U包含除s外的其他顶点,且U中顶点的距离为"起点s到该顶点的距离"[例如,U中顶点v的距离为(s,v)的长度,然后s和v不相邻,则v的距离为∞] +- U中选出"距离最短的顶点k",并将顶点k加入到S中;同时,从U中移除顶点k +- 更新U中各个顶点到起点s的距离。之所以更新U中顶点的距离,是由于上一步中确定了k是求出最短路径的顶点,从而可以利用k来更新其它顶点的距离;例如,(s,v)的距离可能大于(s,k)+(k,v)的距离 +- 重复步骤(2)和(3),直到遍历完所有顶点 + + + +**迪杰斯特拉算法图解** + +![img](images/Algorithm/1117043-20170407105053816-427306966.png) + +以上图G4为例,来对迪杰斯特拉进行算法演示(以第4个顶点D为起点): + +![img](images/Algorithm/1117043-20170407105111300-518814658.png) + + **代码案例** + +```java +public class Dijkstra { + // 代表正无穷 + public static final int M = 10000; + public static String[] names = new String[]{"A", "B", "C", "D", "E", "F", "G",}; + + public static void main(String[] args) { + // 二维数组每一行分别是 A、B、C、D、E 各点到其余点的距离, + // A -> A 距离为0, 常量M 为正无穷 + int[][] weight1 = { + {0, 12, M, M, M, 16, 14}, + {12, 0, 10, M, M, 7, M}, + {M, 10, 0, 3, 5, 6, M}, + {M, M, 3, 0, 4, M, M}, + {M, M, 5, 4, 0, 2, 8}, + {16, 7, 6, M, 2, 0, 9}, + {14, M, M, M, 8, 9, 0} + }; + + int start = 0; + int[] shortPath = dijkstra(weight1, start); + System.out.println("==============="); + for (int i = 0; i < shortPath.length; i++) { + System.out.println("从" + names[start] + "出发到" + names[i] + "的最短距离为:" + shortPath[i]); + } + } + + /** + * Dijkstra算法 + * + * @param weight 图的权重矩阵 + * @param start 起点编号start(从0编号,顶点存在数组中) + * @return 返回一个int[] 数组,表示从start到它的最短路径长度 + */ + public static int[] dijkstra(int[][] weight, int start) { + // 顶点个数 + int n = weight.length; + // 标记当前该顶点的最短路径是否已经求出,1表示已求出 + int[] visited = new int[n]; + // 保存start到其他各点的最短路径 + int[] shortPath = new int[n]; + + // 保存start到其他各点最短路径的字符串表示 + String[] path = new String[n]; + for (int i = 0; i < n; i++) { + path[i] = names[start] + "-->" + names[i]; + } + + // 初始化,第一个顶点已经求出 + shortPath[start] = 0; + visited[start] = 1; + + // 要加入n-1个顶点 + for (int count = 1; count < n; count++) { + // 选出一个距离初始顶点start最近的未标记顶点 + int k = -1; + int dMin = Integer.MAX_VALUE; + for (int i = 0; i < n; i++) { + if (visited[i] == 0 && weight[start][i] < dMin) { + dMin = weight[start][i]; + k = i; + } + } + + // 将新选出的顶点标记为已求出最短路径,且到start的最短路径就是dmin + shortPath[k] = dMin; + visited[k] = 1; + + // 以k为中间点,修正从start到未访问各点的距离 + for (int i = 0; i < n; i++) { + // 如果 '起始点到当前点距离' + '当前点到某点距离' < '起始点到某点距离', 则更新 + if (visited[i] == 0 && weight[start][k] + weight[k][i] < weight[start][i]) { + weight[start][i] = weight[start][k] + weight[k][i]; + path[i] = path[k] + "-->" + names[i]; + } + } + } + + for (int i = 0; i < n; i++) { + System.out.println("从" + names[start] + "出发到" + names[i] + "的最短路径为:" + path[i]); + } + + return shortPath; + } + +} +``` + + + +### kruskal(克鲁斯卡尔)算法 + +https://www.cnblogs.com/skywang12345/category/508186.html + + + +## 排序算法 + +十种常见排序算法可以分为两大类: + +- **非线性时间比较类排序** + + 通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。 + +- **线性时间非比较类排序** + + 不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。 + +![排序算法](images/Algorithm/排序算法.png) + +### 冒泡排序(Bubble Sort) + +冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。 + +![冒泡排序](images/Algorithm/冒泡排序.jpg) + +![冒泡排序](images/Algorithm/冒泡排序.gif) + +**算法步骤** + +- 比较相邻的元素。如果第一个比第二个大,就交换它们两个 +- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数 +- 针对所有的元素重复以上的步骤,除了最后一个 +- 重复步骤1~3,直到排序完成 + +**代码实现** + +```java +/** * 冒泡排序 *

* 描述:每轮连续比较相邻的两个数,前数大于后数,则进行替换。每轮完成后,本轮最大值已被移至最后 * * @param arr 待排序数组 */public static int[] bubbleSort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - 1 - i; j++) { // 每次比较2个相邻的数,前一个小于后一个 if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } return arr;} +``` + +以下是冒泡排序算法复杂度: + +| 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | +| :------------- | :------- | :------- | :--------- | +| O(n²) | O(n) | O(n²) | O(1) | + +冒泡排序是最容易实现的排序, 最坏的情况是每次都需要交换, 共需遍历并交换将近n²/2次, 时间复杂度为O(n²). 最佳的情况是内循环遍历一次后发现排序是对的, 因此退出循环, 时间复杂度为O(n)。平均来讲, 时间复杂度为O(n²). 由于冒泡排序中只有缓存的temp变量需要内存空间, 因此空间复杂度为常量O(1)。 + +Tips: 由于冒泡排序只在相邻元素大小不符合要求时才调换他们的位置, 它并不改变相同元素之间的相对顺序, 因此它是稳定的排序算法。 + + + +### 选择排序(Selection Sort) + +选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。 + +![选择排序](images/Algorithm/选择排序.jpg) + +![选择排序](images/Algorithm/选择排序.gif) + +**算法描述** + +n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下: + +- 初始状态:无序区为R[1..n],有序区为空 +- 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区 +- n-1趟结束,数组有序化了 + +**代码实现** + +```java +/** * 选择排序 *

* 描述:每轮选择出最小值,然后依次放置最前面 * * @param arr 待排序数组 */public static int[] selectSort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { // 选最小的记录 int min = i; for (int j = i + 1; j < arr.length; j++) { if (arr[min] > arr[j]) { min = j; } } // 内层循环结束后,即找到本轮循环的最小的数以后,再进行交换:交换a[i]和a[min] if (min != i) { int temp = arr[i]; arr[i] = arr[min]; arr[min] = temp; } } return arr;} +``` + +以下是选择排序复杂度: + +| 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | +| :------------- | :------- | :------- | :--------- | +| O(n²) | O(n²) | O(n²) | O(1) | + +选择排序的简单和直观名副其实,这也造就了它”出了名的慢性子”,无论是哪种情况,哪怕原数组已排序完成,它也将花费将近n²/2次遍历来确认一遍。即便是这样,它的排序结果也还是不稳定的。 唯一值得高兴的是,它并不耗费额外的内存空间。 + + + +### 插入排序(Insertion Sort) + +插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序由于操作不尽相同,可分为 `直接插入排序`、`折半插入排序`(又称二分插入排序)、`链表插入排序`、`希尔排序` 。 + +![插入排序](images/Algorithm/插入排序.jpg) + +![插入排序](images/Algorithm/插入排序.gif) + +**算法描述** + +一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下: + +- 从第一个元素开始,该元素可以认为已经被排序 +- 取出下一个元素,在已经排序的元素序列中从后向前扫描 +- 如果该元素(已排序)大于新元素,将该元素移到下一位置 +- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置 +- 将新元素插入到该位置后 +- 重复步骤2~5 + +**代码实现** + +```java +/** * 直接插入排序 *

* 1. 从第一个元素开始,该元素可以认为已经被排序 * 2. 取出下一个元素,在已经排序的元素序列中从后向前扫描 * 3. 如果该元素(已排序)大于新元素,将该元素移到下一位置 * 4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置 * 5. 将新元素插入到该位置后 * 6. 重复步骤2~5 * * @param arr 待排序数组 */public static int[] insertionSort(int[] arr) { for (int i = 1; i < arr.length; i++) { // 取出下一个元素,在已经排序的元素序列中从后向前扫描 int temp = arr[i]; for (int j = i; j >= 0; j--) { if (j > 0 && arr[j - 1] > temp) { // 如果该元素大于取出的元素temp,将该元素移到下一位置 arr[j] = arr[j - 1]; } else { // 将新元素插入到该位置后 arr[j] = temp; break; } } } return arr;}/** * 折半插入排序 *

* 往前找合适的插入位置时采用二分查找的方式,即折半插入 *

* 交换次数较多的实现 * * @param arr 待排序数组 */public static int[] insertionBinarySort(int[] arr) { for (int i = 1; i < arr.length; i++) { if (arr[i] < arr[i - 1]) { int tmp = arr[i]; // 记录搜索范围的左边界,右边界 int low = 0, high = i - 1; while (low <= high) { // 记录中间位置Index int mid = (low + high) / 2; // 比较中间位置数据和i处数据大小,以缩小搜索范围 if (arr[mid] < tmp) { // 左边指针则一只中间位置+1 low = mid + 1; } else { // 右边指针则一只中间位置-1 high = mid - 1; } } // 将low~i处数据整体向后移动1位 for (int j = i; j > low; j--) { arr[j] = arr[j - 1]; } arr[low] = tmp; } } return arr;} +``` + +插入排序复杂度: + +| 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | +| :------------- | :------- | :------- | :--------- | +| O(n²) | O(n) | O(n²) | O(1) | + +Tips:由于直接插入排序每次只移动一个元素的位, 并不会改变值相同的元素之间的排序, 因此它是一种稳定排序。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 + + + +### 希尔排序(Shell Sort) + +1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫**缩小增量排序**。 + +![希尔排序](images/Algorithm/希尔排序.gif) + +**算法描述** + +先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述: + +- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1 +- 按增量序列个数k,对序列进行k 趟排序 +- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度 + +**代码实现** + +```java +/** * 希尔排序 *

* 1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;(一般初次取数组半长,之后每次再减半,直到增量为1) * 2. 按增量序列个数k,对序列进行k 趟排序; * 3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。 * 仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。 * * @param arr 待排序数组 */public static int[] shellSort(int[] arr) { int gap = arr.length / 2; // 不断缩小gap,直到1为止 for (; gap > 0; gap /= 2) { // 使用当前gap进行组内插入排序 for (int j = 0; (j + gap) < arr.length; j++) { for (int k = 0; (k + gap) < arr.length; k += gap) { if (arr[k] > arr[k + gap]) { int temp = arr[k + gap]; arr[k + gap] = arr[k]; arr[k] = temp; } } } } return arr;} +``` + +以下是希尔排序复杂度: + +| 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | +| :------------- | :--------- | :--------- | :--------- | +| O(nlog2 n) | O(nlog2 n) | O(nlog2 n) | O(1) | + +Tips:希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。动态定义间隔序列的算法是《算法(第4版)》的合著者Robert Sedgewick提出的。  + + + +### 归并排序(Merging Sort) + +归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。 + +![归并排序](images/Algorithm/归并排序.jpg) + +![归并排序](images/Algorithm/归并排序.gif) + +**算法描述** + +**a.递归法**(假设序列共有n个元素) + +①. 将序列每相邻两个数字进行归并操作,形成 floor(n/2)个序列,排序后每个序列包含两个元素; +②. 将上述序列再次归并,形成 floor(n/4)个序列,每个序列包含四个元素; +③. 重复步骤②,直到所有元素排序完毕。 + +**b.迭代法** + +①. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列 +②. 设定两个指针,最初位置分别为两个已经排序序列的起始位置 +③. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置 +④. 重复步骤③直到某一指针到达序列尾 +⑤. 将另一序列剩下的所有元素直接复制到合并序列尾 + +**代码实现** + +```java +/** * 归并排序(递归) *

* ①. 将序列每相邻两个数字进行归并操作,形成 floor(n/2)个序列,排序后每个序列包含两个元素; * ②. 将上述序列再次归并,形成 floor(n/4)个序列,每个序列包含四个元素; * ③. 重复步骤②,直到所有元素排序完毕。 * * @param arr 待排序数组 */public static int[] mergeSort(int[] arr) { return mergeSort(arr, 0, arr.length - 1);}private static int[] mergeSort(int[] arr, int low, int high) { int center = (high + low) / 2; if (low < high) { // 递归,直到low==high,也就是数组已不能再分了, mergeSort(arr, low, center); mergeSort(arr, center + 1, high); // 当数组不能再分,开始归并排序 mergeSort(arr, low, center, high); } return arr;}private static void mergeSort(int[] a, int low, int mid, int high) { int[] temp = new int[high - low + 1]; int i = low, j = mid + 1, k = 0; // 把较小的数先移到新数组中 while (i <= mid && j <= high) { if (a[i] < a[j]) { temp[k++] = a[i++]; } else { temp[k++] = a[j++]; } } // 把左边剩余的数移入数组 while (i <= mid) { temp[k++] = a[i++]; } // 把右边边剩余的数移入数组 while (j <= high) { temp[k++] = a[j++]; } // 把新数组中的数覆盖nums数组 for (int x = 0; x < temp.length; x++) { a[x + low] = temp[x]; }} +``` + +以下是归并排序算法复杂度: + +| 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | +| :------------- | :-------- | :-------- | :--------- | +| O(nlog₂n) | O(nlog₂n) | O(nlog₂n) | O(n) | + +从效率上看,归并排序可算是排序算法中的”佼佼者”. 假设数组长度为n,那么拆分数组共需logn,, 又每步都是一个普通的合并子数组的过程, 时间复杂度为O(n), 故其综合时间复杂度为O(nlogn)。另一方面, 归并排序多次递归过程中拆分的子数组需要保存在内存空间, 其空间复杂度为O(n)。 + +Tips:和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是`O(nlogn)`的时间复杂度。代价是需要额外的内存空间。 + + + +### 快速排序(Quick Sort) + +快速排序(Quicksort)是对冒泡排序的一种改进,借用了分治的思想,由C. A. R. Hoare在1962年提出。基本思想是通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。 + +![快速排序](images/Algorithm/快速排序.gif) + +**算法描述** + +快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下: + +- 从数列中挑出一个元素,称为 “基准”(pivot) +- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作 +- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序 + +**代码实现** + +```java +/** * 快速排序(递归) *

* ①. 从数列中挑出一个元素,称为"基准"(pivot)。 * ②. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。 * ③. 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。 * * @param arr 待排序数组 */public static int[] quickSort(int[] arr) { return quickSort(arr, 0, arr.length - 1);}private static int[] quickSort(int[] arr, int low, int high) { if (arr.length <= 0 || low >= high) { return arr; } int left = low; int right = high; // 挖坑1:保存基准的值 int temp = arr[left]; while (left < right) { // 坑2:从后向前找到比基准小的元素,插入到基准位置坑1中 while (left < right && arr[right] >= temp) { right--; } arr[left] = arr[right]; // 坑3:从前往后找到比基准大的元素,放到刚才挖的坑2中 while (left < right && arr[left] <= temp) { left++; } arr[right] = arr[left]; } // 基准值填补到坑3中,准备分治递归快排 arr[left] = temp; quickSort(arr, low, left - 1); quickSort(arr, left + 1, high); return arr;}/** * 快速排序(非递归) *

* ①. 从数列中挑出一个元素,称为"基准"(pivot)。 * ②. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。 * ③. 把分区之后两个区间的边界(low和high)压入栈保存,并循环①、②步骤 * * @param arr 待排序数组 */public static int[] quickSortByStack(int[] arr) { Stack stack = new Stack<>(); // 初始状态的左右指针入栈 stack.push(0); stack.push(arr.length - 1); while (!stack.isEmpty()) { // 出栈进行划分 int high = stack.pop(); int low = stack.pop(); int pivotIdx = partition(arr, low, high); // 保存中间变量 if (pivotIdx > low) { stack.push(low); stack.push(pivotIdx - 1); } if (pivotIdx < high && pivotIdx >= 0) { stack.push(pivotIdx + 1); stack.push(high); } } return arr;}private static int partition(int[] arr, int low, int high) { if (arr.length <= 0) return -1; if (low >= high) return -1; int l = low; int r = high; // 挖坑1:保存基准的值 int pivot = arr[l]; while (l < r) { // 坑2:从后向前找到比基准小的元素,插入到基准位置坑1中 while (l < r && arr[r] >= pivot) { r--; } arr[l] = arr[r]; // 坑3:从前往后找到比基准大的元素,放到刚才挖的坑2中 while (l < r && arr[l] <= pivot) { l++; } arr[r] = arr[l]; } // 基准值填补到坑3中,准备分治递归快排 arr[l] = pivot; return l;} +``` + +以下是快速排序算法复杂度: + +| 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | +| :------------- | :-------- | :------- | :--------------------- | +| O(nlog₂n) | O(nlog₂n) | O(n²) | O(1)(原地分区递归版) | + +快速排序排序效率非常高。 虽然它运行最糟糕时将达到O(n²)的时间复杂度, 但通常平均来看, 它的时间复杂为O(nlogn), 比同样为O(nlogn)时间复杂度的归并排序还要快. 快速排序似乎更偏爱乱序的数列, 越是乱序的数列, 它相比其他排序而言, 相对效率更高。 + +Tips: 同选择排序相似, 快速排序每次交换的元素都有可能不是相邻的, 因此它有可能打破原来值为相同的元素之间的顺序. 因此, 快速排序并不稳定。 + + + +### 基数排序(Radix Sort) + +基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。 + +![基数排序](images/Algorithm/基数排序.gif) + +**算法描述** + +- 取得数组中的最大数,并取得位数 +- arr为原始数组,从最低位开始取每个位组成radix数组 +- 对radix进行计数排序(利用计数排序适用于小范围数的特点) + +**代码实现** + +```java +/** + * 基数排序(LSD 从低位开始) + *

+ * 基数排序适用于: + * (1)数据范围较小,建议在小于1000 + * (2)每个数值都要大于等于0 + *

+ * ①. 取得数组中的最大数,并取得位数; + * ②. arr为原始数组,从最低位开始取每个位组成radix数组; + * ③. 对radix进行计数排序(利用计数排序适用于小范围数的特点); + * + * @param arr 待排序数组 + */ +public static int[] radixSort(int[] arr) { + // 取得数组中的最大数,并取得位数 + int max = 0; + for (int item : arr) { + if (max < item) { + max = item; + } + } + int maxDigit = 1; + while (max / 10 > 0) { + maxDigit++; + max = max / 10; + } + + // 申请一个桶空间 + int[][] buckets = new int[10][arr.length]; + int base = 10; + + // 从低位到高位,对每一位遍历,将所有元素分配到桶中 + for (int i = 0; i < maxDigit; i++) { + // 存储各个桶中存储元素的数量 + int[] bktLen = new int[10]; + + // 分配:将所有元素分配到桶中 + for (int value : arr) { + int whichBucket = (value % base) / (base / 10); + buckets[whichBucket][bktLen[whichBucket]] = value; + bktLen[whichBucket]++; + } + + // 收集:将不同桶里数据挨个捞出来,为下一轮高位排序做准备,由于靠近桶底的元素排名靠前,因此从桶底先捞 + int k = 0; + for (int b = 0; b < buckets.length; b++) { + for (int p = 0; p < bktLen[b]; p++) { + arr[k++] = buckets[b][p]; + } + } + + base *= 10; + } + + return arr; +} +``` + +以下是基数排序算法复杂度,其中k为最大数的位数: + +| 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | +| :------------- | :--------- | :--------- | :--------- | +| O(d*(n+r)) | O(d*(n+r)) | O(d*(n+r)) | O(n+r) | + +其中,**d 为位数,r 为基数,n 为原数组个数**。在基数排序中,因为没有比较操作,所以在复杂上,最好的情况与最坏的情况在时间上是一致的,均为 `O(d*(n + r))`。基数排序更适合用于对时间, 字符串等这些**整体权值未知的数据**进行排序。 + +Tips: 基数排序不改变相同元素之间的相对顺序,因此它是稳定的排序算法。 + + + +### 堆排序(Heap Sort) + +堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆排序的过程就是将待排序的序列构造成一个堆,选出堆中最大的移走,再把剩余的元素调整成堆,找出最大的再移走,重复直至有序。 + +**算法描述** + +- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区 +- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n] +- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成 + +**动图演示** + +![堆排序](images/Algorithm/堆排序.gif) + +**代码实现** + +```java +/** * 堆排序算法 * * @param arr 待排序数组 */public static int[] heapSort(int[] arr) { // 将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆 for (int i = arr.length / 2 - 1; i >= 0; i--) { adjustHeap(arr, i, arr.length); } // 将最大的节点放在堆尾,然后从根节点重新调整 for (int j = arr.length - 1; j > 0; j--) { // 交换 int temp = arr[j]; arr[j] = arr[0]; arr[0] = temp; // 完成将以i对应的非叶子结点的树调整成大顶堆 adjustHeap(arr, 0, j); } return arr;}/** * 功能: 完成将以i对应的非叶子结点的树调整成大顶堆 * * @param arr 待排序数组 * @param i 表示非叶子结点在数组中索引 * @param length 表示对多少个元素继续调整, length 是在逐渐的减少 */private static void adjustHeap(int[] arr, int i, int length) { // 先取出当前元素的值,保存在临时变量 int temp = arr[i]; //开始调整。说明:1. k = i * 2 + 1 k 是 i结点的左子结点 for (int k = i * 2 + 1; k < length; k = k * 2 + 1) { // 说明左子结点的值小于右子结点的值 if (k + 1 < length && arr[k] < arr[k + 1]) { // k 指向右子结点 k++; } // 如果子结点大于父结点 if (arr[k] > temp) { // 把较大的值赋给当前结点 arr[i] = arr[k]; // i 指向 k,继续循环比较 i = k; } else { break; } } //当for 循环结束后,我们已经将以i 为父结点的树的最大值,放在了 最顶(局部)。将temp值放到调整后的位置 arr[i] = temp;} +``` + +| 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | +| :------------- | :------------- | :------------- | :--------- | +| O(n \log_{2}n) | O(n \log_{2}n) | O(n \log_{2}n) | O(1) | + +Tips: **由于堆排序中初始化堆的过程比较次数较多, 因此它不太适用于小序列.** 同时由于多次任意下标相互交换位置, 相同元素之间原本相对的顺序被破坏了, 因此, 它是不稳定的排序. + + + +### 计数排序(Counting Sort) + +计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。 + +**算法描述** + +- 找出待排序的数组中最大和最小的元素 +- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项 +- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加) +- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1 + +**动图演示** + +![计数排序](images/Algorithm/计数排序.gif) + +**代码实现** + +```java +/** * 计数排序算法 * * @param arr 待排序数组 */public static int[] countingSort(int[] arr) { // 得到数列的最大值与最小值,并算出差值d int max = arr[0]; int min = arr[0]; for (int i = 1; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } if (arr[i] < min) { min = arr[i]; } } int d = max - min; // 创建统计数组并计算统计对应元素个数 int[] countArray = new int[d + 1]; for (int value : arr) { countArray[value - min]++; } // 统计数组变形,后面的元素等于前面的元素之和 int sum = 0; for (int i = 0; i < countArray.length; i++) { sum += countArray[i]; countArray[i] = sum; } // 倒序遍历原始数组,从统计数组找到正确位置,输出到结果数组 int[] sortedArray = new int[arr.length]; for (int i = arr.length - 1; i >= 0; i--) { sortedArray[countArray[arr[i] - min] - 1] = arr[i]; countArray[arr[i] - min]--; } return sortedArray;} +``` + +**算法分析** + +计数排序是一个稳定的排序算法。当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。 + + + +### 桶排序(Bucket Sort) + +桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。 + +![桶排序](images/Algorithm/桶排序.png) + +![BucketSort](images/Algorithm/BucketSort.gif) + +**算法描述** + +- 设置一个定量的数组当作空桶 +- 遍历输入数据,并且把数据一个一个放到对应的桶里去 +- 对每个不是空的桶进行排序 +- 从不是空的桶里把排好序的数据拼接起来 + +**代码实现** + +```java +/** * 桶排序算法 * * @param arr 待排序数组 */public static int[] bucketSort(int[] arr) { // 计算最大值与最小值 int max = Integer.MIN_VALUE; int min = Integer.MAX_VALUE; for (int value : arr) { max = Math.max(max, value); min = Math.min(min, value); } // 计算桶的数量 int bucketNum = (max - min) / arr.length + 1; List> bucketArr = new ArrayList<>(bucketNum); for (int i = 0; i < bucketNum; i++) { bucketArr.add(new ArrayList<>()); } // 将每个元素放入桶 for (int value : arr) { int num = (value - min) / (arr.length); bucketArr.get(num).add(value); } // 对每个桶进行排序 for (List integers : bucketArr) { Collections.sort(integers); } // 将桶中的元素赋值到原序列 int index = 0; for (List integers : bucketArr) { for (Integer integer : integers) { arr[index++] = integer; } } return arr;} +``` + +**算法分析** + +桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。 + + + +# Case + +## 双指针 + +### 删除排序数组中的重复项 + +给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 + +```java +/** + * 双指针解决 + */ +public int removeDuplicates(int[] A) { + // 边界条件判断 + if (A == null || A.length == 0){ + return 0; + } + + int left = 0; + for (int right = 1; right < A.length; right++){ + // 如果左指针和右指针指向的值一样,说明有重复的, + // 这个时候,左指针不动,右指针继续往右移。如果他俩 + // 指向的值不一样就把右指针指向的值往前挪 + if (A[left] != A[right]){ + A[++left] = A[right]; + } + } + return ++left; +} +``` + + + +### 移动零 + +给定一个数组 `nums`,编写一个函数将所有 `0` 移动到数组的末尾,同时保持非零元素的相对顺序。 + +**示例:**输入: [0,1,0,3,12],输出: [1,3,12,0,0] + +可以参照双指针的思路解决,指针j是一直往后移动的,如果指向的值不等于0才对他进行操作。而i统计的是前面0的个数,我们可以把j-i看做另一个指针,它是指向前面第一个0的位置,然后我们让j指向的值和j-i指向的值交换 + + +```java +public void moveZeroes(int[] nums) { + int i = 0;// 统计前面0的个数 + for (int j = 0; j < nums.length; j++) { + if (nums[j] == 0) {//如果当前数字是0就不操作 + i++; + } else if (i != 0) { + //否则,把当前数字放到最前面那个0的位置,然后再把 + //当前位置设为0 + nums[j - i] = nums[j]; + nums[j] = 0; + } + } +} +``` + + + +## 反转 + +### 旋转数组 + +给定一个数组,将数组中的元素向右移动 `k` 个位置,其中 `k` 是非负数。 + +先反转全部数组,在反转前k个,最后在反转剩余的,如下所示: + +![旋转数组](images/Algorithm/旋转数组.png) + + +```java +public void rotate(int[] nums, int k) { + int length = nums.length; + k %= length; + reverse(nums, 0, length - 1);//先反转全部的元素 + reverse(nums, 0, k - 1);//在反转前k个元素 + reverse(nums, k, length - 1);//接着反转剩余的 +} + +//把数组中从[start,end]之间的元素两两交换,也就是反转 +public void reverse(int[] nums, int start, int end) { + while (start < end) { + int temp = nums[start]; + nums[start++] = nums[end]; + nums[end--] = temp; + } +} +``` + + + +## 栈 + +### 判断字符串括号是否合法 + +字符串中只有字符'('和')'。合法字符串需要括号可以配对。如:(),()(),(())是合法的。)(,()(,(()是非法的。 + +**① 栈** + +![判断字符串括号是否合法](images/Algorithm/判断字符串括号是否合法.gif) + +```java +/** + * 判断字符串括号是否合法 + * + * @param s + * @return + */ +public boolean isValid(String s) { + // 当字符串本来就是空的时候,我们可以快速返回true + if (s == null || s.length() == 0) { + return true; + } + + // 当字符串长度为奇数的时候,不可能是一个有效的合法字符串 + if (s.length() % 2 == 1) { + return false; + } + + // 消除法的主要核心逻辑: + Stack t = new Stack<>(); + for (int i = 0; i < s.length(); i++) { + // 取出字符 + char c = s.charAt(i); + if (c == '(') { + // 如果是'(',那么压栈 + t.push(c); + } else if (c == ')') { + // 如果是')',那么就尝试弹栈 + if (t.empty()) { + // 如果弹栈失败,那么返回false + return false; + } + t.pop(); + } + } + + return t.empty(); +} +``` + +**复杂度分析**:每个字符只入栈一次,出栈一次,所以时间复杂度为 O(N),而空间复杂度为 O(N),因为最差情况下可能会把整个字符串都入栈。 + + + +**② 栈深度扩展** + +如果仔细观察,你会发现,栈中存放的元素是一样的。全部都是左括号'(',除此之外,再也没有别的元素,优化方法如下。**栈中元素都相同时,实际上没有必要使用栈,只需要记录栈中元素个数。** 我们可以通过画图来解决这个问题,如下图所示: + +![leftBraceNumber加减](images/Algorithm/leftBraceNumber加减.gif) + +```java +/** + * 判断字符串括号是否合法 + * + * @param s + * @return + */ +public boolean isValid(String s) { + // 当字符串本来就是空的时候,我们可以快速返回true + if (s == null || s.length() == 0) { + return true; + } + + // 当字符串长度为奇数的时候,不可能是一个有效的合法字符串 + if (s.length() % 2 == 1) { + return false; + } + + // 消除法的主要核心逻辑: + int leftBraceNumber = 0; + for (int i = 0; i < s.length(); i++) { + // 取出字符 + char c = s.charAt(i); + if (c == '(') { + // 如果是'(',那么压栈 + leftBraceNumber++; + } else if (c == ')') { + // 如果是')',那么就尝试弹栈 + if (leftBraceNumber <= 0) { + // 如果弹栈失败,那么返回false + return false; + } + --leftBraceNumber; + } + } + + return leftBraceNumber == 0; +} +``` + +**复杂度分析**:每个字符只入栈一次,出栈一次,所以时间复杂度为 O(N),而空间复杂度为 O(1),因为我们已经只用一个变量来记录栈中的内容。 + + + +**③ 栈广度扩展** + +接下来再来看看如何进行广度扩展。观察题目可以发现,栈中只存放了一个维度的信息:左括号'('和右括号')'。如果栈中的内容变得更加丰富一点,就可以得到下面这道扩展题。给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。有效字符串需满足: + +- 左括号必须用相同类型的右括号闭合 +- 左括号必须以正确的顺序闭合 +- 注意空字符串可被认为是有效字符串 + +```java +/** + * 判断字符串括号是否合法 + * + * @param s + * @return + */ +public boolean isValid(String s) { + if (s == null || s.length() == 0) { + return true; + } + if (s.length() % 2 == 1) { + return false; + } + + Stack t = new Stack<>(); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == '{' || c == '(' || c == '[') { + t.push(c); + } else if (c == '}') { + if (t.empty() || t.peek() != '{') { + return false; + } + t.pop(); + } else if (c == ']') { + if (t.empty() || t.peek() != '[') { + return false; + } + t.pop(); + } else if (c == ')') { + if (t.empty() || t.peek() != '(') { + return false; + } + t.pop(); + } else { + return false; + } + } + + return t.empty(); +} +``` + + + +### 大鱼吃小鱼 + +在水中有许多鱼,可以认为这些鱼停放在 x 轴上。再给定两个数组 Size,Dir,Size[i] 表示第 i 条鱼的大小,Dir[i] 表示鱼的方向 (0 表示向左游,1 表示向右游)。这两个数组分别表示鱼的大小和游动的方向,并且两个数组的长度相等。鱼的行为符合以下几个条件: + +- 所有的鱼都同时开始游动,每次按照鱼的方向,都游动一个单位距离 +- 当方向相对时,大鱼会吃掉小鱼; +- 鱼的大小都不一样。 + +输入:Size = [4, 3, 2, 1, 5], Dir = [1, 1, 1, 1, 0],输出:1。请完成以下接口来计算还剩下几条鱼? + +![大鱼吃小鱼](images/Algorithm/大鱼吃小鱼.gif) + +```java +/** + * 大鱼吃小鱼 + * + * @param fishSize + * @param fishDirection + * @return + */ +public int solution(int[] fishSize, int[] fishDirection) { + // 首先拿到鱼的数量: 如果鱼的数量小于等于1,那么直接返回鱼的数量 + final int fishNumber = fishSize.length; + if (fishNumber <= 1) { + return fishNumber; + } + + // 0表示鱼向左游 + final int left = 0; + // 1表示鱼向右游 + final int right = 1; + Stack t = new Stack<>(); + for (int i = 0; i < fishNumber; i++) { + // 当前鱼的情况:1,游动的方向;2,大小 + final int curFishDirection = fishDirection[i]; + final int curFishSize = fishSize[i]; + // 当前的鱼是否被栈中的鱼吃掉了 + boolean hasEat = false; + // 如果栈中还有鱼,并且栈中鱼向右,当前的鱼向左游,那么就会有相遇的可能性 + while (!t.empty() && fishDirection[t.peek()] == right && curFishDirection == left) { + // 如果栈顶的鱼比较大,那么把新来的吃掉 + if (fishSize[t.peek()] > curFishSize) { + hasEat = true; + break; + } + // 如果栈中的鱼较小,那么会把栈中的鱼吃掉,栈中的鱼被消除,所以需要弹栈。 + t.pop(); + } + // 如果新来的鱼,没有被吃掉,那么压入栈中。 + if (!hasEat) { + t.push(i); + } + } + + return t.size(); +} +``` + + + +### 找出数组中右边比我小的元素 + +一个整数数组 A,找到每个元素:右边第一个比我小的下标位置,没有则用 -1 表示。输入:[5, 2],输出:[1, -1]。 + +```java +/** + * 找出数组中右边比我小的元素 + * + * @param A + * @return + */ +public static int[] findRightSmall(int[] A) { + // 结果数组 + int[] ans = new int[A.length]; + // 注意,栈中的元素记录的是下标 + Stack t = new Stack<>(); + for (int i = 0; i < A.length; i++) { + final int x = A[i]; + // 每个元素都向左遍历栈中的元素完成消除动作 + while (!t.empty() && A[t.peek()] > x) { + // 消除的时候,记录一下被谁消除了 + ans[t.peek()] = i; + // 消除时候,值更大的需要从栈中消失 + t.pop(); + } + // 剩下的入栈 + t.push(i); + } + // 栈中剩下的元素,由于没有人能消除他们,因此,只能将结果设置为-1。 + while (!t.empty()) { + ans[t.peek()] = -1; + t.pop(); + } + return ans; +} +``` + + + +### 字典序最小的 k 个数的子序列 + +给定一个正整数数组和 k,要求依次取出 k 个数,输出其中数组的一个子序列,需要满足:1. **长度为 k**;2.**字典序最小**。 + +输入:nums = [3,5,2,6], k = 2,输出:[2,6] + +**解释**:在所有可能的解:{[3,5], [3,2], [3,6], [5,2], [5,6], [2,6]} 中,[2,6] 字典序最小。所谓字典序就是,给定两个数组:x = [x1,x2,x3,x4],y = [y1,y2,y3,y4],如果 0 ≤ p < i,xp == yp 且 xi < yi,那么我们认为 x 的字典序小于 y。 + +```java +/** + * 字典序最小的 k 个数的子序列 + * + * @param nums + * @param k + * @return + */ +public int[] findSmallSeq(int[] nums, int k) { + int[] ans = new int[k]; + Stack s = new Stack(); + // 这里生成单调栈 + for (int i = 0; i < nums.length; i++) { + final int x = nums[i]; + final int left = nums.length - i; + // 注意我们想要提取出k个数,所以注意控制扔掉的数的个数 + while (!s.empty() && (s.size() + left > k) && s.peek() > x) { + s.pop(); + } + s.push(x); + } + // 如果递增栈里面的数太多,那么我们只需要取出前k个就可以了。 + // 多余的栈中的元素需要扔掉。 + while (s.size() > k) { + s.pop(); + } + // 把k个元素取出来,注意这里取的顺序! + for (int i = k - 1; i >= 0; i--) { + ans[i] = s.peek(); + s.pop(); + } + return ans; +} +``` + + + +## FIFO队列 + +### 二叉树的层次遍历(两种方法) + +从上到下按层打印二叉树,同一层结点按从左到右的顺序打印,每一层打印到一行。输入: + +![二叉树的层次遍历](images/Algorithm/二叉树的层次遍历.png) + +输出:[[3], [9, 8], [6, 7]] + +```java +// 二叉树结点的定义 +public class TreeNode { + // 树结点中的元素值 + int val = 0; + // 二叉树结点的左子结点 + TreeNode left = null; + // 二叉树结点的右子结点 + TreeNode right = null; +} +``` + +**Queue表示FIFO队列解法**: + +![Queue表示FIFO队列解法](images/Algorithm/Queue表示FIFO队列解法.gif) + +```java +public List> levelOrder(TreeNode root) { + // 生成FIFO队列 + Queue Q = new LinkedList<>(); + // 如果结点不为空,那么加入FIFO队列 + if (root != null) { + Q.offer(root); + } + + // ans用于保存层次遍历的结果 + List> ans = new LinkedList<>(); + // 开始利用FIFO队列进行层次遍历 + while (Q.size() > 0) { + // 取出当前层里面元素的个数 + final int qSize = Q.size(); + // 当前层的结果存放于tmp链表中 + List tmp = new LinkedList<>(); + // 遍历当前层的每个结点 + for (int i = 0; i < qSize; i++) { + // 当前层前面的结点先出队 + TreeNode cur = Q.poll(); + // 把结果存放当于当前层中 + tmp.add(cur.val); + // 把下一层的结点入队,注意入队时需要非空才可以入队。 + if (cur.left != null) { + Q.offer(cur.left); + } + if (cur.right != null) { + Q.offer(cur.right); + } + } + + // 把当前层的结果放到返回值里面。 + ans.add(tmp); + } + + return ans; +} +``` + +**ArrayList表示FIFO队列解法**: + +![ArrayList表示FIFO队列解法](images/Algorithm/ArrayList表示FIFO队列解法.gif) + +```java +public List> levelOrder(TreeNode root) { + List> ans = new ArrayList<>(); + // 初始化当前层结点 + List curLevel = new ArrayList<>(); + // 注意:需要root不空的时候才加到里面。 + if (root != null) { + curLevel.add(root); + } + while (curLevel.size() > 0) { + // 准备用来存放下一层的结点 + List nextLevel = new ArrayList<>(); + // 用来存放当前层的结果 + List curResult = new ArrayList<>(); + // 遍历当前层的每个结点 + for (TreeNode cur : curLevel) { + // 把当前层的值存放到当前结果里面 + curResult.add(cur.val); + // 生成下一层 + if (cur.left != null) { + nextLevel.add(cur.left); + } + if (cur.right != null) { + nextLevel.add(cur.right); + } + } + // 注意这里的更迭!滚动前进 + curLevel = nextLevel; + // 把当前层的值放到结果里面 + ans.add(curResult); + } + return ans; +} +``` + + + +### 循环队列 + +设计一个可以容纳 k 个元素的循环队列。需要实现以下接口: + +```java +public class MyCircularQueue { + // 参数k表示这个循环队列最多只能容纳k个元素 + public MyCircularQueue(int k){} + // 将value放到队列中, 成功返回true + public boolean enQueue(int value){return false;} + // 删除队首元素,成功返回true + public boolean deQueue(){return false;} + // 得到队首元素,如果为空,返回-1 + public int Front(){return 0;} + // 得到队尾元素,如果队列为空,返回-1 + public int Rear(){return 0;} + // 看一下循环队列是否为空 + public boolean isEmpty(){return false;} + // 看一下循环队列是否已放满k个元素 + public boolean isFull(){return false;} +} +``` + +循环队列的重点在于**循环使用固定空间**,难点在于**控制好 front/rear 两个首尾指示器**。 + +**方法一:k个元素空间** + +只使用 k 个元素的空间,三个变量 front, rear, used 来控制循环队列。标记 k = 6 时,循环队列的三种情况,如下图所示: + +![循环队列k个元素空间](images/Algorithm/循环队列k个元素空间.png) + +```java +public class MyCircularQueue { + // 已经使用的元素个数 + private int used = 0; + // 第一个元素所在位置 + private int front = 0; + // rear是enQueue可在存放的位置,注意开闭原则, [front, rear) + private int rear = 0; + // 循环队列最多可以存放的元素个数 + private int capacity = 0; + // 循环队列的存储空间 + private int[] a = null; + + public MyCircularQueue(int k) { + // 初始化循环队列 + capacity = k; + a = new int[capacity]; + } + + public boolean enQueue(int value) { + // 如果已经放满了 + if (isFull()) { + return false; + } + + // 如果没有放满,那么a[rear]用来存放新进来的元素 + a[rear] = value; + // rear注意取模 + rear = (rear + 1) % capacity; + // 已经使用的空间 + used++; + // 存放成功! + return true; + } + + public boolean deQueue() { + // 如果是一个空队列,当然不能出队 + if (isEmpty()) { + return false; + } + + // 第一个元素取出 + int ret = a[front]; + // 注意取模 + front = (front + 1) % capacity; + // 已经存放的元素减减 + used--; + // 取出元素成功 + return true; + } + + public int front() { + // 如果为空,不能取出队首元素 + if (isEmpty()) { + return -1; + } + + // 取出队首元素 + return a[front]; + } + + public int rear() { + // 如果为空,不能取出队尾元素 + if (isEmpty()) { + return -1; + } + + // 注意:这里不能使用rear - 1 + // 需要取模 + int tail = (rear - 1 + capacity) % capacity; + return a[tail]; + } + + // 队列是否为空 + public boolean isEmpty() { + return used == 0; + } + + // 队列是否满了 + public boolean isFull() { + return used == capacity; + } +} +``` + +**复杂度分析**:入队操作与出队操作都是 O(1)。 + +**方法二:k+1个元素空间** + +方法 1 利用 used 变量对满队列和空队列进行了区分。实际上,这种区分方式还有另外一种办法,使用 k+1 个元素的空间,两个变量 front, rear 来控制循环队列的使用。具体如下: + +- 在申请数组空间的时候,申请 k + 1 个空间 +- 在放满循环队列的时候,必须要保证 rear 与 front 之间有空隙 + +如下图(此时 k = 5)所示: + +![循环队列k+1个元素空间](images/Algorithm/循环队列k+1个元素空间.png) + +```java +public class MyCircularQueue { + // 队列的头部元素所在位置 + private int front = 0; + // 队列的尾巴,注意我们采用的是前开后闭原则, [front, rear) + private int rear = 0; + private int[] a = null; + private int capacity = 0; + + public MyCircularQueue(int k) { + // 初始化队列,注意此时队列中元素个数为 + // k + 1 + capacity = k + 1; + a = new int[k + 1]; + } + + public boolean enQueue(int value) { + // 如果已经满了,无法入队 + if (isFull()) { + return false; + } + // 把元素放到rear位置 + a[rear] = value; + // rear向后移动 + rear = (rear + 1) % capacity; + return true; + } + + public boolean deQueue() { + // 如果为空,无法出队 + if (isEmpty()) { + return false; + } + // 出队之后,front要向前移 + front = (front + 1) % capacity; + return true; + } + + public int front() { + // 如果能取出第一个元素,取a[front]; + return isEmpty() ? -1 : a[front]; + } + + public int rear() { + // 由于我们使用的是前开后闭原则 + // [front, rear) + // 所以在取最后一个元素时,应该是 + // (rear - 1 + capacity) % capacity; + int tail = (rear - 1 + capacity) % capacity; + return isEmpty() ? -1 : a[tail]; + } + + public boolean isEmpty() { + // 队列是否为空 + return front == rear; + } + + public boolean isFull() { + // rear与front之间至少有一个空格 + // 当rear指向这个最后的一个空格时, + // 队列就已经放满了! + return (rear + 1) % capacity == front; + } +} +``` + +**复杂度分析**:入队与出队操作都是 O(1)。 + + + +## 单调队列 + +单调队列属于**双端队列**的一种。双端队列与 FIFO 队列的区别在于: + +- FIFO 队列只能从尾部添加元素,首部弹出元素 +- 双端队列可以从首尾两端 push/pop 元素 + +### 滑动窗口的最大值 + +给定一个数组和滑动窗口的大小,请找出所有滑动窗口里的最大值。输入:nums = [1,3,-1,-3,5,3], k = 3,输出:[3,3,5,5]。 + +![滑动窗口的最大值](images/Algorithm/滑动窗口的最大值.png) + +```java +public class Solution { + + // 单调队列使用双端队列来实现 + private ArrayDeque Q = new ArrayDeque<>(); + + // 入队的时候,last方向入队,但是入队的时候 + // 需要保证整个队列的数值是单调的 + // (在这个题里面我们需要是递减的) + // 并且需要注意,这里是Q.getLast() < val + // 如果写成Q.getLast() <= val就变成了严格单调递增 + private void push(int val) { + while (!Q.isEmpty() && Q.getLast() < val) { + Q.removeLast(); + } + // 将元素入队 + Q.addLast(val); + } + + // 出队的时候,要相等的时候才会出队 + private void pop(int val) { + if (!Q.isEmpty() && Q.getFirst() == val) { + Q.removeFirst(); + } + } + + public int[] maxSlidingWindow(int[] nums, int k) { + List ans = new ArrayList<>(); + for (int i = 0; i < nums.length; i++) { + push(nums[i]); + // 如果队列中的元素还少于k个 + // 那么这个时候,还不能去取最大值 + if (i < k - 1) { + continue; + } + // 队首元素就是最大值 + ans.add(Q.getFirst()); + // 尝试去移除元素 + pop(nums[i - k + 1]); + } + // 将ans转换成为数组返回! + return ans.stream().mapToInt(Integer::valueOf).toArray(); + } + +} +``` + +**复杂度分析**:每个元素都只入队一次,出队一次,每次入队与出队都是 O(1) 的复杂度,因此整个算法的复杂度为 O(n)。 + + + +### 捡金币游戏 + +给定一个数组 A[],每个位置 i 放置了金币 A[i],小明从 A[0] 出发。当小明走到 A[i] 的时候,下一步他可以选择 A[i+1, i+k](当然,不能超出数组边界)。每个位置一旦被选择,将会把那个位置的金币收走(如果为负数,就要交出金币)。请问,最多能收集多少金币? + +输入:[1,-1,-100,-1000,100,3], k = 2,输出:4。 + +**解释**:从 A[0] = 1 出发,收获金币 1。下一步走往 A[2] = -100, 收获金币 -100。再下一步走到 A[4] = 100,收获金币 100,最后走到 A[5] = 3,收获金币 3。最多收获 1 - 100 + 100 + 3 = 4。没有比这个更好的走法了。 + +```java +public class Solution { + public int maxResult(int[] A, int k) { + // 处理掉各种边界条件! + if (A == null || A.length == 0 || k <= 0) { + return 0; + } + final int N = A.length; + // 每个位置可以收集到的金币数目 + int[] get = new int[N]; + // 单调队列,这里并不是严格递减 + ArrayDeque Q = new ArrayDeque(); + for (int i = 0; i < N; i++) { + // 在取最大值之前,需要保证单调队列中都是有效值。 + // 也就是都在区间里面的值 + // 当要求get[i]的时候, + // 单调队列中应该是只能保存[i-k, i-1]这个范围 + if (i - k > 0) { + if (!Q.isEmpty() && Q.getFirst() == get[i - k - 1]) { + Q.removeFirst(); + } + } + // 从单调队列中取得较大值 + int old = Q.isEmpty() ? 0 : Q.getFirst(); + get[i] = old + A[i]; + // 入队的时候,采用单调队列入队 + while (!Q.isEmpty() && Q.getLast() < get[i]) { + Q.removeLast(); + } + Q.addLast(get[i]); + } + return get[N - 1]; + } + +} +``` + +**复杂度分析**:每个元素只入队一次,出队一次,每次入队与出队复杂度都是 O(n)。因此,时间复杂度为 O(n),空间复杂度为 O(n)。 + + + +# Design Pattern + +在Java编程语言中,常用的设计模式可分为三种类型: + +- **建造类设计模式**:主要用于定义和约束如何创建一个新的对象 +- **结构类设计模式**:主要用于定义如何使用多个对象组合出一个或多个复合对象 +- **行为类设计模式**:主要用于定义和描述对象之间的交互规则和限定对象的职责边界线 + + + +设计模式功能: + +- **建造类** + - **单例(Singleton)模式**:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式 + - **工厂方法(Factory Method)模式**:定义一个用于创建产品的接口,由子类决定生产什么产品 + - **抽象工厂(AbstractFactory)模式**:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品 + - **建造者(Builder)模式**:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象 + - **原型(Prototype)模式**:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例 + +- **结构类** + - **适配器(Adapter)模式**:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作 + - **组合(Composite)模式**:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性 + - **代理(Proxy)模式**:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性 + - **享元(Flyweight)模式**:运用共享技术来有效地支持大量细粒度对象的复用 + - **外观(Facade)模式**:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问 + - **桥接(Bridge)模式**:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度 + - **装饰(Decorator)模式**:动态的给对象增加一些职责,即增加其额外的功能 + +- **行为类** + - **模板方法(TemplateMethod)模式**:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤 + - **策略(Strategy)模式**:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户 + - **命令(Command)模式**:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开 + - **职责链(Chain of Responsibility)模式**:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合 + - **状态(State)模式**:允许一个对象在其内部状态发生改变时改变其行为能力 + - **观察者(Observer)模式**:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为 + - **中介者(Mediator)模式**:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解 + - **迭代器(Iterator)模式**:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示 + - **访问者(Visitor)模式**:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问 + - **备忘录(Memento)模式**:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它 + - **解释器(Interpreter)模式**:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器 + + + +## 创建型模式 + +建造类设计模式提供了对创建对象的基本定义和约束条件,以寻求最佳的实例化Java对象解决方案。 + +### 简单工厂模式 + +![简单工厂模式](images/Algorithm/简单工厂模式.png) + + + +### 工厂模式-Factory + +在Java程序设计过程中,当一个超类(super class)具有多个子类(sub class),且需要频繁的创建子类对象时,我们可以采用工厂模式。工厂模式的作用是将子类的实例化工作统一交由工厂类来完成,通过对输入参数的判断,工厂类自动实例化具体的子类。实现工厂模式需要满足三个条件: + +- 超类(super class):超类是一个抽象类 +- 子类(sub class):子类需继承超类 +- 工厂类(factory class):工厂类根据输入参数实例化子类 + +![工厂模式](images/Algorithm/工厂模式.png) + + + +### 抽象工厂模式-Abstract Factory + +抽象工厂模式与工厂模式很类似,抽象工厂模式可以简单的理解为“工厂的工厂”。在工厂模式中,根据提供的输入参数返回产品类的实例化对象,这个过程需要通过if-else或者switch这样的逻辑判断语句来完成具体子类的判定。而在抽象工厂模式中,每种产品都有具体的工厂类与之对应,从而避免在编码过程中使用大量的逻辑判断代码。抽象工厂模式会根据输入的工厂类型以返回具体的工厂子类。抽象工厂类只负责实例化工厂子类,不参与商品子类的实例化工作。 + + + + + +### 单例模式-Singleton + +单例模式限制类的实例化过程,以确保在Java虚拟机(JVM)中有且只有一个类的实例化对象。单例模式是Java中最常用,也是最简单的设计模式之一。单例模式通常需具备如下的几个特征: + +- 单例模式限制类的实例化,且Java虚拟机中只能存在一个该类的示例化对象 +- 单例模式必须提供一个全局可用的访问入口来获取该类的实例化对象 +- 单例模式常被用于日志记录,驱动程序对象设计,缓存以及线程池 +- 单例模式也会被用于其他的设计模式当中,如抽象工厂模式,建造者模式,原型模式等 + +![单例模式](images/Algorithm/单例模式.png) + + + +### 建造者模式-Builder + +建造者模式通常被用于需要多个步骤创建对象的场景中。建造者模式的主要意图是将类的构建逻辑转移到类的实例化之外,当一个类有许多的属性,当在实例化该类的对象时,并不一定拥有该实例化对象的全部属性信息,便可使用建造者模式通过逐步获取实例化对象的属性信息,来完成该类的实例化过程。而工厂模式和抽象工厂模式需要在实例化时获取该类实例化对象的全部属性信息。 + +![建造者模式](images/Algorithm/建造者模式.png) + + + +### 原型模式-Prototype + +原型模式的主要作用是可以利用现有的类通过复制(克隆)的方式创建一个新的对象。当示例化一个类的对象需要耗费大量的时间和系统资源时,可是采用原型模式,将原始已存在的对象通过复制(克隆)机制创建新的对象,然后根据需要,对新对象进行修改。原型模式要求被复制的对象自身具备拷贝功能,此功能不能由外界完成。 + + + + + +## 结构型模式 + +结构类设计模式主要解决如何通过多个小对象组合出一个大对象的问题,如使用继承和接口实现将多个类组合在一起。 + +### 适配器模式-Adapter + +适配器模式的主要作用是使现有的多个可用接口能够在一起为客服端提供新的接口服务。在适配器模式中,负责连接不同接口的对象成为适配器。在现实生活中,我们也能够找到很多实际的案例来理解适配器的工作原理,例如常用的手机充电头,在手机和电源插座之间,手机充电头就扮演一个适配器的角色,它能够同时适配220V,200V,120V等不同的电压,最终将电转换成手机可用的5V电压为手机进行充电。 + +![适配器模式-默认适配器](images/Algorithm/适配器模式-默认适配器.png) + +![适配器模式-对象适配器](images/Algorithm/适配器模式-对象适配器.png) + +![适配器模式-类继承](images/Algorithm/适配器模式-类继承.png) + + + +### 组合模式-Composite + +组合模式的主要作用是让整体与局部之前具有相同的行为。例如我们需要绘制一个图形(正方形,三角形,圆形或其他多边形),首先需要准备一张空白的纸,然后是选择一种绘制图案的颜色,再次是确定绘制图案的大小,最后是绘制图案。不管是绘制正方形还是三角形,都需要按照这个步骤进行。在软件设计过程中,组合模式的最大意义在于保证了客户端在调用单个对象与组合对象时,在其操作流程上是保持一致的。 + +**案例**:每个员工都有姓名、部门、薪水这些属性,同时还有下属员工集合(虽然可能集合为空),而下属员工和自己的结构是一样的,也有姓名、部门这些属性,同时也有他们的下属员工集合。 + +![组合模式](images/Algorithm/组合模式.png) + + + +### 代理模式-Proxy + +代理模式的主要作用是通过提供一个代理对象或者一个占位符来控制对实际对象的访问行为。代理模式通常用于需要频繁操作一些复杂对象的地方,通过使用代理模式,可以借由代理类来操作目标对象,简化操作流程。 + +![代理模式](images/Algorithm/代理模式.png) + + + +### 享元模式-Flywight + +享元模式的主要作用是通过共享来有效地支持大量细粒度的对象。例如当需要创建一个类的很多对象时,可以使用享元模式,通过共享对象信息来减轻内存负载。如果在软件设计过程中采用享元模式,需要考虑以下三个问题: + +- 应用程序需要创建的对象数量是否很大? +- 对象的创建对内存消耗和时间消耗是否有严格的要求? +- 对象的属性是否可以分为内在属性和外在属性?对象的外在属性是否支持有客户端定义? + + + + + +### 门面模式-Facade + +门面模式(也叫外观模式)的主要作用是为子系统中的一组接口提供一个统一的接口,以便客户端更容易去使用子系统中的接口。简单的理解是外观模式为众多复杂接口定义了一个更高级别的接口。外观模式的目的是让接口更容易被使用。 + +![门面模式](images/Algorithm/门面模式.png) + + + +### 桥梁模式-Bridge + +桥梁模式的主要用途是将抽象类与抽象类的具体实现相分离,以实现结构上的解耦,使抽象和实现可以独立的进行变化。桥梁模式的实现优先遵循组合而不是继承,当使用桥梁模式时,在一定程度上可以在客户端中因此接口的内部实现。 + +![桥梁模式-1](images/Algorithm/桥梁模式-1.png) + +![桥梁模式-2](images/Algorithm/桥梁模式-2.png) + + + +### 修饰模式-Decorator + +修饰模式的主要作用是在运行时动态的组合类的行为。通常,你会添加一些新的类或者新的方法来扩展已有的代码库,然而,在某些情况下你需要在程序运行时为某个对象组合新的行为,此时你可以采用修饰模式。 + + + +### 过滤器模式-Filter + +过滤器模式是使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式将对象组合起来。 + + + + + +## 行为型模式 + +行为类设计模式主要用于定义和描述对象之间的交互规则和职责边界,为对象之间更好的交互提供解决方案。 + +### 模板方法模式-Template Method + +模板方法模式的主要作用是在一个方法里实现一个算法,可以将算法中的的一些步骤抽象为方法,并将这些方法的实现推迟到子类中去实现。例如建造一栋房子,我们需要设计图纸,打地基,构筑墙体,安装门窗和内部装修。我们可以设计不同的房屋样式(别墅,高楼,板房等),不同的门窗和不同的装修材料和风格,但是其顺序不能颠倒。在这种情况下,我们可以定义一个模板方法,规定方法的执行顺序,而将方法的实现推迟到子类中完成。 + +![模板方法模式](images/Algorithm/模板方法模式.png) + + + +### 解释器模式-Mediator + +解释器(中介)模式的主要设计意图是定义一个中间对象,封装一组对象的交互,从而降低对象的耦合度,避免了对象间的显示引用,并可以独立地改变对象的行为。解释器(中介)模式可以在系统中的不同对象之间提供集中式的交互介质,降低系统中各组件的耦合度。 + + + + + +### 责任链模式-Chain of Responsibility + +责任链模式主要作用是让多个对象具有对同一任务(请求)的处理机会,以解除请求发送者与接收者之间的耦合度。try-catch就是一个典型的责任链模式的应用案例。在try-catch语句中,可以同时存在多个catch语句块,每个catch语句块都是处理该特定异常的处理器。当try语句块中发生异常是,异常将被发送到第一个catch语句块进行处理,如果第一个语句块无法处理它,它将会被请求转发到链中的下一个catch语句块。如果最后一个catch语句块仍然不能处理该异常,则该异常将会被向上抛出。 + +![责任链模式](images/Algorithm/责任链模式.png) + + + +### 观察者模式-Observer + +观察者模式的目的是在多个对象之间定义一对多的依赖关系,当一个对象的状态发生改变时,观察者会通知依赖它的对象,并根据新状态做出相应的反应。简单来说,如果你需要在对象状态发生改变时及时收到通知,你可以定义一个监听器,对该对象的状态进行监听,此时的监听器即为观察者(Observer),被监听对象称为主题(Subject)。Java消息服务(JMS)即采用了观察者设计模式(同时还使用了中介模式),允许应用程序订阅数据并将数据发布到其他应用程序中。 + +![观察者模式](images/Algorithm/观察者模式.png) + + + +### 策略模式-Strategy + +策略模式的主要目的是将可互换的方法封装在各自独立的类中,并且让每个方法都实现一个公共的操作。策略模式定义了策略的输入与输出,实现则由各个独立的类完成。策略模式可以让一组策略共存,代码互不干扰,它不仅将选择策略的逻辑从策略本身中分离出来,还帮助我们组织和简化了代码。一个典型的例子是Collections.sort()方法,采用Comparator作为方法参数,根据Comparator接口实现类的不同,对象将以不同的方式进行排序。 + +![策略模式](images/Algorithm/策略模式.png) + + + +### 命令模式-Command + +命令模式的设计意图是将请求封装在对象的内部。直接调用是执行方法的通常做法,然而,在有些时候我们无法控制方法被执行的时机和上下文信息。在这种情况下,可以将方法封装到对象的内部,通过在对象内部存储调用方所需要的信息,就可以让客户端或者服务自由决定何时调用方法。 + +![命令模式](images/Algorithm/命令模式.png) + + + +### 状态模式-State + +状态模式的设计意图是更具对象的状态改变其行为。如果我们必须根据对象的状态改变对象的行为,可以在对象中定义一个状态变量,并使用逻辑判断语句块(如if-else)根据状态执行不同的操作。 + +![状态模式](images/Algorithm/状态模式.png) + + + +### 访客模式-Visitor + +访客模式的设计意图是在不改变现有类层次结构的前提下,对该层次结构进行扩展。例如在购物网站中,我们将不同的商品添加进购物车,然后支付按钮时,它会计算出需要支付的总金额数。我们可以在购物车类中完成金额的计算,也可以使用访客模式,将购物应付金额逻辑转移到新的类中。 + + + +### 转义模式-Interpreter + +转义(翻译)模式的设计意图是让你根据事先定义好的一系列组合规则,组合可执行的对象。实现转义(翻译)模式的一个基本步骤如下: + +- 创建执行解释工作的上下文引擎 +- 根据不同的表达式实现类,实现上下文中的解释工作 +- 创建一个客户端,客户端从用户那里获取输入,并决定使用哪一种表达式来输出转义后的内容 + + + +### 迭代器模式-Iterator + +迭代器模式为迭代一组对象提供了一个标准的方法。迭代器模式被广泛的应用于Java Collection框架中,Iterator接口提供了遍历集合元素的方法。迭代器模式不仅仅是遍历集合,我们还可以根据不同的要求提供不同类型的迭代器。迭代器模式通过集合隐藏内部的遍历细节,客户端只需要使用对应的迭代方法即可完成元素的遍历操作。 + + + +### 备忘录模式-Memento + +备忘录模式的设计意图是为对象的状态提供存储和恢复功能。备忘录模式由两个对象来实现-Originator和Caretaker。Originator需要具有保存和恢复对象状态的能力,它使用内部类来保存对象的状态。内部内则称为备忘录,因为它是对象私有的,因此外部类不能直接访问它。 + diff --git a/Architecture.md b/Architecture.md new file mode 100644 index 0000000..c5c5f25 --- /dev/null +++ b/Architecture.md @@ -0,0 +1,4418 @@ +

Architecture
+ +Author:李茹钰(`echo`) + +Introduction:收纳技术相关的 `架构` 等总结! + +[TOC] + +# 架构演进 + +## 软件架构模式 + +有没有想过要设计多大的企业规模系统?在主要的软件开发开始之前,我们必须选择一个合适的体系结构,它将为我们提供所需的功能和质量属性。因此,在将它们应用到我们的设计之前,我们应该了解不同的体系结构。 + +![软件架构模式](images/Architecture/软件架构模式.png) + +架构模式是一个通用的、可重用的解决方案,用于在给定上下文中的软件体系结构中经常出现的问题。架构模式与软件设计模式类似,但具有更广泛的范围。 + + + +### 分层模式 + +这种模式也称为多层体系架构模式。它可以用来构造可以分解为子任务组的程序,每个子任务都处于一个特定的抽象级别。每个层都为下一个提供更高层次服务。 + +一般信息系统中最常见的是如下所列的4层。 + +- 表示层(也称为UI层) +- 应用层(也称为服务层) +- 业务逻辑层(也称为领域层) +- 数据访问层(也称为持久化层) + +使用场景: + +- 一般的桌面应用程序 +- 电子商务Web应用程序 + +![Layered-pattern](images/Architecture/Layered-pattern.png) + + + +### 客户端-服务器模式 + +这种模式由两部分组成:一个服务器和多个客户端。服务器组件将为多个客户端组件提供服务。客户端从服务器请求服务,服务器为这些客户端提供相关服务。此外,服务器持续侦听客户机请求。 + +使用场景: + +- 电子邮件,文件共享和银行等在线应用程序 + +![Client-server-pattern](images/Architecture/Client-server-pattern.png) + + + +### 主从设备模式 + +这种模式由两方组成;主设备和从设备。主设备组件在相同的从设备组件中分配工作,并计算最终结果,这些结果是由从设备返回的结果。 + +使用场景: + +- 在数据库复制中,主数据库被认为是权威的来源,并且要与之同步 +- 在计算机系统中与总线连接的外围设备(主和从驱动器) + +![Master-slave-pattern](images/Architecture/Master-slave-pattern.png) + + + +### 管道-过滤器模式 + +此模式可用于构造生成和处理数据流的系统。每个处理步骤都封装在一个过滤器组件内。要处理的数据是通过管道传递的。这些管道可以用于缓冲或用于同步。 + +使用场景: + +- 编译器。连续的过滤器执行词法分析、解析、语义分析和代码生成 +- 生物信息学的工作流 + +![Pipe-filter-pattern](images/Architecture/Pipe-filter-pattern.png) + + + +### 代理模式 + +此模式用于构造具有解耦组件的分布式系统。这些组件可以通过远程服务调用彼此交互。代理组件负责组件之间的通信协调。 + +服务器将其功能(服务和特征)发布给代理。客户端从代理请求服务,然后代理将客户端重定向到其注册中心的适当服务。 + +使用场景: + +- 消息代理软件,如Apache ActiveMQ,Apache Kafka,RabbitMQ和JBoss Messaging + +![Broker-pattern](images/Architecture/Broker-pattern.png) + + + +### 点对点模式 + +在这种模式中,单个组件被称为对等点。对等点可以作为客户端,从其他对等点请求服务,作为服务器,为其他对等点提供服务。对等点可以充当客户端或服务器或两者的角色,并且可以随时间动态地更改其角色。 + +使用场景: + +- 像Gnutella和G2这样的文件共享网络 +- 多媒体协议,如P2PTV和PDTP +- 像Spotify这样的专有多媒体应用程序 + +![Peer-to-peer-pattern](images/Architecture/Peer-to-peer-pattern.png) + + + +### 事件总线模式 + +这种模式主要是处理事件,包括4个主要组件:事件源、事件监听器、通道和事件总线。消息源将消息发布到事件总线上的特定通道上。侦听器订阅特定的通道。侦听器会被通知消息,这些消息被发布到它们之前订阅的一个通道上。 + +使用场景: + +- 安卓开发 +- 通知服务 + +![Event-bus-pattern](images/Architecture/Event-bus-pattern.png) + + + +### 模型-视图-控制器模式 + +这种模式,也称为MVC模式,把一个交互式应用程序划分为3个部分, + +- 模型:包含核心功能和数据 +- 视图:将信息显示给用户(可以定义多个视图) +- 控制器:处理用户输入的信息 + +这样做是为了将信息的内部表示与信息的呈现方式分离开来,并接受用户的请求。它分离了组件,并允许有效的代码重用。 + +使用场景: + +- 在主要编程语言中互联网应用程序的体系架构 +- 像Django和Rails这样的Web框架 + +![Model-view-controller-pattern](images/Architecture/Model-view-controller-pattern.png) + + + +### 黑板模式 + +这种模式对于没有确定解决方案策略的问题是有用的。黑板模式由3个主要组成部分组成。 + +- 黑板——包含来自解决方案空间的对象的结构化全局内存 +- 知识源——专门的模块和它们自己的表示 +- 控制组件——选择、配置和执行模块 + +所有的组件都可以访问黑板。组件可以生成添加到黑板上的新数据对象。组件在黑板上查找特定类型的数据,并通过与现有知识源的模式匹配来查找这些数据。 + +使用场景: + +- 语音识别 +- 车辆识别和跟踪 +- 蛋白质结构识别 +- 声纳信号的解释 + +![Blackboard-pattern](images/Architecture/Blackboard-pattern.png) + + + +### 解释器模式 + +这个模式用于设计一个解释用专用语言编写的程序的组件。它主要指定如何评估程序的行数,即以特定的语言编写的句子或表达式。其基本思想是为每种语言的符号都有一个分类。 + +使用场景: + +- 数据库查询语言,比如SQL +- 用于描述通信协议的语言 + +![Interpreter-pattern](images/Architecture/Interpreter-pattern.png) + + + +## 单体应用架构(Monoliths) + +互联网早期,一般的网站应用流量较小,只需一个应用,将所有功能代码都部署在一起就可以,这样可以减少开发、部署和维护的成本。比如说一个电商系统,里面会包含很多用户管理,商品管理,订单管理,物流管理等等很多模块,我们会把它们做成一个web项目,然后部署到一台tomcat服务器上。 + +![单体应用架构](images/Architecture/单体应用架构.png) + +**优点** + +- 项目架构简单,小型项目的话, 开发成本低 +- 项目部署在一个节点上, 维护方便 + +**缺点** + +- 全部功能集成在一个工程中,对于大型项目来讲不易开发和维护 +- 项目模块之间紧密耦合,单点容错率低 +- 无法针对不同模块进行针对性优化和水平扩展 + + + +## 垂直应用架构(Vertical) + +随着访问量的逐渐增大,单一应用只能依靠增加节点来应对,但是这时候会发现并不是所有的模块都会有比较大的访问量。还是以上面的电商为例子, 用户访问量的增加可能影响的只是用户和订单模块, 但是对消息模块的影响就比较小. 那么此时我们希望只多增加几个订单模块, 而不增加消息模块. 此时单体应用就做不到了, 垂直应用就应运而生了。所谓的垂直应用架构,就是将原来的一个应用拆成互不相干的几个应用,以提升效率。比如我们可以将上面电商的单体应用拆分成: + +- 电商系统(用户管理 商品管理 订单管理) +- 后台系统(用户管理 订单管理 客户管理) +- CMS系统(广告管理 营销管理) + +这样拆分完毕之后,一旦用户访问量变大,只需要增加电商系统的节点就可以了,而无需增加后台和CMS的节点。 + +![垂直应用架构](images/Architecture/垂直应用架构.png) + +**优点** + +- 系统拆分实现了流量分担,解决了并发问题,而且可以针对不同模块进行优化和水扩展 +- 一个系统的问题不会影响到其他系统,提高容错率 + +**缺点** + +- 系统之间相互独立, 无法进行相互调用 +- 系统之间相互独立, 会有重复的开发任务 + + + +## 分布式架构(Distributed) + +即分布式架构当垂直应用越来越多,重复的业务代码就会越来越多。这时候,我们就思考可不可以将重复的代码抽取出来,做成统一的业务层作为独立的服务,然后由前端控制层调用不同的业务层服务呢? + +这就产生了新的分布式系统架构。它将把工程拆分成表现层和服务层两个部分,服务层中包含业务逻辑。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。 + +![分布式架构](images/Architecture/分布式架构.png) + +**优点** + +- 抽取公共的功能为服务层,提高代码复用性 + +**缺点** + +- 系统间耦合度变高,调用关系错综复杂,难以维护 + + + +## 面向服务化架构(SOA) + +在分布式架构下,当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心对集群进行实时管理。此时,用于资源调度和治理中心(SOA Service Oriented Architecture,面向服务的架构)是关键。 + +![SOA架构](images/Architecture/SOA架构.png) + +**优点** + +- 使用注册中心解决了服务间调用关系的自动调节 + +**缺点** + +- 服务间会有依赖关系,一旦某个环节出错会影响较大( 服务雪崩 ) +- 服务关心复杂,运维、测试部署困难 + + + +## 微服务架构(Micro Service) + +https://juejin.cn/user/4177799914474653/posts + +https://www.cnblogs.com/jiujuan/p/13280473.html + +微服务架构在某种程度上是面向服务的架构SOA继续发展的下一步,它更加强调服务的"彻底拆分"。 + +![微服务架构](images/Architecture/微服务架构.png) + +**优点** + +- 更好的开发规模 +- 更快的开发速度 +- 支持迭代开发或现代化增量开发 +- 充分利用现代软件开发生态系统的优势(云、容器、 DevOps、Serverless) +- 支持水平缩放和细粒度缩放 +- 小体量,较低了开发人员的认知复杂性 + +**缺点** + +- 更高数量级的活动组件(服务、数据库、进程、容器、框架) +- 复杂性从代码转移到基础设施 +- RPC 调用和网络通信的大量增加 +- 整个系统的安全性管理更具有挑战性 +- 整个系统的设计变得更加困难 +- 引入了分布式系统的复杂性 + + + +### 微服务定义 + +![微服务定义](images/Architecture/微服务定义.jpg) + +- **一组小的服务** + + 原来的单块服务都是业务能力大而全的打包在一个单块中,微服务主张把这些单块服务进行拆分,形成一个个小的独立的服务。这里有个最大的特点是“小”,那么纠结要小到什么程度才为之小,很多同学都会纠结这个小的点,因为这个小并没有特别和明确的规定,所以这也就引申出了现在很多DDD领域驱动设计来指引微服务的拆分,但基本上一个微服务能让一个开发人员能够独立的理解,基本上就称为一个微服务,具体有多少行代码并不是很关键。 + +- **独立的进程** + + 微服务是运行在独立的进程当中,例如java程序部署在tomcat,也可以部署在容器docker中,容易本身也是一种进程,所以微服务可以以进程的方式去扩展。 + +- **轻量级的通讯** + + 微服务主张使用轻量级去构建通讯机制,例如http,固定消息格式和减少消息格式,服务之间不耦合,让通讯尽量轻量。 + +- **基于业务能力** + + 微服务是基于业务能力进行构建,例如有用户服务,登陆服务,商品服务,基于这些业务能力去构建这些微服务。 + +- **独立部署** + + 微服务被拆分开后,每个团队独立维护自己的微服务,开发,迭代自己的微服务,是可以独立的去部署,团队之间是不需要特别的去协调,这些对业务开发维护可以做到更加的敏捷,轻量,快速。 + +- **无集中式管理** + + 原来单体服务是需要整个技术团队是需要独立的架构团队去管理,统一架构,统一技术栈,统一存储,微服务就不太一样,微服务主张每个团队根据自己的技术需要,选择自己最熟悉,最高效解决问题的技术栈,甚至选择不同存储方式。 + + + +### 康威定律 + +> 康威法则设计系统的组织,其产生的设计和架构,等价组织的组织架构 + + + +**单块应用时代** + +![康威定律-单块应用](images/Architecture/康威定律-单块应用.jpg) + +几个团队共同去对一个单块应用去开发和维护时,如果一个团队对这个单块应用进行改造引入一些新的功能或技术的时候,往往需要其他的团队协作和配合,连同做集成测试才能交付这个应用,这个时候,不仅仅是沟通协调成本高,团队和团队之间往往容易产生摩擦。**也就是说,多团队之间和单应用产生不匹配,违反康威法则**。怎么解决,微服务是一个解决的手段: + +![康威定律-微服务解决方案](images/Architecture/康威定律-微服务解决方案.jpg) + +我们把单块的应用拆分成诺干个独立的应用,每个团队负责自己的服务,相互之间不干扰,当团队A服务的服务进行修改不需要其他的团队来配合,或者说这种配合沟通成本比较少,一般只发生在双方边界交集的地方,那么这个时候发现多团队和微服务之间架构的关系可以映射起来,它符合了康威法则,整体研发效率更高效。 + + + +### 微服务利弊 + +![微服务利弊](images/Architecture/微服务利弊.jpg) + +- **利** + + - **强模块化边界** + + 我们知道做软件架构,软件设计,模块化是非常重要的一点,一开始我们写程序做软件,我们采用类的方式来做模块化,后面开始采用组件或类库的方式做模块化,可以做到工程上的重用和分享给其他团队来使用。微服务在组件的层次上面又高了一层,以服务的方式来做模块化,每个团队独立开始和维护自己的服务,有明显的一个边界,开发完一个服务其他团队可以直接调用这个服务,不需要像组件通过jar或源码的方式去进行分享,所以微服务的边界是比较清晰的。 + + - **可独立部署** + + 可独立部署是微服务最显著的一个特性,每个团队可以根据自己的业务需求,当产品经理或业务方把需求提过来,可以根据需要独立开发和部署服务,一般来说不需要太过依赖其他团队去协同,这个对比单块应用,单块引用在这个方面需求很多团队来协助和帮忙。 + + - **技术多样性** + + 微服务是分散式治理,没有集中治理,每个团队可以根据团队自己的实际情况和业务的实际情况去选择适合自己的技术栈,有些团队可能擅长Java开发,有些团队可能更偏向前端,更适合用nodejs去开发服务,不过这个不是越多越好,技术栈的引入也是有成本 + +- **弊** + + - **分布式复杂性** + + 在原来单块应用就是一个应用,一个对单块应用的架构比较熟悉的人可以对整个单块应用有一个很好的把控。但是到了分布式系统,微服务化了以后可能涉及到的服务有好几十个,一些大公司可能涉及到的服务上百个,服务与服务之间是通过相互沟通来实现业务,那么这个时候整个系统就变成非常复杂,一般的开发人员或一个团队都无法理解整个系统是如何工作的,这个就是分布式带来的复杂性。 + + - **最终一致性** + + 微服务的数据是分散式治理的,每个团队都有自己的数据源和数据拷贝,比方说团队A有订单数据,B团队也有订单数据,团队A修改了订单数据是否应该同步给团队B的数据呢,这里就涉及到数据一致性问题,如果没有很好的解决一致性问题,就可能造成数据的不一致,这个在业务上是不可以接受的。 + + - **运维复杂性** + + 以往的运维需要管理的是机器+单块的应用,分布式系统和单块应用不一样的是,分布式系统需要很多的服务,服务与服务之间相互协同,那么对分布式系统的资源,容量规划,对监控,对整个系统的可靠性稳定性都非常具备挑战的。 + + - **测试复杂性** + + 对测试人员来说,在单块应用上,一个测试团队只需要测试一个单块应用就可以了,到了分布式系统,各个服务是分布在各个团队的,这个对测试团队来说要求就很好,做集成测试的时候需要很多的团队相互配合去联合做集成测试。 + + + +### 微服务分层 + +**BFF是什么?** + +BFF即 Backend For Frontend(服务于前端的后端),也就是服务器设计 API 时会考虑前端的使用,并在服务端直接进行业务逻辑的处理,又称为用户体验适配器。BFF 只是一种逻辑分层,而非一种技术,虽然 BFF 是一个新名词,但它的理念由来已久。 + + + +一般将微服务整个体系大的方向划分为2层,见下图: + +![微服务分层](images/Architecture/微服务分层.jpg) + +在最上层不属于微服务有很多的连接方式,有PC,有H5,有APP等等,在下层,包含了2个层,它们一起组成我们的微服务或SOA,微服务加单的划分为2层,最底下的基础服务层。 + +- **微服务基础服务层** + + 基础服务一般属于互联网平台基础性的支撑服务,比方说,电商网站的基础服务有订单服务,商品服务,用户服务等,这些都属于比较基础和原子性,下沉一个公司的基础设施的低层,向下承接存储,向上提供业务能力,有些公司叫(基础服务,中间层服务,公共服务),netflix成为中间层服务。我们暂且统称为基础服务。 + +- **微服务聚合服务层** + + 已经有了基础服务能提供业务能力,为什么还需要聚合服务,因为我们有不同的接入端,如app和H5,pc等等,它们看似调用大致相同的数据,但其实存在很多差异,例如PC需要展示更多信息,APP需要做信息裁剪等等。一般低层服务都是比较通用的,基础服务应该对外输出相对统一的服务,在抽象上做得比较好。但是对不同的外界app和pc的接入,我们需要作出不同的适配,这个时候需要有一个层去做出聚合裁剪的工作。例如一个商品详情在pc端展示和app端的展示,pc可能会展示更多的信息,而app则需要对信息作出一些裁剪,如果基础服务直接开放接口给到pc和app,那么基础服务也需要去做成各种设配,这个很不利于基础服务的抽象,所以我们在基础层之上加入聚合服务层,这个层可以针对pc和app做成适当的设配进行相应的裁剪。 + + ![聚合服务](images/Architecture/聚合服务.jpg) + + 除了裁剪,还有一个更重要的原因,假定PC端想获取“我的订单”的一个列表,那么Pc端必须知道和调用基础服务的几个模块(用户服务,订单服务,商品服务),pc端发起几次请求之后,在这个几个请求的数据进行汇总,这样不仅性能低下网络开销也比较大,还有pc端要承接数据聚合的这么一个工作,会让pc端变成更为复杂。如果在中间加多一个层为聚合服务层,即对网络开销进行减少,因为微服务内部是通过内网进行数据传输,也让pc端的业务变得比较简单。 + + ![微服务-聚合服务](images/Architecture/微服务-聚合服务.jpg) + + 这个层在各个公司有不同叫法,有叫 聚合服务,边界服务,设配服务 netflix叫边界服务,因为它处在公司微服务和外部的边界之上。这个的划分只是一个逻辑划分,在物理上或者在微服务上这两个层级其实在部署和调用没有差别。 + + + +**代码分层设计** + +![分层设计](images/Architecture/分层设计.png) + + + +### 技术架构体系 + +![微服务技术架构体系](images/Architecture/微服务技术架构体系.jpg) + +- **接入层** + + 接入层负责把外部的流量接入到内部平台系统中来,涉及到更多是基础设施,由运维团队进行负责。 + +- **基础设施层** + + 基础设施层主要是由运维团队来进行维护,设计由计算,也计算资源的分配,网络,存储,监控,安全等等 + +- **网关层** + + 流量接进来之后,会先经过一个网关层,网关在微服务起到举足轻重的作用,主要起到反向路由,限流熔断,安全,鉴权等等的跨横切面的功能,这个层在微服务中起到核心的层次。 + +- **业务服务层** + + 整个业务服务按照逻辑划分为两次,分别是聚合层和基础服务层,聚合层对基础层进行聚合和裁剪,对外部提供业务能力。当然这个视每个公司不同情况而定,此层再划分两层是属于逻辑划分。 + +- **支撑服务层** + + 微服务并不是单单把业务服务启动起来就完毕,微服务在治理的过程中还需要更多的支持,所以由了支撑服务层。支持服务包含了注册发现,集中配置,容错限流,日志聚合,监控告警,后台服务,后台服务涉及到例如MQ,Job,数据访问,这些都是后台服务的内容。 + +- **平台服务层** + + 在微服务逐步完善的过程中,各个团队的都引入一个新的平台服务,例如由容器,镜像的管理,容器的服务编排等等,很多公司先后的引入容器化和容器编排来解决运维管理和发布微服务的难题,docker + keburnetes,确实是被微服务越来越接受。另外,通过CICD支撑起来的devops也是构建在平台服务层的这个能力。由于这个平台服务层的逐步完善也在慢慢解放运维人员一开始对微服务各种治理的不适。 + + + +### 服务发现机制 + +**第一种 传统Lb模式** + +![传统Lb模式](images/Architecture/传统Lb模式.jpg) + +这个模式有一个独立的Lb,例如可以硬件F5做负载均衡器,也可以用软件,例如nginx来做负载均衡器,一般来说生产者上线后,会想运维申请一个域名,将域名配置到负载均衡器上,生产者的服务会部署多份,Lb具有负载均衡的功能。消费方想要去进行消费,会通过dns做域名解析,dns会解析到Lb上面,Lb会负载均衡到后台的生产者服务。这种做法是最传统的做法,也是最简单,消费者接入成本低,但是生产者发布服务需要运维的介入。还有一点问题就是Lb成为整个服务中转中心,如何确保这个Lb为高可用,另外还有一点,就是消费者调用生产者必须穿透Lb,这当中可能会有一些性能开销。 + + + +**第二种 进程内Lb模式** + +![进程内Lb模式](images/Architecture/进程内Lb模式.jpg) + +这种做法把传统的Lb转移到进程内,生产者会把自己的信息注册到一个注册中心,并且定期发送心跳建立生产者和注册中心的连接。消费者去监听注册中心,从注册中心获取生产者的列表,Lb存在消费者的进程内,消费者直接使用内部Lb去调用生产者,消费者的Lb会定期去同步注册中心的服务信息。这种做法的好处是没有中间的一跳,不存在集中式Lb的性能短板,也不存在Lb可能存在的单点问题。但是在多语言中,必须每个语言都维护自己的一个Lb,我们熟知的Dubbo就是采用这种进程内Lb模式。 + + + +**第三种 主机独立Lb模式** + +![主机独立Lb模式](images/Architecture/主机独立Lb模式.jpg) + +主机独立Lb模式是在前面传统Lb和进程内Lb的模式上做了折中,它把一个Lb以一个独立进程的方式部署在一台独立主机上,既不是集中式Lb也不是进程内Lb,这种方式跟第二种有一些类似,生产者一样注册到注册中心,主机上的Lb也会定时同步注册中心的注册信息,把注册信息放在本地进程中进行负载均衡,这种方式Lb既不存在消费者的进程内,可以让消费者更专注于业务,还可以免去集中式Lb每次调用都必须进行中转一跳的网络开销,并且也可以支持多语言跟消费者语言脱离关系。不过这种模式可能在运维的成本会比较高,运维需要关注每一台机器的LB。 + + + +### API网关 + +![微服务-API网关-案例](images/Architecture/微服务-API网关-案例.jpg) + +![微服务-API网关-定义](images/Architecture/微服务-API网关-定义.jpg) + +让客户看公司的服务会认为是一个整体的服务。这个就是我们的网关所起到的作用,能够屏蔽我们内部的细节,统一输出对外的接口。 + +![微服务-API网关-架构](images/Architecture/微服务-API网关-架构.jpg) + +从这张图上看到有四个层次,最上层是我们用户层,第二层是我们的负载均衡器,第三层就是我们的网关,最后是我们内部的微服务,在接入网关的时候为什么需要在上面需要一个负载均衡器,因为我们想让网关无状态,无状态的网关有一个好处,可以部署很多,不会有单点,即使挂了一台,其他的网关还在,这个对整个系统的稳定性起来非常重要的作用。一般的系统会有一个LB,然后对应多个网关。网关能起到的作用很多: + +- 最重要的一个重要反向路由,当外面的请求进来之后,怎么找到内部具体的微服务,这个是网关起来重要职责,将外部的调用转化为内部的服务服务,这个就是反向路由 +- 第二个是认证安全,网关像是一个门卫,有一些访问是正常的访问,有一些是恶意的访问,例如说爬虫,甚至是一些黑客行为,网关需要将其拦截在外部- 第三个重要职责是限流熔断,比方说,有一个门,外面有流量进来,正常来说流量是比较稳定的,但也有可能有突发流量,有可能网站在搞促销,这个时候可能就有流量的洪峰闯进来,如果说内部没有好的限流熔断措施,可能造成内部整个服务的服务器瘫痪,网关就要承担限流熔断的职责。 +- 最后一个功能,网关要承担日志监控的职责,外部的访问所有的流量都要经过网关,那么可以在网关上可以对所有的流量做访问的审计,把它作为日志保存起来,另外可以通过分析日志,知道性能的调用情况,能够对整个流量情况进行监控 + + + +### 路由发现体系 + +![Netflix的路由发现体系](images/Architecture/Netflix的路由发现体系.jpg) + +在Netflix的微服务架构中,有两个非常重要的支撑服务 + +- Netflix的大名鼎鼎的注册中心组件叫Eureka +- 另一个Netflix也是大名鼎鼎的网关组件叫Zuul + +Netflix在内部微服务上也是两层的逻辑划分,低层是基础服务(Netflix叫中间层服务),上一层叫聚合服务层,(Netflix叫边界服务),内部服务的发现也是通过注册中心Eureka,基础服务向Eureka进行服务注册,聚合服务通过Eureka进行服务发现,并把聚合层生产者缓存在本身,就可以进行直接的服务调用。 + +网关层是处在外部调用和聚合服务之间的层,网关层可以看作是一个超级的客户端,它一样可以作为微服务的一个组件,也会同步Eureka注册中心的路由表,外部服务请求进来后,网关根据路由表找到对应的聚合服务进行调用。 + +另外注册中心和网关还可以对整个调用进行治理,比方说对服务的调用进行安全管控,哪些服务是是有严格的安全要求,不允许随便进行调用,哪些服务可以通过网关放出去,这些能力可以通过网关和注册中心进行实现。这些就是服务治理相关的能力。 + + + +### 配置中心 + +![配置中心](images/Architecture/配置中心.jpg) + +配置中心可以简单的理解为一个服务模块,开发人员或运维人员可以通过界面对配种中心进行配置,下面相关的微服务连接到配置中心上面就可以实时连接获取到配置中心上面修改的参数。更新的方式一般有两种 + +- pull模式,服务定时去拉取配置中心的数据 +- push模式,服务一直连接到配置中心上,一旦配置有变成,配种中心将把变更的参数推送到对应的微服务上 + +这两种做法其实各有利弊 + +- pull可以保证一定可以拉取得到数据,pull一般采用定时拉取的方式,即使某一次出现网络没有拉取得到数据,那在下一次定时器也将可以拉取得到数据,最终保证能更新得到配置 +- push也有好处,避免pull定时器获取存在时延,基本可以做到准实时的更新,但push也存在问题,如果有网络抖动,某一次push没有推送成功,将丢失这次配置的更新 + +目前比较流行的配种中心开源组件有springcloud-Config,百度的disconf,阿里的diamond,还有携程的apollo,杨波老师之前是在携程工作过,所以主推的是apollo这套系统。 + +![Apollo配置中心](images/Architecture/Apollo配置中心.jpg) + +也是一个服务器,也带有对应的客户端,它的特色在于客户端,客户端有一个缓存机制,每次拉取成功后,会把数据保存在缓存机制中,甚至爬客户端的缓存丢失,客户端还可以将缓存sync在本地文件缓存,这样的设计非常的巧妙,就算apollo的配置中心挂掉了,或者客户端的服务重启了,但是因为本地缓存还存在,还可以使用本地缓存继续对外提供服务,从这点来看apollo的配置中心在高可用上考虑还是比较周到的。 + +另外一点,配置中心有两种获取配置数据的方式,一种pull,一种push,两者各有有点,apollo把两者的优点进行了结合,开发或运维人员在配置中心进行修改,配置中心服务将实时将修改推送push到apollo的客户端,但考虑到可能由于某些网络抖动没有推送成功,客户端还具备了定时向apollo服务端拉取pull数据的功能,就算推送没成功,但是只要一定时间周期,客户端还是会主动去拉取同步数据,保证能把最终配置同步到服务中。这个也是apollo在高可用方面上非常有特色的设计。 + + + +### 微服务治理 + +![微服务治理](images/Architecture/微服务治理.jpg) + +- **服务注册发现** + + 微服务当中有很多服务,几十个上百个,它们当中有错中复杂的依赖关系,这个时候就存在服务的消费者怎么发现生产者,这个就是服务注册发现需要解决的问题。 + +- **服务的负载均衡** + + 为了应对大的流量,我们的服务提供方一般都是大规模部署,这个时候就存在服务的负载均衡的问题,另外我们的服务需要路由,这个能力非常重要,如果我们需要灰度发布或者蓝绿发布的机制,那么需要考虑软路由的机制。 + +- **监控-日志** + + 日志对于我们后期排错找出问题,定位问题是非常关键,我们一套好的监控治理框架需要集成日志服务 + +- **监控-metrics** + + 当我们需要对服务的调用量进行监控,对服务延迟出错数有一个好的监控手段,这就是me监控环境 + +- **监控-调用链埋点** + + 微服务有错综复杂的调用关系,就像一个网状一般,如果没有好的调用链监控,开发人员很容易迷失在当中,出问题很难定位,有了好的调用链监控会帮助我们快速定位问题,更好的理解整套微服务系统。 + +- **限流熔断** + + 微服务是一个分布式系统,如果没有好的限流熔断措施,当一个服务出现故障或者出现延迟,会造成整个系统瘫痪。 + +- **安全-访问控制** + + 有些服务并不希望所有的人都能去调用到,涉及到一些敏感信息,比例跟钱相关的信息,那么我们需要安全和访问的控制策略,来限制对这些服务的访问。 + +- **rpc & rest** + + rpc和rest根据之前的对比,两者各有优劣,如果一个微服务框架中能支持这两个调用,能兼容更多的技术栈,会更加的灵活 + +- **序列化** + + 序列化中,有高性能但对开发人员不优化的二进制序列化,也有对开发人员相当友好的但性能未必最佳的文本式序列化,这个需要根据场景进行灵活的配置,消息系列化协议 + +- **代码生成** + + 现在在大规模开发的情况下,比较推从一个契约驱动开发的方法,开发人员先定立契约,代码自动生成的方式生成对应的代码脚手架,这个在大规模开发的时候更能确保代码的一致和规整 + +- **统一异常处理** + + 我们希望服务治理的环节能集成统一的服务异常处理的能力,这样的化异常能够达到更加标准化,出现问题能更好定位好属于什么类型的问题。如果说没有这样的一个环节,大家各自的玩法不一样,抛的异常各异,出现问题难以定位和无法标准化友好输出。 + +- **文档** + + 微服务最终是要给消费者去使用,暴露出去的API如果没有好的文档,只提供出一些代码,接入方接入的成本会变成比较高,好的文档体系是各方协调和效率的保证。 + +- **集中配置中心** + + 微服务框架需要集成集中式的配置能力,避免各个服务间各自配置,增快参数调整的速度和规范统一格式。 + +- **后台集成MQ,Cache,DB** + + 微服务治理的核心思路就是把上面讲到的各个环节沉淀下来,变成平台和框架的一部分,开发人员可以更加专注业务逻辑的实现,在实现业务逻辑的时候不需要去关注外部环节的,从而提升开发的效率,治理环节沉淀在框架之中有专门的平台架构团队去进行管控。 + + + +### 微服务分层监控 + +**分层监控** + +![微服务分层监控](images/Architecture/微服务分层监控.jpg) + +- **基础设施监控** + + 一般是由运维人员进行负责,涉及到的方面比较接近硬件体系,例如网络,交换机,路由器等低层设备,这些设备的可靠性稳定性就直接影响到上层服务应用的稳定性,所以需要对网络的流量,丢包情况,错包情况,连接数等等这些基础设施的核心指标进行监控。 + +- **系统层监控** + + 涵盖了物理机,虚拟机,操作系统这些都是属于系统级别监控的方面,对几个核心指标监控,如cpu使用率,内存占用率,磁盘IO和网络带宽情况。 + +- **应用层监控** + + 涉及到方面就跟服务紧密相关,例如对url访问的性能,访问的调用数,访问的延迟,还有对服务提供性能进行监控,服务的错误率,对sql也需要进行监控,查看是否有慢sql,对与cache来说,需要监控缓存的命中率和性能,每个服务的响应时间和qps等等。 + +- **业务监控** + + 比方说一个典型的交易网站,需要关注它的用户登录情况,注册情况,下单情况,支付情况,这些直接影响到实际触发的业务交易情况,这个监控可以提供给运营和公司高管他们需需要关注的数据,直接可能对公司战略产生影响。 + +- **端用户体验监控** + + 一个应用程序可能通过app,h5,pc端的方式交付到用户的手上,用户通过浏览器,客户端打开练到到我们的服务,那么在用户端用户的体验是怎么样,用户端的性能是怎么样,有没有产生错误,这些信息也是需要进行监控并记录下来,如果没有监控,有可能用户的因为某些原因出错或者性能问题造成体验非常的差,而我们并没有感知,这里面包括了,监控用户端的使用性能,返回码,在哪些城市地区他们的使用情况是怎么样,还有运营商的情况,包括电信,联通用户的连接情况。我们需要进一步去知道是否有哪些渠道哪些用户接入的时候存在着问题,包括我们还需要知道客户端使用的操作系统浏览器的版本。 + + + +**监控点** + +可以通过以下几点进行监控: + +- 日志监控 +- Metrics监控 +- 调用链监控 +- 报警系统 +- 健康检查 + +![微服务监控点](images/Architecture/微服务监控点.jpg) + + + +**典型主流的监控架构** + +![典型主流的监控架构](images/Architecture/典型主流的监控架构.jpg) + +在微服务运行的体系下,我们一般把监控的agent分散到各个服务身边,agent分别是收集机器和服务的metrics,发送到后台监控系统,一般来说,我们的服务量非常大,在收集的过程中,会加入队列,一般来说用kafka,用消息队列有个好处就是两边可以进行解耦,还好就是可以起到庞大的日志进行一个缓存的地带,并在mq可以做到高可用,保证消息不会丢失。 + +日志收集目前比较流行的是ELK的一套解决方案,(Elasticsearch,Logstash,Kibana),Elasticsearch 分布式搜索引擎,Logstash 是一个日志收集的agent,Kibana 是一个查询的日志界面。 + +metrice会采用一个时间序列的数据库,influxDB是最近比较主流时间数据库。 + +微服务的agent例如springboot也提供了健康检查的端点,可以检查cpu使用情况,内存使用情况,jvm使用情况,这些需要一个健康检查机制,能够定期对服务的健康和机器的健康进行check,比较常见的是nagios,zabbix等,这些开源平台能够定期去检查到各个微服务的检查程序并能够进行告警给相关人员,在服务未奔溃之前就可以进行提前的预先接入。 + + + +### 调用链 + +微服务是一个分布式非常复杂系统,如果没有一套调用链监控,如果服务之间依赖出现问题就很难进行调位。 + +目前个大主流互联网公司中,ali有非常出现的鹰眼系统,点评也有一套很出名的调用链监控系统CAT。调用链监控其实最早是google提出来的,2010年google发表了一篇调用链的论文,论文以它内部的调用链系统dapper命名,这个论文中讲解调用链在google使用的经验和原理,大致的原理如下图: + +![Dapper](images/Architecture/Dapper.jpg) + +在外界对微服务进行一个请求开始进入我们的微服务体系时,会生成一个Root Span,当web服务去调用后面的服务svc1时又会生成一个span,调用DB也会生成一个span,每一个应用调度都会生成一个新的span,这个是span是整个调用链形成的关键,span中有一些关键的信息,有traceId,spanId。RootSpan是比较特殊的,在启动的时候会生成spanId还会生成TraceId,其他的span会生成自己的spanId,为了维护好调用链上下文的调用关系,span会去记录调用它的链路,以parent spanId记录下来,这样的话父子之间的关系就可以记录下来,每个调用都会把第一个链路traceId也记录下来,这样,当我们把这些span都存在起来,就可以通过分析手段,把整个调用链的关系还原出来。 + +这里可以采用ELK的方式去记录和展示调用链监控日志,当我们一条调用为一行记录存储下来: + +![调用行为记录](images/Architecture/调用行为记录.jpg) + +通过traceId 和 parentSpanId 就可以串联起来为一个整体的链路,并可以从这个链路去分析错误或者调用延时和调用次数等等。 + +![调用行为还原](images/Architecture/调用行为还原.jpg) + +目前市面主流的调用链选型有 zipkin,pinpoint,cat,skywalking,他们之间各有一些偏重点,值得一说的是skywalking国人出品的一款新的调用链工具,采用开源的基于字节码注入的调用链分析,接入段无代码入侵,而且开源支持多种插件,UI在几款工具来说比较功能比较强大,而且ui也比较赏心悦目,目前已经加入了apache孵化器。 + +**skywalking优势** + +- 首先在实现方式上,skywalking基本对于代码做到了无入侵,采用java探针和字节码增强的方式,而在cat还采用了代码埋点,而zipkin采用了拦截请求,pinpoint也是使用java探针和字节码增强 +- 其次在分析的颗粒度上,skywaling是方法级,而zipkin是接口级,其他两款也是方法级 +- 在数据存储上,skywalking可以采用日志体系中比较出名的ES,其他几款,zipkin也可以使用ES,pinpoint使用Hbase,cat使用mysql或HDFS,相对复杂,如果公司对ES熟悉的人才比较有保证,选择熟悉存储方案也是考虑技术选型的重点 +- 还有就是性能影响,根据网上的一些性能报告,虽然未必百分百准备,但也具备参考价值,skywalking的探针对吞吐量的影响在4者中间是最效的,经过对skywalking的一些压测也大致证明 + +下面是网上摘录的几款调用链选型对比: + +**基本原理** + +| 类别 | Zipkin | Pinpoint | SkyWalking CAT | +| -------- | ------------------------------------------ | -------------------- | -------------------- | +| 实现方式 | 拦截请求,发送(HTTP,mq)数据至zipkin服务 | java探针,字节码增强 | java探针,字节码增强 | + +**接入** + +| 类别 | Zipkin | Pinpoint | SkyWalking | CAT | +| ---------------------- | --------------------------------------- | ----------------- | --------------- | -------- | +| 接入方式」 | 基于linkerd或者sleuth方式,引入配置即可 | javaagent字节码」 | javaagent字节码 | 代码侵入 | +| agent到collector的协议 | http,MQ | thrift | gRPC | http/tcp | +| OpenTracing | √ | × | √ | × | + +**分析** + +| 类别 | Zipkin | Pinpoint | SkyWalking | CAT | +| ------------ | ------ | -------- | ---------- | ------ | +| 颗粒度 | 接口级 | 方法级 | 方法级 | 代码级 | +| 全局调用统计 | × | √ | √ | √ | +| traceid查询 | √ | × | √ | × | +| 报警 | × | √ | √ | √ | +| JVM监控 | × | × | √ | √ | + +**页面UI展示** + +| 类别 | Zipkin | Pinpoint | SkyWalking | CAT | +| ------ | ------ | ---------- | ---------- | ---------- | +| 健壮度 | \*\* | \*\*\*\*\* | \*\*\*\* | \*\*\*\*\* | + +**数据存储** + +| 类别 | Zipkin | Pinpoint | SkyWalking | CAT | +| -------- | ------------------------ | -------- | ---------- | ---------- | +| 数据存储 | ES,mysql,Cassandra,内存 | Hbase | ES,H2 | mysql,hdfs | + + + +### 流量治理 + +**熔断** + +如果说房子里面安装了电路熔断器,当你使用超大功率的电路时,有熔断设配帮你保护不至于出问题的时候把问题扩大化。 + +**隔离** + +我们知道计算资源都是有限的,cpu,内存,队列,线程池都是资源,他们都是限定的资源数,如果不进行隔离,一个服务的调用可能要消耗很多的线程资源,把其他服务的资源都给占用了,那么可能出现应为一个服务的问题连带效应造成其他服务不能进行访问。 + +**限流** + +让大流量的访问冲进去我们的服务时,我们需要一定的限流措施,比方说我们规则一定时间内只允许一定的访问数从我们的资源过,如果再大的化系统会出现问题,那么就需要限流保护。 + +**降级** + +如果说系统后题无法提供足够的支撑能力,那么需要一个降级能力,保护系统不会被进一步恶化,而且可以对用户提供比较友好的柔性方案,例如告知用户暂时无法访问,请在一段时候后重试等等。 + + + +**Hystrix** + +Hystrix就把上面说的 熔断,隔离,限流,降级封装在这么一个组件里面 下图是Hystrix内部设计和调用流程 + +![Hystrix调用流程](images/Architecture/Hystrix调用流程.jpg) + +大致的工作流如下: + +- 构建一个HystrixCommand对象,用于封装请求,并在构造方法配置请求被执行需要的参数 +- 执行命令,Hystrix提供了几种执行命令的方法,比较常用到的是synchrous和asynchrous +- 判断电路是否被打开,如果被打开,直接进入fallback方法 +- 判断线程池/队列/信号量是否已经满,如果满了,直接进入fallback方法 +- 执行run方法,一般是HystrixCommand.run(),进入实际的业务调用,执行超时或者执行失败抛出未提前预计的异常时,直接进入fallback方法 +- 无论中间走到哪一步都会进行上报metrics,统计出熔断器的监控指标 +- fallback方法也分实现和备用的环节 +- 最后是返回请求响应 + + + +### 设计模式 + +#### 独享数据库(Database per Microservice) + +当一家公司将大型单体系统替换成一组微服务,首先要面临的最重要决策是关于数据库。单体架构会使用大型中央数据库。即使转移到微服务架构许多架构师仍倾向于保持数据库不变。虽然有一些短期收益,但它却是反模式的,特别是在大规模系统中,微服务将在数据库层严重耦合,整个迁移到微服务的目标都将面临失败(例如,团队授权、独立开发等问题)。 + +更好的方法是为每个微服务提供自己的数据存储,这样服务之间在数据库层就不存在强耦合。这里我使用数据库这一术语来表示逻辑上的数据隔离,也就是说微服务可以共享物理数据库,但应该使用分开的数据结构、集合或者表,这还将有助于确保微服务是按照领域驱动设计的方法正确拆分的。 + +![微服务-独享数据库](images/Architecture/微服务-独享数据库.png) + +优点 + +- 数据由服务完全所有 +- 服务的开发团队之间耦合度降低 + +缺点 + +- 服务间的数据共享变得更有挑战性 +- 在应用范围的保证 ACID 事务变得困难许多 +- 细心设计如何拆分单体数据库是一项极具挑战的任务 + +何时使用独享数据库 + +- 在大型企业应用程序中 +- 当团队需要完全把控微服务以实现开发规模扩展和速度提升 + +何时不宜使用独享数据库 + +- 在小规模应用中 +- 如果是单个团队开发所有微服务 + +可用技术示例 + +所有 SQL、 NoSQL 数据库都提供数据的逻辑分离(例如,单独的表、集合、结构、数据库)。 + + + +#### 事件源(Event Sourcing) + +在微服务架构中,特别使用独享数据库时,微服务之间需要进行数据交换。对于弹性高可伸缩的和可容错的系统,它们应该通过交换事件进行异步通信。在这种情况,您可能希望进行类似更新数据库并发送消息这样的原子操作,如果在大数据量的分布式场景使用关系数据库,您将无法使用两阶段锁协议(2PL),因为它无法伸缩。而 NoSQL 数据库因为大多不支持两阶段锁协议甚至无法实现分布式事务。 + +在这些场景,可以基于事件的架构使用事件源模式。在传统数据库中,直接存储的是业务实体的当前“状态”,而在事件源中任何“状态”更新事件或其他重要事件都会被存储起来,而不是直接存储实体本身。这意味着业务实体的所有更改将被保存为一系列不可变的事件。因为数据是作为一系列事件存储的,而非直接更新存储,所以各项服务可以通过重放事件存储中的事件来计算出所需的数据状态。 + +![微服务-事件源](images/Architecture/微服务-事件源.png) + +优点 + +- 为高可伸缩系统提供原子性操作。 +- 自动记录实体变更历史,包括时序回溯功能。 +- 松耦合和事件驱动的微服务。 + +缺点 + +- 从事件存储中读取实体成为新的挑战,通常需要额外的数据存储(CQRS 模式) +- 系统整体复杂性增加了,通常需要领域驱动设计 +- 系统需要处理事件重复(幂等)或丢失 +- 变更事件结构成为新的挑战 + +何时使用事件源 + +- 使用关系数据库的、高可伸缩的事务型系统 +- 使用 NoSQL 数据库的事务型系统 +- 弹性高可伸缩微服务架构 +- 典型的消息驱动或事件驱动系统(电子商务、预订和预约系统) + +何时不宜使用事件源 + +- 使用 SQL 数据库的低可伸缩性事务型系统 +- 在服务可以同步交换数据(例如,通过 API)的简单微服务架构中 + + + +#### 命令和查询职责分离(CQRS) + +如果我们使用事件源,那么从事件存储中读取数据就变得困难了。要从数据存储中获取实体,我们需要处理所有的实体事件。有时我们对读写操作还会有不同的一致性和吞吐量要求。 + +这种情况,我们可以使用 CQRS 模式。在该模式中,系统的数据修改部分(命令)与数据读取部分(查询)是分离的。而 CQRS 模式有两种容易令人混淆的模式,分别是简单的和高级的。 + +在其简单形式中,不同实体或 ORM 模型被用于读写操作,如下所示: + +![微服务-简单CQRS](images/Architecture/微服务-简单CQRS.png) + +它有助于强化单一职责原则和分离关注点,从而实现更简洁的设计。 + +在其高级形式中,会有不同的数据存储用于读写操作。高级的 CQRS 通常结合事件源模式。根据不同情况,会使用不同类型的写数据存储和读数据存储。写数据存储是“记录的系统”,也就是整个系统的核心源头。 + +![微服务-高级CQRS](images/Architecture/微服务-高级CQRS.png) + +对于读频繁的应用程序或微服务架构,OLTP 数据库(任何提供 ACID 事务保证的关系或非关系数据库)或分布式消息系统都可以被用作写存储。对于写频繁的应用程序(写操作高可伸缩性和大吞吐量),需要使用写可水平伸缩的数据库(如全球托管的公共云数据库)。标准化的数据则保存在写数据存储中。 + +对搜索(例如 Apache Solr、Elasticsearch)或读操作(KV 数据库、文档数据库)进行优化的非关系数据库常被用作读存储。许多情况会在需要 SQL 查询的地方使用读可伸缩的关系数据库。非标准化和特殊优化过的数据则保存在读存储中。 + +数据是从写存储异步复制到读存储中的,所以读存储和写存储之间会有延迟,但最终是一致的。 + +优点 + +- 在事件驱动的微服务中数据读取速度更快 +- 数据的高可用性 +- 读写系统可独立扩展 + +缺点 + +- 读数据存储是弱一致性的(最终一致性) +- 整个系统的复杂性增加了,混乱的 CQRS 会显着危害整个项目 + +何时使用 CQRS + +- 在高可扩展的微服务架构中使用事件源 +- 在复杂领域模型中,读操作需要同时查询多个数据存储 +- 在读写操作负载差异明显的系统中 + +何时不宜使用 CQRS + +- 在没有必要存储大量事件的微服务架构中,用事件存储快照来计算实体状态是一个更好的选择 +- 在读写操作负载相近的系统中 + + + +#### Saga + +如果微服务使用独享数据库,那么通过分布式事务管理一致性是一个巨大的挑战。你无法使用传统的两阶段提交协议,因为它要么不可伸缩(关系数据库),要么不被支持(多数非关系数据库)。 + +但您还是可以在微服务架构中使用 Saga 模式实现分布式事务。Saga 是 1987 年开发的一种古老模式,是关系数据库中关于大事务的一个替代概念。但这种模式的一种现代变种对分布式事务也非常有效。Saga 模式是一个本地事务序列,其每个事务在一个单独的微服务内更新数据存储并发布一个事件或消息。Saga 中的首个事务是由外部请求(事件或动作)初始化的,一旦本地事务完成(数据已保存在数据存储且消息或事件已发布),那么发布消息或事件则会触发 Saga 中的下一个本地事务。 + +![微服务-Saga](images/Architecture/微服务-Saga.png) + +如果本地事务失败,Saga 将执行一系列补偿事务来回滚前面本地事务的更改。 + +Saga 事务协调管理主要有两种形式: + +- 事件编排 Choreography:分散协调,每个微服务生产并监听其他微服务的事件或消息然后决定是否执行某个动作 +- 命令编排 Orchestration:集中协调,由一个协调器告诉参与的微服务哪个本地事务需要执行 + +优点 + +- 为高可伸缩或松耦合的、事件驱动的微服务架构提供一致性事务 +- 为使用了不支持 2PC 的非关系数据库的微服务架构提供一致性事务 + +缺点 + +- 需要处理瞬时故障,并且提供等幂性 +- 难以调试,而且复杂性随着微服务数量增加而增加 + +何时使用 Saga + +- 在使用了事件源的高可伸缩、松耦合的微服务中 +- 在使用了分布式非关系数据库的系统中 + +何时不宜使用 Saga + +- 使用关系数据库的低可伸缩性事务型系统 +- 在服务间存在循环依赖的系统中 + + + +#### 面向前端的后端 (BFF) + +在现代商业应用开发,特别是微服务架构中,前后端应用是分离和独立的服务,它们通过 API 或 GraphQL 连接。如果应用程序还有移动 App 客户端,那么 Web 端和移动客户端使用相同的后端微服务就会出现问题。因为移动客户端和 Web 客户端有不同的屏幕尺寸、显示屏、性能、能耗和网络带宽,它们的 API 需求不同。 + +面向前端的后端模式适用于需要为特殊 UI 定制单独后端的场景。它还提供了其他优势,比如作为下游微服务的封装,从而减少 UI 和下游微服务之间的频繁通信。此外,在高安全要求的场景中,BFF 为部署在 DMZ 网络中的下游微服务提供了更高的安全性。 + +![微服务-BFF](images/Architecture/微服务-BFF.png) + +优点 + +- 分离 BFF 之间的关注点,使得我们可以为具体的 UI 优化他们 +- 提供更高的安全性 +- 减少 UI 和下游微服务之间频繁的通信 + +缺点 + +- BFF 之间代码重复 +- 大量的 BFF 用于其他用户界面(例如,智能电视,Web,移动端,PC 桌面版) +- 需要仔细的设计和实现,BFF 不应该包含任何业务逻辑,而应只包含特定客户端逻辑和行为 + +何时使用 BFF + +- 如果应用程序有多个含不同 API 需求的 UI +- 出于安全需要,UI 和下游微服务之间需要额外的层 +- 如果在 UI 开发中使用微前端 + +何时不宜使用 BFF + +- 如果应用程序虽有多个 UI,但使用的 API 相同 +- 如果核心微服务不是部署在 DMZ 网络中 + +可用技术示例 + +任何后端框架(Node.js,Spring,Django,Laravel,Flask,Play,…)都能支持。 + + + +#### API网关 + +在微服务架构中,UI 通常连接多个微服务。如果微服务是细粒度的(FaaS) ,那么客户端可能需要连接非常多的微服务,这将变得繁杂和具有挑战性。此外,这些服务包括它们的 API 还将不断进化。大型企业还希望能有其他横切关注点(SSL 终止、身份验证、授权、节流、日志记录等)。 + +一个解决这些问题的可行方法是使用 API 网关。API 网关位于客户端 APP 和后端微服务之间充当 facade,它可以是反向代理,将客户端请求路由到适当的后端微服务。它还支持将客户端请求扇出到多个微服务,然后将响应聚合后返回给客户端。它还支持必要的横切关注点。 + +![微服务-API网关](images/Architecture/微服务-API网关.png) + +优点 + +- 在前端和后端服务之间提供松耦合 +- 减少客户端和微服务之间的调用次数 +- 通过 SSL 终端、身份验证和授权实现高安全性 +- 集中管理的横切关注点,例如,日志记录和监视、节流、负载平衡 + +缺点 + +- 可能导致微服务架构中的单点故障 +- 额外的网络调用带来的延迟增加 +- 如果不进行扩展,它们很容易成为整个企业应用的瓶颈 +- 额外的维护和开发费用 + +何时使用 API 网关 + +- 在复杂的微服务架构中,它几乎是必须的 +- 在大型企业中,API 网关是中心化安全性和横切关注点的必要工具 + +何时不宜使用 API 网关 + +- 在安全和集中管理不是最优先要素的私人项目或小公司中 +- 如果微服务的数量相当少 + + + +#### Strangler + +如果想在运行中的项目中使用微服务架构,我们需要将遗留的或现有的单体应用迁移到微服务。将现有的大型在线单体应用程序迁移到微服务是相当有挑战性的,因为这可能破坏应用程序的可用性。 + +一个解决方案是使用 Strangler 模式。Strangler 模式意味着通过使用新的微服务逐步替换特定功能,将单体应用程序增量地迁移到微服务架构。此外,新功能只在微服务中添加,而不再添加到遗留的单体应用中。然后配置一个 Facade (API 网关)来路由遗留单体应用和微服务间的请求。当某个功能从单体应用迁移到微服务,Facade 就会拦截客户端请求并路由到新的微服务。一旦迁移了所有的功能,遗留单体应用程序就会被“扼杀(Strangler)”,即退役。 + +![微服务-Strangler](images/Architecture/微服务-Strangler.png) + +优点 + +- 安全的迁移单体应用程序到微服务 +- 可以并行地迁移已有功能和开发新功能 +- 迁移过程可以更好把控节奏 + +缺点 + +- 在现有的单体应用服务和新的微服务之间共享数据存储变得具有挑战性 +- 添加 Facade (API 网关)将增加系统延迟 +- 端到端测试变得困难 + +何时使用 Strangler + +- 将大型后端单体应用程序的增量迁移到微服务 + +何时不宜使用 Strangler + +- 如果后端单体应用很小,那么全量替换会更好 +- 如果无法拦截客户端对遗留的单体应用程序的请求 + + + +#### 断路器 + +在微服务架构中,微服务通过同步调用其他服务来满足业务需求。服务调用会由于瞬时故障(网络连接缓慢、超时或暂时不可用) 导致失败,这种情况重试可以解决问题。然而,如果出现了严重问题(微服务完全失败),那么微服务将长时间不可用,这时重试没有意义且浪费宝贵的资源(线程被阻塞,CPU 周期被浪费)。此外,一个服务的故障还会引发整个应用系统的级联故障。这时快速失败是一种更好的方法。 + +在这种情况,可以使用断路器模式挽救。一个微服务通过代理请求另一个微服务,其工作原理类似于电气断路器,代理通过统计最近发生的故障数量,并使用它来决定是继续请求还是简单的直接返回异常。 + +![微服务-断路器](images/Architecture/微服务-断路器.png) + +断路器可以有以下三种状态: + +- 关闭:断路器将请求路由到微服务,并统计给定时段内的故障数量,如果超过阈值,它就会触发并进入打开状态 +- 打开:来自微服务的请求会快速失败并返回异常。在超时后,断路器进入半开启状态 +- 半开:只有有限数量的微服务请求被允许通过并进行调用。如果这些请求成功,断路器将进入闭合状态。如果任何请求失败,断路器则会进入开启状态 + +优点 + +- 提高微服务架构的容错性和弹性 +- 阻止引发其他微服务的级联故障 + +缺点 + +- 需要复杂的异常处理 +- 日志和监控 +- 应该支持人工复位 + +何时使用断路器 + +- 在微服务间使用同步通信的紧耦合的微服务架构中 +- 如果微服务依赖多个其他微服务 + +何时不宜使用断路器 + +- 松耦合、事件驱动的微服务架构 +- 如果微服务不依赖于其他微服务 + + + +#### 外部化配置 + +每个业务应用都有许多用于各种基础设施的配置参数(例如,数据库、网络、连接的服务地址、凭据、证书路径)。此外在企业应用程序通常部署在各种运行环境(Local、 Dev、 Prod)中,实现这些的一个方法是通过内部配置。这是一个致命糟糕实践,它会导致严重的安全风险,因为生产凭证很容易遭到破坏。此外,配置参数的任何更改都需要重新构建应用程序,这在在微服务架构中会更加严峻,因为我们可能拥有数百个服务。 + +更好的方法是将所有配置外部化,使得构建过程与运行环境分离,生产的配置文件只在运行时或通过环境变量使用,从而最小化了安全风险。 + +优点 + +- 生产配置不属于代码库,因而最小化了安全漏洞 +- 修改配置参数不需要重新构建应用程序 + +缺点 + +- 我们需要选择一个支持外部化配置的框架 + +何时使用外部化配置 + +- 任何重要的生产应用程序都必须使用外部化配置 + +何时不宜使用外部化配置 + +- 在验证概念的开发中 + + + +#### 消费端驱动的契约测试 + +在微服务架构中,通常有许多有不同团队开发的微服务。这些微型服务协同工作来满足业务需求(例如,客户请求),并相互进行同步或异步通信。消费端微服务的集成测试具有挑战性,通常用 TestDouble 以获得更快、更低成本的测试运行。但是 TestDouble 通常并不能代表真正的微服务提供者,而且如果微服务提供者更改了它的 API 或 消息,那么 TestDouble 将无法确认这些。另一种选择是进行端到端测试,尽管它在生产之前是强制性的,但却是脆弱的、缓慢的、昂贵的且不能替代集成测试(Test Pyramid)。 + +在这方面消费端驱动的契约测试可以帮助我们。在这里,负责消费端微服务的团队针对特定的服务端微服务,编写一套包含了其请求和预期响应(同步)或消息(异步)的测试套件,这些测试套件称为显式的约定。对于微服务服务端,将其消费端所有约定的测试套件都添加到其自动化测试中。当特定服务端微服务的自动化测试执行时,它将一起运行自己的测试和约定的测试并进行验证。通过这种方式,契约测试可以自动的帮助维护微服务通信的完整性。 + +优点 + +- 如果提供程序意外更改 API 或消息,可以被快速的自动发现 +- 更少意外、更健壮,特别是包含大量微服务的企业应用程序 +- 改善团队自主性 + +缺点 + +- 需要额外的工作来开发和集成微服务服务端的契约测试,因为他们可能使用完全不同的测试工具 +- 如果契约测试与真实服务情况不匹配,将可能导致生产故障 + +何时使用需求驱动的契约测试 + +- 在大型企业业务应用程序中,通常由不同的团队开发不同服务 + +何时不宜使用消费端驱动的契约测试 + +- 所有微服务由同一团队负责开发的小型简单的应用程序 +- 如果服务端微服务是相对稳定的,并且不处在活跃的开发状态 + + + +### 关键设计 + +#### 监控-发现故障的征兆 + +![监控-发现故障的征兆](images/Architecture/监控-发现故障的征兆.jpg) + + + +#### 定位问题-链路跟踪 + +![定位问题-链路跟踪](images/Architecture/定位问题-链路跟踪.png) + +![定位问题-taceId](images/Architecture/定位问题-taceId.png) + +要实现链路跟踪,每次服务调用会在HTTP的HEADERS中记录至少记录四项数据: + +- traceId:traceId标识一个用户请求的调用链路。具有相同traceId的调用属于同一条链路 +- spanId:标识一次服务调用的ID,即链路跟踪的节点ID +- parentId:父节点的spanId +- requestTime & responseTime:请求时间和响应时间 + + + +#### 分析问题-日志分析 + +![分析问题-日志分析](images/Architecture/分析问题-日志分析.png) + +一般使用ELK日志分析组件。ELK是Elasticsearch、Logstash和Kibana三个组件的缩写。 + +- Elasticsearch:搜索引擎,同时也是日志的存储。 +- Logstash:日志采集器,它接收日志输入,对日志进行一些预处理,然后输出到Elasticsearch。 +- Kibana:UI组件,通过Elasticsearch的API查找数据并展示给用户。 + +最后还有一个小问题是如何将日志发送到Logstash。一种方案是在日志输出的时候直接调用Logstash接口将日志发送过去。这样一来又(咦,为啥要用“又”)要修改代码……于是小明选用了另一种方案:日志仍然输出到文件,每个服务里再部署个Agent扫描日志文件然后输出给Logstash。 + + + +#### 网关-权限控制,服务治理 + +![网关-权限控制,服务治理](images/Architecture/网关-权限控制,服务治理.png) + + + +#### 服务注册于发现-动态扩容 + +![服务注册于发现-动态扩容](images/Architecture/服务注册于发现-动态扩容.png) + + + +#### 熔断、服务降级、限流 + +**熔断** + +当一个服务因为各种原因停止响应时,调用方通常会等待一段时间,然后超时或者收到错误返回。如果调用链路比较长,可能会导致请求堆积,整条链路占用大量资源一直在等待下游响应。所以当多次访问一个服务失败时,应熔断,标记该服务已停止工作,直接返回错误。直至该服务恢复正常后再重新建立连接。 + +![熔断](images/Architecture/熔断.png) + + + +**服务降级** + +当下游服务停止工作后,如果该服务并非核心业务,则上游服务应该降级,以保证核心业务不中断。比如网上超市下单界面有一个推荐商品凑单的功能,当推荐模块挂了后,下单功能不能一起挂掉,只需要暂时关闭推荐功能即可。 + + + +**限流** + +一个服务挂掉后,上游服务或者用户一般会习惯性地重试访问。这导致一旦服务恢复正常,很可能因为瞬间网络流量过大又立刻挂掉,在棺材里重复着仰卧起坐。因此服务需要能够自我保护——限流。限流策略有很多,最简单的比如当单位时间内请求数过多时,丢弃多余的请求。另外,也可以考虑分区限流。仅拒绝来自产生大量请求的服务的请求。例如商品服务和订单服务都需要访问促销服务,商品服务由于代码问题发起了大量请求,促销服务则只限制来自商品服务的请求,来自订单服务的请求则正常响应。 + +![限流](images/Architecture/限流.png) + + + +#### 测试 + +![三种测试](images/Architecture/三种测试.png) + +![Mock-Server](images/Architecture/Mock-Server.png) + + + +## 无服务器架构(Serverless) + +Serverless的基础是云技术,它是云技术发展到一定阶段而出现的一种革命性的高端架构。Serverless并不是说不需要服务器,而是指不需要开发者去关心底层服务器的状态、资源和扩容等,开发者只需要关注于业务逻辑实现。架构上,我们可以把serverless分为FaaS和BaaS: + +![Serverless](images/Architecture/Serverless.jpg) + +- **FaaS(函数即服务)**:用于创建、运行、管理函数服务的计算平台,它支持多种开发语言,比如java、nodejs、dart等,这有利于不同端测的开发同学介入开发。FaaS是基于事件驱动的思想,只有当一个函数被事件触发时才会占用服务器资源执行,不然都是无需占用服务器资源的 +- **BaaS(后端即服务)**:提供了用于函数调用的第三方基础服务,比如身份校验、日志、数据库等,它是有服务商直接提供,开发者无需关系实现,直接调用即可 + + + +**优点** + +- 降低创业公司启动成本 +- 减少运营成本 +- 降低开发成本 +- 实现快速上线 +- 系统安全性更高 +- 能适应微服务架构和扩展性能力强 + +**缺点** + +- 不适合长时间运行应用 +- 完全会依赖于第三方服务 +- 缺乏调式和开发工具,排查问题困难 +- 无法用于高并发运用 + + + +**应用场景** + +- 发送通知 +- WebHook +- 数据统计分析 +- Trigger及定时任务 +- Chat 机器人 + + + +## 服务网格(Service Mesh) + +### 三种服务发现模式 + +服务发现和负载均衡并不是新问题,业界其实已经探索和总结出一些常用的模式,这些模式的核心其实是代理 (Proxy,如下图所示),以及代理在架构中所处的位置。 + +![三种服务发现模式](images/Architecture/三种服务发现模式.png) + +在服务消费方和服务提供方之间增加一层代理,由代理负责服务发现和负载均衡功能,消费方通过代理间接访问目标服务。根据代理在架构上所处的位置不同,当前业界主要有三种不同的服务发现模式。 + + + +#### 模式一:传统集中式代理 + +![模式一:传统集中式代理](images/Architecture/模式一:传统集中式代理.png) + +这是最简单和传统做法,在服务消费者和生产者之间,代理作为独立一层集中部署,由独立团队 (一般是运维或框架) 负责治理和运维。常用的集中式代理有硬件负载均衡器 (如 F5),或者软件负载均衡器 (如 Nginx),F5(4 层负载)+Nginx(7 层负载) 这种软硬结合两层代理也是业内常见做法,兼顾配置的灵活性 (Nginx 比 F5 易于配置)。 + +这种方式通常在 DNS 域名服务器的配合下实现服务发现,服务注册 (建立服务域名和 IP 地址之间的映射关系) 一般由运维人员在代理上手工配置,服务消费方仅依赖服务域名,这个域名指向代理,由代理解析目标地址并做负载均衡和调用。 + +国外知名电商网站 eBay,虽然体量巨大,但其内部的服务发现机制仍然是基于这种传统的集中代理模式,国内公司如携程,也是采用这种模式。 + + + +#### 模式二:客户端嵌入式代理 + +![模式二:客户端嵌入式代理](images/Architecture/模式二:客户端嵌入式代理.png) + +这是很多互联网公司比较流行的一种做法,代理 (包括服务发现和负载均衡逻辑) 以客户库的形式嵌入在应用程序中。这种模式一般需要独立的服务注册中心组件配合,服务启动时自动注册到注册中心并定期报心跳,客户端代理则发现服务并做负载均衡。 + +Netflix 开源的 Eureka(注册中心)[附录 1] 和 Ribbon(客户端代理)[附录 2] 是这种模式的典型案例,国内阿里开源的 Dubbo 也是采用这种模式。 + + + +#### 模式三:主机独立进程代理 + +这种做法是上面两种模式的一个折中,代理既不是独立集中部署,也不嵌入在客户应用程序中,而是作为独立进程部署在每一个主机上,一个主机上的多个消费者应用可以共用这个代理,实现服务发现和负载均衡,如下图所示。这个模式一般也需要独立的服务注册中心组件配合,作用同模式二。 + +![模式三:主机独立进程代理](images/Architecture/模式三:主机独立进程代理.png) + + + +### 服务网格Service Mesh + +所谓的 Service Mesh,其实本质上就是上面提到的模式三:主机独立进程模式。 + +![服务网格ServiceMesh](images/Architecture/服务网格ServiceMesh.png) + +**Service Mesh特点** + +- 是一个基础设施 +- 轻量级网络代理,应用程序间通讯的中间层 +- 应用程序无感知,对应用程序透明无侵入 +- 解耦应用程序的重试/超时、监控、追踪和服务发现等控制层面的东西 + + + +**Service Mesh开源实现** + +- 第一代Service Mesh + - Linkerd:使用Scala编写,是业界第一个开源的Service Mesh方案 + - Envoy:基于C++ 11编写,无论是理论上还是实际上,Envoy 性能都比 Linkderd 更好 +- 第二代Service Mesh:主要改进集中在更加强大的控制面功能(与之对应的 sidecar proxy 被称之为数据面) + - Istio:是 Google 和 IBM 两位巨人联合 Lyft 的合作开源项目。是当前最主流的Service Mesh方案 + - Conduit:各方面的设计理念与 Istio 非常类似。但是作者抛弃了 Linkerd, 使用Rust重新编写了sidecar, 叫做 Conduit Data Plane, 控制面则由Go编写的 Conduit Control Plane接管 + + + +# 架构规范 + +## 指标 + +### QPS + +**QPS**`Queries Per Second` 是每秒查询率 ,是**一台服务器**每秒能够相应的查询次数,是对一个特定的查询服务器**在规定时间内**所处理流量多少的衡量标准, 即每秒的响应请求数,也即是最大吞吐能力。 + +`峰值QPS=(日总PV×80%)/(日总秒数×20%)` + + + +### TPS + +**TPS**`Transactions Per Second`也就是事务数/秒。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。 + + + +**QPS和TPS区别** + +**Tps即每秒处理事务数,包括了** + +- 用户请求服务器 +- 服务器自己的内部处理 +- 服务器返回给用户 + +这三个过程,每秒能够完成N个这三个过程,Tps也就是N; + +Qps基本类似于Tps,但是不同的是,对于一个页面的一次访问,形成一个Tps;但一次页面请求,可能产生多次对服务器的请求,服务器对这些请求,就可计入“Qps”之中。 + + + +### 并发数 + +并发数(并发度):指系统同时能处理的请求数量,同样反应了系统的负载能力。这个数值可以分析机器1s内的访问日志数量来得到。 + + + +### 吐吞量 + +吞吐量是指系统在单位时间内处理请求的数量,TPS、QPS都是吞吐量的常用量化指标。 + + + +**系统吞吐量要素** + +一个系统的吞吐量(承压能力)与request(请求)对cpu的消耗,外部接口,IO等等紧密关联。 + +单个request 对cpu消耗越高,外部系统接口,IO影响速度越慢,系统吞吐能力越低,反之越高。 + + + +**重要参数** + +- QPS(TPS):每秒钟request/事务数量 +- 并发数:系统同时处理的request/事务数 +- 响应时间:一般取平均响应时间 + + + +**关系** + +QPS(TPS)=并发数/平均响应时间 + +一个系统吞吐量通常有QPS(TPS),并发数两个因素决定,每套系统这个两个值都有一个相对极限值,在应用场景访问压力下,只要某一项达到系统最高值,系统吞吐量就上不去了,如果压力继续增大,系统的吞吐量反而会下降,原因是系统超负荷工作,上下文切换,内存等等其他消耗导致系统性能下降。 + + + +### PV + +**PV**(Page View)是页面访问量,即页面浏览量或点击量,用户每次刷新即被计算一次。可以统计服务一天的访问日志得到。 + + + +### UV + +**UV**(Unique Visitor)是独立访客,统计1天内访问某站点的用户数。可以统计服务一天的访问日志并根据用户的唯一标识去重得到。响应时间(RT):响应时间是指系统对请求作出响应的时间,一般取平均响应时间。可以通过Nginx、Apache之类的Web Server得到。 + + + +### DAU + +**DAU**(Daily Active User)是日活跃用户数量。常用于反映网站、互联网应用或网络游戏的运营情况。DAU通常统计一日(统计日)之内,登录或使用了某个产品的用户数(去除重复登录的用户),与UV概念相似。 + + + +### MAU + +**MAU**(Month Active User)是月活跃用户数量,指网站、app等去重后的月活跃用户数量。 + + + +### 系统吞吐量评估 + +我们在做系统设计的时候就需要考虑CPU运算,IO,外部系统响应因素造成的影响以及对系统性能的初步预估。 + +而通常情况下,我们面对需求,我们评估出来的出来QPS,并发数之外,还有另外一个维度:日pv。 + +通过观察系统的访问日志发现,在用户量很大的情况下,各个时间周期内的同一时间段的访问流量几乎一样。比如工作日的每天早上。只要能拿到日流量图和QPS我们就可以推算日流量。 + +通常的技术方法: + +- 找出系统的最高TPS和日PV,这两个要素有相对比较稳定的关系(除了放假、季节性因素影响之外) +- 通过压力测试或者经验预估,得出最高TPS,然后跟进1的关系,计算出系统最高的日吞吐量。B2B中文和淘宝面对的客户群不一样,这两个客户群的网络行为不应用,他们之间的TPS和PV关系比例也不一样 + + + +### 指标测试 + +#### 基准测试 + +性能基线指标测试是当软件系统中增加一个新的模块的时候,需要做基准测试,以判断新模块对整个软件系统的性能影响。按照基准测试的方法,需要打开/关闭新模块至少各做一次测试。关闭模块之前的系统各个性能指标记下来作为基准(Benchmark),然后与打开模块状态下的系统性能指标作比较,以判断模块对系统性能的影响。 + + + +#### 负载测试 + +开发前期测试。考察软件系统在既定负载下的性能表现指标。指标体现为响应时间、交易容量、并发容量、资源使用率等。 + +- 根据系统详细设计文档,分析系统可能存在的负载点(并发用户数,业务量,数据量),可以按照特性及功能点进行负载分析 +- 固定测试环境,在其它测试角度(负载方面)不变的情况下,变化一个测试角度并持续增加压力,查看系统的性能曲线和处理极限,以及是否有性能瓶颈存在(拐点) + +目的在预定的指标基础上,从多个不同的测试角度去探测分析系统的性能变化情况,获得性能指标,配合性能调优 + +- 确定测试组网模型 +- 设计负载注入用例(系统处理能力) +- 针对不同的负载点,开发负载注入工具 +- 开发性能指标采集工具 + + + +#### 并发测试 + +在开发中后期测试。模拟并发访问,测试多用户并发访问同一个应用、模块、数据时是否产生隐藏的并发问题,如内存泄漏、线程锁、资源争用问题。目的并非为了获得性能指标,而是为了发现并发引起的问题: + +- 设计用户事务并发模型 +- 设计测试用例 +- 设计问题分析方法 + + + +#### 配置测试 + +在开发中后期测试。通过对被测系统的软硬件环境的调整,了解各种不同环境对性能影响的程度,从而找到系统各项资源的最有分配原则。目的是主要用于性能调优,在经过测试获得了基准测试数据后,进行环境调整(包括硬件配置、网络、操作系统、应用服务器、数据库等),再将测试结果与基准数据进行对比,判断调整是否达到最佳状态。 + +- 确定资源调整标准 +- 设计配置测试用例 + + + +#### 强度测试 + +在开发中后期测试。特殊场景分析,构造异常或极端条件(如告警风暴、资源减少增多),查看系统状态。目的是核实测试对象性能行为在异常或极端条件之下的可接受性。 + + + +#### 压力测试 + +在开发中后期测试。测试系统在一定饱和状态下系统能够处理的会话能力,以及是否出现错误。目的是通过测试调优保证系统即使在极端的压力情况下也不会出错甚至系统崩溃。 + + + +#### 稳定性测试 + +在开发中后期测试。测试系统在一定负载下运行长时间后是否会发生问题。测试系统在饱和状态的70%压力下处理会话能力,以及是否出现错误。目的是测试系统在长时间运行的情况下不会出错及性能下降问题 + + + +## 前后端分离 + +### 规范原则 + +- 接口返回数据即显示:前端仅做渲染逻辑处理 +- 渲染逻辑禁止跨多个接口调用 +- 前端关注交互、渲染逻辑,尽量避免业务逻辑处理的出现 +- 请求响应传输数据格式:JSON,JSON 数据尽量简单轻量,避免多级 JSON 的出现 +- 统一响应格式规范 +- 响应分页格式:使用统一响应格式 +- 下拉框、复选框、单选框:由后端接口统一逻辑判定是否选中,通过 isSelect 标示是否选中 +- Boolean 类型:关于 Boolean 类型,JSON 数据传输中一律使用 1/0 来标示,1 为是 / True,0 为否 / False +- 日期类型:关于日期类型,JSON 数据传输中一律使用字符串,具体日期格式因业务而定 + + + +## API接口设计 + +### 安全性问题 + +安全性问题是一个接口必须要保证的规范。如果接口保证不了安全性,那么接口相当于直接暴露在公网环境中任人蹂躏。 + +#### 调用接口的先决条件-token + +获取token一般会涉及到参数`appid`,`appkey`,`timestamp`,`nonce`,`sign`。我们通过以上几个参数来获取调用系统的凭证: + +- `appid`和`appkey`可以直接通过平台线上申请,也可以线下直接颁发。`appid`是全局唯一的,每个`appid`将对应一个客户,`appkey`需要高度保密 +- `timestamp`是时间戳,使用系统当前的unix时间戳。时间戳的目的就是为了减轻DOS攻击。防止请求被拦截后一直尝试请求接口。服务器端设置时间戳阀值,如果请求时间戳和服务器时间超过阀值,则响应失败 +- `nonce`是随机值。随机值主要是为了增加`sign`的多变性,也可以保护接口的幂等性,相邻的两次请求`nonce`不允许重复,如果重复则认为是重复提交,响应失败 +- `sign`是参数签名,将`appkey`,`timestamp`,`nonce`拼接起来进行md5加密(当然使用其他方式进行不可逆加密也没问题)。 + +`token`,使用参数`appid`,`timestamp`,`nonce`,`sign`来获取token,作为系统调用的唯一凭证。`token`可以设置一次有效(这样安全性更高),也可以设置时效性,这里推荐设置时效性。如果一次有效的话这个接口的请求频率可能会很高。`token`推荐加到请求头上,这样可以跟业务参数完全区分开来。 + + + +#### 使用POST作为接口请求方式 + +一般调用接口最常用的两种方式就是GET和POST。两者的区别也很明显,GET请求会将参数暴露在浏览器URL中,而且对长度也有限制。为了更高的安全性,所有接口都采用POST方式请求。 + + + +#### 客户端IP白名单 + +ip白名单是指将接口的访问权限对部分ip进行开放。这样就能避免其他ip进行访问攻击,设置ip白名单比较麻烦的一点就是当你的客户端进行迁移后,就需要重新联系服务提供者添加新的ip白名单。设置ip白名单的方式很多,除了传统的防火墙之外,spring cloud alibaba提供的组件sentinel也支持白名单设置。为了降低api的复杂度,推荐使用防火墙规则进行白名单设置。 + + + +#### 单个接口针对ip限流 + +限流是为了更好的维护系统稳定性。使用redis进行接口调用次数统计,ip+接口地址作为key,访问次数作为value,每次请求value+1,设置过期时长来限制接口的调用频率。 + + + +#### 记录接口请求日志 + +使用aop全局记录请求日志,快速定位异常请求位置,排查问题原因。 + + + +#### 敏感数据脱敏 + +在接口调用过程中,可能会涉及到订单号等敏感数据,这类数据通常需要脱敏处理,最常用的方式就是加密。加密方式使用安全性比较高的`RSA`非对称加密。非对称加密算法有两个密钥,这两个密钥完全不同但又完全匹配。只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。 + + + +### 幂等性问题 + +幂等性是指任意多次请求的执行结果和一次请求的执行结果所产生的影响相同。说的直白一点就是查询操作无论查询多少次都不会影响数据本身,因此查询操作本身就是幂等的。但是新增操作,每执行一次数据库就会发生变化,所以它是非幂等的。幂等问题的解决有很多思路,这里讲一种比较严谨的。提供一个生成随机数的接口,随机数全局唯一。调用接口的时候带入随机数。第一次调用,业务处理成功后,将随机数作为key,操作结果作为value,存入redis,同时设置过期时长。第二次调用,查询redis,如果key存在,则证明是重复提交,直接返回错误。 + + + +### 数据规范问题 + +#### 版本控制 + +一套成熟的API文档,一旦发布是不允许随意修改接口的。这时候如果想新增或者修改接口,就需要加入版本控制,版本号可以是整数类型,也可以是浮点数类型。一般接口地址都会带上版本号,http://ip:port//v1/list。 + + + +#### 响应状态码规范 + +一个牛逼的API,还需要提供简单明了的响应值,根据状态码就可以大概知道问题所在。我们采用http的状态码进行数据封装,例如200表示请求成功,4xx表示客户端错误,5xx表示服务器内部发生错误。状态码设计参考如下: + +| 分类 | 描述 | +| :--- | :------------------------------------------- | +| 1xx | 信息,服务器收到请求,需要请求者继续执行操作 | +| 2xx | 成功 | +| 3xx | 重定向,需要进一步的操作以完成请求 | +| 4xx | 客户端错误,请求包含语法错误或无法完成请求 | +| 5xx | 服务端错误 | + +状态码枚举类: + +```java +public enum CodeEnum { + + // 根据业务需求进行添加 + SUCCESS(200,"处理成功"), + ERROR_PATH(404,"请求地址错误"), + ERROR_SERVER(505,"服务器内部发生错误"); + + private int code; + private String message; + + CodeEnum(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} +``` + + + +#### 统一响应数据格式 + +为了方便给客户端响应,响应数据会包含三个属性,状态码(code),信息描述(message),响应数据(data)。客户端根据状态码及信息描述可快速知道接口,如果状态码返回成功,再开始处理数据。响应结果定义及常用方法: + +```java +public class R implements Serializable { + + private static final long serialVersionUID = 793034041048451317L; + + private int code; + private String message; + private Object data = null; + + public int getCode() { + return code; + } + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + public void setMessage(String message) { + this.message = message; + } + + public Object getData() { + return data; + } + + /** + * 放入响应枚举 + */ + public R fillCode(CodeEnum codeEnum){ + this.setCode(codeEnum.getCode()); + this.setMessage(codeEnum.getMessage()); + return this; + } + + /** + * 放入响应码及信息 + */ + public R fillCode(int code, String message){ + this.setCode(code); + this.setMessage(message); + return this; + } + + /** + * 处理成功,放入自定义业务数据集合 + */ + public R fillData(Object data) { + this.setCode(CodeEnum.SUCCESS.getCode()); + this.setMessage(CodeEnum.SUCCESS.getMessage()); + this.data = data; + return this; + } +} +``` + + + +# OAuth2.0 + +`OAuth2.0` 的授权简单理解其实就是获取令牌(`token`)的过程,`OAuth` 协议定义了四种获得令牌的授权方式(`authorization grant` )如下: + +- 授权码(`authorization-code`) +- 隐藏式(`implicit`) +- 密码式(`password`) +- 客户端凭证(`client credentials`) + +但值得注意的是,不管我们使用哪一种授权方式,在三方应用申请令牌之前,都必须在系统中去申请身份唯一标识:客户端 ID(`client ID`)和 客户端密钥(`client secret`)。这样做可以保证 `token` 不被恶意使用。 + + + +下面我们会分析每种授权方式的原理,在进入正题前,先了解 `OAuth2.0` 授权过程中几个重要的参数: + +- `response_type`:code 表示要求返回授权码,token 表示直接返回令牌 +- `client_id`:客户端身份标识 +- `client_secret`:客户端密钥 +- `redirect_uri`:重定向地址 +- `scope`:表示授权的范围,`read`只读权限,`all`读写权限 +- `grant_type`:表示授权的方式,`AUTHORIZATION_CODE`(授权码)、`password`(密码)、`client_credentials`(凭证式)、`refresh_token` 更新令牌 +- `state`:应用程序传递的一个随机数,用来防止`CSRF` + + + +## 授权码模式 + +授权码模式(Authorization Code Grant)。 + + ![授权码模式](images/Architecture/授权码模式.png) + +- 第一步:用户访问页面 +- 第二步:访问的页面将请求重定向到认证服务器 +- 第三步:认证服务器向用户展示授权页面,等待用户授权 +- 第四步:用户授权,认证服务器生成一个code和带上client_id发送给应用服务器。然后,应用服务器拿到code,并用client_id去后台查询对应的client_secret +- 第五步:将code、client_id、client_secret传给认证服务器换取access_token和 refresh_token +- 第六步:将access_token和refresh_token传给应用服务器 +- 第七步:验证token,访问真正的资源页面 + + ![授权码模式优缺点](images/Architecture/授权码模式优缺点.png) + + + +**掘金授权案例** + +`OAuth2.0`四种授权中授权码方式是最为复杂,但也是安全系数最高的,比较常用的一种方式。这种方式适用于兼具前后端的`Web`项目,因为有些项目只有后端或只有前端,并不适用授权码模式。下图我们以用`WX`登录掘金为例,详细看一下授权码方式的整体流程。 + +![OAuth2.0-授权码](images/Architecture/OAuth2.0-授权码.png) + +用户选择`WX`登录掘金,掘金会向`WX`发起授权请求,接下来 `WX`询问用户是否同意授权(常见的弹窗授权)。其中 `response_type` 为 `code` 要求返回授权码,`scope` 参数表示本次授权范围为只读权限,`redirect_uri` 重定向地址。 + +```javascript +https://wx.com/oauth/authorize? + response_type=code& + client_id=CLIENT_ID& + redirect_uri=http://juejin.im/callback& + scope=read& + state=10001 +``` + +用户同意授权后,`WX` 根据 `redirect_uri`重定向并带上授权码。 + +```javascript +http://juejin.im/callback?code=AUTHORIZATION_CODE +``` + +当掘金拿到授权码(code)时,带授权码和密匙等参数向`WX`申请令牌。`grant_type`表示本次授权为授权码方式 `authorization_code` ,获取令牌要带上客户端密匙 `client_secret`,和上一步得到的授权码 `code`。 + +```javascript +https://wx.com/oauth/token? + client_id=CLIENT_ID& + client_secret=CLIENT_SECRET& + grant_type=authorization_code& + code=AUTHORIZATION_CODE& + redirect_uri=http://juejin.im/callback +``` + +最后 `WX` 收到请求后向 `redirect_uri` 地址发送 `JSON` 数据,其中的`access_token` 就是令牌。 + +```javascript + { + "access_token":"ACCESS_TOKEN", + "token_type":"bearer", + "expires_in":2592000, + "refresh_token":"REFRESH_TOKEN", + "scope":"read", + ...... +} +``` + + + +## 简化模式 + +简化模式(Implicit Grant)。 +![简化模式](images/Architecture/简化模式.png) + +- 第一步:用户访问页面时,重定向到认证服务器 +- 第二步:认证服务器给用户一个认证页面,等待用户授权 +- 第三步:用户授权,认证服务器想应用页面返回Token +- 第四步:验证Token,访问真正的资源页面 + +![简化模式优缺点](images/Architecture/简化模式优缺点.png) + + + +## 密码模式 + +密码模式(Resource Owner Password Credentials Grant)。 + +![密码模式](images/Architecture/密码模式.png) + +- 第一步:用户访问用页面时,输入第三方认证所需要的信息(QQ/微信账号密码) +- 第二步:应用页面那种这个信息去认证服务器授权 +- 第三步:认证服务器授权通过,拿到token,访问真正的资源页面 + +**优点**:不需要多次请求转发,额外开销,同时可以获取更多的用户信息。 + +**缺点**:局限性,认证服务器和应用方必须有超高的信赖。 + +**应用场景**:自家公司搭建的认证服务器。 + + + +## 客户端模式 + +客户端模式(Client Credentials Grant)。 + +![客户端模式](images/Architecture/客户端模式.png) + +- 第一步:用户访问应用客户端 +- 第二步:通过客户端定义的验证方法,拿到token,无需授权 +- 第三步:访问资源服务器A +- 第四步:拿到一次token就可以畅通无阻的访问其他的资源页面。 + +这是一种最简单的模式,只要client请求,我们就将AccessToken发送给它。这种模式是最方便但最不安全的模式。因此这就要求我们对client完全的信任,而client本身也是安全的。因此这种模式一般用来提供给我们完全信任的服务器端服务。在这个过程中不需要用户的参与。 + + + + + +## 隐藏式 + +上边提到有一些`Web`应用是没有后端的, 属于纯前端应用,无法用上边的授权码模式。令牌的申请与存储都需要在前端完成,跳过了授权码这一步。前端应用直接获取 `token`,`response_type` 设置为 `token`,要求直接返回令牌,跳过授权码,`WX`授权通过后重定向到指定 `redirect_uri` 。 + +```javascript +https://wx.com/oauth/authorize? + response_type=token& + client_id=CLIENT_ID& + redirect_uri=http://juejin.im/callback& + scope=read +``` + + + +## 密码式 + +密码模式比较好理解,用户在掘金直接输入自己的`WX`用户名和密码,掘金拿着信息直接去`WX`申请令牌,请求响应的 `JSON`结果中返回 `token`。`grant_type` 为 `password` 表示密码式授权。 + +```javascript +https://wx.com/token? + grant_type=password& + username=USERNAME& + password=PASSWORD& + client_id=CLIENT_ID +``` + +这种授权方式缺点是显而易见的,非常的危险,如果采取此方式授权,该应用一定是可以高度信任的。 + + + +## 凭证式 + +凭证式和密码式很相似,主要适用于那些没有前端的命令行应用,可以用最简单的方式获取令牌,在请求响应的 `JSON` 结果中返回 `token`。`grant_type` 为 `client_credentials` 表示凭证式授权,`client_id` 和 `client_secret` 用来识别身份。 + +```javascript +https://wx.com/token? + grant_type=client_credentials& + client_id=CLIENT_ID& + client_secret=CLIENT_SECRET +``` + + + +## 常见问题 + +**问题一:令牌怎么用?** + +拿到令牌可以调用 `WX` API 请求数据了,那令牌该怎么用呢?每个到达`WX`的请求都必须带上 `token`,将 `token` 放在 `http` 请求头部的一个`Authorization`字段里。如果使用`postman` 模拟请求,要在`Authorization` -> `Bearer Token` 放入 `token`,**注意**:低版本`postman` 没有这个选项。 + +![OAuth2.0-令牌怎么用](images/Architecture/OAuth2.0-令牌怎么用.png) + + + +**问题二:令牌过期怎么办?** + +`token`是有时效性的,一旦过期就需要重新获取,但是重走一遍授权流程,不仅麻烦而且用户体验也不好,那如何让更新令牌变得优雅一点呢?一般在颁发令牌时会一次发两个令牌,一个令牌用来请求`API`,另一个负责更新令牌 `refresh_token`。`grant_type` 为 `refresh_token` 请求为更新令牌,参数 `refresh_token` 是用于更新令牌的令牌。 + +```javascript +https://wx.com/oauth/token? + grant_type=refresh_token& + client_id=CLIENT_ID& + client_secret=CLIENT_SECRET& + refresh_token=REFRESH_TOKEN +``` + + + +# 用户体系 + +## 登录安全规则 + +- 失败次数超过3次,启用图片验证码 +- 失败次数超过10次,启用手机验证码 +- 启用HTTPS解决中间人攻击 +- 敏感数据进行加密传输 +- 操作日志 +- 异常操作或登录体系 +- 拒绝弱密码 +- 防止用户名被遍历 + + + +## 注册登录流程 + +### 账号注册流程 + +在判断账号是否注册时,为了防止恶意攻击,通过手动或自动化程序测试账号是否注册,这里应该做风险控制,如连续3次账号未注册,后续每次输入账号都需要验证(如输入验证码,滑动验证等)才会继续流程。 + +![账号注册流程](images/Architecture/账号注册流程.png) + + + +### 账号登录主流程 + +账号可能是手机号、邮箱号或其他,大部分App均支持手机号验证码登录,这里也分析此流程。第三方账号登录一般情况下有两种情况: + +- 主流社交账号如微信、QQ、微博、Facebook等的登录,对于已绑定的账号,直接唤起相应App进行授权登录,对于未绑定的账户,则还需要进行绑定 + +- 同一公司体系下产品的账号,如腾讯系的腾讯视频、王者荣耀等,无需注册,使用一套账号(微信、QQ)授权登录 + + ![账号登录流程](images/Architecture/账号登录流程.png) + + + +### 找回密码流程 + +![找回密码流程](images/Architecture/找回密码流程.png) + + + +## 密码设计 + +一般使用 **https 协议 + 非对称加密算法(如 RSA)**来传输用户密码,为了更加安全,可以在前端构造一下随机因子。使用 **BCrypt + 盐存储用户密码**。在感知到暴力破解危害的时候,**「开启短信验证、图形验证码、账号暂时锁定」**等防御机制来抵御暴力破解。 + +### 传输密码 + +#### https协议 + +**http 的三大风险** + +为什么要使用 https 协议呢?**「http 它不香」**吗? 因为 http 是明文信息传输的。如果在茫茫的网络海洋,使用 http 协议,有以下三大风险: + +- 窃听/嗅探风险:第三方可以截获通信数据 +- 数据篡改风险:第三方获取到通信数据后,会进行恶意修改 +- 身份伪造风险:第三方可以冒充他人身份参与通信 + +如果传输不重要的信息还好,但是传输用户密码这些敏感信息,那可不得了。所以一般都要使用**「https 协议」**传输用户密码信息。 + + + +**https 原理** + +https 原理是什么呢?为什么它能解决 http 的三大风险呢? + +**https = http + SSL/TLS**, SSL/TLS 是传输层加密协议,它提供内容加密、身份认证、数据完整性校验,以解决数据传输的安全性问题。 + + + +**https一定安全吗?** + +https 的数据传输过程,数据都是密文的,那么,使用了 https 协议传输密码信息,一定是安全的吗?其实**「不然」** + +- 比如,https 完全就是建立在证书可信的基础上的呢。但是如果遇到中间人伪造证书,一旦客户端通过验证,安全性顿时就没了哦!平时各种钓鱼不可描述的网站,很可能就是黑客在诱导用户安装它们的伪造证书 +- 通过伪造证书,https 也是可能被抓包的 + + + +#### 对称加密算法 + +既然使用了 https 协议传输用户密码,还是不一定安全,那么就给用户密码加密再传输。加密算法有对称加密和非对称加密两大类。对称加密:加密和解密使用**「相同密钥」**的加密算法。 + +![img](images/Architecture/2cabf15de992c630b80b55b7734744bf.png) + +常用的对称加密算法主要有以下几种: + +![img](images/Architecture/cdefa098def4fa1cb6c181f700df4c49.png) + +如果使用对称加密算法,需要**考虑密钥如何给到对方**,如果密钥还是网络传输给对方,传输过程,被中间人拿到的话,也是有风险的哦。 + + + +#### 非对称加密算法 + +非对称加密算法需要两个密钥(公开密钥和私有密钥)。公钥与私钥是成对存在的,如果用公钥对数据进行加密,只有对应的私钥才能解密。 + +![img](images/Architecture/468036d6bd7316bc39d8c783af042fc8.png) + +常用的非对称加密算法主要有以下几种:![img](images/Architecture/788b54bf0bb97d401f13a0fdc20bc9c1.png) + +如果使用非对称加密算法,也需要考虑**「密钥公钥如何给到对方」**,如果公钥还是网络传输给对方,传输过程,被中间人拿到的话,会有什么问题呢?**「他们是不是可以伪造公钥,把伪造的公钥给客户端,然后,用自己的私钥等公钥加密的数据发过来?」** + +我们直接**「登录一下百度」**,抓下接口请求,验证一发大厂是怎么加密的。可以发现有获取公钥接口,如下: + +![img](images/Architecture/1a9f1463d439bc07a6b44df1ce973916.png) + +再看下登录接口,发现就是 RSA 算法,RSA 就是**「非对称加密算法」**。其实百度前端是用了 JavaScript 库**「jsencrypt」**,在 github 的 star 还挺多的。 + +![img](images/Architecture/dc291356663fc69dd4e8f7cfe7be63b6.png) + +因此,我们可以用**「https + 非对称加密算法(如 RSA)」** 传输用户密码。 + + + +### 存储密码 + +假设密码已经安全到达服务端啦,那么,如何存储用户的密码呢?一定不能明文存储密码到数据库哦!可以用**「哈希摘要算法加密密码」**,再保存到数据库。 + +**哈希摘要算法**:只能从明文生成一个对应的哈希值,不能反过来根据哈希值得到对应的明文。 + +#### MD5摘要算法 + +MD5 是一种非常经典的哈希摘要算法,被广泛应用于数据完整性校验、数据(消息)摘要、数据加密等。但是仅仅使用 MD5 对密码进行摘要,并不安全。在 MD5 免费破解网站一输入,马上就可以看到原密码了。 + +试想一下,如果黑客构建一个超大的数据库,把所有 20 位数字以内的数字和字母组合的密码全部计算 MD5 哈希值出来,并且把密码和它们对应的哈希值存到里面去(这就是**「彩虹表」**)。在破解密码的时候,只需要查一下这个彩虹表就完事了。所以**「单单 MD5 对密码取哈希值存储」**,已经不安全啦。 + + + +#### MD5+盐摘要算法 + +在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。用户密码+盐之后,进行哈希散列,再保存到数据库。这样可以有效应对彩虹表破解法。但是呢,使用加盐,需要注意以下几点: + +- 不能在代码中写死盐,且盐需要有一定的长度(盐写死太简单的话,黑客可能注册几个账号反推出来) +- 每一个密码都有独立的盐,并且盐要长一点,比如超过 20 位。(盐太短,加上原始密码太短,容易破解) +- 最好是随机的值,并且是全球唯一的,意味着全球不可能有现成的彩虹表给你用 + + + +#### Bcrypt + +即使是加了盐,密码仍有可能被暴力破解。因此,我们可以采取更**「慢一点」**的算法,让黑客破解密码付出更大的代价,甚至迫使他们放弃。提升密码存储安全的利器——Bcrypt。 + +实际上,Spring Security 已经废弃了 MessageDigestPasswordEncoder,推荐使用BCryptPasswordEncoder,也就是BCrypt来进行密码哈希。BCrypt 生而为保存密码设计的算法,相比 MD5 要慢很多。看个例子: + +```java +public class BCryptTest { + public static void main(String[] args) { + String password = "123456"; + long md5Begin = System.currentTimeMillis(); + DigestUtils.md5Hex(password); + long md5End = System.currentTimeMillis(); + System.out.println("md5 time:"+(md5End - md5Begin)); + long bcrytBegin = System.currentTimeMillis(); + BCrypt.hashpw(password, BCrypt.gensalt(10)); + long bcrytEnd = System.currentTimeMillis(); + System.out.println("bcrypt Time:" + (bcrytEnd- bcrytBegin)); + } +} +``` + +运行结果: + +```java +md5 time:47 +bcrypt Time:1597 +``` + +粗略对比发现,BCrypt 比 MD5 慢几十倍,黑客想暴力破解的话,就需要花费几十倍的代价。因此一般情况,建议使用 Bcrypt 来存储用户的密码。 + + + +# 权限设计 + +下述分析的几种权限管理其实并无优劣之分。权限管理作为系统的基石,应依据系统目标用户特点、后续发展方向、维护成本等方面进行综合评估,找到最适合系统的模式即可。 + +- 用户管理核心是解决用户与权限的问题 +- 角色可理解为一类用户,或者一堆权限的集合,链接了用户与权限的关系 +- 权限可以分为功能权限和数据范围 +- 复杂的继承关系,数据范围依赖用户组织架构树,功能权限依赖角色树,将用户放置于组织架构树的不同节点上,并赋予角色,即解决了功能权限和数据范围的问题 + + + +**权限设计=功能权限+数据权限** + + + +参考文档 + +- https://www.cnblogs.com/jpfss/p/11677430.html + + + +## 权限模型 + +### 用户-权限 + +用户直接与权限映射,这种设计的优势在于简洁直观,适合用户数量不多,功能较为简单的系统,它的劣势也是非常的明显当用户量增多时,维护成本较高,可扩展性差。 + +![用户-权限](images/Architecture/用户-权限.png) + +系统页面Demo: + +![用户-权限Demo](images/Architecture/用户-权限Demo.png) + + + +### 用户-角色-权限 + +若系统用户和功能增多,为每一个用户匹配单独的权限,会变得非常的繁琐,因此权限分门别类,形成“权限包”。从用户的角度,将用户分类,同一类用户固定为相同的角色,赋予相同的权限,会让操作变得简单很多。 + +![用户-角色-权限](images/Architecture/用户-角色-权限.png) + +系统页面Demo: + +![用户-角色-权限Demo用户管理](images/Architecture/用户-角色-权限Demo用户管理.png) + +![用户-角色-权限Demo角色管理](images/Architecture/用户-角色-权限Demo角色管理.png) + + + +### 组织-用户-角色-权限 + +对于管理性后台,往往人员是职级区分的,比如用户王五为销售组长,其组员赵六和孙七,王五需要看到赵六和孙七的销售额数据和可以进行赵六和孙七的所有操作,而赵六仅能看到自己的销售额数据,进行和自己相关的操作。这个事例中引入了两个新的概念: + +- **用户的权限可以细分为功能权限和数据范围权限** +- **数据范围和功能权限都有了继承的层级概念** + +所谓功能权限大家比较好理解,比如能否看到某个页面,能否点击某个按钮。而数据权限略抽象些,比如在业绩报表模块,从功能权限角度,可以决定用户能否打开这个报表页面,但是不同用户进来看到不同的数据(王五需要看到赵六和孙七的销售额数据,而赵六仅能看到自己的销售额数据,看不到孙七的),则是由数据范围来控制的。 + +![组织-用户-角色-权限](images/Architecture/组织-用户-角色-权限.png) + + + +#### 数据范围的继承 + +我们可以将用户进行分级管理,比如建立多层级的组织架构树,将不同用户放置于组织架构的不同根节点上,来实现多层级用户的建立和管理。 + +- 如果用户的层级如果比较简单(不多于三级),可以将层级关系融入到角色之中。比如用户分为三级:管理员-组长-组员,管理员能看到全部的数据范围和拥有全部的功能权限,而组长能查看组员的数据范围和拥有部分功能权限(比如无法编辑用户),而组员的数据范围仅仅为自己负责的数据和拥有部分功能权限。 + + 我们可以在组长这个角色下,允许其绑定组员,针对不同的角色设置不同的数据范围读取方式,管理员可以读取全部数据范围,组长需要读取其组员的,组员只能读取自己的。大家有兴趣可以去了解下RBAC0/1模型 + + ![组织-用户-角色-权限数据范围](images/Architecture/组织-用户-角色-权限数据范围.png) + +- 而对于用户层级较多的情况,建议采用多层级的组织架构的管理模式,先建立公司的组织部门,再将人置于组织架构树的不同节点之下。以用户在组织节点的位置,以及是否为某个节点组织的负责人,来确定其数据范围。 + + + +#### 功能权限的继承 + +一般是将角色进行分级,有了所谓的角色树的概念: + +![功能权限的继承](images/Architecture/功能权限的继承.png) + +由此,我们就得到了自由度更高但是逻辑更复杂的权限管理: + +![功能权限的继承层级](images/Architecture/功能权限的继承层级.png) + +涉及到组织架构树和角色树,需要考虑到的场景更多,这些细节要根据实际的业务场景来制定方案。比如: + +- 组织有成立和解散的时间,员工也存在转岗换组织部门的情况,因此要考虑引入时间轴的概念。增加很多的校验逻辑,比如需要增加员工入职、离职时间,且员工在该部门任职的时间不能早于部门成立的时间等等 +- 同一员工能否兼岗,允许在多个组织下存在 +- 角色上是否会有互斥情况存在(RBAC-2模型),比如实际业务中,发钞和验钞的工作不允许同一个人做,因此在功能设计时要考虑角色之间的互斥性,通过为用户配置角色来实现用户与权限的映射关系,不同权限间是存在优先级的,比如刚刚的例子,互斥关系为最高优先级 + +实体关系图 : + +![功能权限的实体关系图](images/Architecture/功能权限的实体关系图.png) + +系统页面Demo: + +![功能权限的Demo](images/Architecture/功能权限的Demo.png) + + + +## RBAC + +RBAC是一套成熟的权限模型。在传统权限模型中,我们直接把权限赋予用户。而在RBAC中,增加了“角色”的概念,我们首先把权限赋予角色,再把角色赋予用户。这样,由于增加了角色,授权会更加灵活方便。在RBAC中,根据权限的复杂程度,又可分为RBAC0、RBAC1、RBAC2、RBAC3。其中,RBAC0是基础,RBAC1、RBAC2、RBAC3都是以RBAC0为基础的升级。我们可以根据自家产品权限的复杂程度,选取适合的权限模型。 + +![RBAC](images/Architecture/RBAC.png) + +### 基本模型RBAC0 + +RBAC0是基础,很多产品只需基于RBAC0就可以搭建权限模型了。在这个模型中,我们把权限赋予角色,再把角色赋予用户。用户和角色,角色和权限都是多对多的关系。用户拥有的权限等于他所有的角色持有权限之和。 + +![RBAC0](images/Architecture/RBAC0.png) + + + +### 角色分层模型RBAC1 + +RBAC1建立在RBAC0基础之上,在角色中引入了继承的概念。简单理解就是,给角色可以分成几个等级,每个等级权限不同,从而实现更细粒度的权限管理。 + +![RBAC1](images/Architecture/RBAC1.png) + + + +### 角色限制模型RBAC2 + +RBAC2同样建立在RBAC0基础之上,仅是对用户、角色和权限三者之间增加了一些限制。这些限制可以分成两类,即静态职责分离SSD(Static Separation of Duty)和动态职责分离DSD(Dynamic Separation of Duty)。具体限制如下图: + +![RBAC2](images/Architecture/RBAC2.png) + + + +### 统一模型RBAC3 + +RBAC3是RBAC1和RBAC2的合集,所以RBAC3既有角色分层,也包括可以增加各种限制。 + +![RBAC3](images/Architecture/RBAC3.png) + + + +### 延展用户组 + +基于RBAC模型,还可以适当延展,使其更适合我们的产品。譬如增加用户组概念,直接给用户组分配角色,再把用户加入用户组。这样用户除了拥有自身的权限外,还拥有了所属用户组的所有权限。 + +![RBAC用户组](images/Architecture/RBAC用户组.png) + + + +# 订单支付 + +## 系统设计 + +### 业务关系 + +订单系统与各业务系统的关系: + +![订单系统](images/Architecture/订单系统.jpg) + +- **对外系统** + + 所有给企业外部用户使用的系统都在这一层,包括官网、普通用户使用的C端,还包括给商户使用的商家后台和在各个销售渠道进行分销的系统,比如与银行信用卡中心合作、微信合作在合作商的平台露出本企业的产品。这类系统站在与客户接触的最前线,是公司实现商业模式的桥头堡。 + +- **管理中后台** + + 每个C端的业务形态都会有一个对应的系统模块,如负责管理平台交易的订单系统,管理优惠信息的促销系统,管理平台所有产品的产品系统,以及管理所有对外系统显示内容的内容系统等。 + +- **公共服务系统** + + 随着企业的发展,信息化建设到达一定程度后,企业需要将通用功能服务化、平台化,以保证应用架构的合理性,提升服务效率。这类系统主要给其他应用系统提供基础服务能力支持。 + + + +### 上下游关系 + +![订单上下游关系](images/Architecture/订单上下游关系.jpg) + +由此可见,订单系统对上接收用户信息,将用户信息转化为产品订单,同时管理并跟踪订单信息和数据,承载了公司整个交易线的重要对客环节。对下则衔接产品系统、促销系统、仓储系统、会员系统、支付系统等,对整个电商平台起着承上启下的作用。 + + + +### 业务架构 + +![订单业务架构](images/Architecture/订单业务架构.jpg) + +- **订单服务** + + 该模块的主要功能是用户日常使用的服务和页面,主要有订单列表、订单详情、在线下单等,还包括为公共业务模块提供的多维度订单数据服务。 + +- **订单逻辑** + + 订单系统的核心,起着至关重要的作用,在订单系统负责管理订单创建、订单支付、订单生产、订单确认、订单完成、取消订单等订单流程。还涉及到复杂的订单状态规则、订单金额计算规则以及增减库存规则等。在4节核心功能设计中会重点来说。 + +- **底层服务** + + 信息化建设达到一定程度的企业,一般会将公司公共服务模块化,比如:产品,会构建对应的产品系统,代码、数据库,接口等相对独立。但是,这也带来了一个问题,比如:订单创建的场景下需要获取的信息分散在各个系统。 + + 如果需要从各个公共服务系统调用:一是会花费大量时间,二是代码的维护成本非常高。因此,订单系统接入所需的公共服务模块接口,在订单系统即可完成对接公共系统的服务。 + + + +## 流程设计 + +### 订单流程 + +订单流程主要是订单产生到交易结束的整个流程,按照现在电子商城(E-mall),仓库管理(WMS),物流管理系统(TMS)的流转过程主要如下图: + +![订单流程](images/Architecture/订单流程.png) + + + +### 正向流程 + +**订单创建>订单支付>订单生产>订单确认>订单完成** + + + +### 逆向流程 + +![订单逆向流程](images/Architecture/订单逆向流程.jpg) + +上面说到逆向流程是各种修改订单、取消订单、退款、退货等操作,需要梳理清楚这些流程与正向流程的关系,才能理清订单系统完整的订单流程。 + +- **订单修改**:可梳理订单内信息,根据信息关联程度及业务诉求,设定订单的可修改范围是什么,比如:客户下单后,想修改收货人地址及电话。此时只需对相应数据进行更新即可。 + +- **订单取消**:用户提交订单后没有进行支付操作,此时用户原则上属于取消订单,因为还未付款,则比较简单,只需要将原本提交订单时扣减的库存补回,促销优惠中使用的优惠券,权益等视平台规则,进行相应补回。 + +- **退款**:用户支付成功后,客户发出退款的诉求后,需商户进行退款审核,双方达成一致后,系统应以退款单的形式完成退款,关联原订单数据。因商品无变化,所以不需考虑与库存系统的交互,仅需考虑促销系统及支付系统交互即可。 + +- **退货**:用户支付成功后,客户发出退货的诉求后,需商户进行退款审核,双方达成一致后,需对库存系统进行补回,支付系统、促销系统以退款单形式完成退款。最后,在退款/退货流程中,需结合平台业务场景,考虑优惠分摊的逻辑,在发生退款/退货时,优惠该如何退回的处理规则和流程。 + + + +## 关键设计 + +### 订单信息 + +![订单详细信息](images/Architecture/订单详细信息.png) + + + +### 订单单号 + +订单单号是订单信息中的主Key,代表了该订单的唯一性,并且使用在仓库管理系统中,WMS作为拆分合并订单中与电子商城中的订单关联的Key值。订单单号一般组成方式有以下两种: + +- **日期时间+随机数字** + - 初期业务量不多的时候20-26位足够应付 + - **yyyyMMddHHmmss(年月日时分秒) + 6位随机码** + - 6位随机码表示一秒钟可能生成的订单数上,存在一百万分之一的随机并发相同导致下单失败,因此在初期业务每秒下单量不高的时候选择这种简单的方法足够满足需求 + +- **日期时间+自增** + - 不会产生随机数生成冲突 + - 注意防治被查看到销售量需要将数字加密设置 + + + +### 倒计时时间 + +订单里面显示倒计时有: + +- **下单未支付** + + - 商品下单后开始倒计时,一定时间内如果还未下单则超时关闭订单 + - 普通商品一般采取3天时间,特价商品根据情况一般采取的是30分钟,快消品一般采用的15分钟 + +- **已发货确认收货倒计时** + + 商品一般是发货开始后开始倒计时10天时间,O2O商品应该是送达即收货。 + + - 满1天记录1天 XX天hh小时mm分钟 + - 小于1天小时则hh小时mm分钟ss秒 + + 防止发货时间过长,发货后用户可以采用一次延长收货,商家/平台端则可以多次**延长收货**。 + + + +### 扣减库存 + +用户下单后,系统需要生成订单,此时需要先获取下单中涉及的商品信息,然后获取该商品所涉及到的优惠信息,如果商品不参与优惠信息,则无此环节。扣减库存规则是指订单中的商品,何时从仓储系统中对相应商品库存进行扣除。方式各有优缺点,需结合实际场景进行考虑,如:秒杀、抢购、促销活动等,可使用下单减库存的方式。而对于产品库存量大,并发流量没有那么强的产品使用付款减库存的方式。 + + + +**秒杀场景下如何扣减库存?** + +- **采用下单减库存** + + 因秒杀场景下,大部分用户都是想直接购买商品的,可以直接用下单减库存。大量用户和恶意用户都是同时进行的,区别是正常用户会直接购买商品,恶意用户虽然在竞争抢购的名额,但是获取到的资格和普通用户一样,所以下单减库存在秒杀场景下,恶意用户下单并不能造成之前说的缺点。而且下单直接扣减库存,这个方案更简单,在第一步就扣减库存了。 + +- **Redis 缓存** + + 查询缓存要比查询数据库快,所以将库存数放在缓存中,直接在缓存中扣减库存。如果并发很高,还可以采取分布式锁的方案。 + +- **限流** + + 秒杀场景中,对请求做了很多限流操作,如前端页面的限流和后端令牌桶限流,真正到扣减库存时,请求数很少了。 + + + +#### 下单减库存 + +即用户下单成功时减少库存数量。 + +**优势**:用户体验友好,系统逻辑简洁。 + +**缺点**:会导致恶意下单或下单后却不买,使得真正有需求的用户无法购买,影响真实销量。 + + + +**解决办法** + +- **设置订单有效时间**:若订单创建成功N分钟不付款,则订单取消,库存回滚 +- **限购**:用各种条件来限制买家的购买件数,比如一个账号、一个ip,只能买一件 +- **风控**:从技术角度进行判断,屏蔽恶意账号,禁止恶意账号购买 + + + +#### 付款减库存 + +即用户支付完成并反馈给平台后再减少库存数量。 + +**优势**:减少无效订单带来的资源损耗。 + +**缺点**:因第三方支付返回结果存在时差,同一时间多个用户同时付款成功,会导致下单数目超过库存,商家库存不足容易引发断货和投诉,成本增加。 + + + +**解决办法** + +- 付款前再次校验库存,如确认订单要付款时再验证一次,并友好提示用户库存不足 +- 增加提示信息:在商品详情页,订单步骤页面提示不及时付款,不能保证有库存等 + + + +#### 预扣库存 + +下单页面显示最新的库存,下单后保留这个库存一段时间(比如10分钟),超过保留时间后,库存释放。若保留时间过后再支付,如果没有库存,则支付失败。 + +**优势**:结合下单减库存的优点,实时减库存,且缓解恶意买家大量下单的问题,保留时间内未支付,则释放库存。 + +**缺点**:保留时间内,恶意买家大量下单将库存用完。并发量很高的时候,依然会出现下单数超过库存数。 + + + +### 恶意下单 + +如何解决恶意买家下单的问题?这里的恶意买家指短时间内大量下单,将库存用完的买家。 + +- **限制用户下单数量** + + **优点**:限制恶意买家下单 + + **缺点**:用户想要多买几件,被限制了,会降低销售量 + +- **标识恶意买家** + + 通过标识用户设备id或会员id,将用户加入黑名单,不足之处是有些用户是模拟的,识别不出来是不是真正的恶意买家。 + + + +### 支付失败 + +如何解决下单成功而支付失败(库存不足)的问题? + +- **备用库存** + + **优点**:缓解部分用户支付失败的问题。 + + **缺点**:备用库存只能缓解问题,不能从根本上解决问题。另外备用库存针对普通商品可以,针对特殊商品这种库存少的,备用库存量也不会很大,还是会出现大量用户下单成功却因库存不足而支付失败的问题。 + + + +### 库存超卖 + +如何解决高并发下库存超卖的场景?库存超卖最简单的解释就是多成交了订单而发不了货。 + +**场景**:用户 A 和 B 成功下单,在支付时扣减库存,当前库存数为 10。因 A 和 B 查询库存时,都还有库存数,所以 A 和 B 都可以付款。 + +A 和 B 同时支付,A 和 B 支付完成后,可以看做两个请求`回调`后台系统扣减库存,有两个线程处理请求,两个线程查询出来的库存数 `inventory = 10`。 + +![库存超卖](images/Architecture/库存超卖.png) + +A 线程更新最终库存数 :lastInventory = inventory - 1 = 9, + +B 线程更新库存数: lastInventory = inventory - 1 = 9。 + +而实际最终的库存应是 8 才对,这样就出现库存超卖的情况,而发不出货。那如何解决库存超卖的情况呢?以下方案都是基于数据库层面的。 + +- **方案一:SQL语句直接更新库存,而不是先查询出来,然后赋值** + + ```mysql + UPDATE [库存表] SET 库存数 - 1 + ``` + +- **方案二:SQL语句更新库存时,如果扣减库存后,库存数为负数,直接抛异常,利用事务的原子性进行自动回滚。** + +- **方案三:利用SQL语句更新库存,防止库存为负数** + + ```mysql + UPDATE [库存表] SET 库存数 - 1 WHERE 库存数 - 1 > 0 + ``` + + 如果影响条数大于1,则表示扣减库存成功,否则不更新库存,并退款。 + + + +### 订单拆分 + +用户支付完订单后,需要获取订单的支付信息,包括支付流水号、支付时间等。支付完订单接着就是等商家发货,但在发货过程中,根据平台业务模式的不同,可能会涉及到订单的拆分。订单拆分一般分两种: + +- 一种是用户挑选的商品来自于不同渠道(自营与商家,商家与商家) +- 另一种是在SKU层面上拆分订单:不同仓库,不同运输要求的SKU,包裹重量体积限制等因素需要将订单拆分 + + + +### 异步处理 + +随着公司的发展你可能会发现你项目的**请求链路越来越长**,例如刚开始的电商项目,可以就是粗暴的扣库存、下单。慢慢地又加上积分服务、短信服务等。这一路同步调用下来客户可能等急了,这时候就是消息队列登场的好时机。 + +**调用链路长、响应就慢了**,并且相对于扣库存和下单,积分和短信没必要这么的 “及时”。因此只需要在下单结束那个流程,扔个消息到消息队列中就可以直接返回响应了。而且积分服务和短信服务可以并行的消费这条消息。可以看出消息队列可以**减少请求的等待,还能让服务异步并发处理,提升系统总体性能**。 + +![订单服务-异步处理](images/Architecture/订单服务-异步处理.png) + + + +### 服务解耦 + +上面我们说到加了积分服务和短信服务,这时候可能又要来个营销服务,之后领导又说想做个大数据,又来个数据分析服务等等。所以一般会选用消息队列来解决系统之间耦合的问题,订单服务把订单相关消息塞到消息队列中,下游系统谁要谁就订阅这个主题。 + +![订单服务-服务解耦](images/Architecture/订单服务-服务解耦.jpg) + + + +### 流量控制 + +后端服务相对而言都是比较弱的,因为业务较重,处理时间较长。如秒杀活动爆发式流量打过来可能就顶不住了,因此需要引入一个中间件来做缓冲,基于消息队列实现削峰填谷功效。 + +- 网关的请求先放入消息队列中,后端服务尽自己最大能力去消息队列中消费请求。超时的请求可以直接返回错误 +- 当然还有一些服务特别是某些后台任务,不需要及时地响应,并且业务处理复杂且流程长,那么过来的请求先放入消息队列中,后端服务按照自己的节奏处理 + +上面两种情况分别对应着生产者生产过快和消费者消费过慢两种情况,消息队列都能在其中发挥很好的缓冲效果。 + +![订单服务-流量控制](images/Architecture/订单服务-流量控制.jpg) + + + +## 异常设计 + +### 超时关闭 + +下单后发布延迟消息至MQ,等延迟时间到后,MQ会通知应用该消息,然后检查是否已被支付,未被支付则关闭超时订单。 + + + +### 重复支付 + +![重复支付-下单流程](images/Architecture/重复支付-下单流程.png) + +如图是一个简化的下单流程,首先是提交订单,然后是支付。支付的话,一般是走支付网关(支付中心),然后支付中心与第三方支付渠道(微信、支付宝、银联)交互,支付成功以后,异步通知支付中心,支付中心更新自身支付订单状态,再通知业务应用,各业务再更新各自订单状态。 + +这个过程中经常可能遇到的问题是掉单,无论是超时未收到回调通知也好,还是程序自身报错也好,总之由于各种各样的原因,没有如期收到通知并正确的处理后续逻辑等等,都会造成用户支付成功了,但是服务端这边订单状态没更新,这个时候有可能产生投诉,或者用户重复支付。 + +由于③⑤造成的掉单称之为外部掉单,由④⑥造成的掉单我们称之为内部掉单。为了防止掉单,这里可以这样处理: + +- 支付订单增加一个中间状态“支付中”,当同一个订单去支付的时候,先检查有没有状态为“支付中”的支付流水,当然支付(prepay)的时候要加个锁。支付完成以后更新支付流水状态的时候再讲其改成“支付成功”状态 +- 支付中心这边要自己定义一个超时时间(比如:30秒),在此时间范围内如果没有收到支付成功回调,则应调用接口主动查询支付结果,比如10s、20s、30s查一次,如果在最大查询次数内没有查到结果,应做异常处理 +- 支付中心收到支付结果以后,将结果同步给业务系统,可以发MQ,也可以直接调用,直接调用的话要加重试(比如:SpringBoot Retry) +- 无论是支付中心,还是业务应用,在接收支付结果通知时都要考虑接口幂等性,消息只处理一次,其余的忽略 +- 业务应用也应做超时主动查询支付结果 + +对于上面说的超时主动查询可以在发起支付的时候将这些支付订单放到一张表中,用定时任务去扫。 + +为了防止订单重复提交,可以这样处理: + +**创建订单的时候,用订单信息计算一个哈希值,判断redis中是否有key,有则不允许重复提交,没有则生成一个新key,放到redis中设置个过期时间,然后创建订单。其实就是在一段时间内不可重复相同的操作。** + +附上微信支付最佳实践: + +![微信支付最佳实践](images/Architecture/微信支付最佳实践.png) + + + +### 支付对账 + +![对账业务流程](images/Architecture/对账业务流程.png) + + + +### 支付掉单 + +一个最常见的支付平台架构关系如下所示: + +![支付系统异常处理-支付平台](images/Architecture/支付系统异常处理-支付平台.jpg) + +上图我们是站在第三方支付公司支付角度,如果是自己公司的内部支付系统,那么外部商户这一块其实就是公司内部一些系统,比如说订单系统,而外部支付渠道其实就是第三方支付公司。我们以携程为例,在其上面发起一笔订单支付,将会经过三个系统: + +- 携程创建订单,向第三方支付公司发起支付请求 +- 第三方支付公司创建订单,并向工行发起支付请求 +- 工行完成扣款操作,返回第三方支付公司 +- 第三方支付完成订单更新并返回携程 +- 携程变更订单状态 + +上面的流程,简单如下图所示: + +![支付系统异常处理-携程](images/Architecture/支付系统异常处理-携程.jpg) + +在这个过程就可能会碰到,用户工行卡已经扣款,但是携程订单却还是待支付,我们通常将这种情况称为「掉单」。上述掉单的场景,多数是因为「③、⑤」环节信息丢失导致,这种掉单我们将其称为「外部掉单」。还有一种极少数的情况,收到 「③、⑤」环节返回信息,但是在「④、⑥」环节内部系统更新订单状态失败,从而导致丢失支付成功的信息,这类掉单由于是内部问题,我们通常将其称之为「内部掉单」。 + + + +#### 外部掉单 + +外部掉单是因为没有收到对端返回信息,这种情况极有可能是网络问题,也有可能对端处理逻辑太慢,导致我方请求超时,直接断开了网络请求。 + +**① 增加超时时间** + +适当的增加超时时间,在增加网络超时时间之后,可能还需要调整整个链路的超时时间,不然有可能导致整个链路内部超时从而引起内部掉单。 + +**注意**:对接外部渠道,一定要「设置网络连接超时时间与读取超时时间」。 + +**② 接收异步通知** + +接收渠道异步回执通知信息,一般来说,现在支付渠道接口都可以上送一个异步回调地址,当渠道端处理成功,将会把成功信息通知到这个回调地址上。这种情况下,只需要接收通知信息,然后解析,再更新内部订单状态。 + +![支付系统异常处理-支付异步通知](images/Architecture/支付系统异常处理-支付异步通知.jpg) + +**注意** + +- 对于异步请求信息,一定需要对通知内容进行签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失 +- 异步通知将会发送多次,所以异步通知处理需要幂等 + +**③ 掉单查询** + +有的渠道可能没有提供异步通知的功能,只提供了订单查询的接口,这种情况下,只能使用定时掉单查询解决。可以将这类超时未知的订单的单独保存到掉单表,然后定时向渠道端查询订单的状态。若查询成功或者明确失败(比如订单不存在等),可以更新订单状态,并且删除掉单表记录;若查询依旧未知,这时我们需要等待下次查询的结果。 + +![支付系统异常处理-定时查询](images/Architecture/支付系统异常处理-定时查询.jpg) + +**注意**:有些情况下,有可能无法查询返回订单的状态,所以需要设置订单查询的最大次数,防止无限查询浪费性能。 + +**④ 对账** + +最后,极少数的情况下,订单查询与异步通知都无法获取的支付结果,这就只能进行对账处理。如果第二天渠道端给的对账文件有这一笔支付结果,那么可以根据这个记录更新直接更新内部支付记录。 + +**注意**:稳妥一点,可以先发起查询,然后根据查询结果更新订单记录。不过有些极端情况,查询无法获取结果,那么直接更新内部记录即可。那如果第二天也没有这笔记录的结果,这种情况下,我们可以认为这笔是失败的。如果用户被扣款,渠道端内部将会发起退款,将支付金额返回给用户。所以这种情况可以无需处理。 + + + +#### 内部掉单 + +**① 支付公司内部订单关系** + +如下图所示,第三方支付公司内部表通常为支付订单与渠道订单这样一种 1 比 N 的关系: + +![支付系统异常处理-支付订单渠道1N](images/Architecture/支付系统异常处理-支付订单渠道1N.jpg) + +**渠道订单**代表着第三方支付公司与外部渠道的关系,其实对**外部渠道系统**来讲,第三方支付公司就是一个外部商户。如果使用上图 1 对1 的订单关系,当第一次支付支付失败,外部商户可能会再次使用相同订单号对第三方支付公司发起支付。 + +![支付系统异常处理-支付订单渠道11](images/Architecture/支付系统异常处理-支付订单渠道11.jpg) + +如果第三方支付公司也拿相同的内部订单去请求外部渠道系统,有可能外部渠道系统并不支持同一订单号再次请求。但是现实的情况,很多外部商户并不是那么容易更换生成新的订单号,所以一般第三方支付公司都需要支持同一外部商户订单号在未成功的情况下,支持重复支付。在这种情况下,就需要我们上面的 1:N 的订单关系图了。 + + + +**② 内部掉单异常的原因** + +当收到外部渠道系统的成功的返回信息,成功更新了渠道订单表的记录。但是由于渠道订单表与支付订单表可能不是同一个数据库,也有可能两者并不在同一个应用中,这就有可能导致更新支付订单表的更新失败。 + +![支付系统异常处理-内部掉单](images/Architecture/支付系统异常处理-内部掉单.jpg) + +由于支付订单是表保存着外部商户订单与内部订单关系,支付订单未成功,所以外部商户也无法查询得到成功的支付结果。 + +此时渠道订单表已经成功,所以上面外部掉单的方法并不适用内部掉单。 + + + +**内部掉单异常解决办法:** + +- **分布式事务** + + 内部掉单异常,说白就是因为支付订单表与渠道订单表无法使用数据库事务保证两者同时更新成功或失败。 + +- **异步补偿更新** + + 当发生内部掉单的情况,即更新支付订单失败等情况,可以将这里支付订单保存(无法保证绝对成功)到一张内部掉单表。所以还需要定时查询,查询一段时间内支付订单未成功,而渠道订单表已成功的支付订单记录,然后也将其插入到内部掉单表。另一个系统应用,只需要定时扫描内部掉单表,将支付订单成功,然后再删除内部掉单记录即可。 + +**注意**:当支付订单表数据量很大后,定时查询可能会慢,为了防止影响主库,所以这类查询可以在备库进行。 + + + +#### 解决方案 + +支付掉单、卡单是支付过程中经常会碰到的事,可以采用异步补偿的方案,解决该问题。异步补偿方案可以采用如下两种: + +- 定时轮询补偿方案 +- 延迟消息补偿方案 + +定时轮询补偿方案实现起来比较简单,但是时效性稍差。而延迟消息补偿方案总体来说比较优秀,但是实现起来比较复杂。如果没有自定义的延迟时间的需求,可以直接采用 RocketMQ 延迟消息,简单快捷。另外**延迟队列**使用场景还是比较多,不仅仅能用在掉单补偿上,还可以用于支付关单等场景。所以有能力开发的团队,可以开发一个通用的延迟队列。 + + + +**方案一:定时轮询补偿方案** + +**① 整体流程** + +该方案主要采用定时任务,批量查询掉单记录,从而驱动查询具体支付结果,然后更新内部订单。整体方案流程图如下: + +![支付掉单-定时任务补偿](images/Architecture/支付掉单-定时任务补偿.jpg) + +前三步流程没什么好说的,正常的支付流程,咱们针对后面几步具体详细说下。 + +第三步调用支付通道之后,如果支付通道端返回**支付受理成功或者支付处理中**,我们就需要调用第四步,将这类订单插入掉单表。如果支付直接成功了,那就正常流程返回即可。 + +> 复习一下,网关类支付,比如支付宝、微信支付、网银支付,这种支付模式,支付通道仅仅返回支付受理成功,具体支付结果需要接收支付通道端的支付通知,这类支付我们将其称为异步支付。相应的还有同步支付,比如银行卡支付,微信、支付宝代扣类支付,这类支付,同步就能返回支付结果。 + +第五步,补单应用将会定时查询数据库,批量查询掉单记录。 + +第六步,补单应用使用线程池,多线程异步的方式发起掉单查询。 + +第七步,调用支付通道支付查询接口。 + +重点来了,如果第七步支付结果查询为以下状态: + +- **支付结果为扣款成功** +- **支付结果为明确失败** +- **掉单记录查询达到最大次数** + +**第八步就会删除掉单记录。** + +最后,如果掉单查询依旧还是处理中,那么经过一定的延时之后,重复第五步,再次重新掉单补偿,直到成功或者查询到达最大次数。 + +**② 相关问题** + +**为什么需要新建一张掉单表?不能直接使用支付订单表,查询未成功的订单吗?** + +这个问题,实际上确实可以直接使用的支付订单表,然后批量查询当天未成功的订单,补单程序发起支付查询。那为什么需要新建一张掉单表?主要是因为数据库查询效率问题,因为支付订单表每天都会大量记录新增,随着时间,这张表记录将会越来越多,越来越大。 + +**支付记录越多,批量范围查询效率就会变低,查询速度将会变慢**。所以为了查询效率,新建一张掉单表。这张表里仅记录支付未成功的订单,所以数据量就会很小,那么查询效率就会很高。另外,掉单表里的记录,不会被永久保存,只是临时性。当支付结果查询成功,或者支付结果明确失败,再或者查询次数到达规定最大次数,就会删除掉单记录。 + +**这就是第八步为什么需要删除掉单表的原因。** + +如果需要保存每次掉单表查询详情,那么这里建议再新增一张掉单查询记录表,保存每一次的查询记录。针对这个方案,如果还有其他问题,欢迎留言。 + +**③ 方案优缺点** + +定时轮询补偿方案,最大的优点可能就是系统架构方案比较简单,比较容易实施。那么这个方案的缺点主要在于**定时任务**上。定时任务轮询方案天然会存在以下不足: + +- **轮询效率稍低** +- 每次查询数据库,已经被执行过记录,仍然会被扫描(补单程序将会根据一定策略决定是否发起支付通道查询),有**重复计算**的嫌疑 +- **时效性不够好**,如果每小时轮询一次,最差的情况下,时间误差会达到1小时 +- 如果为了解决时效性问题,增加定时任务查询效率,那么 1 中查询效率跟 2 的重复计算问题将会更加明显 + + + +**方案二:延迟消息补偿方案** + +下面介绍另外一种掉单补偿方案,延迟消息补偿方案,这个方案整体流程与定时任务方案类似,最大区别可能在于,从一种**拉模式**变成一种**推模式**。 + +**① 整体流程** + +整体方案流程图如下: + +![支付掉单-延迟消息补偿方案](images/Architecture/支付掉单-延迟消息补偿方案.jpg) + +这个方案主要流程跟定时方案类似,主要区别在于第四步,第五步,第八步。 + +第四步的流程从插入掉单表变更为往**延迟队列发送掉单消息**。 + +第五步,补单程序接收掉单消息,然后触发支付掉单查询。 + +第八步,如果第七步支付结果查询为以下状态: + +- 支付结果为扣款成功 +- 支付结果为明确失败 +- 掉单记录查询达到最大次数 + +补单程序将会告知延迟队列消费成功,延迟队列将会删除这条掉单消息。其他状态将会告知消费失效,延迟队列将会在一定延时之后,再次发送掉单消息,然后继续重复第五步。 + +**② 延迟队列** + +这里的延迟队列需要自己实现,复杂度还是比较高的,这里给大家推荐几种实现方案: + +第一种,基于 **Redis SortedSet** 实现延迟队列。可以参考一下有赞的实现方案https://tech.youzan.com/queuing_delay/ + +第二种,基于时间轮算法(**TimingWheel**)实现延迟队列,具体可以参考 Kafka 延时队列。 + +第三种,基于 **RocketMQ** 延迟消息。 + +前两种方案说起来还需要再开发,所以还是比较复杂的。这里重点说下第三种方案,该方案是 **RocketMQ** 已经支持的特性,开箱即用,使用起来还是比较简单的。RocketMQ 延迟消息支持 18 个等级,分别如下: + +```properties +1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h +``` + +消息发送方可以通过Message.setDelayTimeLevel方式指定延迟等级,对应上方的延迟时间。消息消费方,如果消费失败,默认将会在消息发送方的的延迟等级基础上加 1。如果消息消费方需要指定其他的延迟等级,可以使用ConsumeConcurrentlyContext.setDelayLevelWhenNextConsume方式。RocketMQ 延迟消息,支持的特性还是比较基础、简单,不支持自定义延迟时间。不过对于掉单补偿的这个场景刚好够用,但是如果需要自定义延迟的,那还是得采用其他的方案。 + +**③ 方案优缺点** + +延迟消息的方案相对于定时轮询方案来讲: + +- 无需再查询全部订单,效率高 +- 时效性较好 + +不过延迟消息这种方案,需要基于**延迟队列**,实现起来比较复杂,目前开源实现也比较少。 + + + +## 微信支付 + +### 前提条件 + +#### 公众号 + +微信公众号大体上可以分为服务号和订阅号。微信支付接入需要**已经完成微信认证的服务号**。如果是小程序的话,也需要完成**微信认证**。公众号可以关联同一主体的10个小程序,不同主体的3个小程序,如果是和公众号同一主体的小程序并且公众号已经完成认证,则直接可以在公众号后台的`小程序管理`中,进行快速注册并认证,这样就无需重复支付微信认证所需的`300`RMB了。 + +![微信支付-公众号](images/Architecture/微信支付-公众号.png) + + + +#### 微信商户平台 + +微信认证完成后,在公众号后台的 `微信支付` 中开通微信支付功能。提交微信支付申请后,3-5个工作日内,会进行审核,审核通过后会往你填写的邮箱里发送一份包含商户号信息的邮件,同时会往你填写的对公账户中打几毛钱的汇款,需要你查看具体金额后在商户平台中验证。 + +商户分为普通商户和服务商商户,千万不要申请错了。普通商户是可以进行交易,但是不能拓展商户。服务商可以拓展商户,但是不能交易。服务商就是提供统一的支付入口,它需要绑定具体的普通商户,微信支付时会在支付接口中携带普通商户参数,支付成功后金额会直接到具体的普通商户账户上。 + +![微信支付-微信商户平台](images/Architecture/微信支付-微信商户平台.png) + +申请时直接申请**普通商户**就可以了。 + + + +#### 绑定商户 + +微信支付发起依赖于公众号、小程序等应用与商户号的绑定关系。因此在进行开发前,需要将商户与具体应用进行绑定。如果商户和需要绑定的AppID是同一主体,只需要以下步骤即可完成绑定。 + +- 在商户平台-产品中心-AppID账户管理中关联AppID,输入AppId申请绑定 +- 在公众号或小程序后台微信支付-商户号管理中进行确认。 + +![微信支付-绑定商户](images/Architecture/微信支付-绑定商户.png) + +如果商户和需要绑定的AppID是不同主体,步骤和上述一样,除了输入AppId之外,还需要填入AppId的认证信息。 + +![微信支付-新增授权](images/Architecture/微信支付-新增授权.png) + + + +### 相关配置 + +#### 支付产品类型 + +- **付款码支付** + + 用户打开微信钱包-付款码的界面,商户扫码后提交完成支付。 + +- **JSAPI支付** + + 用户通过微信扫码,关注公众号等方式进入商家H5页面,并在微信内调用JSSDK完成支付。 + +- **Native支付** + + 用户打开微信扫一扫,扫描商户的二维码后完成支付。 + +- **APP支付** + + 商户APP中集成微信SDK,用户点击后跳转到微信内完成支付。 + +- **H5支付** + + 用户在微信以外的手机浏览器请求微信支付的场景唤起微信支付。 + +- **小程序支付** + + 用户在微信小程序中使用微信支付的场景。 + +- **刷脸支付** + + 无需掏出手机,刷脸完成支付,适合线下各种场景。 + + + +在商户平台-产品中心-我的产品中申请开通支付产品。 + +![微信支付-产品大全](images/Architecture/微信支付-产品大全.png) + + + +#### 支付授权目录配置 + +在商户平台-产品中心-开发配置中进行支付授权目录的配置(即你开发的下单接口地址),需要注意的是授权目录最多可以配置**五个**,在开发过程中请合理定义支付接口。 + +![微信支付-开发配置](images/Architecture/微信支付-开发配置.png) + + + +#### 配置商户密钥 + +在商户平台-账户中心-API安全中设置API密钥。 + +![微信支付-API秘钥](images/Architecture/微信支付-API秘钥.png) + +第一次设置时,需要安装操作证书,傻瓜式安装,按照提示一步一步操作就可以。API密钥需要一个**32位**的随机字符串,记得**不要随意更改API密钥**。 + +![微信支付-设置API秘钥](images/Architecture/微信支付-设置API秘钥.png) + +在微信API v3版本中,除了要配置API密钥外,还需要配置APIv3密钥和申请CA颁发的API证书。 + +- API v3密钥主要用于平台证书解密、回调信息解密 +- API证书用于调用更高级别的api接口,包含退款、红包等接口 + +**如果使用开源的微信开发包,请了解是否支持v3版本**。 + + + +#### 配置服务器 + +在公众号后台-开发-基本配置-服务器配置中启用并填写服务器信息。 + +![微信支付-服务器配置](images/Architecture/微信支付-服务器配置.png) + + + +#### 白名单配置 + +在公众号后台-开发-基本配置-公众号开发信息中配置开发者密钥,同时填写IP白名单。 + +![微信支付-白名单配置](images/Architecture/微信支付-白名单配置.png) + + + +#### JS接口安全域名 + +在公众号后台-公众号设置-功能设置中设置JS接口安全域名。 + +![微信支付-JS接口安全域名](images/Architecture/微信支付-JS接口安全域名.png) + +上面的配置是基于公众号支付配置的,小程序支付没有这么麻烦,小程序支付不用配置支付授权目录和授权域名。 + +| | JSAPI | 小程序 | +| :------- | :--------- | :----- | +| 支付协议 | HTTP/HTTPS | HTTPS | +| 支付目录 | 有 | 无 | +| 授权域名 | 有 | 无 | + + + +### 支付流程 + +由于微信升级了API接口,在API v3接口中,需要加载申请的API证书,微信已经封装了相关jar包,并且提供了加载示例,具体可参考“https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_3.shtml”,这里就不再赘述。我们以API v2为例详细学习一下微信接入的主要流程(因为API v3的一些接口还在持续升级,v2接口相对完整)。 + +![微信支付-支付流程](images/Architecture/微信支付-支付流程.png) + +上面的这张图片来自微信开发文档,我们详细分析一下支付流程。 + + + +#### 微信下单接口 + +用户通过微信客户端发起支付,在商戶后台生成订单,然后调用微信下单接口,生成预支付订单,返回订单号!下单接口涉及到的主要参数,只列举重要的几个参数: + +| 请求参数 | 是否必传 | 类型 | 描述 | +| :----------- | :------- | :----- | :------------------------ | +| appid | 是 | String | 公众号appid | +| mch_id | 是 | String | 商户号 | +| nonce_str | 是 | String | 随机字符串,32位以内 | +| sign | 是 | String | 签名,默认使用MD5进行加密 | +| out_trade_no | 是 | String | 系统内部订单号 | +| total_fee | 是 | Int | 订单总金额,单位是分 | +| notify_url | 是 | String | 支付结果通知接口 | + +`sign` 的签名也比较通用,涉及了一个保证签名不可预测的`nonce_str: + +- 将所有发送的非空参数使用字典排序生成键值对(key1=value1&key2=value2) +- 将商户平台密钥拼接在上述字符串的最后("String"+&key=密钥) +- 将上述字符串采用MD5加密 + + + +#### 支付 + +拉起微信支付,输入密码,完成支付。这一步需要在H5网页中执行JS调起支付。需要以下参数,因此在预付订单返回时,需要将下列参数封装后响应给页面,由页面完成支付。 + +| 参数名 | 是否必传 | 类型 | 描述 | +| :-------- | :------- | :----- | :------------------------------ | +| appId | 是 | String | 公众号id | +| timeStamp | 是 | String | 当前时间戳 | +| nonceStr | 是 | String | 随机字符串 | +| package | 是 | String | 预支付订单,格式为prepay_id=*** | +| signType | 是 | String | 签名类型,默认MD5 | +| paySign | 是 | String | 签名 | + + + +签名和下单接口的签名方式一样。 + +JS伪代码如下: + +```javascript + +function onBridgeReady(){ + WeixinJSBridge.invoke( + 'getBrandWCPayRequest', { + // 公众号ID,由商户传入 + "appId":"wx2421b1c4370ec43b", + // 时间戳,自1970年以来的秒数 + "timeStamp":"1395712654", + // 随机串 + "nonceStr":"e61463f8efa94090b1f366cccfbbb444", + "package":"prepay_id=u802345jgfjsdfgsdg888", + // 微信签名方式 + "signType":"MD5", + // 微信签名 + "paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" + }, + function(res){ + if(res.err_msg == "get_brand_wcpay_request:ok" ){ + // 使用以上方式判断前端返回,微信团队郑重提示: + // res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。 + } + }); +} +if (typeof WeixinJSBridge == "undefined"){ + if( document.addEventListener ){ + document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); + }else if (document.attachEvent){ + document.attachEvent('WeixinJSBridgeReady', onBridgeReady); + document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); + } +}else{ + onBridgeReady(); +} +``` + +注意伪代码中的这句话`// res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。`为什么这么说呢,我举个例子应该就明白了。假如你去超市买东西,是不是你说支付成功了你就可以把东西带走呢?肯定不是,是当商家收到钱后才算你支付成功,你才可以把东西带走。也就是说,这里提示的成功并不能说一定支付成功了,具体是否成功,微信平台会以异步的方式给你进行通知。 + + + +#### 异步通知 + +异步通知是比较重要的一步,在这里你可以根据通知结果处理你的业务逻辑。但是,可能会由于网络波动等原因通知不到,或者说微信接收到的响应不符合API的规定,微信会持续发起多次通知(请在回调通知接口中合理处理,**避免重复通知造成业务重复处理**),直到成功为止,通知频率为`15s`/`15s`/`30s`/`3m`/`10m`/`20m`/`30m`/`30m`/`30m`/`60m`/`3h`/`3h`/`3h`/`6h`/`6h` - 总计 `24h4m`)。但是微信不保证通知最终一定会成功。异步通知**响应参数**如下: + +| 参数名 | 是否必传 | 类型 | 描述 | +| :---------- | :------- | :----- | :--------------------------- | +| return_code | 是 | String | 返回状态码,`SUCCESS`/`FAIL` | +| return_msg | 否 | String | 返回信息 | + +如果微信一直通知不成功怎么?还是刚才那个例子,你明明支付成功了,但是商家却一直说她没收到钱,这时候你怎么办?肯定是去看一下她的手机是否真的没有收到钱!这里也一样。 + + + +#### 支付状态查询 + +![微信支付-支付状态查询](images/Architecture/微信支付-支付状态查询.png) + +- 商户APP或者前端页面收到支付返回时,商户需要调用商户查单接口确认订单状态,并把查询结果展示给用户 +- 商户后台需要准确、高效地处理微信支付发送的异步支付结果通知,并按接口规范把处理结果返回给微信支付 +- 商户后台未收到异步支付结果通知时,商户应该主动调用 `微信支付查单接口`,同步订单状态 +- 商户在T+1日从微信支付侧获取T日的交易账单,并与商户系统中的订单核对。如出现订单在微信支付侧成功,但是在商户侧未成功的情况,商户需要给用户补发货或者退款处理 + + + +**总结** + +本文主要以公众号支付为例,总结了接入微信支付需要的相关配置和支付流程。其他支付像APP支付也是开发中比较常见的应用场景,APP支付需要在 `微信开放平台` 去创建应用来接入微信支付。除此之外,微信支付API在向v3平滑升级,有些接口也还没有升级完成,升级完的接口相较于v2发生了一些数据格式方面的变化。如果引用第三方开发包进行开发,需要注意接口对应的版本。 + + + +# 分布式锁 + +**库存超卖问题** + +系统A是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会给用户下单。由于系统有一定的并发,所以会预先将商品的库存保存在redis中,用户下单的时候会更新redis的库存。此时系统架构如下: + +![库存超卖-问题](images/Architecture/库存超卖-问题.png) + +但是这样一来会**产生一个问题**:假如某个时刻,redis里面的某个商品库存为1,此时两个请求同时到来,其中一个请求执行到上图的第3步,更新数据库的库存为0,但是第4步还没有执行。而另外一个请求执行到了第2步,发现库存还是1,就继续执行第3步。这样的结果,是导致卖出了2个商品,然而其实库存只有1个。这就是典型的`库存超卖`问题。 + +**解决方案**:此时,我们很容易想到解决方案:用锁把2、3、4步锁住,让它们执行完之后,另一个线程才能进来执行第2步。 + +![库存超卖-解决方案](images/Architecture/库存超卖-解决方案.jpg) + + + +**分布式锁的特点** + +- **互斥性**:和我们本地锁一样互斥性是最基本,但是分布式锁需要保证在不同节点的不同线程的互斥 +- **可重入性**:同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁 +- **锁超时**:和本地锁一样支持锁超时,防止死锁 +- **高性能和高可用**:加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级 +- **支持阻塞和非阻塞**:和ReentrantLock一样支持lock和trylock以及tryLock(long timeout) +- **支持公平锁和非公平锁(可选)**:公平锁的意思是按照请求加锁的顺序获得锁,非公平锁就相反是无序的 + + + +**三种方案对比** + +- **从理解的难易程度角度(从低到高)**:数据库 > 缓存 > Zookeeper + +- **从实现的复杂性角度(从低到高)**:Zookeeper >= 缓存 > 数据库 + +- **从性能角度(从高到低)**:缓存 > Zookeeper >= 数据库 + +- **从可靠性角度(从高到低)**:Zookeeper > 缓存 > 数据库 + + + +## MySQL + +**适用场景** + +`Mysql` 分布式锁一般适用于资源不存在数据库,如果数据库存在比如订单,那么可以直接对这条数据加行锁,不需要我们下面多的繁琐的步骤。如一个订单,可以用 `select * from order_table where id = 'xxx' for update` 进行加行锁,那么其它事务就不能对其进行修改。 + + + +**优缺点** + +- **优点**:简单,易于理解,不需要维护额外的第三方中间件(如Redis,Zk) + +- **缺点**:实现起来较为繁琐、需要自己考虑锁超时和加事务、性能局限于数据库 + + + +### 表主键唯一 + 乐观锁 + +利用主键唯一特性,如有多个请求同时提交到数据库,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,当方法执行完毕之后,想要释放锁的话,删除这条数据库记录即可。上述方案简单实现有以下几个问题: + +- 强依赖数据库可用性,是一个单点(部署双实例) +- 没有失效时间,一旦解锁失败,就会导致死锁(添加定时任务扫描表) +- 一旦插入失败就会直接报错,不会进入排队队列(使用while循环,成功后才返回) +- 是非重入锁,同一线程在没有释放锁之前无法再次获得该锁(添加字段记录机器和线程信息,查询时相同则直接分配) +- 非公平锁(建中间表记录等待锁的线程,根据创建时间排序后进行依次处理) +- 采用主键冲突防重,在大并发情况下有可能会造成锁表现象(采用程序生产主键进行防重) + + + +### 表字段版本号 + 乐观锁 + +这个策略源于 `mysql` 的 `mvcc` 机制,使用这个策略其实本身没有什么问题,唯一的问题就是对数据表侵入较大,我们要为每个表设计一个版本号字段,然后写一条判断 `sql` 每次进行判断,增加了数据库操作的次数,在高并发的要求下,对数据库连接的开销也是无法忍受的。 + + + +### 基于数据库排他锁 + 悲观锁 + +在查询语句后面增加`for update`(会在执行成功后立即返回,在执行失败时一直处于阻塞状态,直到成功),数据库会在查询过程中给数据库表增加排他锁(注意: `InnoDB` 引擎在加锁的时候,只有通过索引进行检索的时候才会使用行级锁,否则会使用表级锁)。当获取到锁之后,可以执行方法的业务逻辑,执行完方法之后,通过`connection.commit()`操作来释放锁。依然没有办法直接解决数据库单点和可重入的问题。 + +- **表结构设计** + + ![mysqllock](images/Architecture/mysqllock.png) + +- **lock()** + + ![mysqllock-lock-java](images/Architecture/mysqllock-lock-java.png) + + ![mysqllock-lock-sql](images/Architecture/mysqllock-lock-sql.png) + +- **trylock()** + +![mysqllock-trylock](images/Architecture/mysqllock-trylock.png) + +- **trylock(long timeout)** + +![mysqllock-trylock-timeout](images/Architecture/mysqllock-trylock-timeout.png) + +- **unlock()** + +![mysqllock-unlock](images/Architecture/mysqllock-unlock.png) + + + +## Redis + +基于缓存的分布式锁常用方式包括如下: + +- 使用**Redis**的`setnx()`用于分布式锁(`setnx`直接设置值为当前时间+超时时间,保持操作原子性) +- 使用**Memcached**的`add()`方法用于分布式锁 +- 使用**Tair**的`put()`方法用于分布式锁 + + + +**优缺点** + +- **优点**:对于`Redis`实现简单,性能对比`ZK`和`Mysql`较好。如果不需要特别复杂的要求,那么自己就可以利用`setNx`进行实现,如果自己需要复杂的需求的话那么可以利用或者借鉴`Redission`。对于一些要求比较严格的场景来说的话可以使用`RedLock` + +- **缺点**:需要维护`Redis`集群,如果要实现`RedLock`那么需要维护更多的集群 + + + +### LUA+SETNX+EXPIRE + +先用`setnx`来抢锁,如果抢到之后,再用`expire`给锁设置一个过期时间,防止锁忘记了释放。 + +- **setnx()** + + `setnx` 的含义就是 `SET if Not Exists`,其主要有两个参数 `setnx(key, value)`,该方法是原子的 + + - 如果 `key` 不存在,则设置当前 `key` 成功,返回 `1` + - 如果当前 `key` 已经存在,则设置当前 `key` 失败,返回 `0` + +- **expire()** + + `expire` 设置过期时间,要注意的是 `setnx` 命令不能设置 `key` 的超时时间,只能通过 `expire()` 来对 `key` 设置。 + +```java +if(jedis.setnx(key_resource_id,lock_value) == 1){ // 加锁 + expire(key_resource_id,100); // 设置过期时间 + try { + do something // 业务请求 + }catch(){ + } + finally { + jedis.del(key_resource_id); // 释放锁 + } +} +``` + +但该方案中,`setnx`和`expire`两个命令分开了,**「不是原子操作」**。如果执行完`setnx`加锁,正要执行`expire`设置过期时间时,进程crash或者要重启维护了,那么这个锁就永远不会过期,其它线程永远获取不到锁。 + + + +**使用Lua脚本(SETNX+EXPIRE)** + +实际上,我们还可以使用Lua脚本来保证原子性(包含setnx和expire两条指令),lua脚本如下: + +```lua +if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then + redis.call('expire',KEYS[1],ARGV[2]) +else + return 0 +end; +``` + +加锁代码如下: + +```java + String lua_scripts = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" + + " redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end"; +Object result = jedis.eval(lua_scripts, Collections.singletonList(key_resource_id), Collections.singletonList(values)); +//判断是否成功 +return result.equals(1L); +``` + + + +### Redisson + + + +### Redlock + + + + + +## Zookeeper + +**实现原理** + +**基于zookeeper临时有序节点可以实现的分布式锁**。每个客户端对某个方法加锁时,在`Zookeeper`上与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。 + +![zookeeperlock](images/Architecture/zookeeperlock.png) + +**Curator** + +`Curator`封装了Zookeeper底层的Api,使我们更加容易方便的对Zookeeper进行操作,并且它封装了分布式锁的功能。 + +- **InterProcessMutex**:可重入锁。在可重入锁中还实现了读写锁 +- **InterProcessSemaphoreMutex**:不可重入锁 + + + +**优缺点** + +- **优点**:ZK可以不需要关心锁超时时间,实现起来有现成的第三方包,比较方便,并且支持读写锁,ZK获取锁会按照加锁的顺序,所以其是公平锁。对于高可用利用ZK集群进行保证 +- **缺点**:ZK需要额外维护,增加维护成本,性能和Mysql相差不大,依然比较差。并且需要开发人员了解ZK是什么 + + + +### InterProcessMutex + +`InterProcessMutex`是Curator实现的**可重入锁**,我们可以通过下面的一段代码实现我们的可重入锁: + +![InterProcessMutex](images/Architecture/InterProcessMutex.png) + +**加锁流程:** + +- 首先进行可重入的判定:这里的可重入锁记录在`ConcurrentMap threadData`这个Map里面,如果`threadData.get(currentThread)`是有值的那么就证明是可重入锁,然后记录就会加1。我们之前的Mysql其实也可以通过这种方法去优化,可以不需要`count`字段的值,将这个维护在本地可以提高性能 +- 然后在我们的资源目录下创建一个节点:比如这里创建一个`/0000000002`这个节点,这个节点需要设置为`EPHEMERAL_SEQUENTIAL`也就是临时节点并且有序 +- 获取当前目录下所有子节点,判断自己的节点是否位于子节点第一个 +- 如果是第一个,则获取到锁,那么可以返回 +- 如果不是第一个,则证明前面已经有人获取到锁,那么需要获取自己节点的前一个节点。`/0000000002`的前一个节点是`/0000000001`,我们获取到该节点后,再上面注册`Watcher`(这里的`watcher`其实调用的是`object.notifyAll()`,用来解除阻塞) +- `object.wait(timeout)`或`object.wait()`:进行阻塞等待这里和我们第5步的`watcher`相对应 + +**解锁流程:** + +- 首先进行可重入锁的判定:如果有可重入锁只需要次数减1即可,减1之后加锁次数为0的话继续下面步骤,不为0直接返回 +- 删除当前节点 +- 删除`threadDataMap`里面的可重入锁的数据 + + + +### InterProcessReadWriteLock + +`Curator`提供了**读写锁**,其实现类是`InterProcessReadWriteLock`,这里的每个节点都会加上前缀: + +```java +private static final String READ_LOCK_NAME = "__READ__"; +private static final String WRITE_LOCK_NAME = "__WRIT__"; +``` + +根据不同的前缀区分是读锁还是写锁,对于读锁,如果发现前面有写锁,那么需要将`Watcher`注册到和自己最近的写锁。写锁的逻辑和`InterProcessMutex`的分析依然保持不变。 + + + +### 锁超时 + +`Zookeeper`不需要配置锁超时,由于我们设置节点是临时节点,我们的每个机器维护着一个`ZK`的`Session`,通过这个`Session`,`Zookeeper`可以判断机器是否宕机。如果我们的机器挂掉的话,那么这个临时节点对应的就会被删除,所以我们不需要关心锁超时。 + + + +# 数据脱敏 + +先来看看什么是数据脱敏?数据脱敏也叫数据的去隐私化,在我们给定脱敏规则和策略的情况下,对敏感数据比如 `手机号`、`银行卡号` 等信息,进行转换或者修改的一种技术手段,防止敏感数据直接在不可靠的环境下使用。像政府、医疗行业、金融机构、移动运营商是比较早开始应用数据脱敏的,因为他们所掌握的都是用户最核心的私密数据,如果泄露后果是不可估量的。数据脱敏的应用在生活中是比较常见的,比如我们在淘宝买东西订单详情中,商家账户信息会被用 `*` 遮挡,保障了商户隐私不泄露,这就是一种数据脱敏方式。数据脱敏又分为静态数据脱敏(`SDM`)和 动态数据脱敏(`DDM`)。 + + + +## 静态数据脱敏 + +静态数据脱敏(`SDM`):适用于将数据抽取出生产环境脱敏后分发至测试、开发、培训、数据分析等场景。 + +有时我们可能需要将生产环境的数据 `copy` 到测试、开发库中,以此来排查问题或进行数据分析,但出于安全考虑又不能将敏感数据存储于非生产环境,此时就要把敏感数据从生产环境脱敏完毕之后再在非生产环境使用。 + +这样脱敏后的数据与生产环境隔离,满足业务需要的同时又保障了生产数据的安全。 + +![数据脱敏过程](images/Architecture/数据脱敏过程.png) + +如图所示,将用户的真实 `姓名`、`手机号`、`身份证`、`银行卡号` 通过 `替换`、`无效化`、`乱序`、`对称加密` 等方案进行脱敏改造。 + + + +## 动态数据脱敏 + +动态数据脱敏(`DDM`):一般用在生产环境,访问敏感数据时实时进行脱敏,因为有时在不同情况下对于同一敏感数据的读取,需要做不同级别的脱敏处理,例如:不同角色、不同权限所执行的脱敏方案会不同。 + +**注意**:在抹去数据中的敏感内容同时,也需要保持原有的数据特征、业务规则和数据关联性,保证我们在开发、测试以及数据分析类业务不会受到脱敏的影响,使脱敏前后的数据一致性和有效性。**总之一句话:你爱怎么脱就怎么脱,别影响我使用就行**。 + + + +## 数据脱敏方案 + +### 无效化 + +无效化方案在处理待脱敏的数据时,通过对字段数据值进行 `截断`、`加密`、`隐藏` 等方式让敏感数据脱敏,使其不再具有利用价值。一般采用特殊字符(`*`等)代替真值,这种隐藏敏感数据的方法简单,但缺点是用户无法得知原数据的格式,如果想要获取完整信息,要让用户授权查询。 + +![截断方式](images/Architecture/截断方式.png) + +比如我们将身份证号用 * 替换真实数字就变成了 "220724 \*\*\*\*\*\* 3523",非常简单。 + +![隐藏方式](images/Architecture/隐藏方式.png) + + + +### 随机值 + +随机值替换,字母变为随机字母,数字变为随机数字,文字随机替换文字的方式来改变敏感数据,这种方案的优点在于可以在一定程度上保留原有数据的格式,往往这种方法用户不易察觉的。 + +我们看到 `name` 和 `idnumber` 字段进行了随机化脱敏,而名字姓、氏随机化稍有特殊,需要有对应姓氏字典数据支持。 + +![随机值](images/Architecture/随机值.png) + + + +### 数据替换 + +数据替换与前边的无效化方式比较相似,不同的是这里不以特殊字符进行遮挡,而是用一个设定的虚拟值替换真值。比如说我们将手机号统一设置成 “13651300000”。 + +![数据替换](images/Architecture/数据替换.png) + + + +### 对称加密 + +对称加密是一种特殊的可逆脱敏方法,通过加密密钥和算法对敏感数据进行加密,密文格式与原始数据在逻辑规则上一致,通过密钥解密可以恢复原始数据,要注意的就是密钥的安全性。 + +![对称加密](images/Architecture/对称加密.png) + + + +### 平均值 + +平均值方案经常用在统计场景,针对数值型数据,我们先计算它们的均值,然后使脱敏后的值在均值附近随机分布,从而保持数据的总和不变。 + +![原始数据](images/Architecture/原始数据.png) + +对价格字段 `price` 做平均值处理后,字段总金额不变,但脱敏后的字段值都在均值 60 附近。 + +![平均值](images/Architecture/平均值.png) + + + +### 偏移和取整 + +这种方式通过随机移位改变数字数据,偏移取整在保持了数据的安全性的同时保证了范围的大致真实性,比之前几种方案更接近真实数据,在大数据分析场景中意义比较大。 + +比如下边的日期字段`create_time`中 `2020-12-08 15:12:25` 变为 `2018-01-02 15:00:00`。 + +![取整](images/Architecture/取整.png) + +数据脱敏规则在实际应用中往往都是多种方案配合使用,以此来达到更高的安全级别。 + + + +# 附近的人 + +## 操作命令 + +自Redis 3.2开始,Redis基于geohash和有序集合提供了地理位置相关功能。 Redis Geo模块包含了以下6个命令: + +- **GEOADD**: 将给定的位置对象(纬度、经度、名字)添加到指定的key; +- GEOPOS: 从key里面返回所有给定位置对象的位置(经度和纬度); +- GEODIST: 返回两个给定位置之间的距离; +- GEOHASH: 返回一个或多个位置对象的Geohash表示; +- **GEORADIUS**: 以给定的经纬度为中心,返回目标集合中与中心的距离不超过给定最大距离的所有位置对象; +- GEORADIUSBYMEMBER: 以给定的位置对象为中心,返回与其距离不超过给定最大距离的所有位置对象。 + +其中,组合使用GEOADD和GEORADIUS可实现“附近的人”中“增”和“查”的基本功能。要实现微信中“附近的人”功能,可直接使用GEORADIUSBYMEMBER命令。其中“给定的位置对象”即为用户本人,搜索的对象为其他用户。不过本质上,GEORADIUSBYMEMBER = GEOPOS + GEORADIUS,即先查找用户位置再通过该位置搜索附近满足位置相互距离条件的其他用户对象。以下会从源码角度入手对GEOADD和GEORADIUS命令进行分析,剖析其算法原理。 + +> Redis geo操作中只包含了“增”和“查”的操作,并没有专门的“删除”命令。主要是因为Redis内部使用有序集合(zset)保存位置对象,可用zrem进行删除。 + +> 在Redis源码geo.c的文件注释中,只说明了该文件为GEOADD、GEORADIUS和GEORADIUSBYMEMBER的实现文件(其实在也实现了另三个命令)。从侧面看出其他三个命令为辅助命令。 + + + +## GEOADD + +**使用方式** + +```shell +GEOADD key longitude latitude member [longitude latitude member ...] +``` + +将给定的位置对象(纬度、经度、名字)添加到指定的key。其中,key为集合名称,member为该经纬度所对应的对象。在实际运用中,当所需存储的对象数量过多时,可通过设置多key(如一个省一个key)的方式对对象集合变相做sharding,避免单集合数量过多。成功插入后的返回值: + +``` +(integer) N +``` + +其中N为成功插入的个数。通过源码分析可以看出Redis内部使用有序集合(zset)保存位置对象,有序集合中每个元素都是一个带位置的对象,元素的score值为其经纬度对应的52位的geohash值。 + +> double类型精度为52位; +> geohash是以base32的方式编码,52bits最高可存储10位geohash值,对应地理区域大小为0.6\*0.6米的格子。换句话说经Redis geo转换过的位置理论上会有约0.3\*1.414=0.424米的误差。 + + + +**算法小结** + +简单总结下GEOADD命令都干了啥: + +- 参数提取和校验 +- 将入参经纬度转换为52位的geohash值(score) +- 调用ZADD命令将member及其对应的score存入集合key中 + + + +## GEORADIUS + +**使用方式** + +``` +GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count] [STORE key] [STORedisT key] +``` + +以给定的经纬度为中心,返回目标集合中与中心的距离不超过给定最大距离的所有位置对象。 + +范围单位:`m | km | ft | mi --> 米 | 千米 | 英尺 | 英里` + +额外参数: + +- WITHDIST:在返回位置对象的同时,将位置对象与中心之间的距离也一并返回。距离的单位和用户给定的范围单位保持一致 +- WITHCOORD:将位置对象的经度和维度也一并返回 +- WITHHASH:以 52 位有符号整数的形式,返回位置对象经过原始 geohash 编码的有序集合分值。这个选项主要用于底层应用或者调试,实际中的作用并不大 +- ASC|DESC:从近到远返回位置对象元素 | 从远到近返回位置对象元素 +- COUNT count:选取前N个匹配位置对象元素。(不设置则返回所有元素) +- STORE key:将返回结果的地理位置信息保存到指定key +- STORedisT key:将返回结果离中心点的距离保存到指定key + +由于 STORE 和 STORedisT 两个选项的存在,GEORADIUS 和 GEORADIUSBYMEMBER 命令在技术上会被标记为写入命令,从而只会查询(写入)主实例,QPS过高时容易造成主实例读写压力过大。 为解决这个问题,在 Redis 3.2.10 和 Redis 4.0.0 中,分别新增了 GEORADIUS_RO 和 GEORADIUSBYMEMBER_RO两个只读命令。 + 不过,在实际开发中笔者发现 在java package `Redis.clients.jedis.params.geo` 的 GeoRadiusParam 参数类中并不包含 STORE 和 STORedisT 两个参数选项,在调用georadius时是否真的只查询了主实例,还是进行了只读封装。感兴趣的朋友可以自己研究下。成功查询后的返回值: +不带WITH限定,返回一个member list,如: + +``` +["member1","member2","member3"] +``` + +带WITH限定,member list中每个member也是一个嵌套list,如: + +``` +[ + ["member1", distance1, [longitude1, latitude1]] + ["member2", distance2, [longitude2, latitude2]] +] +``` + + + +**算法小结** + +抛开众多可选参数不谈,简单总结下GEORADIUS命令是怎么利用geohash获取目标位置对象的: + +- 参数提取和校验 +- 利用中心点和输入半径计算待查区域范围。这个范围参数包括满足条件的最高的geohash网格等级(精度) 以及 对应的能够覆盖目标区域的九宫格位置 +- 对九宫格进行遍历,根据每个geohash网格的范围框选出位置对象。进一步找出与中心点距离小于输入半径的对象,进行返回 + +直接描述不太好理解,我们通过如下两张图在对算法进行简单的演示: + +![georadius](images/Architecture/georadius.jpg) ![georadius-range](images/Architecture/georadius-range.jpg) + +令左图的中心为搜索中心,绿色圆形区域为目标区域,所有点为待搜索的位置对象,红色点则为满足条件的位置对象。 + 在实际搜索时,首先会根据搜索半径计算geohash网格等级(即右图中网格大小等级),并确定九宫格位置(即红色九宫格位置信息);再依次查找计算九宫格中的点(蓝点和红点)与中心点的距离,最终筛选出距离范围内的点(红点)。 + + + +# 亿级数据统计 + +常见的场景如下: + +- 给一个 userId ,判断用户登陆状态 +- 两亿用户最近 7 天的签到情况,统计 7 天内连续签到的用户总数 +- 统计每天的新增与第二天的留存用户数 +- 统计网站的对访客(Unique Visitor,UV)量 +- 最新评论列表 +- 根据播放量音乐榜单 + +通常情况下,我们面临的用户数量以及访问量都是巨大的,比如百万、千万级别的用户数量,或者千万级别、甚至亿级别的访问信息。所以,我们必须要选择能够非常高效地统计大量数据(例如亿级)的集合类型。 + +**如何选择合适的数据集合,我们首先要了解常用的统计模式,并运用合理的数据来解决实际问题。**四种统计类型: + +- 二值状态统计 +- 聚合统计 +- 排序统计 +- 基数统计 + + + +## 二值统计 + +**什么是二值状态统计呀?** + +也就是集合中的元素的值只有 0 和 1 两种,在签到打卡和用户是否登陆的场景中,只需记录`签到(1)`或 `未签到(0)`,`已登录(1)`或`未登陆(0)`。假如我们在判断用户是否登陆的场景中使用 Redis 的 String 类型实现(**key -> userId,value -> 0 表示下线,1 - 登陆**),假如存储 100 万个用户的登陆状态,如果以字符串的形式存储,就需要存储 100 万个字符串了,内存开销太大。 + + + +**为什么String类型内存开销大?** + +String 类型除了记录实际数据以外,还需要额外的内存记录数据长度、空间使用等信息。当保存的数据包含字符串,String 类型就使用简单动态字符串(SDS)结构体来保存,如下图所示: + +![SDS](images/Architecture/SDS.png) + +- **len**:占 4 个字节,表示 buf 的已用长度 +- **alloc**:占 4 个字节,表示 buf 实际分配的长度,通常 > len +- **buf**:字节数组,保存实际的数据,Redis 自动在数组最后加上一个 “\0”,额外占用一个字节的开销 + +所以,在 SDS 中除了 buf 保存实际的数据, len 与 alloc 就是额外的开销。另外,还有一个 **RedisObject 结构的开销**,因为 Redis 的数据类型有很多,而且,不同数据类型都有些相同的元数据要记录(比如最后一次访问的时间、被引用的次数等)。所以,Redis 会用一个 RedisObject 结构体来统一记录这些元数据,同时指向实际数据。 + +![RedisObject](images/Architecture/RedisObject.png) + +对于二值状态场景,我们就可以利用 Bitmap 来实现。比如登陆状态我们用一个 bit 位表示,一亿个用户也只占用 一亿 个 bit 位内存 ≈ (100000000 / 8/ 1024/1024)12 MB。 + +``` +大概的空间占用计算公式是:($offset/8/1024/1024) MB +``` + + + +**什么是 Bitmap 呢?** + +Bitmap 的底层数据结构用的是 String 类型的 SDS 数据结构来保存位数组,Redis 把每个字节数组的 8 个 bit 位利用起来,每个 bit 位 表示一个元素的二值状态(不是 0 就是 1)。可以将 Bitmap 看成是一个 bit 为单位的数组,数组的每个单元只能存储 0 或者 1,数组的下标在 Bitmap 中叫做 offset 偏移量。为了直观展示,我们可以理解成 buf 数组的每个字节用一行表示,每一行有 8 个 bit 位,8 个格子分别表示这个字节中的 8 个 bit 位,如下图所示: + +![Bitmap](images/Architecture/Bitmap.png) + +**8 个 bit 组成一个 Byte,所以 Bitmap 会极大地节省存储空间。** 这就是 Bitmap 的优势。 + +当遇到的统计场景只需要统计数据的二值状态,比如用户是否存在、 ip 是否是黑名单、以及签到打卡统计等场景就可以考虑使用 Bitmap。只需要一个 bit 位就能表示 0 和 1。在统计海量数据的时候将大大减少内存占用。 + + + +### 判断用户登陆态 + +怎么用 Bitmap 来判断海量用户中某个用户是否在线呢?Bitmap 提供了 `GETBIT、SETBIT` 操作,通过一个偏移值 offset 对 bit 数组的 offset 位置的 bit 位进行读写操作,需要注意的是 offset 从 0 开始。 + +只需要一个 key = login_status 表示存储用户登陆状态集合数据, 将用户 ID 作为 offset,在线就设置为 1,下线设置 0。通过 `GETBIT`判断对应的用户是否在线。50000 万 用户只需要 6 MB 的空间。 + +- **SETBIT 命令** + + ``` + SETBIT + ``` + + 设置或者清空 key 的 value 在 offset 处的 bit 值(只能是 0 或者 1)。 + +- **GETBIT 命令** + + ``` + GETBIT + ``` + + 获取 key 的 value 在 offset 处的 bit 位的值,当 key 不存在时,返回 0。 + + + +假如我们要判断 ID = 10086 的用户的登陆情况: + +- 第一步,执行以下指令,表示用户已登录。 + + ``` + SETBIT login_status 10086 1 + ``` + +- 第二步,检查该用户是否登陆,返回值 1 表示已登录。 + + ``` + GETBIT login_status 10086 + ``` + +- 第三步,登出,将 offset 对应的 value 设置成 0。 + + ``` + SETBIT login_status 10086 0 + ``` + + + +### 用户每个月的签到情况 + +在签到统计中,每个用户每天的签到用 1 个 bit 位表示,一年的签到只需要 365 个 bit 位。一个月最多只有 31 天,只需要 31 个 bit 位即可。 + +比如统计编号 89757 的用户在 2021 年 5 月份的打卡情况要如何进行?key 可以设计成 `uid:sign:{userId}:{yyyyMM}`,月份的每一天的值 - 1 可以作为 offset(因为 offset 从 0 开始,所以 `offset = 日期 - 1`)。 + +- 第一步,执行下面指令表示记录用户在 2021 年 5 月 16 号打卡。 + + ``` + SETBIT uid:sign:89757:202105 15 1 + ``` + +- 第二步,判断编号 89757 用户在 2021 年 5 月 16 号是否打卡。 + + ``` + GETBIT uid:sign:89757:202105 15 + ``` + +- 第三步,统计该用户在 5 月份的打卡次数,使用 `BITCOUNT` 指令。该指令用于统计给定的 bit 数组中,值 = 1 的 bit 位的数量。 + + ``` + BITCOUNT uid:sign:89757:202105 + ``` + +这样我们就可以实现用户每个月的打卡情况了,是不是很赞。 + + + +**如何统计这个月首次打卡时间呢?** + +Redis 提供了 `BITPOS key bitValue [start] [end]`指令,返回数据表示 Bitmap 中第一个值为 `bitValue` 的 offset 位置。在默认情况下, 命令将检测整个位图, 用户可以通过可选的 `start` 参数和 `end` 参数指定要检测的范围。所以我们可以通过执行以下指令来获取 userID = 89757 在 2021 年 5 月份**首次打卡**日期: + +``` +BITPOS uid:sign:89757:202105 1 +``` + +需要注意的是,我们需要将返回的 value + 1 ,因为 offset 从 0 开始。 + + + +### 连续签到用户总数 + +在记录了一个亿的用户连续 7 天的打卡数据,如何统计出这连续 7 天连续打卡用户总数呢? + +我们把每天的日期作为 Bitmap 的 key,userId 作为 offset,若是打卡则将 offset 位置的 bit 设置成 1。key 对应的集合的每个 bit 位的数据则是一个用户在该日期的打卡记录。一共有 7 个这样的 Bitmap,如果我们能对这 7 个 Bitmap 的对应的 bit 位做 『与』运算。同样的 UserID offset 都是一样的,当一个 userID 在 7 个 Bitmap 对应对应的 offset 位置的 bit = 1 就说明该用户 7 天连续打卡。结果保存到一个新 Bitmap 中,我们再通过 `BITCOUNT` 统计 bit = 1 的个数便得到了连续打卡 7 天的用户总数了。 + +Redis 提供了 `BITOP operation destkey key [key ...]`这个指令用于对一个或者多个 键 = key 的 Bitmap 进行位元操作。 + +`opration` 可以是 `and`、`OR`、`NOT`、`XOR`。当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 `0` 。空的 `key` 也被看作是包含 `0` 的字符串序列。便于理解,如下图所示: + +![BITOP](images/Architecture/BITOP.png) + +3 个 Bitmap,对应的 bit 位做「与」操作,结果保存到新的 Bitmap 中。操作指令表示将 三个 bitmap 进行 AND 操作,并将结果保存到 destmap 中。接着对 destmap 执行 BITCOUNT 统计。 + +``` +// 与操作 +BITOP AND destmap bitmap:01 bitmap:02 bitmap:03 +// 统计 bit 位 = 1 的个数 +BITCOUNT destmap +``` + +简单计算下 一个一亿个位的 Bitmap占用的内存开销,大约占 12 MB 的内存(10^8/8/1024/1024),7 天的 Bitmap 的内存开销约为 84 MB。同时我们最好给 Bitmap 设置过期时间,让 Redis 删除过期的打卡数据,节省内存。 + + + +## 基数统计 + +基数统计:统计一个集合中不重复元素的个数,常见于计算独立用户数(UV)。 + +实现基数统计最直接的方法,就是采用集合(Set)这种数据结构,当一个元素从未出现过时,便在集合中增加一个元素;如果出现过,那么集合仍保持不变。当页面访问量巨大,就需要一个超大的 Set 集合来统计,将会浪费大量空间。 + +另外,这样的数据也不需要很精确,到底有没有更好的方案呢?这个问题问得好,Redis 提供了 `HyperLogLog` 数据结构就是用来解决种种场景的统计问题。`HyperLogLog` 是一种不精确的去重基数方案,它的统计规则是基于概率实现的,标准误差 0.81%,这样的精度足以满足 UV 统计需求了。 + + + +### 网站的 UV + +#### Set方案 + +一个用户一天内多次访问一个网站只能算作一次,所以很容易就想到通过 Redis 的 Set 集合来实现。用户编号 89757 访问 「Redis 为什么这么快 」时,我们将这个信息放到 Set 中。 + +```shell +SADD Redis为什么这么快:uv 89757 +``` + +当用户编号 89757 多次访问「Redis 为什么这么快」页面,Set 的去重功能能保证不会重复记录同一个用户 ID。通过 `SCARD` 命令,统计「Redis 为什么这么快」页面 UV。指令返回一个集合的元素个数(也就是用户 ID)。 + +``` +SCARD Redis为什么这么快:uv +``` + + + +#### Hash方案 + +可以利用 Hash 类型实现,将用户 ID 作为 Hash 集合的 key,访问页面则执行 HSET 命令将 value 设置成 1。即使用户重复访问,重复执行命令,也只会把这个 userId 的值设置成 “1"。最后,利用 `HLEN` 命令统计 Hash 集合中的元素个数就是 UV。如下: + +``` +HSET redis集群:uv userId:89757 1 +// 统计 UV +HLEN redis集群 +``` + + + +#### HyperLogLog方案 + +Set 虽好,如果文章非常火爆达到千万级别,一个 Set 就保存了千万个用户的 ID,页面多了消耗的内存也太大了。同理,Hash数据类型也是如此。咋办呢? + +利用 Redis 提供的 `HyperLogLog` 高级数据结构(不要只知道 Redis 的五种基础数据类型了)。这是一种用于基数统计的数据集合类型,即使数据量很大,计算基数需要的空间也是固定的。每个 `HyperLogLog` 最多只需要花费 12KB 内存就可以计算 2 的 64 次方个元素的基数。Redis 对 `HyperLogLog` 的存储进行了优化,在计数比较小的时候,存储空间采用系数矩阵,占用空间很小。只有在计数很大,稀疏矩阵占用的空间超过了阈值才会转变成稠密矩阵,占用 12KB 空间。 + +- **PFADD**:将访问页面的每个用户 ID 添加到 `HyperLogLog` 中。 + + ``` + PFADD Redis主从同步原理:uv userID1 userID 2 useID3 + ``` + +- **PFCOUNT**:利用 `PFCOUNT` 获取 「Redis主从同步原理」页面的 UV值。 + + ``` + PFCOUNT Redis主从同步原理:uv + ``` + +- **PFMERGE**:将多个 `HyperLogLog` 合并在一起形成一个新的 `HyperLogLog` 值。 + + ``` + PFMERGE destkey sourcekey [sourcekey ...] + ``` + + **使用场景** + + 比如在网站中我们有两个内容差不多的页面,运营说需要这两个页面的数据进行合并。其中页面的 UV 访问量也需要合并,那这个时候 `PFMERGE` 就可以派上用场了,也就是**同样的用户访问这两个页面则只算做一次**。 + + 如下所示:Redis、MySQL 两个 Bitmap 集合分别保存了两个页面用户访问数据。 + + ``` + PFADD Redis数据 user1 user2 user3 + PFADD MySQL数据 user1 user2 user4 + PFMERGE 数据库 Redis数据 MySQL数据 + PFCOUNT 数据库 // 返回值 = 4 + ``` + + 将多个 HyperLogLog 合并(merge)为一个 HyperLogLog , 合并后的 HyperLogLog 的基数接近于所有输入HyperLogLog 的可见集合(observed set)的**并集**。user1、user2 都访问了 Redis 和 MySQL,只算访问了一次。 + + + +## 排序统计 + +Redis的4个集合类型中(List、Set、Hash、Sorted Set),List和Sorted Set就是有序的。 + +- List:按照元素插入 List 的顺序排序,使用场景通常可以作为 消息队列、最新列表、排行榜 +- Sorted Set:根据元素的score权重排序,我们可以自己决定每个元素的权重值。使用场景(排行榜,比如按照播放量、点赞数) + + + +### 最新评论列表 + +**可以利用List插入的顺序排序实现评论列表**。比如微信公众号的后台回复列表(不要杠,举例子),每一公众号对应一个 List,这个List保存该公众号的所有的用户评论。每当一个用户评论,则利用`LPUSH key value [value ...]`插入到List队头。 + +``` +LPUSH 码哥字节 1 2 3 4 5 6 +``` + +接着再用 `LRANGE key star stop` 获取列表指定区间内的元素。 + +``` +> LRANGE 码哥字节 0 4 +1) "6" +2) "5" +3) "4" +4) "3" +5) "2" +``` + +**注意**:并不是所有最新列表都能用 List 实现,对于因为对于频繁更新的列表,list类型的分页可能导致列表元素重复或漏掉。比如当前评论列表 `List ={A, B, C, D}`,左边表示最新的评论,D 是最早的评论。 + +``` +LPUSH 码哥字节 D C B A +``` + +展示第一页最新 2 个评论,获取到 A、B: + +``` +LRANGE 码哥字节 0 1 +1) "A" +2) "B" +``` + +按照我们想要的逻辑来说,第二页可通过 `LRANGE 码哥字节 2 3` 获取 C,D。如果在展示第二页之前,产生新评论 E,评论 E 通过 `LPUSH 码哥字节 E` 插入到 List 队头,List = {E, A, B, C, D }。现在执行 `LRANGE 码哥字节 2 3` 获取第二页评论发现, B 又出现了。 + +``` +LRANGE 码哥字节 2 3 +1) "B" +2) "C" +``` + +出现这种情况的原因在于 List 是利用元素所在的位置排序,一旦有新元素插入,`List = {E,A,B,C,D}`。原先的数据在 List 的位置都往后移动一位,导致读取都旧元素。 + +![List最新列表](images/Architecture/List最新列表.png) + + + +**小结** + +只有不需要分页(比如每次都只取列表的前 5 个元素)或者更新频率低(比如每天凌晨统计更新一次)的列表才适合用 List 类型实现。对于需要分页并且会频繁更新的列表,需用使用有序集合 Sorted Set 类型实现。另外,需要通过时间范围查找的最新列表,List 类型也实现不了,需要通过有序集合 Sorted Set 类型实现,如以成交时间范围作为条件来查询的订单列表。 + + + +### 排行榜 + +对于最新列表的场景,List 和 Sorted Set 都能实现,为啥还用 List 呢?直接使用 Sorted Set 不是更好,它还能设置 score 权重排序更加灵活。原因是 Sorted Set 类型占用的内存容量是 List 类型的数倍之多,对于列表数量不多的情况,可以用 Sorted Set 类型来实现。 + +比如要一周音乐榜单,我们需要实时更新播放量,并且需要分页展示。除此以外,排序是根据播放量来决定的,这个时候 List 就无法满足了。我们可以将音乐 ID 保存到 Sorted Set 集合中,`score` 设置成每首歌的播放量,该音乐每播放一次则设置 score = score +1。 + +- **ZADD** + + 比如我们将《青花瓷》和《花田错》播放量添加到 musicTop 集合中: + + ``` + ZADD musicTop 100000000 青花瓷 8999999 花田错 + ``` + +- **ZINCRBY** + + 《青花瓷》每播放一次就通过 `ZINCRBY`指令将 score + 1。 + + ``` + > ZINCRBY musicTop 1 青花瓷 + 100000001 + ``` + +- **ZRANGEBYSCORE** + + 最后我们需要获取 musicTop **前十**播放量音乐榜单,目前最大播放量是 N ,可通过如下指令获取: + + ``` + ZRANGEBYSCORE musicTop N-9 N WITHSCORES + ``` + + **注意**:可是这个 N 我们怎么获取呀? + +- **ZREVRANGE** + + 可通过 `ZREVRANGE key start stop [WITHSCORES]`指令。其中元素的排序按 `score` 值递减(从大到小)来排列。具有相同 `score` 值的成员按字典序的逆序(reverse lexicographical order)排列。 + + ``` + > ZREVRANGE musicTop 0 0 WITHSCORES + 1) "青花瓷" + 2) 100000000 + ``` + + + +**小结** + +即使集合中的元素频繁更新,Sorted Set 也能通过 `ZRANGEBYSCORE`命令准确地获取到按序排列的数据。**在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议优先考虑使用 Sorted Set。** + + + +## 聚合统计 + +指的就是统计多个集合元素的聚合结果,比如说: + +- 统计多个元素的共有数据(交集) +- 统计两个集合其中的一个独有元素(差集统计) +- 统计多个集合的所有元素(并集统计) + +什么样的场景会用到交集、差集、并集呢? + +Redis 的 Set 类型支持集合内的增删改查,底层使用了 Hash 数据结构,无论是 add、remove 都是 O(1) 时间复杂度。并且支持多个集合间的交集、并集、差集操作,利用这些集合操作,解决上边提到的统计问题。 + +### 交集-共同好友 + +比如 QQ 中的共同好友正是聚合统计中的交集。我们将账号作为 Key,该账号的好友作为 Set 集合的 value。模拟两个用户的好友集合: + +``` +SADD user:码哥字节 R大 Linux大神 PHP之父 +SADD user:大佬 Linux大神 Python大神 C++菜鸡 +``` + +![交集-共同好友](images/Architecture/交集-共同好友.png)交集 + +统计两个用户的共同好友只需要两个 Set 集合的交集,如下命令: + +``` +SINTERSTORE user:共同好友 user:码哥字节 user:大佬 +``` + +命令的执行后,「user:码哥字节」、「user:大佬」两个集合的交集数据存储到 user:共同好友这个集合中。 + + + +### 差集-每日新增好友数 + +比如,统计某个 App 每日新增注册用户量,只需要对近两天的总注册用户量集合取差集即可。如2021-06-01 的总注册用户量存放在 `key = user:20210601` set 集合中,2021-06-02 的总用户量存放在 `key = user:20210602` 的集合中。 + +![差集-每日新增好友数](images/Architecture/差集-每日新增好友数.png) + +如下指令,执行差集计算并将结果存放到 `user:new` 集合中。 + +``` +SDIFFSTORE user:new user:20210602 user:20210601 +``` + +执行完毕,此时的 user:new 集合将是 2021/06/02 日新增用户量。除此之外,QQ 上有个可能认识的人功能,也可以使用差集实现,就是把你朋友的好友集合减去你们共同的好友即是可能认识的人。 + + + +### 并集-总共新增好友 + +还是差集的例子,统计 2021/06/01 和 2021/06/02 两天总共新增的用户量,只需要对两个集合执行并集。 + +``` +SUNIONSTORE userid:new user:20210602 user:20210601 +``` + +此时新的集合 userid:new 则是两日新增的好友。 + + + +**小结** + +Set 的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致 Redis 实例阻塞。所以,可以专门部署一个集群用于统计,让它专门负责聚合计算,或者是把数据读取到客户端,在客户端来完成聚合统计,这样就可以规避由于阻塞导致其他服务无法响应。 + + + +# 短信服务 + +## 短信防刷 + +- **时间限制:60秒后才能再次发送** + + 从发送验证码开始,前端(客户端)会进行一个60秒的倒数,在这一分钟之内,用户是无法提交多次发送信息的请求的。这种方法虽然使用得比较普遍,但是却不是非常有用,技术稍微好点的人完全可以绕过这个限制,直接发送短信验证码。 + +- **手机号限制:同一个手机号,24小时之内不能够超过5条** + + 对使用同一个手机号码进行注册或者其他发送短信验证码的操作的时候,系统可以对这个手机号码进行限制,例如,24小时只能发送5条短信验证码,超出限制则进行报错(如:系统繁忙,请稍后再试)。然而,这也只能够避免人工手动刷短信而已,对于批量使用不同手机号码来刷短信的机器,这种方法也是无可奈何的。 + +- **短信验证码限制:30分钟之内发送同一个验证码** + + 网上还有一种方法说:30分钟之内,所有的请求,所发送的短信验证码都是同一个验证码。第一次请求短信接口,然后缓存短信验证码结果,30分钟之内再次请求,则直接返回缓存的内容。对于这种方式,不是很清楚短信接口商会不会对发送缓存信息收取费用,如果有兴趣可以了解了解。 + +- **前后端校验:提交Token参数校验** + + 这种方式比较少人说到,个人觉得可以这种方法值得一试。前端(客户端)在请求发送短信的时候,同时向服务端提交一个Token参数,服务端对这个Token参数进行校验,校验通过之后,再向请求发送短信的接口向用户手机发送短信。 + +- **唯一性限制:微信产品,限制同一个微信ID用户的请求数量** + + 如果是微信的产品的话,可以通过微信ID来进行识别,然后对同一个微信ID的用户限制,24小时之内最多只能够发送一定量的短信。 + +- **产品流程限制:分步骤进行** + + 例如注册的短信验证码使用场景,我们将注册的步骤分成2步,用户在输入手机号码并设置了密码之后,下一步才进入验证码的验证步骤。 + +- **图形验证码限制:图形验证通过后再请求接口** + + 用户输入图形验证码并通过之后,再请求短信接口获取验证码。为了有更好的用户体验,也可以设计成:一开始不需要输入图形验证码,在操作达到一定量之后,才需要输入图形验证码。具体情况请根据具体场景来进行设计。 + +- **IP及Cookie限制:限制相同的IP/Cookie信息最大数量** + + 使用Cookie或者IP,能够简单识别同一个用户,然后对相同的用户进行限制(如:24小时内最多只能够发送20条短信)。然而,Cookie能够清理、IP能够模拟,而且IP还会出现局域网相同IP的情况,因此,在使用此方法的时候,应该根据具体情况来思考。 + +- **短信预警机制,做好出问题之后的防护** + + 以上的方法并不一定能够完全杜绝短信被刷,因此,我们也应该做好短信的预警机制,即当短信的使用量达到一定量之后,向管理员发送预警信息,管理员可以立刻对短信的接口情况进行监控和防护。 + + + +# 安全漏洞 + +我们日常开发中,很多小伙伴容易忽视安全漏洞问题,认为只要正常实现业务逻辑就可以了。其实,**安全性才是最重要的**。本文将跟大家一起学习常见的安全漏洞问题,希望对大家有帮助哈。如果本文有什么错误的话,希望大家提出哈,感谢感谢~ + +## SQL注入 + +### 什么是SQL注入? + +SQL注入是一种代码注入技术,一般被应用于攻击web应用程序。它通过在web应用接口传入一些特殊参数字符,来欺骗应用服务器,执行恶意的SQL命令,以达到非法获取系统信息的目的。它目前是黑客对数据库进行攻击的最常用手段之一。 + + + +### SQL注入是如何攻击的? + +举个常见的**业务场景**:在web表单搜索框输入员工名字,然后后台查询出对应名字的员工。 + +![SQL注入业务场景](images/Architecture/SQL注入业务场景.jpg) + +这种场景下,一般都是前端页面把一个名字参数name传到后台,然后后台通过SQL把结果查询出来 + +```java +name = "田螺"; //前端传过来的 +SQL= "select * from staff where name=" + name; //根据前端传过来的name参数,查询数据库员工表staff +``` + +因为SQL是直接拼接的,如果我们完全信任前端传的参数的话。假如前端传这么一个参数时`'' or '1'='1'`,SQL就变成酱紫的啦。 + +```mysql +select * from staff where name='' or '1'='1'; +``` + +这个SQL会把所有的员工信息全都查出来了,酱紫请求用户已经越权啦。请求者可以获取所有员工的信息,其他用户信息已经暴露了啦。 + + + +### 如何预防SQL注入问题 + +**使用#{}而不是${}** + +在MyBatis中,使用`#{}`而不是`${}`,可以很大程度防止sql注入。 + +> - 因为`#{}`是一个参数占位符,对于字符串类型,会自动加上"",其他类型不加。由于Mybatis采用**预编译**,其后的参数不会再进行SQL编译,所以一定程度上防止SQL注入。 +> - `${}`是一个简单的字符串替换,字符串是什么,就会解析成什么,存在SQL注入风险 + +**不要暴露一些不必要的日志或者安全信息,比如避免直接响应一些sql异常信息。** + +如果SQL发生异常了,不要把这些信息暴露响应给用户,可以自定义异常进行响应 + + + +**不相信任何外部输入参数,过滤参数中含有的一些数据库关键词关键词** + +可以加个参数校验过滤的方法,过滤`union,or`等数据库关键词 + + + +**适当的权限控制** + +在你查询信息时,先校验下当前用户是否有这个权限。如实现代码的时候,可以让用户多传一个企业Id什么的,或者获取当前用户的session信息等,在查询前,先校验一下当前用户是否是这个企业下的等等,是的话才有这个查询员工的权限。 + + + +## JSON反序列化漏洞 + +JSON反序列化漏洞——如Fastjson安全漏洞。 + +### 什么是JSON序列化,JSON发序列化 + +- 序列化:把对象转换为字节序列的过程 +- 反序列:把字节序列恢复为Java对象的过程 + +![JSON序列化过程](images/Architecture/JSON序列化过程.jpg) + +**Json序列化**就是将对象转换成Json格式的字符串,**JSON反序列化**就是Json串转换成对象 + + + +### JSON 反序列化漏洞是如何被攻击? + +不安全的反序列化可以导致远程代码执行、重放攻击、注入攻击或特权升级攻击。之前Fastjson频繁爆出安全漏洞,我们现在分析fastjson 1.2.24版本的一个反序列化漏洞吧,这个漏洞比较常见的利用手法就是通过jndi注入的方式实现RCE。 + +我们先来看fastjson一个反序列化的简单例子: + +```java +public class User { + private String name; + + private int age; + + public String getName() { + return name; + } + + public void setName(String name) { + System.out.println("调用了name方法"); + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + System.out.println("调用了age方法"); + this.age = age; + } + + public static void main(String[] args) { + String str = "{\"@type\":\"cn.eovie.bean.User\",\"age\":26,\"name\":\"捡田螺的小男孩\"}"; + User user = JSON.parseObject(str,User.class); + } +} +``` + +运行结果: + +```java +调用了age方法 +调用了name方法 +``` + +加了`@type`属性就能调用对应对象的`setXXX`方法,而`@type`表示指定反序列化成某个类。如果我们能够找到一个类,而这个类的某个`setXXX`方法中通过我们的精心构造能够完成命令执行,即可达到攻击的目的啦。 + +> com.sun.rowset.JdbcRowSetImpl 就是类似这么一个类,它有两个set方法,分别是setAutoCommit和setDataSourceName + +有兴趣的小伙伴,可以看下它的源代码: + +```java + public void setDataSourceName(String var1) throws SQLException { + if (this.getDataSourceName() != null) { + if (!this.getDataSourceName().equals(var1)) { + super.setDataSourceName(var1); + this.conn = null; + this.ps = null; + this.rs = null; + } + } else { + super.setDataSourceName(var1); + } + + } + + public void setAutoCommit(boolean var1) throws SQLException { + if (this.conn != null) { + this.conn.setAutoCommit(var1); + } else { + this.conn = this.connect(); + this.conn.setAutoCommit(var1); + } + + } + + private Connection connect() throws SQLException { + if (this.conn != null) { + return this.conn; + } else if (this.getDataSourceName() != null) { + try { + InitialContext var1 = new InitialContext(); + DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName()); + return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection(); + } catch (NamingException var3) { + throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString()); + } + } else { + return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null; + } + } +``` + +`setDataSourceName` 简单设置了设置了dataSourceName的值,`setAutoCommit`中有connect操作,connect方法中有典型的jndi的`lookup`方法调用,参数刚好就是在`setDataSourceName`中设置的dataSourceName。 + +因此,有漏洞的反序列代码实现如下即可: + +```java +public class FastjsonTest { + + public static void main(String[] argv){ + testJdbcRowSetImpl(); + } + + public static void testJdbcRowSetImpl(){ + //JDK 8u121以后版本需要设置改系统变量 + System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true"); + //RMI + String payload2 = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\"," + + " \"autoCommit\":true}"; + JSONObject.parseObject(payload2); + } +} +``` + +漏洞复现的流程如下哈: + +![JSON序列化漏洞复现](images/Architecture/JSON序列化漏洞复现.jpg) + +参考的代码来源这里哈,fastjson漏洞代码测试(https://github.com/earayu/fastjson_jndi_poc) + + + +**如何解决json反序列化漏洞问题** + +- 可以升级版本,比如fastjson后面版本,增强AutoType打开时的安全性 fastjson,增加了AutoType黑名单等等,都是为了应对这些安全漏洞 +- 反序列化有fastjson、gson、jackson等等类型,可以替换其他类型 +- 升级+打开safemode + + + +## XSS攻击 + +### 什么是XSS? + +XSS 攻击全称跨站脚本攻击(Cross-Site Scripting),这会与层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,因此有人将跨站脚本攻击缩写为XSS。它指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意攻击用户的特殊目的。XSS攻击一般分三种类型:存储型 、反射型 、DOM型XSS。 + + + +### XSS是如何攻击的? + +拿反射型举个例子吧,流程图如下: + +![XSS攻击-反射案例](images/Architecture/XSS攻击-反射案例.jpg) + +我们搞点简单代码样例吧,首先正常html页面如下: + +```xml + + + +``` + +![XSS攻击-Html](images/Architecture/XSS攻击-Html.jpg) + +- 用户输入搜索信息,点击搜索按钮,就是到达正常服务器的。如果黑客在url后面的参数中加入如下的恶意攻击代码 + + ```java + http://127.0.0.1/search?keyword=" + ``` + +- 当用户打开带有恶意代码的URL的时候,正常服务器会解析出请求参数 name,得到"",拼接到 HTML 中返回给浏览器 + +- 用户浏览器接收到响应后执行解析,其中的恶意代码也会被执行到 + +![XSS攻击-Html结果](images/Architecture/XSS攻击-Html结果.jpg) + +- 这里的链接我写的是百度搜索页,实际上黑客攻击的时候,是引诱用户输入某些重要信息,然后跳到他们自己的服务器,以窃取用户提交的内容信息 + + + +### 如何解决XSS攻击问题 + +- 不相信用户的输入,对输入进行过滤,过滤标签等,只允许合法值 +- HTML 转义 + +![XSS攻击-转义字符](images/Architecture/XSS攻击-转义字符.jpg) + +- 对于链接跳转,如` + +]> +]> +&xxe; +``` + +- 场景2. 攻击者通过将上面的实体行更改为一下内容来探测服务器的专用网络 + +```xml +]> +``` + +- 场景3. 攻击者通过恶意文件执行拒绝服务攻击 + +```xml +]> +``` + + + +### 如何防御XXE + +- 使用开发语言提供的禁用外部实体的方法 +- 过滤用户提交的XML数据,过滤 我开了一家有五十个座位的重庆火锅店,由于用料上等,童叟无欺。平时门庭若市,生意特别红火,而对面二狗家的火锅店却无人问津。二狗为了对付我,想了一个办法,叫了五十个人来我的火锅店坐着却不点菜,让别的客人无法吃饭。 + + + +### 如何应对DDoS攻击? + +- **高防服务器**,即能独立硬防御 50Gbps 以上的服务器,能够帮助网站拒绝服务攻击,定期扫描网络主节点等 +- **黑名单** +- **DDoS 清洗** +- **CDN 加速** + + + +## 框架或应用漏洞 + +- Struts 框架漏洞:远程命令执行漏洞和开放重定向漏洞 +- QQ Browser 9.6:API 权限控制问题导致泄露隐私模式 +- Oracle GlassFish Server:REST CSRF +- WebLogic: 未授权命令执行漏洞 +- Hacking Docker:Registry API 未授权访问 +- WordPress 4.7 / 4.7.1:REST API 内容注入漏洞 + + + +## 其它漏洞 + +### 弱口令 + +- 空口令 +- 口令长度小于8 +- 口令不应该为连续的某个字符(QQQQQQ) +- 账号密码相同(例:root:root) +- 口令与账号相反(例:root:toor) +- 口令纯数字(例:112312324234, 电话号) +- 口令纯字母(例:asdjfhask) +- 口令已数字代替字母(例:hello word, hell0 w0rd) +- 口令采用连续性组合(例:123456,abcdef,654321,fedcba) +- 服务/设备默认出厂口令 + + + +### 证书有效性验证漏洞 + +如果不对证书进行有效性验证,那https就如同虚设啦。 + +- 如果是客户生成的证书,需要跟系统可信根CA形成信任链,不能为了解决ssl证书报错的问题,选择在客户端代码中信任客户端中所有证书的方式 +- 证书快过期时,需要提前更换 + + + +### 未鉴权等权限相关漏洞 + +一些比较重要的接口,一般建议鉴权。比如你查询某账号的转账记录,肯定需要先校验该账号是不是操作人旗下的啦。 + + + +# 其它设计 + +## 短链接 + +公认方案: + +- **分布式ID生成器产生ID** +- **ID转62进制字符串** +- **记录数据库,根据业务要求确定过期时间,可以保留部分永久链接** + +主要难点在于分布式ID生成。鉴于短链一般没有严格递增的需求,可以使用预先分发一个号段,然后生成的方式。看了下新浪微博的短链接,8位,理论上可以保存超过200万亿对关系,具体怎么存储的还有待研究。 + + + +## 秒杀系统 + +**设计难点**:并发量大,应用、数据库都承受不了。另外难控制超卖。 + +**设计要点**: + +- 将请求尽量拦截在系统上游,html尽量静态化,部署到cdn上面。按钮及时设置为不可用,禁止用户重复提交请求 +- 设置页面缓存,针对同一个页面和uid一段时间内返回缓存页面 +- 数据用缓存抗,不直接落到数据库 +- 读数据的时候不做强一致性教研,写数据的时候再做 +- 在每台物理机上也缓存商品信息等等变动不大的相关的数据 +- 像商品中的标题和描述这些本身不变的会在秒杀开始之前全量推送到秒杀机器上并一直缓存直到秒杀结束 +- 像库存这种动态数据会采用被动失效的方式缓存一定时间(一般是数秒),失效后再去Tair缓存拉取最新的数据 +- 如果允许的话,用异步的模式,等缓存都落库之后再返回结果 +- 如果允许的话,增加答题教研等验证措施 + +![秒杀系统](images/Architecture/秒杀系统.png) + +**其它业务和技术保障措施**: + +- **业务隔离**。把秒杀做成一种营销活动,卖家要参加秒杀这种营销活动需要单独报名,从技术上来说,卖家报名后对我们来说就是已知热点,当真正开始时我们可以提前做好预热 +- **系统隔离**。系统隔离更多是运行时的隔离,可以通过分组部署的方式和另外 99% 分开。秒杀还申请了单独的域名,目的也是让请求落到不同的集群中 +- **数据隔离**。秒杀所调用的数据大部分都是热数据,比如会启用单独 cache 集群或 MySQL 数据库来放热点数据,目前也是不想0.01%的数据影响另外99.99% +- **缓存数据库高可用**。主要流量都落在缓存数据库上,需针对缓存数据库的高可用作保障。研究缓存穿透、雪崩等等问题 + + + +## 红包系统 + +红包系统其实很像秒杀系统,只不过同一个秒杀的总量不大,但是全局的并发量非常大,如春晚可能几百万人同时抢红包。 + +**技术难点**: + +- 主要在数据库,减库存的时候会抢锁 +- 由于业务需求不同,没办法异步,也不能超卖,事务更加严格 + +**不能采用的方式**: + +- 乐观锁:手慢会失败,DB 面临更大压力,所以不能采用 +- 直接用缓存顶,涉及到钱,一旦缓存挂掉就完了 + +**建议方式**: + +- 接入层垂直切分,根据红包ID,发红包、抢红包、拆红包、查详情详情等都在同一台机器上处理,互不影响,分而治之 +- 请求进行排队,到数据库的时候是串行的,就不涉及抢锁的问题了 +- 为了防止队列太长过载导致队列被降级,直接打到数据库上,所以数据库前面再加上一个缓存,用CAS自增控制并发,太高的并发直接返回失败 +- 红包冷热数据分离,按时间分表 + + + +## 分布式定时任务 + +任务轮询或任务轮询+抢占排队方案 + +- 每个服务器首次启动时加入队列 +- 每次任务运行首先判断自己是否是当前可运行任务,如果是便运行 +- 如果不是当前运行的任务,检查自己是否在队列中,如果在,便退出,如果不在队列中,进入队列 + + + +## 微博推送 + +**主要难点**:关系复杂,数据量大。一个人可以关注非常多的用户,一个大 V 也有可能有几千万的粉丝。 + +**基本方案**: + +- 推模式:推模式就是,用户A关注了用户 B,用户 B 每发送一个动态,后台遍历用户B的粉丝,往他们粉丝的 feed 里面推送一条动态 +- 拉模式:推模式相反,拉模式则是,用户每次刷新 feed 第一页,都去遍历关注的人,把最新的动态拉取回来 + +一般采用推拉结合的方式,用户发送状态之后,先推送给粉丝里面在线的用户,然后不在线的那部分等到上线的时候再来拉取。另外冷热数据分离,用户关系在缓存里面可以设置一个过期时间,比如七天。七天没上线的可能就很少用这个 APP。 diff --git a/BigData.md b/BigData.md new file mode 100644 index 0000000..6f25cd2 --- /dev/null +++ b/BigData.md @@ -0,0 +1,1654 @@ +
BigData
+ +Author:李茹钰(`echo`) + +Introduction:收纳技术相关的基础知识 `Redis`、`RocketMQ`、`Zookeeper`、`Netty`、`Tomcat` 等总结! + +[TOC] + +# Hadoop + +![Hadoop生态架构图](images/BigData/Hadoop生态架构图.png) + + + +# 数据采集 + +## Flume + +Flume是一个分布式、可靠、和高可用的海量日志采集、聚合和传输的系统。支持在日志系统中定制各类数据发送方,用于收集数据。同时,Flume提供对数据进行简单处理,并写到各种数据接受方(比如文本、HDFS、Hbase等)的能力 。 + +### 应用架构 + +![Flume复杂应用](images/BigData/Flume复杂应用.png)# + + + +### 安装使用 + +第一步:在apache-flume-1.7.0-bin\conf目录下创建一个example.conf配置文件。然后把官文档中的案例内容复制到example.conf文件中,如下内容: + +```properties +# example.conf: A single-node Flume configuration + +# Name the components on this agent +a1.sources = r1 +a1.sinks = k1 +a1.channels = c1 + +# Describe/configure the source +a1.sources.r1.type = netcat +a1.sources.r1.bind = localhost +a1.sources.r1.port = 44444 + +# Describe the sink +a1.sinks.k1.type = logger + +# Use a channel which buffers events in memory +a1.channels.c1.type = memory +a1.channels.c1.capacity = 1000 +a1.channels.c1.transactionCapacity = 100 + +# Bind the source and sink to the channel +a1.sources.r1.channels = c1 +a1.sinks.k1.channel = c1 +``` + +第二步:进入到解压flume目录中执行命令,文档中的执行命令如下: + +```shell +[root@mini3 apache-flume-1.7.0-bin]# bin/flume-ng agent --conf conf --conf-file conf/example.conf --name a1 -Dflume.root.logger=INFO,console +``` + +**参数的简要说明**:指明conf文件路径、指明conf文件、指定agent、指明log打印信息级别和位置。执行效果: + +```shell +Info: Including Hive libraries found via () for Hive access ++ exec /usr/local/jdk1.7.0_65/bin/java -Xmx20m -Dflume.root.logger=INFO,console -cp '/usr/local/apache-flume-1.7.0-bin/conf:/usr/local/apache-flume-1.7.0-bin/lib/*:/lib/*' -Djava.library.path= org.apache.flume.node.Application --conf-file example.conf --name a1 +...... +2018-01-31 18:14:45,870 (lifecycleSupervisor-1-4) [INFO - org.apache.flume.source.NetcatSource.start(NetcatSource.java:155)] Source starting +2018-01-31 18:14:45,894 (lifecycleSupervisor-1-4) [INFO - org.apache.flume.source.NetcatSource.start(NetcatSource.java:169)] Created serverSocket:sun.nio.ch.ServerSocketChannelImpl[/127.0.0.1:44444] +``` + +如上打印日志,启动成功。 + +第三步:通过telnet客户端进行测试。另开一个终端命令行:输入命令 + +```shell +telnet localhost 44444 +``` + +连接成功后即可进行模拟通信,即经过44444端口发送消息,让flume监听到。执行结果如下: + +![Flume-telnet](images/BigData/Flume-telnet.jpg) + +flume后台监听打印: + +```shell +2018-01-31 18:15:48,913 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { headers:{} body: 6E 69 68 61 6F 0D nihao. } +``` + +注:如果不能使用telnet,通过yum安装即可。 + + + +## Kafka + +Kafka是最初由Linkedin公司开发,是一个分布式、分区的、多副本的、多订阅者,基于zookeeper协调的分布式日志系统(也可以当做MQ系统),常见可以用于web/nginx日志、访问日志,消息服务等等,Linkedin于2010年贡献给了Apache基金会并成为顶级开源项目。主要应用场景是:日志收集系统和消息系统。Kafka主要设计目标如下: + +- 以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能保证常数时间的访问性能 +- 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒100K条消息的传输 +- 支持Kafka Server间的消息分区,及分布式消费,同时保证每个partition内的消息顺序传输 +- 同时支持离线数据处理和实时数据处理 +- Scale out:支持在线水平扩展 + +消息传递模式:**点对点传递模式、发布-订阅模式**。大部分消息系统选用发布-订阅模式。**Kafka就是一种发布-订阅模式**。 + + + +### Kafka架构 + +![Kafka架构](images/BigData/Kafka架构.png) + +kafka支持消息持久化,消费端是主动拉取数据,消费状态和订阅关系由客户端负责维护,**消息消费完后,不会立即删除,会保留历史消息**。因此支持多订阅时,消息只会存储一份就可以。 + +- **broker**:kafka集群中包含一个或者多个服务实例(节点),这种服务实例被称为broker(一个broker就是一个节点/一个服务器) +- **topic**:每条发布到kafka集群的消息都属于某个类别,这个类别就叫做topic +- **partition**:partition是一个物理上的概念,每个topic包含一个或者多个partition +- **segment**:一个partition当中存在多个segment文件段,每个segment分为两部分,.log文件和 .index 文件,其中 .index 文件是索引文件,主要用于快速查询, .log 文件当中数据的偏移量位置 +- **producer**:消息的生产者,负责发布消息到 kafka 的 broker 中 +- **consumer**:消息的消费者,向 kafka 的 broker 中读取消息的客户端 +- **consumer group**:消费者组,每一个 consumer 属于一个特定的 consumer group(可以为每个consumer指定 groupName) +- **.log**:存放数据文件 +- **.index**:存放.log文件的索引数据 + + + +### Kafka优点 + +- **解耦** + + 在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。 + +- **冗余(副本)** + + 有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的"插入-获取-删除"范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。 + +- **扩展性** + + 因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。扩展就像调大电力按钮一样简单。 + +- **灵活性&峰值处理能力** + + 在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。 + +- **可恢复性** + + 系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。 + +- **顺序保证** + + 在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。Kafka保证一个Partition内的消息的有序性。 + +- **缓冲** + + 在任何重要的系统中,都会有需要不同的处理时间的元素。例如,加载一张图片比应用过滤器花费更少的时间。消息队列通过一个缓冲层来帮助任务最高效率的执行———写入队列的处理会尽可能的快速。该缓冲有助于控制和优化数据流经过系统的速度。 + +- **异步通信** + + 很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。 + + + +### 主要组件 + +#### broker + +Kafka 服务器,负责消息存储和转发;一个 broker 就代表一个 kafka 节点。**一个 broker 可以包含多个 topic**。 + + + +#### topic(主题) + +- kafka将消息以topic为单位进行归类 +- topic特指kafka处理的消息源(feeds of messages)的不同分类 +- topic是一种分类或者发布的一些列记录的名义上的名字。kafka主题始终是支持多用户订阅的;也就是说,一 个主题可以有零个,一个或者多个消费者订阅写入的数据 +- 在kafka集群中,可以有无数的主题 +- 生产者和消费者消费数据一般以主题为单位。更细粒度可以到分区级别 + +kafka学习了数据库里面的设计,在里面设计了topic(主题),这个东西类似于关系型数据库的表 + +![kafka-topic](images/BigData/kafka-topic.jpg) + +此时我需要获取数据,那就直接监听TopicA即可。 + + + +#### partition(分区) + +kafka当中,topic是消息的归类,一个topic可以有多个分区(partition),每个分区保存部分topic的数据,所有的partition当中的数据全部合并起来,就是一个topic当中的所有的数据。一个broker服务下,可以创建多个分区,broker数与分区数没有关系; 在kafka中,每一个分区会有一个编号:编号从0开始。**每一个分区内的数据是有序的,但全局的数据不能保证是有序的。**(有序是指生产什么样顺序,消费时也是什么样的顺序) + +kafka还有一个概念叫Partition(分区),分区具体在服务器上面表现起初就是一个目录,一个主题下面有多个分区,这些分区会存储到不同的服务器上面,或者说,其实就是在不同的主机上建了不同的目录。这些分区主要的信息就存在了.log文件里面。跟数据库里面的分区差不多,是为了提高性能。 + +![kafka-partition](images/BigData/kafka-partition.jpg) + +至于为什么提高了性能,很简单,多个分区多个线程,多个线程并行处理肯定会比单线程好得多 + +Topic和partition像是HBASE里的table和region的概念,table只是一个逻辑上的概念,真正存储数据的是region,这些region会分布式地存储在各个服务器上面,对应于kafka,也是一样,**Topic也是逻辑概念**,而partition就是分布式存储单元。这个设计是保证了海量数据处理的基础。我们可以对比一下,如果HDFS没有block的设计,一个100T的文件也只能单独放在一个服务器上面,那就直接占满整个服务器了,引入block后,大文件可以分散存储在不同的服务器上。 + +注意: + +1.分区会有单点故障问题,所以我们会为每个分区设置副本数 + +2.分区的编号是从0开始的 + + + +topic 的分区,一个 topic 可以包含多个 partition,topic 消息保存在各个 partition 上;由于一个 topic 能被分到多个分区上,给 kafka 提供给了并行的处理能力,这也正是 kafka 高吞吐的原因之一。 + +partition 物理上由多个 segment 文件组成,每个 segment 大小相等,**顺序读写(这也是 kafka 比较快的原因之一,不需要随机写)**。每个 Segment 数据文件以该段中最小的 offset ,文件扩展名为.log。当查找 offset 的 Message 的时候,通过二分查找快找到 Message 所处于的 Segment 中。 + +![kafka-partition](images/BigData/kafka-partition.png) + + + +#### offset + +- 消息在日志中的位置,可以理解是消息在 partition 上的偏移量,也是代表该消息的**唯一序号**。 +- 同时也是主从之间的需要同步的信息 + +![kafka-offset](images/BigData/kafka-offset.png) + + + +#### producer(生产者) + +producer主要是用于生产消息,是kafka当中的消息生产者,生产的消息通过topic进行归类,保存到kafka的broker里面。往消息系统里面发送数据的就是生产者。 + +![kafka-producer](images/BigData/kafka-producer.jpg) + + + +#### consumer(消费者) + +consumer是kafka当中的消费者,主要用于消费kafka当中的数据,消费者一定是归属于某个消费组中的。从kafka里读取数据的就是消费者: + +![kafka-consumer](images/BigData/kafka-consume.jpg) + + + +#### consumer group(消费者组) + +消费者组由一个或者多个消费者组成,**同一个组中的消费者对于同一条消息只消费一次**。每个消费者都属于某个消费者组,如果不指定,那么所有的消费者都属于默认的组。每个消费者组都有一个ID,即group ID。组内的所有消费者协调在一起来消费一个订阅主题( topic)的所有分区(partition)。当然,**每个分区只能由同一个消费组内的一个消费者(consumer)来消费**,可以由不同的**消费组**来消费。partition数量决定了每个consumer group中并发消费者的最大数量。如下图: + +![consumer_group示例](images/BigData/consumer_group示例.png) + +如上面左图所示,如果只有两个分区,即使一个组内的消费者有4个,也会有两个空闲的。如上面右图所示,有4个分区,每个消费者消费一个分区,并发量达到最大4。在来看如下一幅图: + +![consumer_group示例2](images/BigData/consumer_group示例2.png) + +如上图所示,不同的消费者组消费同一个topic,这个topic有4个分区,分布在两个节点上。左边的 消费组1有两个消费者,每个消费者就要消费两个分区才能把消息完整的消费完,右边的 消费组2有四个消费者,每个消费者消费一个分区即可。 + + + +**总结下kafka中分区与消费组的关系**: + +消费组:由一个或者多个消费者组成,同一个组中的消费者对于同一条消息只消费一次。 + +某一个主题下的分区数,对于消费该主题的同一个消费组下的消费者数量,应该小于等于该主题下的分区数。 + +如:某一个主题有4个分区,那么消费组中的消费者应该小于等于4,而且最好与分区数成整数倍 1 2 4 这样。同一个分区下的数据,在同一时刻,不能同一个消费组的不同消费者消费。 + +总结:**分区数越多,同一时间可以有越多的消费者来进行消费,消费数据的速度就会越快,提高消费的性能**。 + + + +#### partition replicas(分区副本) + +kafka 中的分区副本如下图所示: + +![kafka分区副本](images/BigData/kafka分区副本.png) + +**副本数**(replication-factor):控制消息保存在几个broker(服务器)上,一般情况下副本数等于broker的个数。 + +一个broker服务下,不可以创建多个副本因子。**创建主题时,副本因子应该小于等于可用的broker数**。副本因子操作以分区为单位的。每个分区都有各自的主副本和从副本;主副本叫做leader,从副本叫做 follower(在有多个副本的情况下,kafka会为同一个分区下的所有分区,设定角色关系:一个leader和N个 follower),**处于同步状态的副本叫做in-sync-replicas(ISR)**。 + + + +#### segment文件 + +一个partition当中由多个segment文件组成,每个segment文件,包含两部分,一个是 .log 文件,另外一个是 .index 文件,其中 .log 文件包含了我们发送的数据存储,.index 文件,记录的是我们.log文件的数据索引值,便于我们加快数据查询速度。 + + + +#### message物理结构 + +生产者发送到kafka的每条消息,都被kafka包装成了一个message。message 的物理结构如下图所示: + +![message物理结构](images/BigData/message物理结构.png) + +所以生产者发送给kafka的消息并不是直接存储起来,而是经过kafka的包装,每条消息都是上图这个结构,只有最后一个字段才是真正生产者发送的消息数据。 + + + +#### zookeeper + +管理 kafka 集群,负责存储了集群 broker、topic、partition 等 meta 数据存储,同时也负责 broker 故障发现,partition leader 选举,负载均衡等功能。 + +![kafka-zookeeper](images/BigData/kafka-zookeeper.png) + + + +### 消息传递 + +#### 点对点消息传递模式 + +在点对点消息系统中,消息持久化到一个队列中。此时,将有一个或多个消费者消费队列中的数据。但是一条消息只能被消费一次。当一个消费者消费了队列中的某条数据之后,该条数据则从消息队列中删除。该模式即使有多个消费者同时消费数据,也能保证数据处理的顺序。这种架构描述示意图如下: + +![Kafka点对点消息传递模式](images/BigData/Kafka点对点消息传递模式.png) + +**生产者发送一条消息到queue,只有一个消费者能收到**。 + + + +#### 发布-订阅消息传递模式 + +在发布-订阅消息系统中,消息被持久化到一个topic中。与点对点消息系统不同的是,消费者可以订阅一个或多个topic,消费者可以消费该topic中所有的数据,同一条数据可以被多个消费者消费,数据被消费后不会立马删除。在发布-订阅消息系统中,消息的生产者称为发布者,消费者称为订阅者。该模式的示例图如下: + +![Kafka发布-订阅消息传递模式](images/BigData/Kafka发布-订阅消息传递模式.png) + +**发布者发送到topic的消息,只有订阅了topic的订阅者才会收到消息**。 + + + +### 服务治理 + +既然 Kafka 是分布式的发布/订阅系统,这样如果做的集群之间数据同步和一致性,kafka 是不是肯定不会丢消息呢?以及宕机的时候如果进行 Leader 选举呢? + + + +#### 数据同步 + +在 Kafka 中的 Partition 有一个 leader 与多个 follower,producer 往某个 Partition 中写入数据是,只会往 leader 中写入数据,然后数据才会被复制进其他的 Replica 中。而每一个 follower 可以理解成一个消费者,定期去 leader 去拉去消息。而只有数据同步了后,kafka 才会给生产者返回一个 ACK 告知消息已经存储落地了。 + + + +#### ISR + +在 Kafka 中,为了保证性能,Kafka 不会采用强一致性的方式来同步主从的数据。而是维护了一个:in-sync Replica 的列表,Leader 不需要等待所有 Follower 都完成同步,只要在 ISR 中的 Follower 完成数据同步就可以发送 ack 给生产者即可认为消息同步完成。同时如果发现 ISR 里面某一个 follower 落后太多的话,就会把它剔除。 + +具体流程如下: + +![Kafka服务治理-ISR-1](images/BigData/Kafka服务治理-ISR-1.png) + +![Kafka服务治理-ISR-2](images/BigData/Kafka服务治理-ISR-2.png) + +![Kafka服务治理-ISR-3](images/BigData/Kafka服务治理-ISR-3.png) + +![Kafka服务治理-ISR-4](images/BigData/Kafka服务治理-ISR-4.png) + +**上述的做法并无法保证 kafka 一定不丢消息。**虽然 Kafka 通过多副本机制中最大限度保证消息不会丢失,但是如果数据已经写入系统 page cache 中但是还没来得及刷入磁盘,此时突然机器宕机或者掉电,那消息自然而然的就会丢失。 + + + +#### Kafka故障恢复 + +![Kafka服务治理-故障恢复](images/BigData/Kafka服务治理-故障恢复.png) + +Kafka 通过 Zookeeper 连坐集群的管理,所以这里的选举机制采用的是 Zab(zookeeper 使用)。 + +- 生产者发生消息给 leader,这个时候 leader 完成数据存储,突然发生故障,没有给 producer 返回 ack +- 通过 ZK 选举,其中一个 follower 成为 leader,这个时候 producer 重新请求新的 leader,并存储数据 + + + +### 数据不丢失机制 + +#### 生产者生产数据不丢失 + +**发送消息方式** + +生产者发送给kafka数据,可以采用**同步方式**或**异步方式**: + +- **同步方式** + + 发送一批数据给kafka后,等待kafka返回结果: + + - 生产者等待10s,如果broker没有给出ack响应,就认为失败 + - 生产者重试3次,如果还没有响应,就报错 + +- **异步方式** + + 发送一批数据给kafka,只是提供一个回调函数: + + - 先将数据保存在生产者端的buffer中。buffer大小是2万条 + - 满足数据阈值或者数量阈值其中的一个条件就可以发送数据 + - 发送一批数据的大小是500条 + +注意:如果broker迟迟不给ack,而buffer又满了,开发者可以设置是否直接清空buffer中的数据。 + + + +**ack机制(确认机制)** + +生产者数据发送出去,需要服务端返回一个确认码,即ack响应码;ack的响应有三个状态值: + +- 0:生产者只负责发送数据,不关心数据是否丢失,丢失的数据,需要再次发送 + +- 1:partition的leader收到数据,不管follow是否同步完数据,响应的状态码为1 + +- -1:所有的从节点都收到数据,响应的状态码为-1 + +**注意**:如果broker端一直不返回ack状态,producer永远不知道是否成功;producer可以设置一个超时时间10s,超过时间认为失败。 + + + +#### broker中数据不丢失 + +在broker中,保证数据不丢失主要是通过副本因子(冗余),防止数据丢失。 + + + +#### 消费者消费数据不丢失 + +在消费者消费数据的时候,只要每个消费者记录好offset值即可,就能保证数据不丢失。也就是需要我们自己维护偏移量(offset),可保存在 Redis 中。 + + + +### 常见问题 + +**问题一:Kafka性能好在什么地方?** + +- **顺序写磁盘** + + 操作系统每次从磁盘读写数据的时候,需要先寻址,也就是先要找到数据在磁盘上的物理位置,然后再进行数据读写,如果是机械硬盘,寻址就需要较长的时间。 kafka的设计中,数据其实是存储在磁盘上面,一般来说,会把数据存储在内存上面性能才会好。但是kafka用的是顺序写,追加数据是追加到末尾,磁盘顺序写的性能极高,在磁盘个数一定,转数达到一定的情况下,基本和内存速度一致。随机写的话是在文件的某个位置修改数据,性能会较低。 + +- **Page Cache** + + Kafka 在 OS 系统方面使用了 Page Cache 而不是我们平常所用的 Buffer。Page Cache 其实不陌生,也不是什么新鲜事物 + + ![Kafka-PageCache](images/BigData/Kafka-PageCache.png) + + 我们在 linux 上查看内存的时候,经常可以看到 buff/cache,两者都是用来加速 IO 读写用的,而 cache 是作用于读,也就是说,磁盘的内容可以读到 cache 里面这样,应用程序读磁盘就非常快;而 buff 是作用于写,我们开发写磁盘都是,一般如果写入一个 buff 里面再 flush 就非常快。而 kafka 正是把这两者发挥了极致: + + Kafka 虽然是 scala 写的,但是依旧在 Java 的虚拟机上运行,尽管如此,它尽量避开了 JVM 的限制,它利用了 Page cache 来存储,这样躲开了数据在 JVM 因为 GC 而发生的 STD。另一方面也是 Page Cache 使得它实现了零拷贝,具体下面会讲。 + +- **零拷贝** + + 先来看看非零拷贝的情况: + + ![kafka-零拷贝](images/BigData/kafka-零拷贝.jpg) + + 可以看到数据的拷贝从内存拷贝到kafka服务进程那块,又拷贝到socket缓存那块,整个过程耗费的时间比较高,kafka利用了Linux的sendFile技术(NIO),省去了进程切换和一次数据拷贝,让性能变得更好。 + + ![kafka-零拷贝优化](images/BigData/kafka-零拷贝优化.jpg) + + **传统的一次应用程请求数据的过程** + + ![kafka-传统数据请求](images/BigData/kafka-传统数据请求.png) + + 这里大致可以发传统的方式发生了 4 次拷贝,2 次 DMA 和 2 次 CPU,而 CPU 发生了 4 次的切换。(DMA 简单理解就是,在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情) + + **零拷贝的方式** + + ![kafka-零拷贝的方式](images/BigData/kafka-零拷贝的方式.png) + + 通过优化我们可以发现,CPU 只发生了 2 次的上下文切换和 3 次数据拷贝。(linux 系统提供了系统事故调用函数“ sendfile()”,这样系统调用,可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态) + +- **分区分段** + + 我们上面也介绍过了,kafka 采取了分区的模式,而每一个分区又对应到一个物理分段,而查找的时候可以根据二分查找快速定位。这样不仅提供了数据读的查询效率,也提供了并行操作的方式。 + +- **数据压缩** + + Kafka 对数据提供了:Gzip 和 Snappy 压缩协议等压缩协议,对消息结构体进行了压缩,一方面减少了带宽,也减少了数据传输的消耗。 + + + +**问题二:日志如何分段存储?** + +Kafka规定了一个分区内的.log文件最大为1G,做这个限制目的是为了方便把.log加载到内存去操作 + +```shell +00000000000000000000.index +00000000000000000000.log +00000000000000000000.timeindex + +00000000000005367851.index +00000000000005367851.log +00000000000005367851.timeindex + +00000000000009936472.index +00000000000009936472.log +00000000000009936472.timeindex +``` + +这个9936472之类的数字,就是代表了这个日志段文件里包含的起始offset,也就说明这个分区里至少都写入了接近1000万条数据了。Kafka broker有一个参数,log.segment.bytes,限定了每个日志段文件的大小,最大就是1GB,一个日志段文件满了,就自动开一个新的日志段文件来写入,避免单个文件过大,影响文件的读写性能,这个过程叫做log rolling,正在被写入的那个日志段文件,叫做active log segment。如果大家有看前面的两篇有关于HDFS的文章时,就会发现NameNode的edits log也会做出限制,所以这些框架都是会考虑到这些问题。 + + + +**问题三:Kafka如何网络设计?** + +kafka的网络设计和Kafka的调优有关,这也是为什么它能支持高并发的原因: + +![Kafka的网络设计](images/BigData/Kafka的网络设计.jpg) + +首先客户端发送请求全部会先发送给一个Acceptor,broker里面会存在3个线程(默认是3个),这3个线程都是叫做processor,Acceptor不会对客户端的请求做任何的处理,直接封装成一个个socketChannel发送给这些processor形成一个队列,发送的方式是轮询,就是先给第一个processor发送,然后再给第二个,第三个,然后又回到第一个。消费者线程去消费这些socketChannel时,会获取一个个request请求,这些request请求中就会伴随着数据。 + +线程池里面默认有8个线程,这些线程是用来处理request的,解析请求,如果request是写请求,就写到磁盘里。读的话返回结果。 processor会从response中读取响应数据,然后再返回给客户端。这就是Kafka的网络三层架构。 + +所以如果我们需要对kafka进行增强调优,增加processor并增加线程池里面的处理线程,就可以达到效果。request和response那一块部分其实就是起到了一个缓存的效果,是考虑到processor们生成请求太快,线程数不够不能及时处理的问题。所以这就是一个加强版的reactor网络线程模型。 + + + +### 安装Kafka + +第一步:安装 JDK + +第二步:安装 Zookeeper + +第三步:下载Kafka:https://www.apache.org/dyn/closer.cgi?path=/kafka/2.8.0/kafka-2.8.0-src.tgz + +第四步:安装Kafka: + +```javascript + tar -xzvf kafka_2.12-2.0.0.tgz +``` + +第五步:配置环境变量: + +```javascript + export ZK=/usr/local/src/apache-zookeeper-3.7.0-bin + export PATH=$PATH:$ZK/bin + export KAFKA=/usr/local/src/kafka + export PATH=$PATH:$KAFKA/bin +``` + +第六步:启动Kafka: + +```javascript + nohup kafka-server-start.sh 自己的配置文件路径/server.properties & +``` + +![启动Kafka日志](images/BigData/启动Kafka日志.png) + + + +### Spring Boot案例 + +第一步:添加依赖 + +```xml + + org.springframework.kafka + spring-kafka + +``` + +第二步:配置文件application.yml + +```yaml +kafka: + bootstrap: + servers: localhost:9092 + topic: + user: topic-user + group: + id: group-user +``` + +第三步:创建kafka生产者类 + +```java +/** + * Kafka消息生产类 + */ +@Log +@Component +public class KafkaProducer { + @Resource + private KafkaTemplate kafkaTemplate; + @Value("${kafka.topic.user}") + private String topicUser;// topic名称 + /** + * 发送用户消息 + * @param user 用户信息 + */ + public void sendUserMessage(User user) { + GsonBuilder builder = new GsonBuilder(); + builder.setPrettyPrinting(); + builder.setDateFormat("yyyy-MM-dd HH:mm:ss"); + String message = builder.create().toJson(user); + kafkaTemplate.send(topicUser, message); + log.info("\n生产消息至Kafka\n" + message); + } +} + +/** + * 测试控制器 + */ +@RestController +@RequestMapping("/kafka") +public class KafkaController { + @Autowired + private User user; + @Autowired + private KafkaProducer kafkaProducer; + @RequestMapping("/createMsg") + public void createMsg() { + kafkaProducer.sendUserMessage(user); + } +} +``` + +第四步:创建kafka消费者类,并通过控制器调用 + +```java +public class KafkaConsumerDemo { + + @Value("${kafka.topic.user}") + private String topicUser;// topic名称 + + public void consume() { + Properties props = new Properties(); + // 必须设置的属性 + props.put("bootstrap.servers", "127.0.0.1:9092"); + props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + props.put("group.id", "group-user"); + // 自动提交offset,每1s提交一次(提交后的消息不再消费,避免重复消费问题) + props.put("enable.auto.commit", "true");// 自动提交offset:true【PS:只有当消息提交后,此消息才不会被再次接受到】 + props.put("auto.commit.interval.ms", "1000");// 自动提交的间隔 + /** + * 消费方式配置 + * earliest: 当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费 + * latest: 当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据 + * none: topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常 + */ + props.put("auto.offset.reset", "earliest "); + // 拉取消息设置,每次poll操作最多拉取多少条消息(一般不主动设置,取默认的就好) + props.put("max.poll.records", "100 "); + + //根据上面的配置,新增消费者对象 + KafkaConsumer consumer = new KafkaConsumer<>(props); + // 订阅topic-user topic + consumer.subscribe(Collections.singletonList(topicUser)); + while (true) { + // 从服务器开始拉取数据 + ConsumerRecords records = consumer.poll(Duration.ofMillis(100)); + records.forEach(record -> { + System.out.printf("成功消费消息:topic = %s ,partition = %d,offset = %d, key = %s, value = %s%n", + record.topic(), record.partition(), record.offset(), record.key(), record.value()); + }); + } + } +} +``` + + + +# 数据存储 + +## HBase + +HBase是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase技术可在廉价PC Server上搭建大规模结构化的存储集群。HBase的目标是存储并处理大型数据,具体来说是仅需使用普通的硬件配置,就能够处理由成千上万的行和列所组成的大型数据。与MapReduce的离线批处理计算框架不同,HBase是一个可以随机访问的存储和检索数据平台,弥补了HDFS不能随机访问数据的缺陷,适合实时性要求不是非常高的业务场景。HBase存储的都是Byte数组,它不介意数据类型,允许动态、灵活的数据模型。 + +![HBase](images/BigData/HBase.jpg) + +上图描述了Hadoop 2.0生态系统中的各层结构。其中HBase位于结构化存储层,HDFS为HBase提供了高可靠性的底层存储支持, MapReduce为HBase提供了高性能的批处理能力,Zookeeper为HBase提供了稳定服务和failover机制,Pig和Hive为HBase提供了进行数据统计处理的高层语言支持,Sqoop则为HBase提供了便捷的RDBMS数据导入功能,使业务数据从传统数据库向HBase迁移变的非常方便。 + + + +HBase是基于列存储、构建在HDFS上的分布式存储系统,其主要功能是存储海量结构化数据。HBase构建在HDFS之上,因此HBase也是通过增加廉价的PC机提高系统运行和存储的能力。HBase中存储的表有如下特点: + +- 大表:一个表可以有数十亿行,上百万列 +- 无模式:每行都有一个可排序主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有截然不同的列 +- 面向列:面向列(族)的存储和权限控制,列(族)独立检索 +- 稀疏:对于空(null)的列,并不占用存储空间,表可以设计的非常稀疏 +- 数据多版本:每个单元中的数据可以有多个版本,默认情况下版本号自动分配,是单元格插入时的时间戳 +- 数据类型单一:Hbase中的数据都是字符串,没有类型 + + + +### 基本架构 + +![HBase基本架构](images/BigData/HBase基本架构.jpg) + +由上图可知,hbase包括Clinet、HMaster、HRegionServer、ZooKeeper组件: + +- Client + + Client主要通过ZooKeeper与Hbaser和HRegionServer通信,对于管理操作:client向master发起请求,对于数据读写操作:client向regionserver发起请求 + +- ZooKeeper + + zk负责存储root表的地址,也负责存储当前服务的master地址,regsion server也会将自身的信息注册到zk中,以便master能够感知region server的状态,zk也会协调active master,也就是可以提供一个选举master leader,也会协调各个region server的容灾流程 + +- HMaster + + master可以启动多个master,master主要负责table和region的管理工作,响应用户对表的CRUD操作,管理region server的负载均衡,调整region 的分布和分配,当region server停机后,负责对失效的regionn进行迁移操作 + +- HRegionServer + + region server主要负责响应用户的IO请求,并把IO请求转换为读写HDFS的操作 + + + +### 使用场景 + +**适用场景** + +- 存在高并发读写 +- 表结构的列族经常需要调整 +- 存储结构化或半结构化数据 +- 高并发的key-value存储 +- key随机写入,有序存储 +- 针对每个key保存一个固定大小的集合 多版本 + + + +**不适用场景** + +- 由于hbase只能提供行锁,它对分布式事务支持不好 +- 对于查询操作中的join、group by 性能很差 +- 查询如果不使用row-key,性能会很差,因为此时会进行全表扫描,建立二级索引或多级索引需要同时维护一张索引表 +- 高并发的随机读支持有限 + + + +### SpringBoot案例 + +**第一步:引入相关依赖** + +```xml + + org.springframework.data + spring-data-hadoop-hbase + 2.5.0.RELEASE + + + org.apache.hbase + hbase-client + 1.1.2 + + + org.springframework.data + spring-data-hadoop + 2.5.0.RELEASE + +``` + +**第二步:增加配置** + +官方提供的方式是通过xml方式,简单改写后如下: + +```java +@Configuration +public class HBaseConfiguration { + + @Value("${hbase.zookeeper.quorum}") + private String zookeeperQuorum; + + @Value("${hbase.zookeeper.property.clientPort}") + private String clientPort; + + @Value("${zookeeper.znode.parent}") + private String znodeParent; + + @Bean + public HbaseTemplate hbaseTemplate() { + org.apache.hadoop.conf.Configuration conf = new org.apache.hadoop.conf.Configuration(); + conf.set("hbase.zookeeper.quorum", zookeeperQuorum); + conf.set("hbase.zookeeper.property.clientPort", clientPort); + conf.set("zookeeper.znode.parent", znodeParent); + return new HbaseTemplate(conf); + } +} +``` + +application.yml + +```yml +hbase: + zookeeper: + quorum: hbase1.xxx.org,hbase2.xxx.org,hbase3.xxx.org + property: + clientPort: 2181 + +zookeeper: + znode: + parent: /hbase +``` + +**第三步:在service类注入HBaseTemplate** + +```java +@Service +@Slf4j +public class HBaseService { + + @Autowired + private HbaseTemplate hbaseTemplate; + + public List getRowKeyAndColumn(String tableName, String startRowkey, String stopRowkey, String column, String qualifier) { + FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL); + if (StringUtils.isNotBlank(column)) { + log.debug("{}", column); + filterList.addFilter(new FamilyFilter(CompareFilter.CompareOp.EQUAL, new BinaryComparator(Bytes.toBytes(column)))); + } + if (StringUtils.isNotBlank(qualifier)) { + log.debug("{}", qualifier); + filterList.addFilter(new QualifierFilter(CompareFilter.CompareOp.EQUAL, new BinaryComparator(Bytes.toBytes(qualifier)))); + } + Scan scan = new Scan(); + if (filterList.getFilters().size() > 0) { + scan.setFilter(filterList); + } + scan.setStartRow(Bytes.toBytes(startRowkey)); + scan.setStopRow(Bytes.toBytes(stopRowkey)); + + return hbaseTemplate.find(tableName, scan, (rowMapper, rowNum) -> rowMapper); + } + + public List getListRowkeyData(String tableName, List rowKeys, String familyColumn, String column) { + return rowKeys.stream().map(rk -> { + if (StringUtils.isNotBlank(familyColumn)) { + if (StringUtils.isNotBlank(column)) { + return hbaseTemplate.get(tableName, rk, familyColumn, column, (rowMapper, rowNum) -> rowMapper); + } else { + return hbaseTemplate.get(tableName, rk, familyColumn, (rowMapper, rowNum) -> rowMapper); + } + } + return hbaseTemplate.get(tableName, rk, (rowMapper, rowNum) -> rowMapper); + }).collect(Collectors.toList()); + } +} +``` + + + +## HDFS + +随着数据量越来越大,在一个操作系统存不下所有的数据,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。HDFS只是分布式文件管理系统中的一种。 + +HDFS(Hadoop Distributed File System),它是一个文件系统,用于存储文件,通过目录树来定位文件;其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色。 + +**使用场景**:适合一次写入,多次读出的场景,且不支持文件的修改。适合用来做数据分析,并不适合用来做网盘应用。 + + + +**HDFS优缺点** + +- 优点 + - 高容错性 + - 数据自动保存多个副本。它通过增加副本的形式,提高容错性 + - 某一个副本丢失以后,它可以自动恢复 + - 适合处理大数据 + - 可构建在廉价机器上,通过多副本机制,提高可靠性 + +- 缺点 + - 不适合低延时数据访问,比如毫秒级的存储数据 + - 无法高效的对大量小文件进行存储 + - 不支持并发写入、文件随机修改 + + + +### HDFS组成架构 + +![HDFS组成架构1](images/BigData/HDFS组成架构1.png) + +![HDFS组成架构2](images/BigData/HDFS组成架构2.png) + + + +**HDFS文件块大小** + +HDFS 中的文件在物理上是分块存储(Block),块的大小可以通过配置参数(dfs.blocksize)来规定,默认大小在Hadoop2.x 版本中是 128M,老版本中是 64M。 + +如果寻址时间为 100ms,即查找目标 Block 的时间是 100ms。寻址时间与传输时间的比例为 100 : 1为最佳状态,因此传输时间为 1ms。目前磁盘的传输速率大概在 100MB/s,取个整大概就是 128MB。 + + + +**客户端操作** + +```java +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.net.URI; + +public class HdfsClient { + + FileSystem fileSystem = null; + + @Before + public void init() { + try { + fileSystem = FileSystem.get(URI.create("hdfs://hadoop102:9000"), new Configuration(), "djm"); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * 上传文件 + */ + @Test + public void put() { + try { + fileSystem.copyFromLocalFile(new Path("C:\\Users\\Administrator\\Desktop\\Hadoop 入门.md"), new Path("/")); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 下载文件 + */ + @Test + public void download() { + try { + // useRawLocalFileSystem表示是否开启文件校验 + fileSystem.copyToLocalFile(false, new Path("/Hadoop 入门.md"), + new Path("C:\\Users\\Administrator\\Desktop\\Hadoop 入门1.md"), true); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 删除文件 + */ + @Test + public void delete() { + try { + // recursive表示是否递归删除 + fileSystem.delete(new Path("/Hadoop 入门.md"), true); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 文件重命名 + */ + @Test + public void rename() { + try { + fileSystem.rename(new Path("/tmp"), new Path("/temp")); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 查看文件信息 + */ + @Test + public void ls() { + try { + RemoteIterator listFiles = fileSystem.listFiles(new Path("/etc"), true); + while (listFiles.hasNext()) { + LocatedFileStatus fileStatus = listFiles.next(); + if (fileStatus.isFile()) { + // 仅输出文件信息 + System.out.print(fileStatus.getPath().getName() + " " + + fileStatus.getLen() + " " + fileStatus.getPermission() + " " + fileStatus.getGroup() + " "); + // 获取文件块信息 + BlockLocation[] blockLocations = fileStatus.getBlockLocations(); + for (BlockLocation blockLocation : blockLocations) { + // 获取节点信息 + String[] hosts = blockLocation.getHosts(); + for (String host : hosts) { + System.out.print(host + " "); + } + } + System.out.println(); + } + + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @After + public void exit() { + try { + fileSystem.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} +``` + + + +### HDFS写数据流程 + +**剖析文件写入** + +![HDFS剖析文件写入](images/BigData/HDFS剖析文件写入.png) + +- 客户端通过 Distributed FileSystem 模块向 NameNode 请求上传文件,NameNode 检查目标文件是否已存在,父目录是否存在 +- NameNode 返回是否可以上传 +- 客户端请求第一个 Block 上传到哪几个 DataNode +- NameNode 返回三个节点,分别是 dn1、dn2、dn3 +- 客户端通过 FSDataOutputStream 模块请求 dn1 上传数据,dn1 收到请求会继续调用 dn2,然后 dn2 调用 dn3,将这个通信管道建立完成 +- 按倒序逐级响应客户端 +- 客户端开始往 dn1 上传第一个 Block(先从磁盘读取数据放到一个本地内存缓存),以 Packet 为单位,dn1 收到一个Packet 就会传给 dn2,dn2 传给 dn3;dn1 每传一个 packet 会放入一个应答队列等待应答 +- 当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block的服务器 + + + +**网络拓扑-节点距离计算** + +在HDFS写数据过程中,NameNode会选择距离待上传数据最近距离的DataNode接收数据。那么这个最近距离怎么计算呢? + +![HDFS网络拓扑-节点距离计算](images/BigData/HDFS网络拓扑-节点距离计算.png) + + + +**机架感知** + +![HDFS机架感知](images/BigData/HDFS机架感知.png) + + + +### HDFS读数据流程 + +![HDFS读数据流程](images/BigData/HDFS读数据流程.png) + +- 客户端通过 Distributed FileSystem 向 NameNode 请求下载文件,NameNode通过查询元数据,找到文件块所在的DataNode地址 +- 根据就近原则挑选一台 DataNode,请求读取数据 +- DataNode 开始传输数据给客户端 +- 客户端以 Packet 为单位接收,先在本地缓存,然后写入目标文件 + + + +### DataNode + +**DataNode工作机制** + +![HDFSDataNode工作机制](images/BigData/HDFSDataNode工作机制.png) + +- 一个数据块在 DataNode 上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳 +- DataNode 启动后向 NameNode 注册,通过后,周期性(1小时)的向 NameNode 上报所有的块信息 +- 心跳是每 3 秒一次,心跳返回结果带有 NameNode 给该 DataNode 的命令如复制块数据到另一台机器,或删除某个数据块,如果超过 10 分钟没有收到某个 DataNode 的心跳,则认为该节点不可用 +- 集群运行中可以安全加入和退出一些机器 + + + +**数据完整性** + +![HDFS数据完整性](images/BigData/HDFS数据完整性.png) + +- 当 DataNode 读取 Block 的时候,它会计算 CheckSum +- 如果计算后的 CheckSum,与 Block 创建时值不一样,说明 Block 已经损坏 +- Client 读取其他 DataNode 上的 Block +- 在其文件创建后周期验证 + + + +### 掉线时限参数设置 + +![HDFS掉线时限参数设置](images/BigData/HDFS掉线时限参数设置.png) + +[hdfs-site.xml] + +```xml + + dfs.namenode.heartbeat.recheck-interval + 300000 + 毫秒 + + + dfs.heartbeat.interval + 3 + +1.2.3.4.5.6.7.8.9.10. +``` + + + +### 服役新数据节点 + +将 hadoop102 上的 java、hadoop、profile 发送到新主机,source 一下 profile,直接启动即可加入集群。 + + + +### HDFS 2.X新特性 + +**集群间数据拷贝** + +采用 distcp 命令实现两个 Hadoop 集群之间的递归数据复制 + +```shell +[djm@hadoop102 hadoop-2.7.2]$ hadoop distcp hdfs://haoop102:9000/user/djm/hello.txt hdfs://hadoop103:9000/user/djm/hello.txt +``` + + + +**小文件存档** + +![HDFS小文件存档](images/BigData/HDFS小文件存档.png) + + + +# 数据分析 + +## Apache Storm + +在Storm中,需要先设计一个实时计算结构,我们称之为拓扑(topology)。之后,这个拓扑结构会被提交给集群,其中主节点(master node)负责给工作节点(worker node)分配代码,工作节点负责执行代码。在一个拓扑结构中,包含spout和bolt两种角色。数据在spouts之间传递,这些spouts将数据流以tuple元组的形式发送;而bolt则负责转换数据流。 + +![Apache-Storm](images/BigData/Apache-Storm.png) + + + +## Apache Spark + +Spark Streaming,即核心Spark API的扩展,不像Storm那样一次处理一个数据流。相反,它在处理数据流之前,会按照时间间隔对数据流进行分段切分。Spark针对连续数据流的抽象,我们称为DStream(Discretized Stream)。 DStream是小批处理的RDD(弹性分布式数据集), RDD则是分布式数据集,可以通过任意函数和滑动数据窗口(窗口计算)进行转换,实现并行操作。 + +![Apache-Spark](images/BigData/Apache-Spark.png) + + + +## Apache Flink + +Apache Flink是一个**框架**和**分布式处理引擎**,用于在**无界**和**有界**数据流上进行**有状态的计算**。Flink被设计为在所有常见的集群环境中运行,以内存中的速度和任何规模执行计算。 + +![Apache-Flink](images/BigData/Apache-Flink.png) + +针对流数据+批数据的计算框架。把批数据看作流数据的一种特例,延迟性较低(毫秒级),且能保证消息传输不丢失不重复。 + +Flink创造性地统一了流处理和批处理,作为流处理看待时输入数据流是无界的,而批处理被作为一种特殊的流处理,只是它的输入数据流被定义为有界的。Flink程序由Stream和Transformation这两个基本构建块组成,其中Stream是一个中间结果数据,而Transformation是一个操作,它对一个或多个输入Stream进行计算处理,输出一个或多个结果Stream。 + + + +### 处理无界和有界数据 + +数据可以作为无界流或有界流被处理: + +- **Unbounded streams**(无界流)有一个起点,但没有定义的终点。它们不会终止,而且会源源不断的提供数据。无边界的流必须被连续地处理,即事件达到后必须被立即处理。等待所有输入数据到达是不可能的,因为输入是无界的,并且在任何时间点都不会完成。处理无边界的数据通常要求以特定顺序(例如,事件发生的顺序)接收事件,以便能够推断出结果的完整性。 +- **Bounded streams**(有界流)有一个定义的开始和结束。在执行任何计算之前,可以通过摄取(提取)所有数据来处理有界流。处理有界流不需要有序摄取,因为有界数据集总是可以排序的。有界流的处理也称为批处理。 + +![处理无界和有界数据](images/BigData/处理无界和有界数据.png) + +**Apache Flink擅长处理无界和有界数据集**。对时间和状态的精确控制使Flink的运行时能够在无边界的流上运行任何类型的应用程序。有界流由专门为固定大小的数据集设计的算法和数据结构在内部处理,从而产生出色的性能。 + + + +### 分层API + +Flink提供了三层API。每个API在简洁性和表达性之间提供了不同的权衡,并且针对不同的使用场景 + +![Flink-分层API](images/BigData/Flink-分层API.png) + + + +### 应用场景 + +Apache Flink是开发和运行许多不同类型应用程序的最佳选择,因为它具有丰富的特性。Flink的特性包括支持流和批处理、复杂的状态管理、事件处理语义以及确保状态的一致性。此外,Flink可以部署在各种资源提供程序上,例如YARN、Apache Mesos和Kubernetes,也可以作为裸机硬件上的独立集群进行部署。配置为高可用性,Flink没有单点故障。Flink已经被证明可以扩展到数千个内核和TB级的应用程序状态,提供高吞吐量和低延迟,并支持世界上一些最苛刻的流处理应用程序。 + +下面是Flink支持的最常见的应用程序类型: + +- Event-driven Applications(事件驱动的应用程序) +- Data Analytics Applications(数据分析应用程序) +- Data Pipeline Applications(数据管道应用程序) + + + +#### Event-driven Applications + +Event-driven Applications(事件驱动的应用程序)。事件驱动的应用程序是一个有状态的应用程序,它从一个或多个事件流中获取事件,并通过触发计算、状态更新或外部操作对传入的事件作出反应。 + +事件驱动的应用程序基于有状态的流处理应用程序。在这种设计中,数据和计算被放在一起,从而可以进行本地(内存或磁盘)数据访问。通过定期将检查点写入远程持久存储,可以实现容错。下图描述了传统应用程序体系结构和事件驱动应用程序之间的区别。 + +![Event-driven-Applications](images/BigData/Event-driven-Applications.png) + +代替查询远程数据库,事件驱动的应用程序在本地访问其数据,从而在吞吐量和延迟方面获得更好的性能。可以定期异步地将检查点同步到远程持久存,而且支持增量同步。不仅如此,在分层架构中,多个应用程序共享同一个数据库是很常见的。因此,数据库的任何更改都需要协调,由于每个事件驱动的应用程序都负责自己的数据,因此更改数据表示或扩展应用程序所需的协调较少。 + +对于事件驱动的应用程序,Flink的突出特性是savepoint。保存点是一个一致的状态镜像,可以用作兼容应用程序的起点。给定一个保存点,就可以更新或调整应用程序的规模,或者可以启动应用程序的多个版本进行A/B测试。 + +典型的事件驱动的应用程序有: + +- 欺诈检测 +- 异常检测 +- 基于规则的提醒 +- 业务流程监控 +- Web应用(社交网络) + + + +#### Data Analytics Applications + +Data Analytics Applications(数据分析应用程序)。传统上的分析是作为批处理查询或应用程序对已记录事件的有限数据集执行的。为了将最新数据合并到分析结果中,必须将其添加到分析数据集中,然后重新运行查询或应用程序,结果被写入存储系统或作为报告发出。 + +有了复杂的流处理引擎,分析也可以以实时方式执行。流查询或应用程序不是读取有限的数据集,而是接收实时事件流,并在使用事件时不断地生成和更新结果。结果要么写入外部数据库,要么作为内部状态进行维护。Dashboard应用程序可以从外部数据库读取最新的结果,也可以直接查询应用程序的内部状态。 + +Apache Flink支持流以及批处理分析应用程序,如下图所示: + +![Data-Analytics-Applications](images/BigData/Data-Analytics-Applications.png) + +典型的数据分析应用程序有: + +- 电信网络质量监控 +- 产品更新分析及移动应用实验评估 +- 消费者技术中实时数据的特别分析 +- 大规模图分析 + + + +#### Data Pipeline Applications + +Data Pipeline Applications(数据管道应用程序)。提取-转换-加载(ETL)是在存储系统之间转换和移动数据的常用方法。通常,会定期触发ETL作业,以便将数据从事务性数据库系统复制到分析数据库或数据仓库。 + +数据管道的作用类似于ETL作业。它们转换和丰富数据,并可以将数据从一个存储系统移动到另一个存储系统。但是,它们以连续流模式运行,而不是周期性地触发。因此,它们能够从不断产生数据的源读取记录,并以低延迟将其移动到目的地。例如,数据管道可以监视文件系统目录中的新文件,并将它们的数据写入事件日志。另一个应用程序可能将事件流物化到数据库,或者增量地构建和完善搜索索引。 + +下图描述了周期性ETL作业和连续数据管道之间的差异: + +![Data-Pipeline-Applications](images/BigData/Data-Pipeline-Applications.png) + +与周期性ETL作业相比,连续数据管道的明显优势是减少了将数据移至其目的地的等待时间。此外,数据管道更通用,可用于更多场景,因为它们能够连续消费和产生数据。 + +典型的数据管道应用程序有: + +- 电商中实时搜索索引的建立 +- 电商中的持续ETL + + + +### 安装Flink + +https://flink.apache.org/downloads.html + +下载安装包,这里下载的是 flink-1.10.1-bin-scala_2.11.tgz + +安装参考 https://ci.apache.org/projects/flink/flink-docs-release-1.10/getting-started/tutorials/local_setup.html + +```shell +./bin/start-cluster.sh # Start Flink +``` + +![安装Flink1](images/BigData/安装Flink1.png) + +访问 http://localhost:8081 + +![安装Flink2](images/BigData/安装Flink2.png) + +运行 WordCount 示例 + +![安装Flink3](images/BigData/安装Flink3.png) + +![安装Flink4](images/BigData/安装Flink4.png) + +![安装Flink5](images/BigData/安装Flink5.png) + + + +### 商品实时推荐 + +基于Flink实现的商品实时推荐系统。flink统计商品热度,放入redis缓存,分析日志信息,将画像标签和实时记录放入Hbase。在用户发起推荐请求后,根据用户画像重排序热度榜,并结合协同过滤和标签两个推荐模块为新生成的榜单的每一个产品添加关联产品,最后返回新的用户列表。 + +#### 系统架构 + +![基于Flink商品实时推荐](images/BigData/基于Flink商品实时推荐.jpg) + +在日志数据模块(flink-2-hbase)中,又主要分为6个Flink任务: + +- **用户-产品浏览历史 -> 实现基于协同过滤的推荐逻辑** + + 通过Flink去记录用户浏览过这个类目下的哪些产品,为后面的基于Item的协同过滤做准备 实时的记录用户的评分到Hbase中,为后续离线处理做准备。数据存储在Hbase的p_history表 + +- **用户-兴趣 -> 实现基于上下文的推荐逻辑** + + 根据用户对同一个产品的操作计算兴趣度,计算规则通过操作间隔时间(如购物 - 浏览 < 100s)则判定为一次兴趣事件 通过Flink的ValueState实现,如果用户的操作Action=3(收藏),则清除这个产品的state,如果超过100s没有出现Action=3的事件,也会清除这个state。数据存储在Hbase的u_interest表 + +- **用户画像计算 -> 实现基于标签的推荐逻辑** + + v1.0按照三个维度去计算用户画像,分别是用户的颜色兴趣,用户的产地兴趣,和用户的风格兴趣.根据日志不断的修改用户画像的数据,记录在Hbase中。数据存储在Hbase的user表 + +- **产品画像记录 -> 实现基于标签的推荐逻辑** + + 用两个维度记录产品画像,一个是喜爱该产品的年龄段,另一个是性别。数据存储在Hbase的prod表 + +- **事实热度榜 -> 实现基于热度的推荐逻辑** + + 通过Flink时间窗口机制,统计当前时间的实时热度,并将数据缓存在Redis中。通过Flink的窗口机制计算实时热度,使用ListState保存一次热度榜。数据存储在redis中,按照时间戳存储list + +- **日志导入** + + 从Kafka接收的数据直接导入进Hbase事实表,保存完整的日志log,日志中包含了用户Id,用户操作的产品id,操作时间,行为(如购买,点击,推荐等)。数据按时间窗口统计数据大屏需要的数据,返回前段展示。数据存储在Hbase的con表 + + + +#### 推荐引擎逻辑 + +**基于热度的推荐逻辑** + +![基于热度的推荐逻辑](images/BigData/基于热度的推荐逻辑.jpg) + +​根据用户特征,重新排序热度榜,之后根据两种推荐算法计算得到的产品相关度评分,为每个热度榜中的产品推荐几个关联的产品。 + + + +**基于产品画像的产品相似度计算方法** + +基于产品画像的推荐逻辑依赖于产品画像和热度榜两个维度,产品画像有三个特征,包含color/country/style三个角度,通过计算用户对该类目产品的评分来过滤热度榜上的产品。 + +![基于产品画像的产品相似度计算方法](images/BigData/基于产品画像的产品相似度计算方法.jpg) + +在已经有产品画像的基础上,计算item与item之间的关联系,通过余弦相似度来计算两两之间的评分,最后在已有物品选中的情况下推荐关联性更高的产品。 + +| 相似度 | A | B | C | +| ------ | ---- | ---- | ---- | +| A | 1 | 0.7 | 0.2 | +| B | 0.7 | 1 | 0.6 | +| C | 0.2 | 0.6 | 1 | + + + +**基于协同过滤的产品相似度计算方法** + +根据产品用户表(Hbase) 去计算公式得到相似度评分: +![基于协同过滤的产品相似度计算方法.jpg](images/BigData/基于协同过滤的产品相似度计算方法.jpg) + + + +**前台推荐页面** + +当前推荐结果分为3列,分别是热度榜推荐,协同过滤推荐和产品画像推荐: +![前台推荐页面.jpg](images/BigData/前台推荐页面.jpg) + + + +### 实时计算TopN热榜 + +本案例将实现一个“实时热门商品”的需求,我们可以将“实时热门商品”翻译成程序员更好理解的需求:每隔5分钟输出最近一小时内点击量最多的前 N 个商品。将这个需求进行分解我们大概要做这么几件事情: + +- 抽取出业务时间戳,告诉 Flink 框架基于业务时间做窗口 +- 过滤出点击行为数据 +- 按一小时的窗口大小,每5分钟统计一次,做滑动窗口聚合(Sliding Window) +- 按每个窗口聚合,输出每个窗口中点击量前N名的商品 + + + +#### 数据准备 + +这里我们准备了一份淘宝用户行为数据集(来自[阿里云天池公开数据集](https://tianchi.aliyun.com/datalab/index.htm))。本数据集包含了淘宝上某一天随机一百万用户的所有行为(包括点击、购买、加购、收藏)。数据集的组织形式和MovieLens-20M类似,即数据集的每一行表示一条用户行为,由用户ID、商品ID、商品类目ID、行为类型和时间戳组成,并以逗号分隔。关于数据集中每一列的详细描述如下: + +| 列名称 | 说明 | +| :--------- | :------------------------------------------------- | +| 用户ID | 整数类型,加密后的用户ID | +| 商品ID | 整数类型,加密后的商品ID | +| 商品类目ID | 整数类型,加密后的商品所属类目ID | +| 行为类型 | 字符串,枚举类型,包括(‘pv’, ‘buy’, ‘cart’, ‘fav’) | +| 时间戳 | 行为发生的时间戳,单位秒 | + +你可以通过下面的命令下载数据集到项目的 `resources` 目录下: + +```shell +$ cd my-flink-project/src/main/resources +$ curl https://raw.githubusercontent.com/wuchong/my-flink-project/master/src/main/resources/UserBehavior.csv > UserBehavior.csv +``` + +这里是否使用 curl 命令下载数据并不重要,你也可以使用 wget 命令或者直接访问链接下载数据。关键是,**将数据文件保存到项目的 `resources` 目录下**,方便应用程序访问。 + + + +#### 编写程序 + + + +#### 创建模拟数据源 + +我们先创建一个 `UserBehavior` 的 POJO 类(所有成员变量声明成`public`便是POJO类),强类型化后能方便后续的处理。 + +```java +/** + * 用户行为数据结构 + **/ +public static class UserBehavior { + public long userId; // 用户ID + public long itemId; // 商品ID + public int categoryId; // 商品类目ID + public String behavior; // 用户行为, 包括("pv", "buy", "cart", "fav") + public long timestamp; // 行为发生的时间戳,单位秒 +} +``` + +接下来我们就可以创建一个 `PojoCsvInputFormat` 了, 这是一个读取 csv 文件并将每一行转成指定 POJO +类型(在我们案例中是 `UserBehavior`)的输入器。 + +```java +// UserBehavior.csv 的本地文件路径 +URL fileUrl = HotItems2.class.getClassLoader().getResource("UserBehavior.csv"); +Path filePath = Path.fromLocalFile(new File(fileUrl.toURI())); +// 抽取 UserBehavior 的 TypeInformation,是一个 PojoTypeInfo +PojoTypeInfo pojoType = (PojoTypeInfo) TypeExtractor.createTypeInfo(UserBehavior.class); +// 由于 Java 反射抽取出的字段顺序是不确定的,需要显式指定下文件中字段的顺序 +String[] fieldOrder = new String[]{"userId", "itemId", "categoryId", "behavior", "timestamp"}; +// 创建 PojoCsvInputFormat +PojoCsvInputFormat csvInput = new PojoCsvInputFormat<>(filePath, pojoType, fieldOrder); +``` + +下一步我们用 `PojoCsvInputFormat` 创建输入源。 + +```java +DataStream dataSource = env.createInput(csvInput, pojoType); +``` + +这就创建了一个 `UserBehavior` 类型的 `DataStream`。 + + + +#### EventTime与Watermark + +当我们说“统计过去一小时内点击量”,这里的“一小时”是指什么呢? 在 Flink 中它可以是指 ProcessingTime ,也可以是 EventTime,由用户决定。 + +- **ProcessingTime**:**事件被处理的时间**。也就是由机器的系统时间来决定 +- **EventTime**:**事件发生的时间**。一般就是数据本身携带的时间 + +在本案例中,我们需要统计业务时间上的每小时的点击量,所以要基于 EventTime 来处理。那么如果让 Flink 按照我们想要的业务时间来处理呢?这里主要有两件事情要做: + +- 告诉 Flink 我们现在按照 EventTime 模式进行处理,Flink 默认使用 ProcessingTime 处理,所以我们要显式设置下。 + + ```java + env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); + ``` + +- 指定如何获得业务时间,以及生成 Watermark。Watermark 是用来追踪业务事件的概念,可以理解成 EventTime 世界中的时钟,用来指示当前处理到什么时刻的数据了。由于我们的数据源的数据已经经过整理,没有乱序,即事件的时间戳是单调递增的,所以可以将每条数据的业务时间就当做 Watermark。这里我们用 `AscendingTimestampExtractor` 来实现时间戳的抽取和 Watermark 的生成。 + + + +**注意**:真实业务场景一般都是存在乱序的,所以一般使用 `BoundedOutOfOrdernessTimestampExtractor`。 + +```java +DataStream timedData = dataSource + .assignTimestampsAndWatermarks(new AscendingTimestampExtractor() { + @Override + public long extractAscendingTimestamp(UserBehavior userBehavior) { + // 原始数据单位秒,将其转成毫秒 + return userBehavior.timestamp * 1000; + } + }); +``` + +这样我们就得到了一个带有时间标记的数据流了,后面就能做一些窗口的操作。 + + + +#### 过滤出点击事件 + +在开始窗口操作之前,先回顾下需求“每隔5分钟输出过去一小时内点击量最多的前 N 个商品”。由于原始数据中存在点击、加购、购买、收藏各种行为的数据,但是我们只需要统计点击量,所以先使用 `FilterFunction` 将点击行为数据过滤出来。 + +```java +DataStream pvData = timedData + .filter(new FilterFunction() { + @Override + public boolean filter(UserBehavior userBehavior) throws Exception { + // 过滤出只有点击的数据 + return userBehavior.behavior.equals("pv"); + } + }); +``` + + + +#### 窗口统计点击量 + +由于要每隔5分钟统计一次最近一小时每个商品的点击量,所以窗口大小是一小时,每隔5分钟滑动一次。即分别要统计 [09:00, 10:00), [09:05, 10:05), [09:10, 10:10)… 等窗口的商品点击量。是一个常见的滑动窗口需求(Sliding Window)。 + +```java +DataStream windowedData = pvData + // 对商品进行分组 + .keyBy("itemId") + // 对每个商品做滑动窗口(1小时窗口,5分钟滑动一次) + .timeWindow(Time.minutes(60), Time.minutes(5)) + // 做增量的聚合操作,它能使用AggregateFunction提前聚合掉数据,减少 state 的存储压力 + .aggregate(new CountAgg(), new WindowResultFunction()); +``` + + + +**CountAgg** + +这里的`CountAgg`实现了`AggregateFunction`接口,功能是统计窗口中的条数,即遇到一条数据就加一。 + +```java +/** + * COUNT 统计的聚合函数实现,每出现一条记录加一 + **/ +public static class CountAgg implements AggregateFunction { + @Override + public Long createAccumulator() { + return 0L; + } + + @Override + public Long add(UserBehavior userBehavior, Long acc) { + return acc + 1; + } + + @Override + public Long getResult(Long acc) { + return acc; + } + + @Override + public Long merge(Long acc1, Long acc2) { + return acc1 + acc2; + } +} +``` + + + +**WindowFunction** + +`.aggregate(AggregateFunction af, WindowFunction wf)` 的第二个参数`WindowFunction`将每个 key每个窗口聚合后的结果带上其他信息进行输出。这里实现的`WindowResultFunction`将主键商品ID,窗口,点击量封装成了`ItemViewCount`进行输出。 + +```java +/** + * 用于输出窗口的结果 + **/ +public static class WindowResultFunction implements WindowFunction { + @Override + public void apply( + Tuple key, // 窗口的主键,即 itemId + TimeWindow window, // 窗口 + Iterable aggregateResult, // 聚合函数的结果,即 count 值 + Collector collector // 输出类型为 ItemViewCount + ) throws Exception { + Long itemId = ((Tuple1) key).f0; + Long count = aggregateResult.iterator().next(); + collector.collect(ItemViewCount.of(itemId, window.getEnd(), count)); + } +} + +/** + * 商品点击量(窗口操作的输出类型) + **/ +public static class ItemViewCount { + public long itemId; // 商品ID + public long windowEnd; // 窗口结束时间戳 + public long viewCount; // 商品的点击量 + public static ItemViewCount of(long itemId, long windowEnd, long viewCount) { + ItemViewCount result = new ItemViewCount(); + result.itemId = itemId; + result.windowEnd = windowEnd; + result.viewCount = viewCount; + return result; + } +} +``` + +现在我们得到了每个商品在每个窗口的点击量的数据流。 + + + +#### TopN计算最热门商品 + +为了统计每个窗口下最热门的商品,我们需要再次按窗口进行分组,这里根据`ItemViewCount`中的`windowEnd`进行`keyBy()`操作。然后使用 `ProcessFunction` 实现一个自定义的 TopN 函数 `TopNHotItems` 来计算点击量排名前3名的商品,并将排名结果格式化成字符串,便于后续输出。 + +```java +DataStream topItems = windowedData + .keyBy("windowEnd") + .process(new TopNHotItems(3)); // 求点击量前3名的商品 +``` + +`ProcessFunction` 是 Flink 提供的一个 low-level API,用于实现更高级的功能。它主要提供了定时器 timer 的功能(支持EventTime或ProcessingTime)。本案例中我们将利用 timer 来判断何时**收齐**了某个 window 下所有商品的点击量数据。由于 Watermark 的进度是全局的, + +在 `processElement` 方法中,每当收到一条数据(`ItemViewCount`),我们就注册一个 `windowEnd+1` 的定时器(Flink 框架会自动忽略同一时间的重复注册)。`windowEnd+1` 的定时器被触发时,意味着收到了`windowEnd+1`的 Watermark,即收齐了该`windowEnd`下的所有商品窗口统计值。我们在 `onTimer()` 中处理将收集的所有商品及点击量进行排序,选出 TopN,并将排名信息格式化成字符串后进行输出。 + +这里我们还使用了 `ListState` 来存储收到的每条 `ItemViewCount` 消息,保证在发生故障时,状态数据的不丢失和一致性。`ListState` 是 Flink 提供的类似 Java `List` 接口的 State API,它集成了框架的 checkpoint 机制,自动做到了 exactly-once 的语义保证。 + +```java +/** + * 求某个窗口中前 N 名的热门点击商品,key 为窗口时间戳,输出为 TopN 的结果字符串 + **/ +public static class TopNHotItems extends KeyedProcessFunction { + private final int topSize; + public TopNHotItems(int topSize) { + this.topSize = topSize; + } + + // 用于存储商品与点击数的状态,待收齐同一个窗口的数据后,再触发 TopN 计算 + private ListState itemState; + + @Override + public void open(Configuration parameters) throws Exception { + super.open(parameters); + // 状态的注册 + ListStateDescriptor itemsStateDesc = new ListStateDescriptor<>("itemState-state", ItemViewCount.class); + itemState = getRuntimeContext().getListState(itemsStateDesc); + } + + @Override + public void processElement(ItemViewCount input, Context context, Collector collector) throws Exception { + // 每条数据都保存到状态中 + itemState.add(input); + // 注册 windowEnd+1 的 EventTime Timer, 当触发时,说明收齐了属于windowEnd窗口的所有商品数据 + context.timerService().registerEventTimeTimer(input.windowEnd + 1); + } + + @Override + public void onTimer(long timestamp, OnTimerContext ctx, Collector out) throws Exception { + // 获取收到的所有商品点击量 + List allItems = new ArrayList<>(); + for (ItemViewCount item : itemState.get()) { + allItems.add(item); + } + // 提前清除状态中的数据,释放空间 + itemState.clear(); + // 按照点击量从大到小排序 + allItems.sort(new Comparator() { + @Override + public int compare(ItemViewCount o1, ItemViewCount o2) { + return (int) (o2.viewCount - o1.viewCount); + } + }); + + // 将排名信息格式化成 String, 便于打印 + StringBuilder result = new StringBuilder(); + result.append("====================================\n"); + result.append("时间: ").append(new Timestamp(timestamp-1)).append("\n"); + for (int i=0;iDatabase + +Author:李茹钰(`echo`) + +Introduction:收纳技术相关的数据库知识 `事务`、`索引`、`锁`、`SQL优化` 等总结! + +[TOC] + +# 数据库范式 + +- **1NF**:所有字段仅包含单值(即单个字段不可在分割使用) +- **2NF**:非键字段必须全完依赖于主键(不能是主键的部分) +- **3NF**:非键字段不能依赖于非键字段(禁止传递依赖) + +- **BCNF**:并且主属性不依赖于主属性 + + +- **第四范式(4NF)**:要求把同一表内的多对多关系删除 + + +- **第五范式(5NF)**:从最终结构重新建立原始结构 + +> 四种范式之间的关系: +> +> 1NF→2NF:消去非主属性对键的部分函数依赖 +> +> 2NF→3NF:消去非主属性对键的传递函数依赖 +> +> 3NF→BCNF:消去主属性对键的传递函数依赖 + +## 第一范式(1NF) + +**列都是不可再分**。即实体中的某个属性有多个值时,必须拆分为不同的属性。例如: + +**用户信息表** + +| 编号 | 姓名 | 年龄 | 地址 | +| ---- | ---- | ---- | ---------------------------- | +| 1 | 小王 | 23 | 浙江省杭州市拱墅区湖州街51号 | + +当实际需求对地址没有特定的要求下,这个用户信息表的每一列都是不可分割的。但是当实际需求对省份或者城市有特别要求时,这个用户信息表中的地址就是可以分割的,改为: + +**用户信息表** + +| 编号 | 姓名 | 年龄 | 省份 | 城市 | 详细地址 | +| ---- | ---- | ---- | ------ | ------ | ---------------- | +| 1 | 小王 | 23 | 浙江省 | 杭州市 | 拱墅区湖州街51号 | + +**好处** + +- 表结构相对清晰 +- 易于查询 + + + +## 第二范式(2NF) + +**每个表只描述一件事情**。满足第一范式( 1NF)的情况下,每行必须有主键,且主键与非主键之间是完全函数依赖关系(消除部分子函数依赖)。即数据库表中的每一列都和主键的所有属性相关,不能只和主键的部分属性相关。完全函数依赖:有属性集 X,Y,通过 X 中的所有属性能够推出 Y 中的任意属性,但是 X 的任何真子集,都不能推出 Y 中的任何属性。例如: + +**学生课程表** + +| 学生编号 | 课程编号 | 学生名称 | 课程名称 | 所在班级 | 班主任 | +| -------- | -------- | -------- | ---------- | --------- | ------ | +| S1 | C1 | 小王 | 计算机导论 | 计算机3班 | 陈老师 | +| S1 | C2 | 小王 | 数据结构 | 计算机3班 | 陈老师 | +| S2 | C1 | 小马 | 计算机导论 | 软件1班 | 李老师 | + +将学生编号和课程编号作为主键,能确定唯一一条数据,但是学生名称只跟学生编号有关,跟课程编号无关,即不满足完全函数依赖,改为: + +**学生表** + +| 学生编号 | 学生名称 | 所在班级 | 班主任 | +| -------- | -------- | --------- | ------ | +| S1 | 小王 | 计算机3班 | 陈老师 | +| S2 | 小马 | 软件1班 | 李老师 | + +**课程表** + +| 课程编号 | 课程名称 | +| -------- | ---------- | +| C1 | 计算机导论 | +| C2 | 数据结构 | + +**学生课程关系表** + +| 学生编号 | 课程编号 | +| -------- | -------- | +| S1 | C1 | +| S1 | C2 | +| S2 | C1 | + +**好处** + +- 相对节约空间,当学生表和课程表属性越多,效果越明显 +- 解决插入异常,当新增一门课程时,原表因为没有学生选课,导致无法插入数据 +- 解决更新繁琐,当更改一门课程名称时,原表要更改多条数据 +- 解决删除异常,当学生学完一门课,原表若要清空学生上课信息,课程编号与课程名称的关系可能会丢失 + + + +## 第三范式(3NF) + +**不存在对非主键列的传递依赖**。满足第二范式( 2NF)的情况下,任何非主属性不依赖于其它非主属性(消除传递函数依赖)。例如: + +**学生表** + +| 学生编号 | 学生名称 | 班级编号 | 班级名字 | +| -------- | -------- | -------- | --------- | +| S1 | 小王 | 001 | 计算机1班 | +| S2 | 小马 | 003 | 计算机3班 | + +学生编号作为主键满足第二范式(2NF)。通过学生编号 ⇒⇒ 班级编号 ⇒⇒ 班级名字,所以班级编号和班级名字之间存在依赖关系,改为: + +**学生表** + +| 学生编号 | 学生名称 | 班级编号 | +| -------- | -------- | -------- | +| S1 | 小王 | 001 | +| S2 | 小马 | 003 | + +**班级表** + +| 班级编号 | 班级名称 | +| -------- | --------- | +| 001 | 计算机1班 | +| 003 | 计算机3班 | + +**好处** + +- 相对节约空间 +- 解决更新繁琐 +- 解决插入异常,当班级分配了老师,还没分配学生的时候,原表将不可插入数据 +- 解决删除异常,当学生毕业后,若要清空学生信息,班级和老师的关系可能会丢失 + + + +## 巴斯-科德范式(BCNF) + +在3NF基础上,**消除主属性之间的传递函数依赖**,即存在多个主属性时,**主属性不依赖于主属性**。例如: + +**配件表** + +| 仓库号 | 配件号 | 职工号 | 配件数量 | +| ------ | ------ | ------ | -------- | +| W1 | P1 | E1 | 10 | +| W1 | P2 | E1 | 10 | +| W2 | P1 | E2 | 20 | + +有以下约束: + +- 一个仓库有多个职工 +- 一个职工只在一个仓库 +- 一种配件可以放多个仓库 +- 一个仓库,一个职工管理多个配件,一种配件由唯一一个职工管理 + +由此,将(仓库号,配件号)作为主键,满足 3NF,但是(仓库号,配件号)⇒⇒ 职工号 ⇒⇒ 仓库号,造成传递函数依赖,改为: + +**仓库表** + +| 仓库号 | 职工号 | +| ------ | ------ | +| W1 | E1 | +| W2 | E2 | + +**工作表** + +| 职工号 | 配件号 | 配件数量 | +| ------ | ------ | -------- | +| E1 | P1 | 10 | +| E1 | P2 | 10 | +| E2 | P1 | 20 | + +**好处** + +- 解决一些冗余和一些异常情况 + +**不足** + +- 丢失一些函数依赖,如丢失(仓库号,配件号)⇒⇒ 职工号,无法通过单表来确定一个职工号 + + + +## 第四范式(4NF) + +在满足第三范式(3NF)的前提下,表中不能包含一个实体的两个或多个互相独立的多值因子。 + +当一个表中非主属性相互独立时(即在 3NF 基础上),这些非主属性不应该有`多值`。即表中不能包含一个实体的两个或多个互相独立的多值因子。例如: + +**客户联系方式** + +| 客户编号 | 固定电话 | 移动电话 | +| -------- | -------- | -------- | +| 10 | 88-123 | 151 | +| 10 | 80-123 | 183 | + +一个用户拥有多个固定电话和移动电话,给表的维护带来很多麻烦。比如增加一个固定电话,那么移动电话这一栏就较难维护,改为: + +**客户电话表** + +| 客户编号 | 电话号码 | 电话类型 | +| -------- | -------- | -------- | +| 10 | 88-123 | 固定电话 | +| 10 | 80-123 | 固定电话 | +| 10 | 151 | 移动电话 | +| 10 | 183 | 移动电话 | + +**好处** + +- 解决一些异常,使表结构更加合理 + + + +## 第五范式(5NF) + +在关系模式 R 中,每一个`连接依赖`均有 R 的`候选码`所隐含。即连接时,所连接的属性都是候选码,例如: + +**销售表** + +| 销售人员 | 供应商 | 产品 | +| -------- | ------ | ---- | +| S1 | V1 | P1 | +| S2 | V2 | P2 | +| S1 | V1 | P1 | +| S2 | V2 | P2 | + +要想找到某一条数据,必须以(销售人员,供应商,产品)为主键,改为: + +**销售人员_供应商表** + +| 销售人员 | 供应商 | +| -------- | ------ | +| S1 | V1 | +| S2 | V2 | + +**销售人员_产品表** + +| 销售人员 | 产品 | +| -------- | ---- | +| S1 | P1 | +| S2 | P2 | + +**供应商_产品表** + +| 供应商 | 产品 | +| ------ | ---- | +| V1 | P1 | +| V2 | P2 | + +**好处** + +- 解决某些异常操作 + + + +# 连接方式 + +## 内连接(INNER JOIN) + +INNER JOIN 一般被译作内连接。内连接查询能将左表(表 A)和右表(表 B)中能关联起来的数据连接后返回。 + +**文氏图** + +![内连接(INNER-JOIN)](images/Database/内连接(INNER-JOIN).png) + +**示例查询** + +```mysql +SELECT A.PK AS A_PK, B.PK AS B_PK,A.Value AS A_Value, B.Value AS B_Value +FROM Table_A A +INNER JOIN Table_B B +ON A.PK = B.PK; +``` + +**查询结果** + +```mysql ++------+------+---------+---------+ +| A_PK | B_PK | A_Value | B_Value | ++------+------+---------+---------+ +| 1 | 1 | both ab | both ab | ++------+------+---------+---------+ +1 row in set (0.00 sec) +``` + +**注意**:其中A为Table_A的别名,B为Table_B的别名,下同。 + + + +## 左连接(LEFT JOIN) + +LEFT JOIN 一般被译作左连接,也写作 LEFT OUTER JOIN。左连接查询会返回左表(表 A)中所有记录,不管右表(表 B)中有没有关联的数据。在右表中找到的关联数据列也会被一起返回。 + +**文氏图** + +![左连接(LEFT-JOIN)](images/Database/左连接(LEFT-JOIN).png) + +**示例查询** + +```mysql +SELECT A.PK AS A_PK, B.PK AS B_PK,A.Value AS A_Value, B.Value AS B_Value +FROM Table_A A +LEFT JOIN Table_B B +ON A.PK = B.PK; +``` + +**查询结果** + +```mysql ++------+------+---------+---------+ +| A_PK | B_PK | A_Value | B_Value | ++------+------+---------+---------+ +| 1 | 1 | both ab | both ba | +| 2 | NULL | only a | NULL | ++------+------+---------+---------+ +2 rows in set (0.00 sec) +``` + + + +## 右连接(RIGHT JOIN) + +RIGHT JOIN 一般被译作右连接,也写作 RIGHT OUTER JOIN。右连接查询会返回右表(表 B)中所有记录,不管左表(表 A)中有没有关联的数据。在左表中找到的关联数据列也会被一起返回。 + +**文氏图** + +![右连接(RIGHT-JOIN)](images/Database/右连接(RIGHT-JOIN).png) + +**示例查询** + +```mysql +SELECT A.PK AS A_PK, B.PK AS B_PK,A.Value AS A_Value, B.Value AS B_Value +FROM Table_A A +RIGHT JOIN Table_B B +ON A.PK = B.PK; +``` + +**查询结果** + +```mysql ++------+------+---------+---------+ +| A_PK | B_PK | A_Value | B_Value | ++------+------+---------+---------+ +| 1 | 1 | both ab | both ba | +| NULL | 3 | NULL | only b | ++------+------+---------+---------+ +2 rows in set (0.00 sec) +``` + + + +## 全连接(FULL OUTER JOIN) + +FULL OUTER JOIN 一般被译作外连接、全连接,实际查询语句中可以写作 FULL OUTER JOIN 或 FULL JOIN。外连接查询能返回左右表里的所有记录,其中左右表里能关联起来的记录被连接后返回。(**MySQL不支持FULL OUTER JOIN**) + +**文氏图** + +![全连接(FULL-OUTER-JOIN)](images/Database/全连接(FULL-OUTER-JOIN).png) + +**示例查询** + +```mysql +SELECT A.PK AS A_PK, B.PK AS B_PK,A.Value AS A_Value, B.Value AS B_Value +FROM Table_A A +FULL OUTER JOIN Table_B B +ON A.PK = B.PK; +``` + +**查询结果** + +```mysql +ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'FULL OUTER JOIN Table_B B +ON A.PK = B.PK' at line 4 +``` + +注意:我当前示例使用的 MySQL 不支持FULL OUTER JOIN。应当返回的结果(使用UNION模拟): + +```mysql +mysql> SELECT * + -> FROM Table_A + -> LEFT JOIN Table_B + -> ON Table_A.PK = Table_B.PK + -> UNION ALL + -> SELECT * + -> FROM Table_A + -> RIGHT JOIN Table_B + -> ON Table_A.PK = Table_B.PK + -> WHERE Table_A.PK IS NULL; ++------+---------+------+---------+ +| PK | Value | PK | Value | ++------+---------+------+---------+ +| 1 | both ab | 1 | both ba | +| 2 | only a | NULL | NULL | +| NULL | NULL | 3 | only b | ++------+---------+------+---------+ +3 rows in set (0.00 sec) +``` + +**SQL常见缩影** + +![SQL常用JOIN](images/Database/SQL常用JOIN.png) + + + +## LEFT JOIN EXCLUDING INNER JOIN + +返回左表有但右表没有关联数据的记录集。 + +**文氏图** + +![LEFT-JOIN-EXCLUDING-INNER-JOIN](images/Database/LEFT-JOIN-EXCLUDING-INNER-JOIN.png) + +**示例查询** + +```mysql +SELECT A.PK AS A_PK, B.PK AS B_PK,A.Value AS A_Value, B.Value AS B_Value +FROM Table_A A +LEFT JOIN Table_B B +ON A.PK = B.PK +WHERE B.PK IS NULL; +``` + +**查询结果** + +```mysql ++------+------+---------+---------+ +| A_PK | B_PK | A_Value | B_Value | ++------+------+---------+---------+ +| 2 | NULL | only a | NULL | ++------+------+---------+---------+ +1 row in set (0.01 sec) +``` + + + +## RIGHT JOIN EXCLUDING INNER JOIN + +返回右表有但左表没有关联数据的记录集。 + +**文氏图** + +![RIGHT-JOIN-EXCLUDING-INNER-JOIN](images/Database/RIGHT-JOIN-EXCLUDING-INNER-JOIN.png) + +**示例查询** + +```mysql +SELECT A.PK AS A_PK, B.PK AS B_PK,A.Value AS A_Value, B.Value AS B_Value +FROM Table_A A +RIGHT JOIN Table_B B +ON A.PK = B.PK +WHERE A.PK IS NULL; +``` + +**查询结果** + +```mysql ++------+------+---------+---------+ +| A_PK | B_PK | A_Value | B_Value | ++------+------+---------+---------+ +| NULL | 3 | NULL | only b | ++------+------+---------+---------+ +1 row in set (0.00 sec) +``` + + + +## FULL OUTER JOIN EXCLUDING INNER JOIN + +返回左表和右表里没有相互关联的记录集。 + +**文氏图** + +![FULL-OUTER-JOIN-EXCLUDING-INNER-JOIN](images/Database/FULL-OUTER-JOIN-EXCLUDING-INNER-JOIN.png) + +**示例查询** + +```mysql +SELECT A.PK AS A_PK, B.PK AS B_PK,A.Value AS A_Value, B.Value AS B_Value +FROM Table_A A +FULL OUTER JOIN Table_B B +ON A.PK = B.PK +WHERE A.PK IS NULL +OR B.PK IS NULL; +``` + +因为使用到了 FULL OUTER JOIN,MySQL 在执行该查询时再次报错。 + +```mysql +ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'FULL OUTER JOIN Table_B B +ON A.PK = B.PK +WHERE A.PK IS NULL +OR B.PK IS NULL' at line 4 +``` + +应当返回的结果(用 UNION 模拟): + +```mysql +mysql> SELECT * + -> FROM Table_A + -> LEFT JOIN Table_B + -> ON Table_A.PK = Table_B.PK + -> WHERE Table_B.PK IS NULL + -> UNION ALL + -> SELECT * + -> FROM Table_A + -> RIGHT JOIN Table_B + -> ON Table_A.PK = Table_B.PK + -> WHERE Table_A.PK IS NULL; ++------+--------+------+--------+ +| PK | Value | PK | Value | ++------+--------+------+--------+ +| 2 | only a | NULL | NULL | +| NULL | NULL | 3 | only b | ++------+--------+------+--------+ +2 rows in set (0.00 sec) +``` + +**SQL所有JOIN** + +![SQL所有JOIN](images/Database/SQL所有JOIN.png) + + + +## CROSS JOIN + +返回左表与右表之间符合条件的记录的迪卡尔集。 + +**图示** + +![CROSS-JOIN](images/Database/CROSS-JOIN.png) + +**示例查询** + +```mysql +SELECT A.PK AS A_PK, B.PK AS B_PK,A.Value AS A_Value, B.Value AS B_Value +FROM Table_A A +CROSS JOIN Table_B B; +``` + +**查询结果** + +```mysql ++------+------+---------+---------+ +| A_PK | B_PK | A_Value | B_Value | ++------+------+---------+---------+ +| 1 | 1 | both ab | both ba | +| 2 | 1 | only a | both ba | +| 1 | 3 | both ab | only b | +| 2 | 3 | only a | only b | ++------+------+---------+---------+ +4 rows in set (0.00 sec) +``` + +上面讲过的几种 JOIN 查询的结果都可以用 CROSS JOIN 加条件模拟出来,比如 INNER JOIN 对应 CROSS JOIN ... WHERE A.PK = B.PK。 + + + +## SELF JOIN + +返回表与自己连接后符合条件的记录,一般用在表里有一个字段是用主键作为外键的情况。比如 Table_C 的结构与数据如下: + +```mysql ++--------+----------+-------------+ +| EMP_ID | EMP_NAME | EMP_SUPV_ID | ++--------+----------+-------------+ +| 1001 | Ma | NULL | +| 1002 | Zhuang | 1001 | ++--------+----------+-------------+ +2 rows in set (0.00 sec) +``` + +EMP_ID 字段表示员工 ID,EMP_NAME 字段表示员工姓名,EMP_SUPV_ID 表示主管 ID。 + + + +**示例查询** + +现在我们想查询所有有主管的员工及其对应的主管 ID 和姓名,就可以用 SELF JOIN 来实现。 + +```mysql +SELECT A.EMP_ID AS EMP_ID, A.EMP_NAME AS EMP_NAME, + B.EMP_ID AS EMP_SUPV_ID, B.EMP_NAME AS EMP_SUPV_NAME +FROM Table_C A, Table_C B +WHERE A.EMP_SUPV_ID = B.EMP_ID; +``` + +**查询结果** + +```mysql ++--------+----------+-------------+---------------+ +| EMP_ID | EMP_NAME | EMP_SUPV_ID | EMP_SUPV_NAME | ++--------+----------+-------------+---------------+ +| 1002 | Zhuang | 1001 | Ma | ++--------+----------+-------------+---------------+ +1 row in set (0.00 sec) +``` + +**补充说明** + +- 文中的图使用 Keynote 绘制 +- 个人的体会是 SQL 里的 JOIN 查询与数学里的求交集、并集等很像;SQLite 不支持 RIGHT JOIN 和 FULL OUTER JOIN,可以使用 LEFT JOIN 和 UNION 来达到相同的效果 +- MySQL 不支持 FULL OUTER JOIN,可以使用 LEFT JOIN 和 UNION 来达到相同的效果 + + + +# 事务 + +**什么叫事务?** + +事务是一系列对系统中数据进行访问与更新的操作组成的一个程序逻辑单元。即不可分割的许多基础数据库操作。 + +## 事务特性(ACID) + +**原子性(Atomicity)** + +事务包含的操作要么全部成功,要么全部失败回滚。 + + + +**一致性(Consistency)** + +一个事务执行前后,应该使数据库从一个一致性状态转换为另一个一致性状态。比方说假设A、B两个人,共有5000元。那么无论A给B转多少钱,转多少次,总数仍然是5000没有改变。 + + + +**隔离性(Isolation)** + +多个用户并发访问数据库时,比如操作同一张表,数据库为每一个用户开启的事务,不能被其他事务的操作干扰。多个并发事务间需要隔离。 + + + +**持久性(Durability)** + +一旦一个事务被提交,对数据库中的数据改变是永久的,即使数据库系统故障,也不会丢失提交事务操作。 + + + +## 隔离级别 + +Oracle数据库中,仅有Serializable(串行化)和Read Committed(读已提交)两种隔离方式,默认选择读已提交的方式。不做隔离操作则会出现: + +- **脏读**:读到未提交更新的数据 +- **第一类丢失更新**:A事务撤销时,把已经提交的B事务的更新数据覆盖了 +- **第二类丢失更新**:A事务提交时,把已经提交的B事务的更新数据覆盖了 +- **不可重复读**:读到已经提交**更新**的数据,但一个事务范围内两个相同的查询却返回了不同数据 +- **幻读**: 事物A在用一个表,此时事物B在表中**增加或删除**了一条数据,A发现多了/少了一条数据,即为幻读 + + + +**InnoDB存储引擎下**的四种隔离级别发生问题的可能性如下: + +| 隔离级别 | 第一类丢失更新 | 第二类丢失更新 | 脏读 | 不可重复读 | 幻读 | +| ---------------------------- | -------------- | -------------- | ---- | ---------- | ---- | +| SERIALIZABLE (串行化) | × | × | × | × | × | +| REPEATABLE READ(可重复读) | × | × | × | × | √ | +| READ COMMITTED (读已提交) | × | √ | × | √ | √ | +| READ UNCOMMITTED(读未提交) | × | √ | √ | √ | √ | + + + +### Serializable(串行化) + +指一个事务在执行过程中完全看不到其他事务对数据库所做的更新。当两个事务同时操作数据库中相同数据时,如果第一个事务已经在访问该数据,第二个事务只能停下来等待,必须等到第一个事务结束后才能恢复运行。因此这两个事务实际上是串行化方式运行。 + + + +**特点**:避免脏读、不可重复读、幻读 + +**可序列化的数据库锁情况** + +- 事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放 +- 事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放 + + + +### Repeatable Read(可重复读) + +由于提交读隔离级别会产生不可重复读的读现象,所以比提交读更高一个级别的隔离级别就可以解决不可重复读的问题,这种隔离级别就叫可重复读(Repeatable reads) + + + +**特点**:MYSQL默认选择为可重复读。避免脏读、不可重复读 + +**可重复读的数据库锁情况** + +- 事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加行级共享锁,直到事务结束才释放 +- 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放 + + + +### Read Committed(读已提交) + +提交读(Read committed)也可以翻译成“读已提交”,通过名字也可以分析出,在一个事务修改数据过程中,如果事务还没提交,其他事务不能读该数据。 + + + +**特点**:避免脏读 + +**提交读的数据库锁情况** + +- 事务对当前被读取的数据加行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁 +- 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放 + + + +**Read Committed隔离级别下的加锁分析** + +隔离级别的实现与锁机制密不可分,所以需要引入锁的概念,首先我们看下InnoDB存储引擎提供的两种标准的行级锁: + +- **共享锁(S Lock)**:又称为读锁,可以允许多个事务并发的读取同一资源,互不干扰。即如果一个事务T对数据A加上共享锁后,其他事务只能对A再加共享锁,不能再加排他锁,只能读数据,不能修改数据 +- **排他锁(X Lock)**: 又称为写锁,如果事务T对数据A加上排他锁后,其他事务不能再对A加上任何类型的锁,获取排他锁的事务既能读数据,也能修改数据 + +**注意**: 共享锁和排他锁是不相容的。 + + + +### Read uncommitted(读未提交) + +未提交读(Read uncommitted)是最低的隔离级别。通过名字咱们就可以知道,在这种事务隔离级别下,一个事务可以读到另外一个事务未提交的数据。 + + + +**特点**:最低级别,任何情况都无法保证 + +**未提交读的数据库锁情况** + +- 事务在读数据的时候并未对数据加锁 +- 事务在修改数据的时候只对数据增加行级共享锁 + + + +## SpringBoot Transaction + +查看 `mysql` 事务隔离级别:`show variables like 'tx_iso%';`。 + + + +### 事务管理方式 + +在Spring中,事务有两种实现方式,分别是编程式事务管理和声明式事务管理两种方式。 + +- **编程式事务管理**: 编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate +- **声明式事务管理**: 建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务管理不需要入侵代码,通过@Transactional就可以进行事务操作,更快捷而且简单,推荐使用 + + + +### 事务提交方式 + +默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。 +对于正常的事务管理,是一组相关的操作处于一个事务之中,因此必须关闭数据库的自动提交模式。不过,这个我们不用担心,spring会将底层连接的自动提交特性设置为false。也就是在使用spring进行事物管理的时候,spring会将是否自动提交设置为false,等价于JDBC中的 `connection.setAutoCommit(false);`,在执行完之后在进行提交,`connection.commit();` 。 + + + +### 事务隔离级别 + +隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量: + +- **TransactionDefinition.ISOLATION_DEFAULT**:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED +- **TransactionDefinition.ISOLATION_READ_UNCOMMITTED**:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别 +- **TransactionDefinition.ISOLATION_READ_COMMITTED**:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值 +- **TransactionDefinition.ISOLATION_REPEATABLE_READ**:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读 +- **TransactionDefinition.ISOLATION_SERIALIZABLE**:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别 + + + +### 事务传播行为 + +所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量: + +- **TransactionDefinition.PROPAGATION_REQUIRED**:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值 +- **TransactionDefinition.PROPAGATION_REQUIRES_NEW**:创建一个新的事务,如果当前存在事务,则把当前事务挂起 +- **TransactionDefinition.PROPAGATION_SUPPORTS**:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行 +- **TransactionDefinition.PROPAGATION_NOT_SUPPORTED**:以非事务方式运行,如果当前存在事务,则把当前事务挂起 +- **TransactionDefinition.PROPAGATION_NEVER**:以非事务方式运行,如果当前存在事务,则抛出异常。 +- **TransactionDefinition.PROPAGATION_MANDATORY**:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 +- **TransactionDefinition.PROPAGATION_NESTED**:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED + + + +### 事务回滚规则 + +指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。 +默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。 +可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。 + + + +### 事务常用配置 + +- **readOnly**:该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) +- **rollbackFor**: 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) +- **rollbackForClassName**: 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类名称@Transactional(rollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”}) +- **noRollbackFor**:该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) +- **noRollbackForClassName**:该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类名称:@Transactional(noRollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”}) +- **propagation** : 该属性用于设置事务的传播行为。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) +- **isolation**:该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 +- **timeout**:该属性用于设置事务的超时秒数,默认值为-1表示永不超时 + + + +### 事物注意事项 + +- 要根据实际的需求来决定是否要使用事物,最好是在编码之前就考虑好,不然到以后就难以维护 +- 如果使用了事物,请务必进行事物测试,因为很多情况下以为事物是生效的,但是实际上可能未生效 +- 事物@Transactional的使用要放再类的**公共(public)方法**中,需要注意的是在 protected、private 方法上使用 @Transactional 注解,它也不会报错(IDEA会有提示),但事务无效 +- 事物@Transactional是不会对该方法里面的子方法生效!也就是你在公共方法A声明的事物@Transactional,但是在A方法中有个子方法B和C,其中方法B进行了数据操作,但是该异常被B自己处理了,这样的话事物是不会生效的!反之B方法声明的事物@Transactional,但是公共方法A却未声明事物的话,也是不会生效的!如果想事物生效,需要将子方法的事务控制交给调用的方法,在子方法中使用`rollbackFor`注解指定需要回滚的异常或者将异常抛出交给调用的方法处理。一句话就是在使用事物的异常由调用者进行处理 +- 事物@Transactional由spring控制的时候,它会在抛出异常的时候进行回滚。如果自己使用catch捕获了处理了,是不生效的,如果想生效可以进行手动回滚或者在catch里面将异常抛出,比如`throw new RuntimeException();` + + + +### 失效场景 + +- **@Transactional 应用在非 public 修饰的方法上** +- **数据库引擎要不支持事务** +- **由于propagation 设置错误,导致注解失效** +- **rollbackFor 设置错误,@Transactional 注解失效** +- **方法之间的互相调用也会导致@Transactional失效** +- **异常被你的 catch“吃了”导致@Transactional失效** + + + +### select for update + +`for update`是一种`行级锁`,又叫`排它锁`。一旦用户对某个行施加了行级加锁,则该用户可以查询也可以更新被加锁的数据行,其它用户只能查询但不能更新被加锁的数据行。 + +- **修改sql**:在 `select` 的 `sql` 尾部添加 `for update`。如:`select * from job_info where id = 1 for update;` +- **启用事务**:为 `service` 添加注解 `@Transactional` + + + +**只有当出现如下之一的条件,才会释放共享更新锁:** + +1. 执行提交(COMMIT)语句 +2. 退出数据库(LOG OFF) +3. 程序停止运行 + + + +假设有个表单products ,里面有id 跟name 二个栏位,id 是主键。 + +```mysql +-- 例1: 明确指定主键,并且有此数据,row lock +SELECT * FROM products WHERE id='3' FOR UPDATE; +-- 例2: 明确指定主键,若查无此数据,无lock +SELECT * FROM products WHERE id='-1' FOR UPDATE; +-- 例2: 无主键,table lock +SELECT * FROM products WHERE name='Mouse' FOR UPDATE; +-- 例3: 主键不明确,table lock +SELECT * FROM products WHERE id<>'3' FOR UPDATE; +-- 例4: 主键不明确,table lock +SELECT * FROM products WHERE id LIKE '3' FOR UPDATE; +``` + +**注意** + +- FOR UPDATE 仅适用于InnoDB,且必须在事务区块(start sta/COMMIT)中才能生效 +- 要测试锁定的状况,可以利用MySQL 的Command Mode ,开二个视窗来做测试 + + + +# 索引 + +建立索引的目的是加快对表中记录的查找或排序。索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。因此应该只为最经常查询和最经常排序的数据列建立索引。MySQL里同一个数据表里的索引总数限制为16个。 + +**优点** + +- 索引大大减小了服务器需要扫描的数据量 +- 索引可以帮助服务器避免排序和临时表 +- 索引可以将随机IO变成顺序IO +- 索引对于InnoDB(对索引支持行级锁)非常重要,因为它可以让查询锁更少的元组 +- 关于InnoDB、索引和锁:InnoDB在二级索引上使用共享锁(读锁),但访问主键索引需要排他锁(写锁) + +**缺点** + +- 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存索引文件 +- 建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快 +- 如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果 +- 对于非常小的表,大部分情况下简单的全表扫描更高效 + + + +**索引规范** + +- 索引的数量要控制 + + - **单张表** 中索引数量不超过 **5** 个 + - **单个索引** 中的字段数不超过 **5** 个(字段超过5个时,实际已经起不到有效过滤数据的作用了) + - 对字符串使⽤ **前缀索引**,前缀索引长度不超过 **8** 个字符,必要时可添加伪列并建立索引 + +- 禁止在 **更新十分频繁**、**区分度不高** 的属性上建立索引 + + - 更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能 + - “性别”这种区分度不大的属性,建立索引是没有意义的,其不能有效过滤数据,性能与全表扫描类似 + +- 不在索引列进行 **数学运算** 和 **函数运算** + +- 建立组合索引:必须 **把区分度高的字段放在前面**(能够更加有效的过滤数据) + +- 重要的SQL必须被索引,比如: + + - **UPDATE**、**DELETE** 语句的 **WHERE** 条件列 + - **ORDER BY**、**GROUP BY**、**DISTINCT** 的字段 + +- **多表JOIN** 的字段注意以下(优化准则) + + - **区分度最大** 的字段放在前面 + - 核⼼SQL优先考虑 **覆盖索引** + - 避免 **冗余** 和 **重复** 索引 + - 索引要综合评估数据 **密度** 和 **分布**以及考虑 **查询** 和 **更新** 比例 + +- 索引命名 + + - 索引名称必须 **全部小写** + - 唯一所以必须以 **uniq _ 字段1 _ 字段2** 命名 + - 非唯一索引必须以 **idx _ 字段1 _ 字段2** 命名 + +- 新建的 **唯一索引** 必须不能和主键重复 + +- 索引字段的默认值不能为 **NULL** (NULL非常影响索引的查询效率) + +- 反复查看与表相关的SQL,符合 **最左前缀** 的特点建立索引 + + 多条件字段重复的语句,要修改语句条件字段的顺序,为其建立一条 **联合索引**,减少索引数量 + +- **优先使用唯一索引**:能使用唯一索引就要使用唯一索引,提高查询效率 + +- 研发要经常使用 **explain**,如果发现索引选择性差,必须让他们学会使用hint + + + +## 索引结构 + +### 二叉树 + +**特点** + +1. 左子节点值 < 节点值 +2. 右子节点值 > 节点值 +3. 当数据量非常大时,要查找的数据又非常靠后,和没有索引相比,那么二叉树结构的查询优势将非常明显 + +**存在问题** + +如下图,可以看出,二叉树出现单边增长时,二叉树变成了“链”,这样查找一个数的时候,速度并没有得到很大的优化。 + +![索引结构-二叉树](images/Database/索引结构-二叉树.png) + + + +### 红黑树 + +**特点** + +1. 节点是红色或者黑色 +2. 根节点是黑色 +3. 每个叶子的节点都是黑色的空节点(NULL) +4. 每个红色节点的两个子节点都是黑色的 +5. 从任意节点到其每个叶子的所有路径都包含相同的黑色节点 + +![索引结构-红黑树](images/Database/索引结构-红黑树.png) + +**存在的问题** + +**红黑树虽然和二叉树相比,一定程度上缓解了单边过长的问题,但是它依旧存储高度问题。**  + +假设现在数据量有100万,那么红黑树的高度大概为 100,0000 = 2^n, n大概为 20。那么,至少要20次的磁盘IO,这样,性能将很受影响。如果数据量更大,IO次数更多,性能损耗更大。**所以红黑树依旧不是最佳方案。** + + + +**思考:针对上面的红黑树结构,我们能否优化一下呢?** + +上述红黑树默认一个节点就存了一个 (索引+磁盘地址),我们设想一个节点存多个 (索引+磁盘地址),这样就可以降低红黑树的高度了。 **实际上我们设想的这种结构就是 B-Tree**。 + + + +### Hash + +**原理** + +1. 事先将索引通过 hash算法后得到的hash值(即磁盘文件指针)存到hash表中 +2. 在进行查询时,将索引通过hash算法,得到hash值,与hash表中的hash值比对。通过磁盘文件指针,只要**一次磁盘IO**就能找到要的值 + +例如:在第一个表中,要查找col=6的值。hash(6) 得到值,比对hash表,就能得到89。性能非常高。 + +**存在问题** + + 但是hash表索引存在问题,如果要查询带范围的条件时,hash索引就歇菜了。 + +```mysql +select *from t where col1>=6; +``` + + + +### B-Tree + +**特点** + +B-Tree索引能很好解决红黑树中遗留的高度问题,B-Tree 是一种平衡的多路查找(又称排序)树,在文件系统中和数据库系统有所应用,主要用作文件的索引,其中的B就表示平衡(Balance)。 + +为了描述B-Tree,首先定义一条数据记录为一个二元组 [key, data],key为记录的键值key,对于不同数据记录,key是互不相同的;**data为数据记录除以key外的数据 (这里指的是聚集索引)**。那么B-Tree是满足下列条件的数据结构: + +1. d 为大于1的一个正整数,称为BTree的度 +2. h为一个正整数,称为BTree的高度 +3. key和指针互相间隔,节点两端是指针 +4. 叶子节点具有相同的深度,叶子节点的指针为空,节点中数据索引(下图中的key)从左往右递增排列 + +**说明**:下图都是以主键索引为例,至于非主键索引(非聚集索引),无非就是data里存的内容不同。 + +![索引结构-B-Tree指针](images/Database/索引结构-B-Tree指针.png) + +![索引结构-B-Tree](images/Database/索引结构-B-Tree.png) + +**分析** + +模拟下查找key为29的data的过程: + +1. 根据根结点指针读取文件目录的根磁盘块1。【磁盘IO操作第**1次**】 +2. 磁盘块1存储17,35和三个指针数据。我们发现17<29<35,因此我们找到指针p2 +3. 根据p2指针,我们定位并读取磁盘块3。【磁盘IO操作**2次**】 +4. 磁盘块3存储26,30和三个指针数据。我们发现26<29<30,因此我们找到指针p2 +5. 根据p2指针,我们定位并读取磁盘块8。【磁盘IO操作**3次**】 +6. 磁盘块8中存储28,29。我们找到29,获取29所对应的数据data + +**存在问题** + +1. 比如,下面查询语句,那么不但需要叶子节点>20的值,也需要非叶子节点在右边节点的值。即下图画圈的两部分, **B-Tree似乎在范围查找没有更简便的方法,为了解决这一问题。我们可以用B+Tree。** + + ```mysql + select *from t where col1 > 20; + ``` + + ![索引结构-B-Tree](images/Database/索引结构-B-Tree问题.png) + +2. 深度问题 + + 从图上可以看到,每个节点中不仅包含数据的key值,还有data值。而每一个节点的存储空间是有限的(mysql默认设置一个节点的大小为16K),如果data中存放的数据较大时,将会导致每个节点(即一个页)能存储的key的数量(索引的数量)很小,所以当数据量很多,且每行数据量很大的时候,同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。所以引入B+Tree + + + +### B+Tree + +**特点** + +`B+Tree`是在`B-Tree`基础上的一种优化,使其更适合实现外存储索引结构。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。 + +1. 非叶子节点不存储data,只存储索引,可以存放更多索引 +2. 叶子节点不存储指针 +3. 顺序访问指针,提高区间访问性能 +4. 非叶子节点中的索引最终还是会在叶子节点上存储一份,也就是叶子节点会包含非叶子节点上的所有索引 +5. 一个父节点,它的**左侧**子节点都**小于**父节点的值,**右侧**的子节点都**大于等于**父节点的值 +6. 每一层节点从左往右都是递增排列,无论是数值型还是字符型 + +**注意**:MySQL索引默认的存储结构使用的就是B+Tree。 + +**![索引结构-B+Tree指针](images/Database/索引结构-B+Tree指针.png)** + +![索引结构-B+Tree](images/Database/索引结构-B+Tree.png) + +**剖析**:如上图,在叶子节点上注意是MySQL已经有成双向箭头(原生B+Tree是单向的),而且从左到右是递增顺序的,所以很好的解决了 > 和 < 这类查找问题。 + +**分析** + +假如:**以一个高度为3的B+Tree为例**,B+Tree的表都存满了,能存储多少数据? + +**首先,**查看MySQL默认一个节点页的大小: + +```mysql +SHOW GLOBAL STATUS like 'Innodb_page_size'; +``` + +如下图:大小为16K。 + +![索引结构-B+Tree案例](images/Database/索引结构-B+Tree案例.png) + +然后,假设主键Id为bigint类型,那么长度就是8B,指针在Innodb源码中大小为6B,所以一共就是14B,再假设最后一层,存放的数据data为1k 大小(能存很多内容了),那么: + +1. 第一层最大节点数为: 16k / (8B + 6B) = 1170 (个) +2. 第二层最大节点数也应为:1170个 +3. 第三层最大节点数为:16k / 1k = 16 (个) + +则,一张B+Tree的表最多存放 1170 * 1170 * 16 = 21902400 ≈ 2千万。所以,通过分析,我们可以得出,B+Tree结构的表可以容纳千万数据量的查询。而且**一般来说,MySQL会把 B+Tree 根节点放在内存中**,那只需要**两次磁盘IO(第二层1次,第三层1次)**就行。 + +**扩展** + +数据库中的B+Tree索引可以分为聚集索引(clustered index,也叫主键索引)和辅助索引(secondary index,也叫非聚集索引)。上面的B+Tree示例图在数据库中的实现对应的是聚集索引,聚集索引的B+Tree中的叶子节点存放的是整张表的行记录数据(除主键以外的所有数据),辅助索引与聚集索引的区别在于辅助索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的对应的聚集索引键,即主键。当通过辅助索引来查询数据时,InnoDB存储引擎会遍历辅助索引找到主键,然后再通过主键在聚集索引中找到完整的行记录数据。 + + + +## 索引类型 + +### 普通索引 + +**普通索引(单列索引)**:单列索引是最基本的索引,它没有任何限制。 + +- 直接创建索引 + +```mysql +CREATE INDEX index_name ON table_name(col_name); +``` + +- 修改表结构的方式添加索引 + +```mysql +ALTER TABLE table_name ADD INDEX index_name(col_name); +``` + +- 创建表的时候同时创建索引 + +```mysql +CREATE TABLE `news` ( + `id` int(11) NOT NULL AUTO_INCREMENT , + `title` varchar(255) NOT NULL , + `content` varchar(255) NULL , + `time` varchar(20) NULL DEFAULT NULL , + PRIMARY KEY (`id`), + INDEX index_name (title(255)) +) +``` + +- 删除索引 + +```mysql +DROP INDEX index_name ON table_name; +# 或 +alter table `表名` drop index 索引名; +``` + + + +### 复合索引 + +**复合索引(组合索引)**:复合索引是在多个字段上创建的索引。复合索引遵守“**最左前缀**”原则**,**即在查询条件中使用了复合索引的第一个字段,索引才会被使用。因此,在复合索引中索引列的顺序至关重要。 + +- 创建一个复合索引 + +```mysql +create index index_name on table_name(col_name1,col_name2,...); +``` + +- 修改表结构的方式添加索引 + +```mysql +alter table table_name add index index_name(col_name,col_name2,...); +``` + + + +### 唯一索引 + +**唯一索引**:唯一索引和普通索引类似,主要的区别在于,**唯一索引限制列的值必须唯一,但允许存在空值(只允许存在一条空值)**。如果在已经有数据的表上添加唯一性索引的话: + +- 如果添加索引的列的值存在两个或者两个以上的空值,则不能创建唯一性索引会失败。(一般在创建表的时候,要对自动设置唯一性索引,需要在字段上加上 not null) +- 如果添加索引的列的值存在两个或者两个以上null值,还是可以创建唯一性索引,只是后面创建的数据不能再插入null值 ,并且严格意义上此列并不是唯一的,因为存在多个null值 + +对于多个字段创建唯一索引规定列值的组合必须唯一。比如:在order表创建orderId字段和 productId字段 的唯一性索引,那么这两列的组合值必须唯一: + +```mysql +“空值” 和”NULL”的概念: +1:空值是不占用空间的 . +2: MySQL中的NULL其实是占用空间的. + +长度验证:注意空值的之间是没有空格的。 + +> select length(''),length(null),length(' '); ++------------+--------------+-------------+ +| length('') | length(null) | length(' ') | ++------------+--------------+-------------+ +| 0 | NULL | 1 | ++------------+--------------+-------------+ +``` + +- 创建唯一索引 + +```mysql +# 创建单个索引 +CREATE UNIQUE INDEX index_name ON table_name(col_name); +# 创建多个索引 +CREATE UNIQUE INDEX index_name on table_name(col_name,...); +``` + +- 修改表结构 + +```mysql +# 单个 +ALTER TABLE table_name ADD UNIQUE index index_name(col_name); +# 多个 +ALTER TABLE table_name ADD UNIQUE index index_name(col_name,...); +``` + +- 创建表的时候直接指定索引 + +```mysql +CREATE TABLE `news` ( + `id` int(11) NOT NULL AUTO_INCREMENT , + `title` varchar(255) NOT NULL , + `content` varchar(255) NULL , + `time` varchar(20) NULL DEFAULT NULL , + PRIMARY KEY (`id`), + UNIQUE index_name_unique(title) +) +``` + + + +### 主键索引 + +主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引: + +- 主键索引(创建表时添加) + +```mysql +CREATE TABLE `news` ( + `id` int(11) NOT NULL AUTO_INCREMENT , + `title` varchar(255) NOT NULL , + `content` varchar(255) NULL , + `time` varchar(20) NULL DEFAULT NULL , + PRIMARY KEY (`id`) +) +``` + +- 主键索引(创建表后添加) + +```mysql +alter table tbl_name add primary key(col_name); +``` + +```mysql +CREATE TABLE `order` ( + `orderId` varchar(36) NOT NULL, + `productId` varchar(36) NOT NULL , + `time` varchar(20) NULL DEFAULT NULL +) +alter table `order` add primary key(`orderId`); +``` + + + +### 全文索引 + +在一般情况下,模糊查询都是通过 like 的方式进行查询。但是,对于海量数据,这并不是一个好办法,在 like "value%" 可以使用索引,但是对于 like "%value%" 这样的方式,执行全表查询,这在数据量小的表,不存在性能问题,但是对于海量数据,全表扫描是非常可怕的事情,所以 like 进行模糊匹配性能很差。 + +这种情况下,需要考虑使用全文搜索的方式进行优化。全文搜索在 MySQL 中是一个 FULLTEXT 类型索引。**FULLTEXT 索引在 MySQL 5.6 版本之后支持 InnoDB,而之前的版本只支持 MyISAM 表**。 + +全文索引主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的where语句的参数匹配。fulltext索引配合match against操作使用,而不是一般的where语句加like。目前只有char、varchar,text 列上可以创建全文索引。 + + + +**小技巧** +在数据量较大时候,先将数据放入一个没有全局索引的表中,然后再用CREATE index创建fulltext索引,要比先为一张表建立fulltext然后再将数据写入的速度快很多。 + +- 创建表的适合添加全文索引 + +```mysql +CREATE TABLE `news` ( + `id` int(11) NOT NULL AUTO_INCREMENT , + `title` varchar(255) NOT NULL , + `content` text NOT NULL , + `time` varchar(20) NULL DEFAULT NULL , + PRIMARY KEY (`id`), + FULLTEXT (content) +) +``` + +- 修改表结构添加全文索引 + +```mysql +ALTER TABLE table_name ADD FULLTEXT index_fulltext_content(col_name) +``` + +- 直接创建索引 + +```mysql +CREATE FULLTEXT INDEX index_fulltext_content ON table_name(col_name) +``` + +**注意**: 默认MySQL不支持中文全文检索!MySQL 全文搜索只是一个临时方案,对于全文搜索场景,更专业的做法是使用全文搜索引擎,例如 ElasticSearch 或 Solr。 + +- 索引的查询和删除 + +```mysql +-- 查看: +show indexes from `表名`; +-- 或 +show keys from `表名`; + +-- 删除 +alter table `表名` drop index 索引名; +``` + +**注意**:MySQl的客户端工具也可以进索引的创建、查询和删除,如 Navicat Premium! + + + +## 索引规范 + +- **【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。** + + 说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明 显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必 然有脏数据产生。 + +- **【强制】超过三个表禁止join。join字段数据类型必须绝对一致;多表关联查询时, 保证被关联字段需要有索引。** + + 说明:即使双表 join 也要注意表索引、SQL 性能。 + +- 【**强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据 实际文本区分度决定索引长度即可。** + + 说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分 度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度 来确定。 + +- **【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。** + + 说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索 引。 + +- **【推荐】如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合 索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。** + + 正例:where a=? and b=? order by c; 索引:a_b_c + + 反例:索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 无法排序。 + +- **【推荐】利用覆盖索引来进行查询操作,避免回表。** + + 说明:如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览 一下就好,这个目录就是起到覆盖索引的作用。 正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查 询的一种效果,用 explain 的结果,extra 列会出现:using index。 + +- **【推荐】利用延迟关联或者子查询优化超多分页场景。** + + 说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过 特定阈值的页数进行 SQL 改写。 正例:先快速定位需要获取的 id 段,然后再关联: SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id + +- **【推荐】SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。** + + 说明: + + 1)consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据 + + 2)ref 指的是使用普通的索引(normal index) + + 3)range 对索引进行范围检索 + + 反例:explain 表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index 级 别比较 range 还低,与全表扫描是小巫见大巫。 + +- **【推荐】建组合索引的时候,区分度最高的在最左边。** + + 正例:如果 where a=? and b=? ,如果 a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。 + + 说明:存在非等号和等号混合时,在建索引时,请把等号条件的列前置。如:where c>? and d=? 那么即使 c 的区分度更高,也必须把 d 放在索引的最前列,即索引 idx_d_c。 + +- **【推荐】防止因字段类型不同造成的隐式转换,导致索引失效。** + +- **【参考】创建索引时避免有如下极端误解:** + - **认为一个查询就需要建一个索引** + - **认为索引会消耗空间、严重拖慢更新和新增速度** + - **认为业务的惟一性一律需要在应用层通过“先查后插”方式解决** + + + +# SQL优化 + +## SQL优化步骤 +**第1步:通过慢查日志等定位那些执行效率较低的SQL语句** + +**第2步:explain分析SQL的执行计划** + +需要重点关注`type`、`rows`、`filtered`、`extra`。 + +- `type`:由上至下,效率越来越高 + + - `ALL`:全表扫描 + - `index`:索引全扫描 + - `range`:索引范围扫描,常用语`<`、`<=`、`>=`、`between`、`in`等操作 + - `ref`:使用非唯一索引扫描或唯一索引前缀扫描,返回单条记录,常出现在关联查询中 + - `eq_ref`:类似ref,区别在于使用的是唯一索引,使用主键的关联查询 + - `const/system`:单条记录,系统会把匹配行中的其他列作为常数处理,如主键或唯一索引查询 + - `null`:MySQL不访问任何表或索引,直接返回结果 + + 虽然上至下,效率越来越高,但是根据cost模型,假设有两个索引`idx1(a, b, c)`,`idx2(a, c)`,SQL为`select * from t where a = 1 and b in (1, 2) order by c;`如果走idx1,那么是type为range,如果走idx2,那么type是ref;当需要扫描的行数,使用idx2大约是idx1的5倍以上时,会用idx1,否则会用idx2 + +- `Extra` + - `Using filesort`:MySQL需要额外的一次传递,以找出如何按排序顺序检索行。通过根据联接类型浏览所有行并为所有匹配WHERE子句的行保存排序关键字和行的指针来完成排序。然后关键字被排序,并按排序顺序检索行。 + - `Using temporary`:使用了临时表保存中间结果,性能特别差,需要重点优化 + - `Using index`:表示相应的 select 操作中使用了覆盖索引(Coveing Index),避免访问了表的数据行,效率不错!如果同时出现 using where,意味着无法直接通过索引查找来查询到符合条件的数据。 + - `Using index condition`:MySQL5.6之后新增的ICP,using index condtion就是使用了ICP(索引下推),在存储引擎层进行数据过滤,而不是在服务层过滤,利用索引现有的数据减少回表的数据。 + +**第3步:show profile 分析** + +了解SQL执行的线程的状态及消耗的时间。默认是关闭的,开启语句“set profiling = 1;” + +```mysql +SHOW PROFILES ; +SHOW PROFILE FOR QUERY #{id}; +``` + +**第4步:trace** + +trace分析优化器如何选择执行计划,通过trace文件能够进一步了解为什么优惠券选择A执行计划而不选择B执行计划。 + +```mysql +set optimizer_trace="enabled=on"; +set optimizer_trace_max_mem_size=1000000; +select * from information_schema.optimizer_trace; +``` + +**第5步:确定问题并采用相应的措施** + +- 优化索引 +- 优化SQL语句:修改SQL、IN 查询分段、时间查询分段、基于上一次数据过滤 +- 改用其他实现方式:ES、数仓等 +- 数据碎片处理 + + + +## 特殊需求 + +### 批量去重插入 + +**问题:MySQL批量插入,如何不插入重复数据?** + +**解决方案1:insert ignore into** + +当插入数据时,如出现错误时,如重复数据,将不返回错误,只以警告形式返回。所以使用ignore请确保语句本身没有问题,否则也会被忽略掉。例如: + +```mysql +INSERT IGNORE INTO user (name) VALUES ('telami') +``` + +这种方法很简便,但是有一种可能,就是插入不是因为重复数据报错,而是因为其他原因报错的,也同样被忽略了~ + + + +**解决方案2:on duplicate key update** + +当primary或者unique重复时,则执行`update`语句,如`update`后为无用语句,如`id=id`,则同1功能相同,但错误不会被忽略掉。在公众号顶级架构师后台回复“架构整洁”,获取一份惊喜礼包。例如,为了实现name重复的数据插入不报错,可使用一下语句: + +```mysql +INSERT INTO user (name) VALUES ('telami') ON duplicate KEY UPDATE id = id +``` + +这种方法有个前提条件,就是,需要插入的约束,需要是主键或者唯一约束(在你的业务中那个要作为唯一的判断就将那个字段设置为唯一约束也就是`unique key`)。 + + + +**解决方案3:insert … select … where not exist** + +根据select的条件判断是否插入,可以不光通过`primary`和`unique`来判断,也可通过其它条件。例如: + +```mysql +INSERT INTO user (name) SELECT 'telami' FROM dual WHERE NOT EXISTS (SELECT id FROM user WHERE id = 1) +``` + +这种方法其实就是使用了`MySQL`的一个临时表的方式,但是里面使用到了子查询,效率也会有一点点影响,如果能使用上面的就不使用这个。 + + + +**解决方案4:replace into** + +如果存在`primary or unique`相同的记录,则先删除掉。再插入新记录。 + +```mysql +REPLACE INTO user SELECT 1, 'telami' FROM books +``` + +这种方法就是不管原来有没有相同的记录,都会先删除掉然后再插入。选择的是第二种方式 + +```xml + + insert into user (id,username,mobile_number) + values + + ( + #{item.id}, + #{item.username}, + #{item.mobileNumber} + ) + + ON duplicate KEY UPDATE id = id + +``` + +这里用的是Mybatis,批量插入的一个操作,`mobile_number`已经加了唯一约束。这样在批量插入时,如果存在手机号相同的话,是不会再插入了的。 + + + +## 场景分析 + +### 案例1:最左匹配 + +**索引** + +```mysql +KEY `idx_shopid_orderno` (`shop_id`,`order_no`) +``` + +**SQL语句** + +```mysql +select * from _t where orderno='xxx'; +``` + +查询匹配从左往右匹配,要使用`order_no`走索引,必须查询条件携带`shop_id`或者索引(`shop_id`,`order_no`)调换前后顺序。 + + + +### 案例2:隐式转换 + +**索引** + +```mysql +KEY `idx_mobile` (`mobile`) +``` + +**SQL语句** + +```mysql +select * from _user where mobile=12345678901; +``` + +隐式转换相当于在索引上做运算,会让索引失效。mobile是字符类型,使用了数字,应该使用字符串匹配,否则MySQL会用到隐式替换,导致索引失效。 + + + +### 案例3:大分页 + +**索引** + +```mysql +KEY `idx_a_b_c` (`a`, `b`, `c`) +``` + +**SQL语句** + +```mysql +select * from _t where a = 1 and b = 2 order by c desc limit 10000, 10; +``` + +对于大分页的场景,可以优先让产品优化需求,如果没有优化的,有如下两种优化方式: + +- 把上一次的最后一条数据,也即上面的c传过来,然后做“c < xxx”处理,但是这种一般需要改接口协议,并不一定可行 +- 采用延迟关联的方式进行处理,减少SQL回表,但是要记得索引需要完全覆盖才有效果,SQL改动如下 + +```mysql +SELECT t1.* FROM _t t1, (SELECT id FROM _t WHERE a=1 AND b=2 ORDER BY c DESC LIMIT 10000,10) t2 WHERE t1.id=t2.id; +``` + + + +### 案例4:in+order by + +**索引** + +```mysql +KEY `idx_shopid_status_created` (`shop_id`, `order_status`, `created_at`) +``` + +**SQL语句** + +```mysql +SELECT * FROM _order WHERE shop_id = 1 AND order_status IN ( 1, 2, 3 ) ORDER BY created_at DESC LIMIT 10 +``` + +in查询在MySQL底层是通过`n*m`的方式去搜索,类似union,但是效率比union高。in查询在进行cost代价计算时(`代价 = 元组数 * IO平均值`),是通过将in包含的数值,一条条去查询获取元组数的,因此这个计算过程会比较的慢,所以MySQL设置了个临界值(`eq_range_index_dive_limit`),5.6之后超过这个临界值后该列的cost就不参与计算了。 + +因此会导致执行计划选择不准确。默认是200,即in条件超过了200个数据,会导致in的代价计算存在问题,可能会导致Mysql选择的索引不准确。处理方式,可以(`order_status`, `created_at`)互换前后顺序,并且调整SQL为延迟关联。 + + + +### 案例5:范围查询索引失效 + +范围查询阻断,后续字段不能走索引。 + +**索引** + +```mysql +KEY `idx_shopid_created_status` (`shop_id`, `created_at`, `order_status`) +``` + +**SQL语句** + +```mysql +SELECT * FROM _order WHERE shop_id=1 AND created_at > '2021-01-01 00:00:00' AND order_status=10; +``` + +范围查询还有“IN、between”。 + + + +### 案例6:避免使用非快速索引 + +不等于、不包含不能用到索引的快速搜索。 + +```mysql +select * from _order where shop_id=1 and order_status not in (1,2); +select * from _order where shop_id=1 and order_status != 1; +``` + +在索引上,避免使用`NOT`、`!=`、`<>`、`!<`、`!>`、`NOT EXISTS`、`NOT IN`、`NOT LIKE`等。 + + + +### 案例7:优化器选择索引失效 + +如果要求访问的数据量很小,则优化器还是会选择辅助索引,但是当访问的数据占整个表中数据的蛮大一部分时(一般是20%左右),优化器会选择通过聚集索引来查找数据。 + +```mysql +select * from _order where order_status = 1 +``` + +查询出所有未支付的订单,一般这种订单是很少的,即使建了索引,也没法使用索引。 + + + +### 案例8:复杂查询 + +```mysql +select sum(amt) from _t where a = 1 and b in (1, 2, 3) and c > '2020-01-01'; +select * from _t where a = 1 and b in (1, 2, 3) and c > '2020-01-01' limit 10; +``` + +如果是统计某些数据,可能改用数仓进行解决;如果是业务上就有那么复杂的查询,可能就不建议继续走SQL了,而是采用其他的方式进行解决,比如使用ES等进行解决。 + + + +### 案例9:asc和desc混用 + +```mysql +select * from _t where a=1 order by b desc, c asc +``` + +desc 和asc混用时会导致索引失效 + + + +### 案例10:大数据 + +对于推送业务的数据存储,可能数据量会很大,如果在方案的选择上,最终选择存储在MySQL上,并且做7天等有效期的保存。那么需要注意,频繁的清理数据,会照成数据碎片,需要联系DBA进行数据碎片处理。 + + + +# 存储引擎 + +## InnoDB引擎 + +InnoDB 是一个事务安全的存储引擎,它具备提交、回滚以及崩溃恢复的功能以保护用户数据。InnoDB 的行级别锁定保证数据一致性提升了它的多用户并发数以及性能。InnoDB 将用户数据存储在聚集索引中以减少基于主键的普通查询所带来的 I/O 开销。为了保证数据的完整性,InnoDB 还支持外键约束。默认使用B+TREE数据结构存储索引。 + + + +**特点** + +- 支持事务,支持4个事务隔离(ACID)级别 +- 行级锁定(更新时锁定当前行) +- 读写阻塞与事务隔离级别相关 +- 既能缓存索引又能缓存数据 +- 支持外键 +- InnoDB更消耗资源,读取速度没有MyISAM快 +- 在InnoDB中存在着缓冲管理,通过缓冲池,将索引和数据全部缓存起来,加快查询的速度; +- 对于InnoDB类型的表,其数据的物理组织形式是聚簇表。所有的数据按照主键来组织。数据和索引放在一块,都位于B+数的叶子节点上 + + + +**业务场景** + +- 需要支持事务的场景(银行转账之类) +- 适合高并发,行级锁定对高并发有很好的适应能力,但需要确保查询是通过索引完成的 +- 数据修改较频繁的业务 + + + +**InnoDB引擎调优** + +- 主键尽可能小,否则会给Secondary index带来负担 +- 避免全表扫描,这会造成锁表 +- 尽可能缓存所有的索引和数据,减少IO操作 +- 避免主键更新,这会造成大量的数据移动 + + + +## MyISAM引擎 + +MyISAM既不支持事务、也不支持外键、其优势是访问速度快,但是表级别的锁定限制了它在读写负载方面的性能,因此它经常应用于只读或者以读为主的数据场景。默认使用B+TREE数据结构存储索引。 + + + +**特点** + +- 不支持事务 +- 表级锁定(更新时锁定整个表) +- 读写互相阻塞(写入时阻塞读入、读时阻塞写入;但是读不会互相阻塞) +- 只会缓存索引(通过key_buffer_size缓存索引,但是不会缓存数据) +- 不支持外键 +- 读取速度快 + + + +**业务场景** + +- 不需要支持事务的场景(像银行转账之类的不可行) +- 一般读数据的较多的业务 +- 数据修改相对较少的业务 +- 数据一致性要求不是很高的业务 + + + +**MyISAM引擎调优** + +- 设置合适索引 +- 启用延迟写入,尽量一次大批量写入,而非频繁写入 +- 尽量顺序insert数据,让数据写入到尾部,减少阻塞 +- 降低并发数,高并发使用排队机制 +- MyISAM的count只有全表扫描比较高效,带有其它条件都需要进行实际数据访问 + + + +# MySQL原理 + +## 架构设计 + +![MySQL架构设计](images/Database/MySQL架构设计.jpg) + +从上面的示意图可以看出,MySQL从上到下包含了:**客户端、Server层和存储引擎层**。 + +- **客户端**:可以是我们常用的MySQL命令行窗口,或者是Java的客户端程序等 +- **Server层**:连接器、查询缓存、分析器、优化器和执行器等。大部分MySQL对用户提供的功能都在这一层实现,包括了内置函数的实现,存储过程、触发器、视图等 +- **存储层**:存储引擎层负责数据的存储和提取,存储引擎的实现是插件式的。也就是说用户可以选择自己所需要的存储引擎,如InnoDB、MyISAM等 + + + +### 连接器 + +连接器是MySQL服务端对外的门户,当我们使用命令行黑窗口或者JDBC的Connection.connect(),连接到MySQL Server端时,会校验用户名和密码;然后会查询用户对应的权限列表。当连接建立后,后续的权限范围就在此时确定了,如果连接没有断开的情况下,更改了用户的权限,此时对于该连接也不生效。 + + + +### 查询缓存 + +当连接建立完成后,执行select 语句的时候,就会来到查询缓存。MySQL会将Select 语句为 KEY,将查询结果为VALUE 的形式保存在内存中。如果匹配到对应的 KEY 就会直接从内存中返回结果。 + +但是常我们不会使用MySQL自身的查询缓存,因为当有一条Update 或 Insert 的改表语句时,就会清空对该表的所有查询缓存。缓存的粒度比较大,可以考虑类似 Redis 的分布式缓存做业务数据的缓存。在MySQL 8.0 中,查询缓存直接被移除了。 + + + +### 分析器 + +如果在查询缓存中没有查到数据,就要真正的开始执行SQL语句了。分析器首先会做“词法分析”。词法分析就是识别上面字符串,id、name 是表的字段名,T 是表的名称等等。之后就是语法分析,如果SQL有语法错误,在此时就会报错。 + + + +### 优化器 + +当分析器处理过之后,MySQL就知道SQL 要干什么了,但是此时还需要优化器对待执行的SQL 进行优化。当然MySQL 提供的优化器,相比其他几款商用收费的数据库来说还是比较弱的。当然MySQL 的优化器还是可以对 join 操作,表达式计算等等进行优化,本篇不做过多的介绍。 + + + +### 执行器 + +执行阶段,首先会检查当前用户有没有权限操作该 SQL 语句。如果有,则继续执行后续的操作。 + + + +## 日志系统 + +**生产优化** + +- 在生产上,建议 innodb_flush_log_at_trx_commit 设置成 1,可以让每次事务的 redo log 都持久化到磁盘上。保证异常重启后,redo log 不丢失 +- 建议 sync_binlog 设置成 1,可以让每次事务的 binlog 都持久化到磁盘上。保证异常重启后,binlog 不丢失 + + + +**IO性能优化** + +- `binlog_group_commit_sync_delay`:表示延迟多少微秒后,再执行 `fsync` +- `binlog_group_commit_sync_no_delay_count`:表示累计多少次后,在调用 `fsync` + + + +当 `MySQL` 出现了 `IO` 的性能问题,可以考虑下面的优化策略: + +- 设置 `binlog_group_commit_sync_delay` 和 `binlog_group_commit_sync_no_delay_count`。可以使用故意等待来减少,`binlog` 的写盘次数,没有数据丢失的风险,但是会有客户端响应变慢的风险 +- 设置 `sync_binlog` 设置为 `100~1000` 之间的某个值。这样做存在的风险是可能造成 `binlog` 丢失 +- 设置 `innodb_flush_log_at_trx_commit = 2`,可能会丢数据 + + + +### redo log(重做日志) + +在MySQL里,如果我们要执行一条更新语句。执行完成之后,数据不会立马写入磁盘,因为这样对磁盘IO的开销比较大。MySQL里面有一种叫做WAL(Write-Ahead Logging),就是先写日志在写磁盘。就是当有一条记录需要更新的时候,InnoDB 会先写redo log 里面,并更新内存,这个时候更新的操作就算完成了。之后,MySQL会在合适的时候将操作记录 flush 到磁盘上面。当然 flush 的条件可能是系统比较空闲,或者是 redo log 空间不足时。redo log 文件的大小是固定的,比如可以是由4个1GB文件组成的集合。如下图所示: + +![redolog位置指针](images/Database/redolog位置指针.jpg) + +write pos 是当前要写入日志的位置,当写到末尾时,会重新到文件头部开始写入。checkpoint 是当前待擦除的位置,以此循环反复利用这 4GB 的空间。有了 redo log,即时数据异常宕机,重启时也不会丢失已经提交的数据,这个能力叫做 crash-safe。 + + + +**redo log写入流程** + +前面介绍过了 redo log 的写入首先会写入 redo log cache,其详细的状态如下所示: + +![redolog写入流程](images/Database/redolog写入流程.jpg) + +redo log 对应上面的 3 种状态分别是: + +- **在 MySQL 应用的 redo log buffer 中** +- **write 到文件系统的 page cache 中,但是没有进行实际的写盘操作(fsync)** +- **执行 fsync 之后,写盘结束** + +InnoDB 有一个后台线程,每个 1 秒钟 就会将 redo log buffer 中的日志,调用 write 写入到 文件系统的 page cache 中,然后再调用 fsync 持久化到磁盘中。redo log buffer 是共享的,因此一些正在执行中的事务的 redo log 也有可能被持久化到磁盘中。 + +通常我们说的 MySQL 的 “双1” 操作,指的是 `sync_binlog = 1 AND innodb_flush_log_at_trx_commit = 1` 。`innodb_flush_log_at_trx_commit` 设置成 `1` 表示 redo log 在 prepare 阶段就需要持久化一次,那么 “双1” 配置 每个事务提交的时候都会刷盘 2 次,一次是 `binlog`,一次是 `redo log`。 + +为了控制 redo log 的写入策略,innodb_flush_log_at_trx_commit 会有下面 3 中取值: + +- **0:每次提交事务只写在 redo log buffer 中** +- **1:每次提交事务持久化到磁盘** +- **2:每次提交事务写到 文件系统的 page cache 中** + +redo log 实际的触发 fsync 操作写盘包含以下几个场景: + +- **后台每隔 1 秒钟的线程轮询** +- **innodb_flush_log_at_trx_commit 设置成 1 时,事务提交时触发** +- **innodb_log_buffer_size 是设置 redo log 大小的参数**。当 redo log buffer 达到 innodb_log_buffer_size / 2 时,也会触发一次 fsync + + + +### binlog(归档日志) + +通过MySQL的架构可以看出,MySQL服务端主要分为2大块:Server层 和 引擎层。redo log 本身是 InnoDB所特有的日志,而Server 层也有自己的日志,那就是binlog。至于为什么会有两种日志,这就是历史原因了。最开始,MySQL原生的存储引擎是MyISAM。它本身不支持事务的特性,而InnoDB 是另外一家公司以插件的形式开发的,为了支持事务等特性,引入了 redo log。两者主要有以下区别: + +![binlog和redolog区别](images/Database/binlog和redolog区别.jpg) + +结合到update 语句中,如:对ID=2的这一行的c 值进行更改,执行流程如下所示: + +![update的binlog执行流程](images/Database/update的binlog执行流程.jpg) + +从上面的执行流程可以看出,对于redo log 的写入拆成了 2 个步骤,这就是**两阶段提交**。 + + + +**binlog写入流程** + +事务执行过程中,binlog 首先会被写到 binlog cache 中;事务提交的时候,再讲binlog cache 写到 binlog 文件中。一个事务的 binlog 是原子的,无论多大都需要保证完整性。 + +系统为每个客户端线程分配一个 binlog cache,其大小由 binlog_cache_size 控制。如果binlog cache 超过阀值,就会临时持久化到磁盘。当事务提交的时候,再将 binlog cache 中完整的事务持久化到磁盘中,并清空 binlog cache。 + +![binlog写入流程](images/Database/binlog写入流程.jpg) + +从上面可以看出,每个客户端线程都有自己独立的 binlog cache,但是会共享一份 binlog files。上面的 write 是指把binlog cache 写到文件系统的 page cache,并没有写入到磁盘中,因此速度较快。fsync 是实际的写盘操作,占用磁盘的 IOPS。write 和 fsync 的写入时机,是由sync_binlog 控制的: + +- sync_binlog=0:每次事务提交都只 write,不 fsync +- sync_binlog=1:每次事务提交都会fsync +- sync_binlog=N(N>1):每次提交事务都会 write,累计N 个后再执行 fsync + +在出现 IO 瓶颈的情况下,可以考虑将 sync_binlog 设置成一个大的值。比较常见的是将 N设置为 100~1000。但是存在的风险是,当主机异常重启时会丢失 N 个最近提交的事务 binlog。 + + + +## 查询过程 + +![MySQL查询过程](images/Database/MySQL查询过程.png) + + + +## 全局锁表锁&行锁 + +### 全局锁 + +**FTWRL** + +全局锁就是对整个数据库实例加锁,MySQL 提供了 flush tables with read lock (FTWRL) 的方式去加全局锁。当你需要让整个库处于只读状态的时候,就可以使用这个命令了,之后所有线程的更改操作都会被阻塞。 + + + +**mysqldump** + +mysqldump 是官方提供的备份工具,可以通过 --single-transaction 参数来启用可重复读隔离级别,从而可以拿到一个一致性视图。 + + + +**set global readonly = true** + +通过上述命令可以让全库进入只读状态,但是在开发当中,事务框架往往会利用这个参数来处理读写分离。所以通常情况下,还是不建议使用这种方式。 + + + +### 表级锁 + +MySQL 的表级锁有 2 种:表锁和元数据锁。 + +**表锁** + +表锁可以使用 lock tables T read/write , 可以使用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放锁。 + + + +**MDL (metadata lock)** + +MDL 没有显示的命令,当执行改表语句时,MDL 会保证读写的正确性。MySQL 在 5.5 版本以后引入了 MDL 锁,当对一个表做增删改查的时候,加 MDL 读锁;当要多表结构做变更的时候,加 MDL 写锁。 + +- MDL 读锁之间不互斥,因此可以有多个线程同时对一张表 增删改查 +- MDL 读-写、写-写之间是互斥的,因此如果同时有 2 个线程给表加字段,则需要顺序执行 + + + +### 行锁 + +**两阶段锁** + +当使用update 更新数据时,会对 where 条件扫描到的行加行锁。在 InnoDB 事务中,行锁是在需要的时候才加上的,并且在事务提交后释放的,这就是两阶段锁协议。所以我们在更新数据时,应尽量把容易产生并发更新的行放在事务末端执行。 + + + +**死锁** + +在事务对不同行加锁的时候,就很有可能出现死锁的情况。 + +MySQL 有 2 中策略去解决死锁: + +- **超时等待**:可以通过 innodb_lock_wait_timeout 来设置,默认 50S。(一般不采用,因为 50S 对应用来说是不可接受的,并且这个值的设置也没有合适的估算值) +- **死锁检测**:发现死锁后,主动回滚其中一个事务。可以通过 innodb_deadlock_detect 设置为 on + +上面 2 种死锁的解决方法,都是MySQL 本身提供的。我们实际开发的过程当中,往往是需要自己从业务的角度去考虑,如何规避死锁和解决死锁的问题: + +- **按规则加锁**:如 A 转账给 B,同时 B 也转账给 A,此时就很可能出现死锁。但是如果我们根据 userId 的升序规则去加锁,就不会产生死锁的问题了 +- **控制并发度**:如支付系统中的账户系统,可以将总账户拆分成子账户,然后每个子账户是一个独立的锁实体 + + + +## mysql复制原理 + +### 基于语句的复制 + +基于语句的复制模式下,主库会记录那些造成数据更改的查询,当备库读取并重放这些事件时,实际上只把主库上执行过的SQL再执行一遍。 + +- **优点** + + 最明显的好处是实现相当简单。理论上讲,简单地记录和执行这些语句,能够让备库保持同步。另外好处是binlog日志里的事件更加紧凑,所以相对而言,基于语句的模式不会使用太多带宽。一条更新好几兆数据的语句在二进制日志里可能只占用几十字节。 + +- **缺点** + + 有些数据更新语句,可能依赖其他因素。例如,同一条sql在主库和备库上执行的时间可能稍微或很不相同,因此在传输的binlog日志中,除了查询语句,还包括一些元数据信息,如当前的时间戳。即便如此,还存在着一些无法被正确复制的SQL,例如,使用CURRENT_USER()函数语句。存储过程和触发器在使用基于语句的复制模式时也可能存在问题。另外一个问题是更新必须是串行的。这需要更多的锁。并且不是所有的存储引擎都支持这种复制模式。 + + + +### 基于行的复制 + +MySQL5.1开始支持基于行的复制,这种方式会将实际数据记录在二进制日志中,跟其他数据库的实现比较相像。 + +- **优点** + + 最大的好处是可以正确的复制每一行,一些语句可以呗更加有效地复制。由于无需重放更新主库数据的查询,使用基于行的复制模式能够更高效地复制数据。重放一些查询的代价会很高 + +- **缺点** + + 全表更行,使用基于行复制开销会大很多,因为每一行数据都会呗记录到二进制日志中,这使得二进制日志时间非常庞大 + + + +# 高可用方案 + +我们在考虑MySQL数据库的高可用架构时,主要考虑如下几方面: + +- 如果数据库发生了宕机或者意外中断等故障,能尽快恢复数据库的可用性,尽可能的减少停机时间,保证业务不会因为数据库的故障而中断 +- 用作备份、只读副本等功能的非主节点的数据应该和主节点的数据实时或者最终保持一致 +- 当业务发生数据库切换时,切换前后的数据库内容应当一致,不会因为数据缺失或者数据不一致而影响业务 + +关于对高可用的分级我们暂不做详细的讨论,这里只讨论常用高可用方案的优缺点以及选型。 + + + +随着人们对数据一致性要求不断的提高,越来越多的方法被尝试用来解决分布式数据一致性的问题,如MySQL自身的优化、MySQL集群架构的优化、Paxos、Raft、2PC算法的引入等。 + +而使用分布式算法用来解决MySQL数据库数据一致性问题的方法,也越来越被人们所接受,一系列成熟的产品如PhxSQL、MariaDB Galera Cluster、Percona XtraDB Cluster等越来越多的被大规模使用。 + +随着官方MySQL Group Replication的GA,使用分布式协议来解决数据一致性问题已经成为了主流的方向。期望越来越多优秀的解决方案被提出,MySQL高可用问题也可以被更好的解决。 + + + +## 主从或主主半同步复制 + +使用双节点数据库,搭建单向或者双向的半同步复制。在5.7以后的版本中,由于lossless replication、logical多线程复制等一些列新特性的引入,使得MySQL原生半同步复制更加可靠。常见架构如下: + +![主从或主主半同步复制](images/Database/主从或主主半同步复制.jpg) + +通常会和Proxy、Keepalived等第三方软件同时使用,即可以用来监控数据库的健康,又可以执行一系列管理命令。如果主库发生故障,切换到备库后仍然可以继续使用数据库。 + +**优点** + +- 架构比较简单,使用原生半同步复制作为数据同步的依据 +- 双节点,没有主机宕机后的选主问题,直接切换即可 +- 双节点,需求资源少,部署简单 + +**缺点** + +- 完全依赖于半同步复制,如果半同步复制退化为异步复制,数据一致性无法得到保证 +- 需要额外考虑HAProxy、Keepalived的高可用机制 + + + +## 半同步复制优化 + +半同步复制机制是可靠的。如果半同步复制一直是生效的,那么可以认为数据是一致的。但是由于网络波动等一些客观原因,导致半同步复制发生超时而切换为异步复制,这时便不能保证数据的一致性。所以尽可能的保证半同步复制,就可以提高数据的一致性。 + +该方案同样使用双节点架构,但是在原有半同复制的基础上做了功能上的优化,使半同步复制的机制变得更加可靠。可参考的优化方案如下: + +### 双通道复制 + +![双通道复制](images/Database/双通道复制.jpg) + +半同步复制由于发生超时后,复制断开,当再次建立起复制时,同时建立两条通道,其中一条半同步复制通道从当前位置开始复制,保证从机知道当前主机执行的进度。另外一条异步复制通道开始追补从机落后的数据。当异步复制通道追赶到半同步复制的起始位置时,恢复半同步复制。 + + + +### binlog文件服务器 + +![binlog文件服务器](images/Database/binlog文件服务器.jpg) + +搭建两条半同步复制通道,其中连接文件服务器的半同步通道正常情况下不启用,当主从的半同步复制发生网络问题退化后,启动与文件服务器的半同步复制通道。当主从半同步复制恢复后,关闭与文件服务器的半同步复制通道。 + +**优点** + +- 双节点,需求资源少,部署简单 +- 架构简单,没有选主的问题,直接切换即可 +- 相比于原生复制,优化后的半同步复制更能保证数据的一致性 + +**缺点** + +- 需要修改内核源码或者使用MySQL通信协议。需要对源码有一定的了解,并能做一定程度的二次开发 +- 依旧依赖于半同步复制,没有从根本上解决数据一致性问题 + + + +## 高可用架构优化 + +将双节点数据库扩展到多节点数据库,或者多节点数据库集群。可以根据自己的需要选择一主两从、一主多从或者多主多从的集群。由于半同步复制,存在接收到一个从机的成功应答即认为半同步复制成功的特性,所以多从半同步复制的可靠性要优于单从半同步复制的可靠性。并且多节点同时宕机的几率也要小于单节点宕机的几率,所以多节点架构在一定程度上可以认为高可用性是好于双节点架构。 + +但由于数据库数量较多,所以需要数据库管理软件来保证数据库的可维护性。可以选择MMM、MHA或者各个版本的Proxy等等。常见方案如下: + +### MHA+多节点集群 + +![MHA+多节点集群](images/Database/MHA+多节点集群.jpg) + +MHA Manager会定时探测集群中的master节点,当master出现故障时,它可以自动将最新数据的slave提升为新的master,然后将所有其他的slave重新指向新的master,整个故障转移过程对应用程序完全透明。MHA Node运行在每台MySQL服务器上,主要作用是切换时处理二进制日志,确保切换尽量少丢数据。MHA也可以扩展到如下的多节点集群: + +![MHA-Manager](images/Database/MHA-Manager.jpg) + +**优点** + +- 可以进行故障的自动检测和转移 +- 可扩展性较好,可以根据需要扩展MySQL的节点数量和结构 +- 相比于双节点的MySQL复制,三节点/多节点的MySQL发生不可用的概率更低 + +**缺点** + +- 至少需要三节点,相对于双节点需要更多的资源 +- 逻辑较为复杂,发生故障后排查问题,定位问题更加困难 +- 数据一致性仍然靠原生半同步复制保证,仍然存在数据不一致的风险 +- 可能因为网络分区发生脑裂现象。 +- 在此我向大家推荐一个架构学习交流群。交流学习群号:575745314 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多 + + + +### ZooKeeper+Proxy + +ZooKeeper使用分布式算法保证集群数据的一致性,使用ZooKeeper可以有效的保证Proxy的高可用性,可以较好地避免网络分区现象的产生。 + +![ZooKeeper+Proxy](images/Database/ZooKeeper+Proxy.jpg) + +**优点** + +- 较好的保证了整个系统的高可用性,包括Proxy、MySQL +- 扩展性较好,可以扩展为大规模集群 + +**缺点** + +- 数据一致性仍然依赖于原生的mysql半同步复制 +- 引入ZK,整个系统的逻辑变得更加复杂 + + + +## 共享存储 + +共享存储实现了数据库服务器和存储设备的解耦,不同数据库之间的数据同步不再依赖于MySQL的原生复制功能,而是通过磁盘数据同步的手段,来保证数据的一致性。 + +### SAN共享储存 + +SAN的概念是允许存储设备和处理器(服务器)之间建立直接的高速网络(与LAN相比)连接,通过这种连接实现数据的集中式存储。常用架构如下: + +![SAN共享储存](images/Database/SAN共享储存.jpg) + +使用共享存储时,MySQL服务器能够正常挂载文件系统并操作,如果主库发生宕机,备库可以挂载相同的文件系统,保证主库和备库使用相同的数据。 + +**优点** + +- 两节点即可,部署简单,切换逻辑简单 +- 很好的保证数据的强一致性 +- 不会因为MySQL的逻辑错误发生数据不一致的情况 + +**缺点** + +- 需要考虑共享存储的高可用 +- 价格昂贵 + + + +### DRBD磁盘复制 + +DRBD是一种基于软件、基于网络的块复制存储解决方案,主要用于对服务器之间的磁盘、分区、逻辑卷等进行数据镜像,当用户将数据写入本地磁盘时,还会将数据发送到网络中另一台主机的磁盘上,这样的本地主机(主节点)与远程主机(备节点)的数据就可以保证实时同步。常用架构如下: + +![DRBD磁盘复制](images/Database/DRBD磁盘复制.jpg) + +当本地主机出现问题,远程主机上还保留着一份相同的数据,可以继续使用,保证了数据的安全。DRBD是Linux内核模块实现的快级别的同步复制技术,可以与SAN达到相同的共享存储效果。 + +**优点** + +- 两节点即可,部署简单,切换逻辑简单 +- 相比于SAN储存网络,价格低廉 +- 保证数据的强一致性 + +**缺点** + +- 对IO性能影响较大 +- 从库不提供读操作 + + + +## 分布式协议 + +分布式协议可以很好地解决数据一致性问题。比较常见的方案如下: + +### MySQL Cluster + +MySQL Cluster是官方集群的部署方案,通过使用NDB存储引擎实时备份冗余数据,实现数据库的高可用性和数据一致性。 + +![MySQL-Cluster](images/Database/MySQL-Cluster.jpg) + +**优点** + +- 全部使用官方组件,不依赖于第三方软件 +- 可以实现数据的强一致性 + +**缺点** + +- 国内使用的较少 +- 配置较复杂,需要使用NDB储存引擎,与MySQL常规引擎存在一定差异 +- 至少三节点 + + + +### Galera + +基于Galera的MySQL高可用集群, 是多主数据同步的MySQL集群解决方案,使用简单,没有单点故障,可用性高。常见架构如下: + +![MySQL-Galera](images/Database/MySQL-Galera.jpg) + +**优点** + +- 多主写入,无延迟复制,能保证数据强一致性 +- 有成熟的社区,有互联网公司在大规模的使用 +- 自动故障转移,自动添加、剔除节点 + +**缺点** + +- 需要为原生MySQL节点打wsrep补丁 +- 只支持innodb储存引擎 +- 至少三节点 + + + +### Paxos + +Paxos算法解决的问题是一个分布式系统如何就某个值(决议)达成一致。这个算法被认为是同类算法中最有效的。Paxos与MySQL相结合可以实现在分布式的MySQL数据的强一致性。常见架构如下: + +![MySQL-Paxos](images/Database/MySQL-Paxos.jpg) + +**优点** + +- 多主写入,无延迟复制,能保证数据强一致性 +- 有成熟理论基础 +- 自动故障转移,自动添加、剔除节点 + +**缺点** + +- 只支持InnoDB储存引擎 +- 至少三节点 +- 在此我向大家推荐一个架构学习交流群。交流学习群号:575745314 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多 + + + +## 主从延迟 + +在实际的生产环境中,由单台MySQL作为独立的数据库是完全不能满足实际需求的,无论是在安全性,高可用性以及高并发等各个方面。因此,一般来说都是通过集群主从复制(Master-Slave)的方式来同步数据,再通过读写分离(MySQL-Proxy)来提升数据库的并发负载能力进行部署与实施。总结MySQL主从集群带来的作用是: + +- 提高数据库负载能力,主库执行读写任务(增删改),备库仅做查询 +- 提高系统读写性能、可扩展性和高可用性 +- 数据备份与容灾,备库在异地,主库不存在了,备库可以立即接管,无须恢复时间 + +![MySQL主从集群](images/Database/MySQL主从集群.jpg) + + + +### biglog + +**binlog是什么?有什么作用?** + +用于记录数据库执行的写入性操作(不包括查询)信息,以二进制的形式保存在磁盘中。可以简单理解为记录的就是sql语句。binlog 是 mysql 的逻辑日志,并且由 `Server`层进行记录,使用任何存储引擎的 mysql 数据库都会记录 binlog 日志。在实际应用中, binlog 的主要使用场景有两个: + +- 用于主从复制,在主从结构中,binlog 作为操作记录从 master 被发送到 slave,slave服务器从 master 接收到的日志保存到 relay log 中 +- 用于数据备份,在数据库备份文件生成后,binlog保存了数据库备份后的详细信息,以便下一次备份能从备份点开始 + + + +**日志格式** + +binlog日志有三种格式,分别为STATMENT 、 ROW 和 MIXED。在 MySQL 5.7.7 之前,默认的格式是 STATEMENT , MySQL 5.7.7 之后,默认值是 ROW。日志格式通过 `binlog-format` 指定。 + +- **STATMENT** :基于 SQL 语句的复制,每一条会修改数据的sql语句会记录到 binlog 中 +- **ROW** :基于行的复制 +- **MIXED** :基于 STATMENT 和 ROW 两种模式的混合复制,比如一般的数据操作使用 row 格式保存,有些表结构的变更语句,使用 statement 来记录 + + + +我们还可以通过mysql提供的查看工具mysqlbinlog查看文件中的内容,例如: + +```mysql +mysqlbinlog mysql-bin.00001 | more +``` + +binlog文件大小和个数会不断的增加,后缀名会按序号递增,例如`mysql-bin.00002`等。 + + + +### 主从复制原理 + +![主从复制原理](images/Database/主从复制原理.jpg) + +可以看到mysql主从复制需要三个线程:**master(binlog dump thread)、slave(I/O thread 、SQL thread)** + +- **binlog dump线程:** 主库中有数据更新时,根据设置的binlog格式,将更新的事件类型写入到主库的binlog文件中,并创建log dump线程通知slave有数据更新。当I/O线程请求日志内容时,将此时的binlog名称和当前更新的位置同时传给slave的I/O线程 +- **I/O线程:** 该线程会连接到master,向log dump线程请求一份指定binlog文件位置的副本,并将请求回来的binlog存到本地的relay log中 +- **SQL线程:** 该线程检测到relay log有更新后,会读取并在本地做redo操作,将发生在主库的事件在本地重新执行一遍,来保证主从数据同步 + + + +**基本过程总结** + +- 主库写入数据并且生成binlog文件。该过程中MySQL将事务串行的写入二进制日志,即使事务中的语句都是交叉执行的 +- 在事件写入二进制日志完成后,master通知存储引擎提交事务 +- 从库服务器上的IO线程连接Master服务器,请求从执行binlog日志文件中的指定位置开始读取binlog至从库 +- 主库接收到从库IO线程请求后,其上复制的IO线程会根据Slave的请求信息分批读取binlog文件然后返回给从库的IO线程 +- Slave服务器的IO线程获取到Master服务器上IO线程发送的日志内容、日志文件及位置点后,会将binlog日志内容依次写到Slave端自身的Relay Log(即中继日志)文件的最末端,并将新的binlog文件名和位置记录到`master-info`文件中,以便下一次读取master端新binlog日志时能告诉Master服务器从新binlog日志的指定文件及位置开始读取新的binlog日志内容 +- 从库服务器的SQL线程会实时监测到本地Relay Log中新增了日志内容,然后把RelayLog中的日志翻译成SQL并且按照顺序执行SQL来更新从库的数据 +- 从库在`relay-log.info`中记录当前应用中继日志的文件名和位置点以便下一次数据复制 + + + +**并行复制** + +在MySQL 5.6版本之前,Slave服务器上有两个线程I/O线程和SQL线程。I/O线程负责接收二进制日志,SQL线程进行回放二进制日志。如果在MySQL 5.6版本开启并行复制功能,那么SQL线程就变为了coordinator线程,coordinator线程主要负责以前两部分的内容: + +![并行复制](images/Database/并行复制.jpg) + +**上图的红色框框部分就是实现并行复制的关键所在**。这意味着coordinator线程并不是仅将日志发送给worker线程,自己也可以回放日志,但是所有可以并行的操作交付由worker线程完成。coordinator线程与worker是典型的生产者与消费者模型。 + +![coordinator线程](images/Database/coordinator线程.jpg) + +不过到MySQL 5.7才可称为真正的并行复制,这其中最为主要的原因就是slave服务器的回放与主机是一致的即master服务器上是怎么并行执行的slave上就怎样进行并行回放。不再有库的并行复制限制,对于二进制日志格式也无特殊的要求。为了兼容MySQL 5.6基于库的并行复制,5.7引入了新的变量`slave-parallel-type`,其可以配置的值有: + +- **DATABASE**:默认值,基于库的并行复制方式 +- **LOGICAL_CLOCK**:基于组提交的并行复制方式 + + + +**按库并行** + +每个 worker 线程对应一个 hash 表,用于保存当前正在这个worker的执行队列里的事务所涉及到的库。其中hash表里的key是数据库名,用于决定分发策略。该策略的优点是构建hash值快,只需要库名,同时对于binlog的格式没有要求。但这个策略的效果,只有在主库上存在多个DB,且各个DB的压力均衡的情况下,这个策略效果好。因此,对于主库上的表都放在同一个DB或者不同DB的热点不同,则起不到多大效果: + +![按库并行](images/Database/按库并行.jpg) + + + +**组提交优化** + +该特性如下: + +- 能够同一组里提交的事务,定不会修改同一行 +- 主库上可以并行执行的事务,从库上也一定可以并行执行 + +具体是如何实现的: + +- 在同一组里面一起提交的事务,会有一个相同的`commit_id`,下一组为`commit_id+1`,该`commit_id`会直接写到binlog中 +- 在从库使用时,相同`commit_id`的事务会被分发到多个worker并行执行,直到这一组相同的`commit_id`执行结束后,coordinator再取下一批 + + + +### 主从延迟 + +根据主从复制的原理可以看出,两者之间是存在一定时间的数据不一致,也就是所谓的主从延迟。导致主从延迟的时间点: + +- 主库 A 执行完成一个事务,写入 binlog,该时刻记为T1 +- 传给从库B,从库接受完这个binlog的时刻记为T2 +- 从库B执行完这个事务,该时刻记为T3 + +那么所谓主从延迟,就是同一个事务,从库执行完成的时间和主库执行完成的时间之间的差值,即T3-T1。我们也可以通过在从库执行`show slave status`,返回结果会显示`seconds_behind_master`,表示当前从库延迟了多少秒。 + +**seconds_behind_master如何计算的?** + +- 每一个事务的binlog都有一个时间字段,用于记录主库上写入的时间 +- 从库取出当前正在执行的事务的时间字段,跟当前系统的时间进行相减,得到的就是`seconds_behind_master`,也就是前面所描述的T3-T1 + + + +**为什么会主从延迟?** + +正常情况下,如果网络不延迟,那么日志从主库传给从库的时间是相当短,所以T2-T1可以基本忽略。最直接的影响就是从库消费中转日志(relaylog)的时间段,而造成原因一般是以下几种: + +- **从库的机器性能比主库要差** + + 比如将20台主库放在4台机器,把从库放在一台机器。这个时候进行更新操作,由于更新时会触发大量读操作,导致从库机器上的多个从库争夺资源,导致主从延迟。不过,目前大部分部署都是采取主从使用相同规格的机器部署 + +- **从库的压力大** + + 按照正常的策略,读写分离,主库提供写能力,从库提供读能力。将进行大量查询放在从库上,结果导致从库上耗费了大量的CPU资源,进而影响了同步速度,造成主从延迟。对于这种情况,可以通过一主多从,分担读压力;也可以采取binlog输出到外部系统,比如Hadoop,让外部系统提供查询能力 + +- **大事务的执行** + + 一旦执行大事务,那么主库必须要等到事务完成之后才会写入binlog。比如主库执行了一条insert … select非常大的插入操作,该操作产生了近几百G的binlog文件传输到只读节点,进而导致了只读节点出现应用binlog延迟。因此,DBA经常会提醒开发,不要一次性地试用delete语句删除大量数据,尽可能控制数量,分批进行 + +- **主库的DDL(alter、drop、create)** + - 只读节点与主库的DDL同步是串行进行,如果DDL操作在主库执行时间很长,那么从库也会消耗同样的时间,比如在主库对一张500W的表添加一个字段耗费了10分钟,那么从节点上也会耗费10分钟 + - 从节点上有一个执行时间非常长的的查询正在执行,那么这个查询会堵塞来自主库的DDL,表被锁,直到查询结束为止,进而导致了从节点的数据延迟 + +- **锁冲突** + + 锁冲突问题也可能导致从节点的SQL线程执行慢,比如从机上有一些select .... for update的SQL,或者使用了MyISAM引擎等 + +- **从库的复制能力** + + 一般场景中,因偶然情况导致从库延迟了几分钟,都会在从库恢复之后追上主库。但若是从库执行速度低于主库,且主库持续具有压力,就会导致长时间主从延迟,很有可能就是从库复制能力的问题。 + +从库上的执行,即`sql_thread`更新逻辑,在5.6版本之前,是只支持单线程,那么在主库并发高、TPS高时,就会出现较大的主从延迟。因此,MySQL自5.7版本后就已经支持并行复制了。可以在从服务上设置 `slave_parallel_workers`为一个大于0的数,然后把`slave_parallel_type`参数设置为`LOGICAL_CLOCK`,这就可以了 + +```mysql +mysql> show variables like 'slave_parallel%'; ++------------------------+----------+ +| Variable_name | Value | ++------------------------+----------+ +| slave_parallel_type | DATABASE | +| slave_parallel_workers | 0 | ++------------------------+----------+ +``` + + + +**怎么减少主从延迟?** + +主从同步问题永远都是一致性和性能的权衡,得看实际的应用场景,若想要减少主从延迟的时间,可以采取下面的办法: + +- 降低多线程大事务并发的概率,优化业务逻辑 +- 优化SQL,避免慢SQL,减少批量操作,建议写脚本以update-sleep这样的形式完成 +- 提高从库机器的配置,减少主库写binlog和从库读binlog的效率差 +- 尽量采用短的链路,也就是主库和从库服务器的距离尽量要短,提升端口带宽,减少binlog传输的网络延时 +- 实时性要求的业务读强制走主库,从库只做灾备,备份 diff --git a/DevOps.md b/DevOps.md index fc9fc0d..7a682e9 100644 --- a/DevOps.md +++ b/DevOps.md @@ -256,13 +256,41 @@ PS Perm Generation: #当前的 “持久代” 内存分布 **统计一【jmap -histo】**:统计所有类的实例数量和所占用的内存容量 ```powershell -[root@localhost ~]# jmap -histo 7243 num #instances #bytes class name---------------------------------------------- 1: 8969 19781168 [B 2: 1835 2296720 [I 3: 19735 2050688 [C 4: 3448 385608 java.lang.Class 5: 3829 371456 [Ljava.lang.Object; 6: 14634 351216 java.lang.String 7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node 8: 6257 100112 java.lang.Object 9: 2155 68960 java.util.HashMap$Node 10: 723 63624 java.lang.reflect.Method 11: 49 56368 [Ljava.util.concurrent.ConcurrentHashMap$Node; 12: 830 46480 java.util.zip.ZipFile$ZipFileInputStream 13: 1146 45840 java.lang.ref.Finalizer ...... +[root@localhost ~]# jmap -histo 7243 + num #instances #bytes class name +---------------------------------------------- + 1: 8969 19781168 [B + 2: 1835 2296720 [I + 3: 19735 2050688 [C + 4: 3448 385608 java.lang.Class + 5: 3829 371456 [Ljava.lang.Object; + 6: 14634 351216 java.lang.String + 7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node + 8: 6257 100112 java.lang.Object + 9: 2155 68960 java.util.HashMap$Node + 10: 723 63624 java.lang.reflect.Method + 11: 49 56368 [Ljava.util.concurrent.ConcurrentHashMap$Node; + 12: 830 46480 java.util.zip.ZipFile$ZipFileInputStream + 13: 1146 45840 java.lang.ref.Finalizer + ...... ``` **统计二【jmap -histo】**:查看对象数最多的对象,并过滤Map关键词,然后按降序排序输出 ```shell -[root@localhost ~]# jmap -histo 7243 |grep Map|sort -k 2 -g -r|lessTotal 96237 26875560 7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node 9: 2155 68960 java.util.HashMap$Node 18: 563 27024 java.util.HashMap 21: 505 20200 java.util.LinkedHashMap$Entry 16: 337 34880 [Ljava.util.HashMap$Node; 27: 336 16128 gnu.trove.THashMap 56: 163 6520 java.util.WeakHashMap$Entry 60: 127 6096 java.util.WeakHashMap 38: 127 10144 [Ljava.util.WeakHashMap$Entry; 53: 126 7056 java.util.LinkedHashMap...... +[root@localhost ~]# jmap -histo 7243 |grep Map|sort -k 2 -g -r|less +Total 96237 26875560 + 7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node + 9: 2155 68960 java.util.HashMap$Node + 18: 563 27024 java.util.HashMap + 21: 505 20200 java.util.LinkedHashMap$Entry + 16: 337 34880 [Ljava.util.HashMap$Node; + 27: 336 16128 gnu.trove.THashMap + 56: 163 6520 java.util.WeakHashMap$Entry + 60: 127 6096 java.util.WeakHashMap + 38: 127 10144 [Ljava.util.WeakHashMap$Entry; + 53: 126 7056 java.util.LinkedHashMap +...... ``` @@ -270,7 +298,19 @@ PS Perm Generation: #当前的 “持久代” 内存分布 **统计三【jmap -histo】**:统计实例数量最多的前10个类 ```shell -[root@localhost ~]# jmap -histo 7243 | sort -n -r -k 2 | head -10 num #instances #bytes class name----------------------------------------------Total 96237 26875560 3: 19735 2050688 [C 6: 14634 351216 java.lang.String 1: 8969 19781168 [B 7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node 8: 6257 100112 java.lang.Object 5: 3829 371456 [Ljava.lang.Object; 4: 3448 385608 java.lang.Class 9: 2155 68960 java.util.HashMap$Node 2: 1835 2296720 [I +[root@localhost ~]# jmap -histo 7243 | sort -n -r -k 2 | head -10 + num #instances #bytes class name +---------------------------------------------- +Total 96237 26875560 + 3: 19735 2050688 [C + 6: 14634 351216 java.lang.String + 1: 8969 19781168 [B + 7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node + 8: 6257 100112 java.lang.Object + 5: 3829 371456 [Ljava.lang.Object; + 4: 3448 385608 java.lang.Class + 9: 2155 68960 java.util.HashMap$Node + 2: 1835 2296720 [I ``` @@ -278,7 +318,19 @@ PS Perm Generation: #当前的 “持久代” 内存分布 **统计四【jmap -histo】**:统计合计容量最多的前10个类 ```shell -[root@localhost ~]# jmap -histo 7243 | sort -n -r -k 3 | head -10 num #instances #bytes class name----------------------------------------------Total 96237 26875560 1: 8969 19781168 [B 2: 1835 2296720 [I 3: 19735 2050688 [C 4: 3448 385608 java.lang.Class 5: 3829 371456 [Ljava.lang.Object; 6: 14634 351216 java.lang.String 7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node 8: 6257 100112 java.lang.Object 9: 2155 68960 java.util.HashMap$Node +[root@localhost ~]# jmap -histo 7243 | sort -n -r -k 3 | head -10 + num #instances #bytes class name +---------------------------------------------- +Total 96237 26875560 + 1: 8969 19781168 [B + 2: 1835 2296720 [I + 3: 19735 2050688 [C + 4: 3448 385608 java.lang.Class + 5: 3829 371456 [Ljava.lang.Object; + 6: 14634 351216 java.lang.String + 7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node + 8: 6257 100112 java.lang.Object + 9: 2155 68960 java.util.HashMap$Node ``` **dump注意事项** @@ -306,6 +358,234 @@ PS Perm Generation: #当前的 “持久代” 内存分布 + + +## jhat + +jhat(JVM Heap Analysis Tool)命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看。在此要注意,一般不会直接在服务器上进行分析,因为jhat是一个耗时并且耗费硬件资源的过程,一般把服务器生成的dump文件复制到本地或其他机器上进行分析。 + +```powershell +# 解析Java堆转储文件,并启动一个 web server +jhat heapDump.dump +``` + + + +## jconsole + +jconsole(Java Monitoring and Management Console)是一个Java GUI监视工具,可以以图表化的形式显示各种数据,并可通过远程连接监视远程的服务器VM。用java写的GUI程序,用来监控VM,并可监控远程的VM,非常易用,而且功能非常强。命令行里打jconsole,选则进程就可以了。 + +**第一步**:在远程机的tomcat的catalina.sh中加入配置: + +```powershell +JAVA_OPTS="$JAVA_OPTS -Djava.rmi.server.hostname=192.168.202.121 -Dcom.sun.management.jmxremote" +JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.port=12345" +JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=true" +JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.ssl=false" +JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.pwd.file=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.101-3.b13.el7_2.x86_64/jre/lib/management/jmxremote.password" +``` + + + +**第二步**:配置权限文件 + +```powershell +[root@localhost bin]# cd /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.101-3.b13.el7_2.x86_64/jre/lib/management/ +[root@localhost management]# cp jmxremote.password.template jmxremote.password +[root@localhost management]# vi jmxremote.password +``` + +monitorRole QED +controlRole chenqimiao + + + +**第三步**:配置权限文件为600 + +```powershell +[root@localhost management]# chmod 600 jmxremote.password jmxremote.access +``` + +这样基本配置就结束了,下面说两个坑,第一个就是防火墙的问题,要开放指定端口的防火墙,我这里配置的是12345端口,第二个是hostname的问题: + +![jconsole-ip2](images/DevOps/jconsole-ip2.png) + +请将127.0.0.1修改为本地真实的IP,我的服务器IP是192.168.202.121: + +![jconsole-ip](images/DevOps/jconsole-ip.png) + + **第四步**:查看JConsole + +![JConsole-新建连接](images/DevOps/JConsole-新建连接.png) + +![JConsole-Console](images/DevOps/JConsole-Console.png) + + + +## jvisualvm + +jvisualvm(JVM Monitoring/Troubleshooting/Profiling Tool)同jconsole都是一个基于图形化界面的、可以查看本地及远程的JAVA GUI监控工具,Jvisualvm同jconsole的使用方式一样,直接在命令行打入Jvisualvm即可启动,不过Jvisualvm相比,界面更美观一些,数据更实时。 jvisualvm的使用VisualVM进行远程连接的配置和JConsole是一摸一样的,最终效果图如下 + +![jvisualvm](images/DevOps/jvisualvm.png) + + + +**Visual GC(监控垃圾回收器)** + +Java VisualVM默认没有安装Visual GC插件,需要手动安装,JDK的安装目录的bin目露下双击 jvisualvm.sh,即可打开Java VisualVM,点击菜单栏: **工具->插件** 安装Visual GC,最终效果如下图所示: + +![Visual-GC](images/DevOps/Visual-GC.png) + + + +**大dump文件** + +从服务器dump堆内存后文件比较大(5.5G左右),加载文件、查看实例对象都很慢,还提示配置xmx大小。表明给VisualVM分配的堆内存不够,找到$JAVA_HOME/lib/visualvm}/etc/visualvm.conf这个文件,修改: + +```shell +default_options="-J-Xms24m -J-Xmx192m" +``` + +再重启VisualVM就行了。 + + + +## jmc + +jmc(Java Mission Control)是JDK自带的一个图形界面监控工具,监控信息非常全面。JMC打开性能日志后,主要包括**一般信息、内存、代码、线程、I/O、系统、事件** 功能。 + +![jmc-main](images/DevOps/jmc-main.jpg) + +JMC的最主要的特征就是JFR(Java Flight Recorder),是基于JAVA的飞行记录器,JFR的数据是一些列JVM事件的历史纪录,可以用来诊断JVM的性能和操作,收集后的数据可以使用JMC来分析。 + + + +### 启动JFR + +在商业版本上面,JFR默认是关闭的,可以通过在启动时增加参数 `-XX:+UnlockCommercialFeatures -XX:+FlightRecorder` 来启动应用。启动之后,也只是开启了JFR特性,但是还没有开始进行事件记录。这就要通过GUI和命令行了。 + +- **通过Java Mission Control启动JFR** + + 打开Java Mission Control点击对应的JVM启动即可,事件记录有两种模式(如果选择第2种模式,那么JVM会使用一个循环使用的缓存来存放事件数据): + + - 记录固定一段时间的事件(比如:1分钟) + - 持续进行记录 + +- **通过命令行启动JFR** + + 通过在启动的时候,增加参数:`-XX:+FlightRecorderOptions=string` 来启动真正地事件记录,这里的 `string` 可以是以下值(下列参数都可以使用jcmd命令,在JVM运行的时候进行动态调整,[参考地址](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html)): + + - `name=name`:标识recording的名字(一个进程可以有多个recording存在,它们使用名字来进行区分) + - `defaultrecording=`:是否启动recording,默认是false,我们要分析,必须要设置为true + - `setting=paths`:包含JFR配置的文件名字 + - `delay=time`:启动之后,经过多长时间(比如:30s,1h)开始进行recording + - `duration=time`:做多长时间的recording + - `filename=path`:recordding记录到那个文件里面 + - `compress=`:是否对recording进行压缩(gzip),默认为false + - `maxage=time`:在循环使用的缓存中,事件数据保存的最大时长 + - `maxsize=size`:事件数据缓存的最大大小(比如:1024k,1M) + + + +### 常用JFR命令 + +- 启动recording + + 命令格式:`jcmd process_id JFR.start [options_list]`,其中options_list就是上述的参数值。 + +- dump出循环缓存中的数据 + + 命令格式:`jcmd process_id JFR.dump [options_list]`,其中options_list参数的可选值如下: + + - `name=name`:recording的名字 + - `recording=n`:JFR recording的数字(一个标识recording的随机数) + - `filename=path`:dump文件的保存路径 + +- 查看进程中所有recording + + 命令格式:` jcmd process_id JFR.check [verbose]`,不同recording使用名字进行区分,同时JVM还为它分配一个随机数。 + +- 停止recording + + 命令格式:` jcmd process_id JFR.stop [options_list]`,其中options_list参数的可选值如下: + + - `name=name`:要停止的recording名字 + - `recording=n`:要停止的recording的标识数字 + - `discard=boolean`:如果为true,数据被丢弃,而不是写入下面指定的文件当中 + - `filename=path`:写入数据的文件名称 + + + +### 命令启动JFR案例 + +- **第一步**:创建一个包含了你自己配置的JFR模板文件(`template.jfc`)。运行jmc, 然后Window->Flight Recording Template Manage菜单。准备好档案后,就可以导出文件,并移动到要排查问题的环境中 + + ![jmc-jfc](images/DevOps/jmc-jfc.png) + +- **第二步**:由于JFR需要JDK的商业证书,这一步需要解锁jdk的商业特性 + + ```powershell + [root@localhost bin]# jcmd 12234 VM.unlock_commercial_features + 12234: Commercial Features already unlocked. + ``` + +- **第三步**:最后你就可以启动JFR,命令格式如下: + + ```powershell + jcmd JFR.start name=test duration=60s [settings=template.jfc] filename=output.jfr + ``` + + ​ 上述命令立即启动JFR并开始使用 `template.jfc`(在 `$JAVA_HOME/jre/lib/jfr` 下有 `default.jfc` 和 `profile.jfc` 模板)的配置收集 `60s` 的JVM信息,输出到 `output.jfr` 中。一旦记录完成之后,就可以复制.jfr文件到你的工作环境使用jmc GUI来分析。它几乎包含了排查jvm问题需要的所有信息,包括堆dump时的异常信息。使用案例如下: + + ```powershell + [root@localhost bin]# jcmd 12234 JFR.start name=test duration=60s filename=output.jfr + 12234: Started recording 6. The result will be written to: /root/zookeeper-3.4.12/bin/output.jfr + [root@localhost bin]# ls -l + -rw-r--r-- 1 root root 298585 6月 29 11:09 output.jfr + ``` + + + +**JFR(Java Flight Recorder)** + +- Java Mission Control的最主要的特征就是Java Flight Recorder。正如它的名字所示,JFR的数据是一些列JVM事件的历史纪录,可以用来诊断JVM的性能和操作 +- JFR的基本操作就是开启哪些事件(比如:线程由于等待锁而阻塞的事件)。当开启的事件发生了,事件相关的数据会记录到内存或磁盘文件上。记录事件数据的缓存是循环使用的,只有最近发生的事件才能够从缓存中找到,之前的都因为缓存的限制被删除了。Java Mission Control能够对这些事件在界面上进行展示(从JVM的内存中读取或从事件数据文件中读取),我们可以通过这些事件来对JVM的性能进行诊断 +- 事件的类型、缓存的大小、事件数据的存储方式等等都是通过JVM参数、Java Mission Control的GUI界面、jcmd命令来控制的。JFR默认是编译进程序的,因为它的开销很小,一般来说对应用的影响小于1%。不过,如果我们增加了事件的数目、修改了记录事件的阈值,都有可能增加JFR的开销 + + + +### JFR概况 + +​ 下面对GlassFish web服务器进行JFR记录的例子,在这个服务器上面运行着在第2章介绍的股票servlet。Java Mission Control加载完JFR获取的事件之后,大概是下面这个样子: + +![jfr-main](images/DevOps/jfr-main.jpg) + +我们可以看到,通过上图可以看到:CPU使用率,Heap使用率,JVM信息,System Properties,JFR的记录情况等等。 + + + +### JFR内存视图 + +Java Mission Control 可以看到非常多的信息,下图只显示了一个标签的内容。下图显示了JVM 的内存波动非常频繁,因为新生代经常被清除(有意思的是,head的大小并没有增长)。下面左边的面板显示了最近一段时间的垃圾回收情况,包括:GC的时长和垃圾回收的类型。如果我们点击一个事件,右边的面板会展示这个事件的具体情况,包括:垃圾垃圾回收的各个阶段及其统计信息。从面板的标签可以看到,还有很多其它信息,比如:有多少对象被清除了,花了多长时间;GC算法的配置;分配的对象信息等等。在第5章和第6章中,我们会详细介绍。 + +![jfr-memory](images/DevOps/jfr-memory.jpg) + + + +### JFR 代码视图 +这张图也有很多tab,可以看到各个包的使用频率和类的使用情况、异常、编译、代码缓存、类加载情况等等: + +![jfr-code](images/DevOps/jfr-code.jpg) + + + +### JFR事件视图 +下图显示了事件的概述视图: + +![jfr-event](images/DevOps/jfr-event.jpg) + + + ## EclipseMAT 虽然Java虚拟机可以帮我们对内存进行回收,但是其回收的是Java虚拟机不再引用的对象。很多时候我们使用系统的IO流、Cursor、Receiver如果不及时释放,就会导致内存泄漏(OOM)。但是,很多时候内存泄漏的现象不是很明显,比如内部类、Handler相关的使用导致的内存泄漏,或者你使用了第三方library的一些引用,比较消耗资源,但又不是像系统资源那样会引起你足够的注意去手动释放它们。以下通过内存泄漏分析、集合使用率、Hash性能分析和OQL快读定位空集合来使用MAT。 @@ -359,7 +639,15 @@ JAVA虚拟机通过可达性(Reachability)来判断对象是否存活,基本 Shallow Size就是对象本身占用内存的大小,不包含其引用的对象内存,实际分析中作用不大。 常规对象(非数组)的Shallow Size由其成员变量的数量和类型决定。数组的Shallow Size有数组元素的类型(对象类型、基本类型)和数组长度决定。案例如下: ```java -public class String { public final class String {8 Bytes header private char value[]; 4 Bytes private int offset; 4 Bytes private int count; 4 Bytes private int hash = 0; 4 Bytes // ......}// "Shallow size“ of a String ==24 Bytes12345678 +public class String { + public final class String {8 Bytes header + private char value[]; 4 Bytes + private int offset; 4 Bytes + private int count; 4 Bytes + private int hash = 0; 4 Bytes + // ...... +} +// "Shallow size“ of a String ==24 Bytes12345678 ``` Java的对象成员都是些引用。真正的内存都在堆上,看起来是一堆原生的byte[]、char[]、int[],对象本身的内存都很小。所以我们可以看到以Shallow Heap进行排序的Histogram图中,排在第一位第二位的是byte和char。 @@ -413,7 +701,11 @@ Java的对象成员都是些引用。真正的内存都在堆上,看起来是 类似SQL查询语言:Classes:Table、Objects:Rows、Fileds:Cols ```mysql -select * from com.example.mat.Listener# 查找size=0并且未使用过的ArrayListselect * from java.util.ArrayList where size=0 and modCount=01# 查找所有的Activity select * from instanceof android.app.Activity +select * from com.example.mat.Listener +# 查找size=0并且未使用过的ArrayList +select * from java.util.ArrayList where size=0 and modCount=01 +# 查找所有的Activity +select * from instanceof android.app.Activity ``` @@ -534,7 +826,9 @@ select * from com.example.mat.Listener# 查找size=0并且未使用过的Array - **通过OQL查询empty并且未修改过的集合:** ```mysql - select * from java.util.ArrayList where size=0 and modCount=01select * from java.util.HashMap where size=0 and modCount=0select * from java.util.Hashtable where count=0 and modCount=012 + select * from java.util.ArrayList where size=0 and modCount=01 + select * from java.util.HashMap where size=0 and modCount=0 + select * from java.util.Hashtable where count=0 and modCount=012 ``` ![mat-实践四-1](images/DevOps/mat-实践四-1.jpg) @@ -549,268 +843,312 @@ select * from com.example.mat.Listener# 查找size=0并且未使用过的Array -## jhat - -jhat(JVM Heap Analysis Tool)命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看。在此要注意,一般不会直接在服务器上进行分析,因为jhat是一个耗时并且耗费硬件资源的过程,一般把服务器生成的dump文件复制到本地或其他机器上进行分析。 +## 🔥火焰图 -```powershell -# 解析Java堆转储文件,并启动一个 web serverjhat heapDump.dump -``` +火焰图是用来分析程序运行瓶颈的工具。火焰图也可以用来分析 Java 应用。 +### 环境安装 +确认你的机器已经安装了**git、jdk、perl、c++编译器**。 -## jconsole - -jconsole(Java Monitoring and Management Console)是一个javaGUI监视工具,可以以图表化的形式显示各种数据,并可通过远程连接监视远程的服务器VM。用java写的GUI程序,用来监控VM,并可监控远程的VM,非常易用,而且功能非常强。命令行里打jconsole,选则进程就可以了。 - -**第一步**:在远程机的tomcat的catalina.sh中加入配置: - -```powershell -JAVA_OPTS="$JAVA_OPTS -Djava.rmi.server.hostname=192.168.202.121 -Dcom.sun.management.jmxremote"JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.port=12345"JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=true"JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.ssl=false"JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.pwd.file=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.101-3.b13.el7_2.x86_64/jre/lib/management/jmxremote.password" -``` +#### 安装Perl - - -**第二步**:配置权限文件 - -```powershell -[root@localhost bin]# cd /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.101-3.b13.el7_2.x86_64/jre/lib/management/[root@localhost management]# cp jmxremote.password.template jmxremote.password[root@localhost management]# vi jmxremote.password +```shell +wget http://www.cpan.org/src/5.0/perl-5.26.1.tar.gz +tar zxvf perl-5.26.1.tar.gz +cd perl-5.26.1 +./Configure -de +make +make test +make install ``` -monitorRole QED -controlRole chenqimiao +wget后面的路径可以按需更改。安装过程比较耗时间,安装完成后可通过**perl -version**查看是否安装成功。 -**第三步**:配置权限文件为600 +#### C++编译器 -```powershell -[root@localhost management]# chmod 600 jmxremote.password jmxremote.access +```shell +apt-get install g++ ``` -这样基本配置就结束了,下面说两个坑,第一个就是防火墙的问题,要开放指定端口的防火墙,我这里配置的是12345端口,第二个是hostname的问题: - -![jconsole-ip2](images/DevOps/jconsole-ip2.png) - -请将127.0.0.1修改为本地真实的IP,我的服务器IP是192.168.202.121: - -![jconsole-ip](images/DevOps/jconsole-ip.png) - - **第四步**:查看JConsole - -![JConsole-新建连接](images/DevOps/JConsole-新建连接.png) - -![JConsole-Console](images/DevOps/JConsole-Console.png) - - - -## jvisualvm - -jvisualvm(JVM Monitoring/Troubleshooting/Profiling Tool)同jconsole都是一个基于图形化界面的、可以查看本地及远程的JAVA GUI监控工具,Jvisualvm同jconsole的使用方式一样,直接在命令行打入Jvisualvm即可启动,不过Jvisualvm相比,界面更美观一些,数据更实时。 jvisualvm的使用VisualVM进行远程连接的配置和JConsole是一摸一样的,最终效果图如下 - -![jvisualvm](images/DevOps/jvisualvm.png) +一般用于编译c++程序,缺少这个编译器进行make编译c++代码时,会报“g++: not found”的错误。 -**Visual GC(监控垃圾回收器)** +#### clone相关项目 -Java VisualVM默认没有安装Visual GC插件,需要手动安装,JDK的安装目录的bin目露下双击 jvisualvm.sh,即可打开Java VisualVM,点击菜单栏: **工具->插件** 安装Visual GC,最终效果如下图所示: +下载下来所需要的两个项目(这里建议放到data目录下): -![Visual-GC](images/DevOps/Visual-GC.png) +```shell +git clone https://github.com/jvm-profiling-tools/async-profiler +git clone https://github.com/brendangregg/FlameGraph +``` -**大dump文件** +#### 编译项目 -从服务器dump堆内存后文件比较大(5.5G左右),加载文件、查看实例对象都很慢,还提示配置xmx大小。表明给VisualVM分配的堆内存不够,找到$JAVA_HOME/lib/visualvm}/etc/visualvm.conf这个文件,修改: +下载好以后,需要打开async-profiler文件,输入make指令进行编译: ```shell -default_options="-J-Xms24m -J-Xmx192m" +cd async-profiler +make ``` -再重启VisualVM就行了。 +### 生成文件 -## jmc +#### 生成火焰图数据 -jmc(Java Mission Control)是JDK自带的一个图形界面监控工具,监控信息非常全面。JMC打开性能日志后,主要包括**一般信息、内存、代码、线程、I/O、系统、事件** 功能。 +可以从 github 上下载 [async-profiler](https://github.com/jvm-profiling-tools/async-profiler) 的压缩包进行相关操作。进入async-profiler项目的目录下,然后输入如下指令: -![jmc-main](images/DevOps/jmc-main.jpg) +```shell +./profiler.sh -d 60 -o collapsed -f /tmp/test_01.txt ${pid} +``` -JMC的最主要的特征就是JFR(Java Flight Recorder),是基于JAVA的飞行记录器,JFR的数据是一些列JVM事件的历史纪录,可以用来诊断JVM的性能和操作,收集后的数据可以使用JMC来分析。 +上面的-d表示的是持续时长,后面60代表持续采集时间60s,-o表示的是采集规范,这里用的是collapsed,-f后面的路径,表示的是数据采集后生成的数据存放的文件路径(这里放在了/tmp/test_01.txt),${pid}表示的是采集目标进程的pid,回车运行,运行期间阻塞,知道约定时间完成。运行完成后去tmp下看看有没有对应文件。 -**启动JFR** +#### 生成svg文件 -在商业版本上面,JFR默认是关闭的,可以通过在启动时增加参数 `-XX:+UnlockCommercialFeatures -XX:+FlightRecorder` 来启动应用。启动之后,也只是开启了JFR特性,但是还没有开始进行事件记录。这就要通过GUI和命令行了。 +上一步产生的文件里的内容,肉眼是很难看懂的,所以现在[FlameGraph](https://github.com/brendangregg/FlameGraph)的作用就体现出来了,它可以读取该文件并生成直观的火焰图,现在进入该项目目录下面,执行如下语句: -- **通过Java Mission Control启动JFR** +```shell +perl flamegraph.pl --colors=java /tmp/test_01.txt > test_01.svg +``` - 打开Java Mission Control点击对应的JVM启动即可,事件记录有两种模式(如果选择第2种模式,那么JVM会使用一个循环使用的缓存来存放事件数据): +因为是perl文件,这里使用perl指令运行该文件,后面--colors表示着色风格,这里是java,后面的是数据文件的路径,这里是刚刚上面生成的那个文件/tmp/test_01.txt,最后的test_01.svg就是最终生成的火焰图文件存放的路径和文件命名,这里是命名为test_01.svg并保存在当前路径,运行后看到该文件已经存在于当前目录下。 - - 记录固定一段时间的事件(比如:1分钟) - - 持续进行记录 -- **通过命令行启动JFR** - 通过在启动的时候,增加参数:`-XX:+FlightRecorderOptions=string` 来启动真正地事件记录,这里的 `string` 可以是以下值(下列参数都可以使用jcmd命令,在JVM运行的时候进行动态调整,[参考地址](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html)): +#### 展示火焰图 - - `name=name`:标识recording的名字(一个进程可以有多个recording存在,它们使用名字来进行区分) - - `defaultrecording=`:是否启动recording,默认是false,我们要分析,必须要设置为true - - `setting=paths`:包含JFR配置的文件名字 - - `delay=time`:启动之后,经过多长时间(比如:30s,1h)开始进行recording - - `duration=time`:做多长时间的recording - - `filename=path`:recordding记录到那个文件里面 - - `compress=`:是否对recording进行压缩(gzip),默认为false - - `maxage=time`:在循环使用的缓存中,事件数据保存的最大时长 - - `maxsize=size`:事件数据缓存的最大大小(比如:1024k,1M) +现在下载下来该文件,使用浏览器打开,效果如下: -常用JFR命令如下: +![火焰图案例](images/DevOps/火焰图案例.svg) -- 启动recording - 命令格式:`jcmd process_id JFR.start [options_list]`,其中options_list就是上述的参数值。 -- dump出循环缓存中的数据 +### 火焰图案例 - 命令格式:`jcmd process_id JFR.dump [options_list]`,其中options_list参数的可选值如下: +生成的[火焰图案例](images/DevOps/火焰图案例.svg)如下: - - `name=name`:recording的名字 - - `recording=n`:JFR recording的数字(一个标识recording的随机数) - - `filename=path`:dump文件的保存路径 +![火焰图案例](images/DevOps/火焰图案例.jpg) -- 查看进程中所有recording +#### 瓶颈点1 - 命令格式:` jcmd process_id JFR.check [verbose]`,不同recording使用名字进行区分,同时JVM还为它分配一个随机数。 +CoohuaAnalytics$KafkaConsumer:::send方法中Gzip压缩占比较高。已经定位到方法级别,再看代码就快速很多,直接找到具体位置,找到第一个消耗大户:**Gzip压缩** -- 停止recording +![火焰图-瓶颈点1](images/DevOps/火焰图-瓶颈点1.jpg) - 命令格式:` jcmd process_id JFR.stop [options_list]`,其中options_list参数的可选值如下: - - `name=name`:要停止的recording名字 - - `recording=n`:要停止的recording的标识数字 - - `discard=boolean`:如果为true,数据被丢弃,而不是写入下面指定的文件当中 - - `filename=path`:写入数据的文件名称 +#### 瓶颈点2 +展开2这个波峰,查看到这个getOurStackTrace方法占用了大比例的CPU,怀疑代码里面频繁用丢异常的方式获取当前代码栈: -**命令启动JFR案例如下**: +![火焰图-瓶颈点2](images/DevOps/火焰图-瓶颈点2.jpg) -- **第一步**:创建一个包含了你自己配置的JFR模板文件(`template.jfc`)。运行jmc, 然后Window->Flight Recording Template Manage菜单。准备好档案后,就可以导出文件,并移动到要排查问题的环境中 +直接看代码: - ![jmc-jfc](images/DevOps/jmc-jfc.png) +![火焰图-瓶颈点2-代码](images/DevOps/火焰图-瓶颈点2-代码.jpg) -- **第二步**:由于JFR需要JDK的商业证书,这一步需要解锁jdk的商业特性 +果然如推断,找到第二个CPU消耗大户:new Exception().getStackTrace()。 - ```powershell - [root@localhost bin]# jcmd 12234 VM.unlock_commercial_features12234: Commercial Features already unlocked. - ``` -- **第三步**:最后你就可以启动JFR,命令格式如下: - ```powershell - jcmd JFR.start name=test duration=60s [settings=template.jfc] filename=output.jfr - ``` +#### 瓶颈点3 - ​ 上述命令立即启动JFR并开始使用 `template.jfc`(在 `$JAVA_HOME/jre/lib/jfr` 下有 `default.jfc` 和 `profile.jfc` 模板)的配置收集 `60s` 的JVM信息,输出到 `output.jfr` 中。一旦记录完成之后,就可以复制.jfr文件到你的工作环境使用jmc GUI来分析。它几乎包含了排查jvm问题需要的所有信息,包括堆dump时的异常信息。使用案例如下: +展开波峰3,可以看到是这个Gzip解压缩: - ```powershell - [root@localhost bin]# jcmd 12234 JFR.start name=test duration=60s filename=output.jfr12234: Started recording 6. The result will be written to: /root/zookeeper-3.4.12/bin/output.jfr[root@localhost bin]# ls -l-rw-r--r-- 1 root root 298585 6月 29 11:09 output.jfr - ``` - - - -**JFR(Java Flight Recorder)** - -- Java Mission Control的最主要的特征就是Java Flight Recorder。正如它的名字所示,JFR的数据是一些列JVM事件的历史纪录,可以用来诊断JVM的性能和操作 -- JFR的基本操作就是开启哪些事件(比如:线程由于等待锁而阻塞的事件)。当开启的事件发生了,事件相关的数据会记录到内存或磁盘文件上。记录事件数据的缓存是循环使用的,只有最近发生的事件才能够从缓存中找到,之前的都因为缓存的限制被删除了。Java Mission Control能够对这些事件在界面上进行展示(从JVM的内存中读取或从事件数据文件中读取),我们可以通过这些事件来对JVM的性能进行诊断 -- 事件的类型、缓存的大小、事件数据的存储方式等等都是通过JVM参数、Java Mission Control的GUI界面、jcmd命令来控制的。JFR默认是编译进程序的,因为它的开销很小,一般来说对应用的影响小于1%。不过,如果我们增加了事件的数目、修改了记录事件的阈值,都有可能增加JFR的开销 - - - -**① JFR概况** +![火焰图-瓶颈点3](images/DevOps/火焰图-瓶颈点3.jpg) -​ 下面对GlassFish web服务器进行JFR记录的例子,在这个服务器上面运行着在第2章介绍的股票servlet。Java Mission Control加载完JFR获取的事件之后,大概是下面这个样子: - -![jfr-main](images/DevOps/jfr-main.jpg) +定位到具体的代码,可以看到对每个请求的参数进行了gzip解压缩: -我们可以看到,通过上图可以看到:CPU使用率,Heap使用率,JVM信息,System Properties,JFR的记录情况等等。 +![火焰图-瓶颈点3-代码](images/DevOps/火焰图-瓶颈点3-代码.jpg) -**② JFR 内存视图** +# Linux Command -Java Mission Control 可以看到非常多的信息,下图只显示了一个标签的内容。下图显示了JVM 的内存波动非常频繁,因为新生代经常被清除(有意思的是,head的大小并没有增长)。下面左边的面板显示了最近一段时间的垃圾回收情况,包括:GC的时长和垃圾回收的类型。如果我们点击一个事件,右边的面板会展示这个事件的具体情况,包括:垃圾垃圾回收的各个阶段及其统计信息。从面板的标签可以看到,还有很多其它信息,比如:有多少对象被清除了,花了多长时间;GC算法的配置;分配的对象信息等等。在第5章和第6章中,我们会详细介绍。 +![AnalysisTools](images/DevOps/AnalysisTools.png) -![jfr-memory](images/DevOps/jfr-memory.jpg) +## Base +`$`和`#`区别:`$`普通用户即可执行,`#`为root用户才可执行,或普通用户使用`sudo`。 +**JDK环境变量配置** -**③ JFR 代码视图** -这张图也有很多tab,可以看到各个包的使用频率和类的使用情况、异常、编译、代码缓存、类加载情况等等: +```shell +# 方式一:非root用户安装JDK +# 编辑用户根目录下的.bash_profile文件 +vi .bash_profile +# 向.bash_profile文件中导入配置 +export JAVA_HOME=/home/lry/jdk1.7.0_80 +export PATH=$JAVA_HOME/bin:$PATH +export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar -![jfr-code](images/DevOps/jfr-code.jpg) +# 立刻生效 +source .bash_profile +``` +```shell +# 方式二:yum安装JDK配置环境变量 +# 查看CentOS自带JDK是否已安装 +yum list installed |grep java +# 批量卸载JDK +rpm -qa | grep java | xargs rpm -e --nodeps +# 直接yum安装1.8.0版本openjdk +yum install java-1.8.0-openjdk* -y +# 默认jre jdk安装路径是/usr/lib/jvm下面 +vim /etc/profile +# 添加以下配置 +export JAVA_HOME=/usr/lib/jvm/java +export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/jre/lib/rt.jar +export PATH=$PATH:$JAVA_HOME/bin -**④ JFR事件视图** -下图显示了事件的概述视图: +# 使得配置生效 +. /etc/profile +``` -![jfr-event](images/DevOps/jfr-event.jpg) +```shell +# 方式三:root用户下是配置JDK +vim /etc/profile +# 添加以下配置 +export JAVA_HOME=/home/hmf/jdk1.7.0_80 +export PATH=.:$JAVA_HOME/bin:$PATH +export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar +# source环境变量 +source /etc/profile +``` -## 火焰图 -火焰图是用来分析程序运行瓶颈的工具。火焰图也可以用来分析 Java 应用。可以从 github 上下载 [async-profiler](https://github.com/jvm-profiling-tools/async-profiler) 的压缩包进行相关操作。比如,我们把它解压到 /root/ 目录,然后以 javaagent 的方式来启动 Java 应用,命令行如下: +**基本常用命令** ```shell -java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar spring-petclinic-2.3.1.BUILD-SNAPSHOT.jar +# 查看机器cpu核数: +# 1.CPU总核数 = 物理CPU个数 * 每颗物理CPU的核数 +# 2.总逻辑CPU数 = 物理CPU个数 * 每颗物理CPU的核数 * 超线程数 +# 查看CPU信息(型号) +cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c +# 查看物理CPU个数 +cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l +# 查看每个物理CPU中core的个数(即核数) +cat /proc/cpuinfo| grep "cpu cores"| uniq +# 查看逻辑CPU的个数 +cat /proc/cpuinfo| grep "processor"| wc -l + +# 重启 +reboot +# 关机 +poweroff +# 添加用户 +useradd +# 设置密码 +passwd +# 查看nginx的位置 +whereis nginx + +# Tab补全 +# 1.未输入状态下连按两次Tab列出所有可用命令 +# 2.已输入部分命令名或文件名,按Tab进行自动补全 + +# 根据端口查看占用情况 +netstat -tln | grep 8080 +# 根据端口查看进程 +lsof -i :8080 +# 查看java关键词的进程 +ps aux|grep java +# 查看所有进程 +ps aux +# 查看java关键词的进程 +ps -ef | grep java ``` -运行一段时间后,停止进程,可以看到在当前目录下,生成了 profile.svg 文件,这个文件是可以用浏览器打开的。 -如下图所示,纵向,表示的是调用栈的深度;横向,表明的是消耗的时间。所以格子的宽度越大,越说明它可能是一个瓶颈。一层层向下浏览,即可找到需要优化的目标。 - -![Java火焰图](../lemon-guide-private/images/DevOps/Java火焰图.gif) +**常用复合命令** +```powershell +# 查找和tomcat相关的所有进程并杀死 +ps -ef | grep tomcat | grep -v grep | awk '{print $2}' | xargs kill -9 +# 从100行开始显示,支持滚动 +less +100 /home/test/example.log +# 从100行开始显示,不支持滚动 +more +100 /home/test/example.log +# 查看文件头10行 +head -n 10 example.txt -# Linux Command +# 每隔3s出12234进程的gc情况,每个20录就打印隐藏列标题 +jstat -gc -t -h20 3s +# Java线程Dump快照导出(建议使用tdump或log格式) +jstack -l > thread-dump.tdump +# Java内存Dump快照导出(建议使用hprof格式),format=b表示二进制文件(一般较大) +jmap -dump:[live,]format=b,file=heap-dump.hprof -![AnalysisTools](images/DevOps/AnalysisTools.png) +# JMC分析 +jcmd VM.unlock_commercial_features +jcmd JFR.start name=test duration=60s filename=output.jfr +``` -## Base -`$`和`#`区别:`$`普通用户即可执行,`#`为root用户才可执行,或普通用户使用`sudo`。 -**JDK环境变量配置** +### cd/ls -```shell -# 方式一:非root用户安装JDK# 编辑用户根目录下的.bash_profile文件vi .bash_profile# 向.bash_profile文件中导入配置export JAVA_HOME=/home/lry/jdk1.7.0_80export PATH=$JAVA_HOME/bin:$PATH export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar # 立刻生效source .bash_profile -``` +`cd` 用于切换当前目录,它的参数是要切换到的目录的路径,可以是绝对路径,也可以是相对路径 ```shell -# 方式二:yum安装JDK配置环境变量# 查看CentOS自带JDK是否已安装yum list installed |grep java# 批量卸载JDKrpm -qa | grep java | xargs rpm -e --nodeps # 直接yum安装1.8.0版本openjdkyum install java-1.8.0-openjdk* -y# 默认jre jdk安装路径是/usr/lib/jvm下面vim /etc/profile# 添加以下配置export JAVA_HOME=/usr/lib/jvm/javaexport CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/jre/lib/rt.jarexport PATH=$PATH:$JAVA_HOME/bin# 使得配置生效. /etc/profile -``` +cd /home # 进入 '/ home' 目录 +cd .. # 返回上一级目录 +cd ../.. # 返回上两级目录 +cd # 进入个人的主目录 +cd ~user1 # 进入个人的主目录 +cd - # 返回上次所在的目录 -```shell -# 方式三:root用户下是配置JDKvim /etc/profile# 添加以下配置export JAVA_HOME=/home/hmf/jdk1.7.0_80export PATH=.:$JAVA_HOME/bin:$PATHexport CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar# source环境变量source /etc/profile +ls # 查看目录中的文件 +ls -l # 显示文件和目录的详细资料 +ls -a # 列出全部文件,包含隐藏文件 +ls -R # 连同子目录的内容一起列出(递归列出),等于该目录下的所有文件都会显示出来 +ls [0-9] # 显示包含数字的文件名和目录名 ``` -**基本常用命令** +### chmod/chown/chgrp ```shell -# 查看机器cpu核数:# 1.CPU总核数 = 物理CPU个数 * 每颗物理CPU的核数# 2.总逻辑CPU数 = 物理CPU个数 * 每颗物理CPU的核数 * 超线程数# 查看CPU信息(型号)cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c# 查看物理CPU个数cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l# 查看每个物理CPU中core的个数(即核数)cat /proc/cpuinfo| grep "cpu cores"| uniq# 查看逻辑CPU的个数cat /proc/cpuinfo| grep "processor"| wc -l# 重启reboot# 关机poweroff# 添加用户useradd# 设置密码passwd# 查看nginx的位置whereis nginx# 修改start.sh文件为最高权限777chmod 777 start.sh# 修改test.txt文件所属的用户和组, – R表示递归处理chown - R username:group test.txt# 改变/opt/local和/book/及其子目录下的所有文件的属组为lry, – R表示递归处理chgrp - R lry /opt/local /book# Tab补全# 1.未输入状态下连按两次Tab列出所有可用命令# 2.已输入部分命令名或文件名,按Tab进行自动补全# 根据端口查看占用情况netstat -tln | grep 8080# 根据端口查看进程lsof -i :8080# 查看java关键词的进程ps aux|grep java# 查看所有进程ps aux# 查看java关键词的进程ps -ef | grep java -``` +# 显示权限 +ls -lh +# 设置目录的所有人(u)、群组(g)以及其他人(o)以读(r,4 )、写(w,2)和执行(x,1)的权限 +chmod ugo+rwx directory1 +# 删除群组(g)与其他人(o)对目录的读写执行权限 +chmod go-rwx directory1 -**常用复合命令** +# chown改变文件的所有者 +# 改变一个文件的所有人属性 +chown user1 file1 +# 改变一个目录的所有人属性并同时改变改目录下所有文件的属性 +chown -R user1 directory1 +# 改变一个文件的所有人和群组属性 +chown user1:group1 file1 -```powershell -# 查找和tomcat相关的所有进程并杀死ps -ef | grep tomcat | grep -v grep | awk '{print $2}' | xargs kill -9# 从100行开始显示,支持滚动less +100 /home/test/example.log# 从100行开始显示,不支持滚动more +100 /home/test/example.log# 查看文件头10行head -n 10 example.txt# 每隔3s出12234进程的gc情况,每个20录就打印隐藏列标题jstat -gc -t -h20 3s# Java线程Dump快照导出(建议使用tdump或log格式)jstack -l > thread-dump.tdump# Java内存Dump快照导出(建议使用hprof格式),format=b表示二进制文件(一般较大)jmap -dump:[live,]format=b,file=heap-dump.hprof # JMC分析jcmd VM.unlock_commercial_featuresjcmd JFR.start name=test duration=60s filename=output.jfr +# chgrp改变文件所属用户组 +# 改变文件的群组 +chgrp group1 file1 + +# 常用命令 +# 修改start.sh文件为最高权限777 +chmod 777 start.sh +# 修改test.txt文件所属的用户和组, – R表示递归处理 +chown - R username:group test.txt +# 改变/opt/local和/book/及其子目录下的所有文件的属组为lry, – R表示递归处理 +chgrp - R lry /opt/local /book ``` @@ -818,7 +1156,64 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### vi/vim ```shell -:set nu # 设置显示行号:set nonu # 取消显示行号ctr+f # 向前翻页ctr+b # 向后翻页u # 恢复修改操作yy # 复制本行nyy # 本行往下n行进行复制p # 粘贴在光标以下的行P # 粘贴在光标以上的行x # 向后删除一个字符X # 向前删除一个字符nx # 向后删除n个字符:w # 保存:q # 退出:q! # 强制退出不保存:w! # 强制保存:wq # 保存并退出:w otherfilename # 另存为# 在文件中移动nG # 光标移动到n行gg # 光标移动到文件第1行G # 光标移动到文件最后1行75% # 光标移动到百分之75的位置# 移动到指定字符fx # 把光标移动到右边的第一个’x’字符上Fx # 把光标移动到左边的第一个’x’字符上3fx # 把光标移动到光标右边的第3个’x’字符上tx # 把光标移动到右边的第一个’x’字符之前Tx # 把光标移动到左边的第一个’x’字之后n空格 # 光标移动到本行第n个字符$ # 光标移动到本行最后一个字符# H/M/L(大写):可以让光标跳到当前窗口的顶部、中间、和底部,停留在第一个非空字符上3H # 表示光标移动到距窗口顶部第3行的位置5L # 表示光标移动到距窗口底部5行的位置# 相对于光标滚屏zt # 把光标所在行移动窗口的顶端zz # 把光标所在行移动窗口的中间zb # 把光标所在行移动窗口的底部# 查找/word # 向光标之后搜索字符串?word # 向光标之前搜索字符串n # 重复上一次的查找命令向后查找N # 重复上一次的查找命令向前查找:n1,n2s/word1/word2/gc # 逐个替换:1,$s/word1/word2/g # 从第一行到最后一行进行替换应该是:n1,n2s/word1/word2/g # 从第n1行到第n2行搜索word1字符串,并替换为word2 +:set nu # 设置显示行号 +:set nonu # 取消显示行号 +ctr+f # 向前翻页 +ctr+b # 向后翻页 +u # 恢复修改操作 +yy # 复制本行 +nyy # 本行往下n行进行复制 +p # 粘贴在光标以下的行 +P # 粘贴在光标以上的行 +x # 向后删除一个字符 +X # 向前删除一个字符 +nx # 向后删除n个字符 + +:w # 保存 +:q # 退出 +:q! # 强制退出不保存 +:w! # 强制保存 +:wq # 保存并退出 +:w otherfilename # 另存为 + +# 在文件中移动 +nG # 光标移动到n行 +gg # 光标移动到文件第1行 +G # 光标移动到文件最后1行 +75% # 光标移动到百分之75的位置 +$ # 移动到行末 +0 # 移动到行首 + +dd # 删除当前行 +dG # 删除当前后面的全部内容 + +# 移动到指定字符 +fx # 把光标移动到右边的第一个’x’字符上 +Fx # 把光标移动到左边的第一个’x’字符上 +3fx # 把光标移动到光标右边的第3个’x’字符上 +tx # 把光标移动到右边的第一个’x’字符之前 +Tx # 把光标移动到左边的第一个’x’字之后 +n空格 # 光标移动到本行第n个字符 +$ # 光标移动到本行最后一个字符 + +# H/M/L(大写):可以让光标跳到当前窗口的顶部、中间、和底部,停留在第一个非空字符上 +3H # 表示光标移动到距窗口顶部第3行的位置 +5L # 表示光标移动到距窗口底部5行的位置 + +# 相对于光标滚屏 +zt # 把光标所在行移动窗口的顶端 +zz # 把光标所在行移动窗口的中间 +zb # 把光标所在行移动窗口的底部 + +# 查找 +/word # 向光标之后搜索字符串 +?word # 向光标之前搜索字符串 +n # 重复上一次的查找命令向后查找 +N # 重复上一次的查找命令向前查找 + +:n1,n2s/word1/word2/gc # 逐个替换 +:1,$s/word1/word2/g # 从第一行到最后一行进行替换应该是 +:n1,n2s/word1/word2/g # 从第n1行到第n2行搜索word1字符串,并替换为word2 ``` @@ -826,7 +1221,13 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### scp ```shell -# 拷贝本机/home/lry/test整个目录至远程主机192.168.1.100的/test目录下scp -r /home/lry/test/ root@192.168.1.100:/test/# 拷贝单个文件至远程主机scp /home/lry/test.txt root@192.168.1.100:/test/# 远程文件/文件夹下载举例# 把192.168.62.10上面的/test/文件夹,下载到本地的/home/lry/下scp -r root@192.168.62.10:/test/ /home/lry/ +# 拷贝本机/home/lry/test整个目录至远程主机192.168.1.100的/test目录下 +scp -r /home/lry/test/ root@192.168.1.100:/test/ +# 拷贝单个文件至远程主机 +scp /home/lry/test.txt root@192.168.1.100:/test/ +# 远程文件/文件夹下载举例 +# 把192.168.62.10上面的/test/文件夹,下载到本地的/home/lry/下 +scp -r root@192.168.62.10:/test/ /home/lry/ ``` @@ -834,7 +1235,42 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### tar ```shell -# 格式:tar [-cxtzjvfpPN] 文件与目录 ....# 参数说明:# -c :压缩# -x :解压# -t :查看内容# 注意:c/x/t只能同时存在一个# # -r:向压缩归档文件末尾追加文件# -u:更新原压缩包中的文件# -v:显示操作过程# -f:指定备份文件,其后不能再跟参数# 压缩文件tar -czf test.tar.gz /test1 /test2# 列出压缩文件列表tar -tzf test.tar.gz# 解压文件tar -xvzf test.tar.gz# 仅打包,不压缩tar -cvf log.tar log01.log # 打包后,以gzip压缩tar -zcvf log.tar.gz log01.log# 打包后,以bzip2压缩tar -jcvf log.tar.bz2 log01.log# 总结# *.tar:用tar –xvf解压# *.gz:用gzip -d或者gunzip解压# *.tar.gz和*.tgz:用tar –xzf解压# *.bz2:用bzip2 -d或者用bunzip2解压# *.tar.bz2:用tar –xjf解压# *.Z:用uncompress解压# *.tar.Z:用tar –xZf解压# *.rar:用unrar解压# *.zip:用unzip解压 +# 格式:tar [-cxtzjvfpPN] 文件与目录 .... +# 参数说明: +# -c :压缩 +# -x :解压 +# -t :查看内容 +# 注意:c/x/t只能同时存在一个 +# +# -r:向压缩归档文件末尾追加文件 +# -u:更新原压缩包中的文件 +# -v:显示操作过程 +# -f:指定备份文件,其后不能再跟参数 + +# 压缩文件 +tar -czf test.tar.gz /test1 /test2 +# 列出压缩文件列表 +tar -tzf test.tar.gz +# 解压文件 +tar -xvzf test.tar.gz + +# 仅打包,不压缩 +tar -cvf log.tar log01.log +# 打包后,以gzip压缩 +tar -zcvf log.tar.gz log01.log +# 打包后,以bzip2压缩 +tar -jcvf log.tar.bz2 log01.log + +# 总结 +# *.tar:用tar –xvf解压 +# *.gz:用gzip -d或者gunzip解压 +# *.tar.gz和*.tgz:用tar –xzf解压 +# *.bz2:用bzip2 -d或者用bunzip2解压 +# *.tar.bz2:用tar –xjf解压 +# *.Z:用uncompress解压 +# *.tar.Z:用tar –xZf解压 +# *.rar:用unrar解压 +# *.zip:用unzip解压 ``` @@ -842,7 +1278,12 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### su ```shell -# 切换到其它身份用户,默认是root,如下相同su -# 切换到root用户,并至root目录,不带-只切换用户su - root# 变更帐号为root并在执行ls指令后退出变回原使用者su -c ls root +# 切换到其它身份用户,默认是root,如下相同 +su - +# 切换到root用户,并至root目录,不带-只切换用户 +su - root +# 变更帐号为root并在执行ls指令后退出变回原使用者 +su -c ls root ``` @@ -850,7 +1291,8 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### df ```shell -# 易读的显示目前磁盘空间和使用情况,-h(1024计算)/-H(1000计算)df -h +# 易读的显示目前磁盘空间和使用情况,-h(1024计算)/-H(1000计算) +df -h ``` @@ -858,7 +1300,15 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### tail ```shell -# tail语法格式: # tail [-f] [-c Number|-n Number|-m Number|-b Number|-k Number] [File] # 倒数300行并进入实时监听文件写入模式tail -300f example.log # 查看文件尾10行tail -n 10 example.log# 实时查看日志文件tail -f example.log +# tail语法格式: +# tail [-f] [-c Number|-n Number|-m Number|-b Number|-k Number] [File] + +# 倒数300行并进入实时监听文件写入模式 +tail -300f example.log +# 查看文件尾10行 +tail -n 10 example.log +# 实时查看日志文件 +tail -f example.log ``` @@ -866,7 +1316,51 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### grep ```shell -# 文件查找grep forest test.txt# 多文件查找grep forest test.txt example.txt# 目录下查号所有符合关键词的文件grep 'log' /home/test -r -n# 在文件 '/var/log/messages'中查找以"Aug"开始的词汇grep ^Aug /var/log/messages# 查找指定文件中的关键词并打印至控制台cat f.txt | grep -i shopbase# 查找指定文件中的关键词并统计其出现次数cat f.txt | grep -c 'shopbase'# 指定文件后缀grep 'shopbase' /home/test -r -n --include *.{vm,java}# 反匹配grep 'shopbase' /home/test -r -n --exclude *.{vm,java}# 上匹配seq 10 | grep 5 -A 3# 下匹配seq 10 | grep 5 -B 3# 上下匹配,平时用这个就妥了seq 10 | grep 5 -C 3# 查找当前目录中的所有jar文件ls -l | grep '.jar'# 查找所以有的包含spring的xml文件grep -H 'spring' *.xml# 显示所有以d开头的文件中包含test的行grep 'test' d*# 显示在aa,bb,cc文件中匹配test的行grep 'test' aa bb cc# 显示所有包含每个字符串至少有5个连续小写字符的字符串的行grep '[a-z]\{5\}' aa# 显示foo及后5行grep -A 5 Exception app.log# 显示foo及前5行grep -B 5 Exception app.log# 显示app.log文件里匹配Exception字串那行以及上下5行grep -C 5 Exception app.log +# 文件查找 +grep forest test.txt +# 多文件查找 +grep forest test.txt example.txt +# 目录下查号所有符合关键词的文件 +grep 'log' /home/test -r -n +# 在文件 '/var/log/messages'中查找以"Aug"开始的词汇 +grep ^Aug /var/log/messages +# 查找指定文件中的关键词并打印至控制台 +cat f.txt | grep -i shopbase +# 查找指定文件中的关键词并统计其出现次数 +cat f.txt | grep -c 'shopbase' +# 指定文件后缀 +grep 'shopbase' /home/test -r -n --include *.{vm,java} +# 反匹配 +grep 'shopbase' /home/test -r -n --exclude *.{vm,java} + +# 上匹配 +seq 10 | grep 5 -A 3 +# 下匹配 +seq 10 | grep 5 -B 3 +# 上下匹配,平时用这个就妥了 +seq 10 | grep 5 -C 3 + +# 查找当前目录中的所有jar文件 +ls -l | grep '.jar' +# 查找所以有的包含spring的xml文件 +grep -H 'spring' *.xml +# 显示所有以d开头的文件中包含test的行 +grep 'test' d* +# 显示在aa,bb,cc文件中匹配test的行 +grep 'test' aa bb cc +# 显示所有包含每个字符串至少有5个连续小写字符的字符串的行 +grep '[a-z]\{5\}' aa +# 找出文件中包含123或者包含abc的行 +grep -E '123|abc' filename.txt +# 找出文件中包含123,且包含abc的行 +grep '123' filename.txt | grep 'abc' + +# 显示foo及后5行 +grep -A 5 Exception app.log +# 显示foo及前5行 +grep -B 5 Exception app.log +# 显示app.log文件里匹配Exception字串那行以及上下5行 +grep -C 5 Exception app.log ``` @@ -874,7 +1368,66 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### awk ```shell -# 1.原理# 逐行处理文件中的数据# 2.语法awk 'pattern + {action}'# 说明:# 单引号''是为了和shell命令区分开# 大括号{}表示一个命令分组# pattern是一个过滤器,表示命中pattern的行才进行action处理# action是处理动作# 使用#作为注释# 3.内置变量FS # 分隔符,默认是空格NR # 当前行数,从1开始NF # 当前记录字段个数$0 # 当前记录$1~$n # 当前记录第n个字段# 4.内置函数gsub(r,s) # 在$0中用s代替rindex(s,t) # 返回s中t的第一个位置length(s) # s的长度match(s,r) # s是否匹配rsplit(s,a,fs) # 在fs上将s分成序列asubstr(s,p) # 返回s从p开始的子串# 5.案例# 显示hello.txt中的第3行至第5行的命令为cat hello.txt | awk 'NR==3, NR==5{print;}'# 显示hello.txt中,正则匹配hello的行的命令为cat hello.txt | awk '/hello/'# 显示hello.txt中,长度大于100的行号的命令为cat hello.txt | awk 'length($0)>80{print NR}'# 显示hello.txt中的第3行至第5行的第一列与最后一列cat hello.txt | awk 'NR==3, NR==5{print $1,$NF}'# 基础命令# 在控制台循环打印打印第4列和第6列数据awk '{print $4,$6}' f.txtawk '{print NR,$0}' f.txt cpf.txtawk '{print FNR,$0}' f.txt cpf.txtawk '{print FNR,FILENAME,$0}' f.txt cpf.txtawk '{print FILENAME,"NR="NR,"FNR="FNR,"$"NF"="$NR}' f.txt cpf.txtecho 1:2:3:4 | awk -F: '{print $1,$2,$3}'# 匹配# 匹配ldbawk '/ldb/ {print}' f.txt# 不匹配ldbawk '!/ldb/ {print}' f.txt# 匹配ldb和LISTENawk '/ldb/ && /LISTEN/ {print}' f.txt# 第五列匹配ldbawk '$5 ~ /ldb/ {print}' f.txt# 内建变量# NR:NR表示从awk开始执行后,按照记录分隔符读取的数据次数,默认的记录分隔符为换行符,因此默认的就是读取的数据行数,NR可以理解为Number of Record的缩写# FNR:在awk处理多个输入文件的时候,在处理完第一个文件后,NR并不会从1开始,而是继续累加,因此就出现了FNR,每当处理一个新文件的时候,FNR就从1开始计数,FNR可以理解为File Number of Record# NF: NF表示目前的记录被分割的字段的数目,NF可以理解为Number of Field +# 1.原理 +# 逐行处理文件中的数据 + +# 2.语法 +awk 'pattern + {action}' +# 说明: +# 单引号''是为了和shell命令区分开 +# 大括号{}表示一个命令分组 +# pattern是一个过滤器,表示命中pattern的行才进行action处理 +# action是处理动作 +# 使用#作为注释 + +# 3.内置变量 +FS # 分隔符,默认是空格 +NR # 当前行数,从1开始 +NF # 当前记录字段个数 +$0 # 当前记录 +$1~$n # 当前记录第n个字段 + +# 4.内置函数 +gsub(r,s) # 在$0中用s代替r +index(s,t) # 返回s中t的第一个位置 +length(s) # s的长度 +match(s,r) # s是否匹配r +split(s,a,fs) # 在fs上将s分成序列a +substr(s,p) # 返回s从p开始的子串 + +# 5.案例 +# 显示hello.txt中的第3行至第5行的命令为 +cat hello.txt | awk 'NR==3, NR==5{print;}' +# 显示hello.txt中,正则匹配hello的行的命令为 +cat hello.txt | awk '/hello/' +# 显示hello.txt中,长度大于100的行号的命令为 +cat hello.txt | awk 'length($0)>80{print NR}' +# 显示hello.txt中的第3行至第5行的第一列与最后一列 +cat hello.txt | awk 'NR==3, NR==5{print $1,$NF}' + +# 基础命令 +# 在控制台循环打印打印第4列和第6列数据 +awk '{print $4,$6}' f.txt +awk '{print NR,$0}' f.txt cpf.txt +awk '{print FNR,$0}' f.txt cpf.txt +awk '{print FNR,FILENAME,$0}' f.txt cpf.txt +awk '{print FILENAME,"NR="NR,"FNR="FNR,"$"NF"="$NR}' f.txt cpf.txt +echo 1:2:3:4 | awk -F: '{print $1,$2,$3}' + +# 匹配 +# 匹配ldb +awk '/ldb/ {print}' f.txt +# 不匹配ldb +awk '!/ldb/ {print}' f.txt +# 匹配ldb和LISTEN +awk '/ldb/ && /LISTEN/ {print}' f.txt +# 第五列匹配ldb +awk '$5 ~ /ldb/ {print}' f.txt + +# 内建变量 +# NR:NR表示从awk开始执行后,按照记录分隔符读取的数据次数,默认的记录分隔符为换行符,因此默认的就是读取的数据行数,NR可以理解为Number of Record的缩写 +# FNR:在awk处理多个输入文件的时候,在处理完第一个文件后,NR并不会从1开始,而是继续累加,因此就出现了FNR,每当处理一个新文件的时候,FNR就从1开始计数,FNR可以理解为File Number of Record +# NF: NF表示目前的记录被分割的字段的数目,NF可以理解为Number of Field ``` @@ -882,7 +1435,46 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### find ```shell -# 根据名称查找/目录下的filename.txt文件find / -name filename.txt# 递归查找所有的xml文件find . -name "*.xml"# 递归查找所有文件内容中包含hello world的xml文件find . -name "*.xml" |xargs grep "hello world"# 删除文件大小为零的文件find ./ -size 0 | xargs rm -f &# 多个目录查找find /home/admin /tmp /usr -name \*.log# 大小写都匹配find . -iname \*.txt# 当前目录下的所有子目录find . -type d# 当前目录下所有的符号链接find /usr -type l# 符号链接的详细信息 eg:inode,目录find /usr -type l -name "z*" -ls# 超过250000k的文件,当然+改成-就是小于了find /home/admin -size +250000k# 按照权限查询文件find /home/admin -f -perm 777 -exec ls -l {} \;# 1天内访问过的文件find /home/admin -atime -1# 1天内状态改变过的文件find /home/admin -ctime -1# 1天内修改过的文件find /home/admin -mtime -1# 1分钟内访问过的文件find /home/admin -amin -1# 1分钟内状态改变过的文件find /home/admin -cmin -1# 1分钟内修改过的文件find /home/admin -mmin -1 +# 根据名称查找/目录下的filename.txt文件 +find / -name filename.txt +# 递归查找所有的xml文件 +find . -name "*.xml" +# 递归查找所有文件内容中包含hello world的xml文件 +find . -name "*.xml" |xargs grep "hello world" +# 删除文件大小为零的文件 +find ./ -size 0 | xargs rm -f & + +# 多个目录查找 +find /home/admin /tmp /usr -name \*.log +# 大小写都匹配 +find . -iname \*.txt +# 当前目录下的所有子目录 +find . -type d +# 当前目录下所有的符号链接 +find /usr -type l +# 符号链接的详细信息 eg:inode,目录 +find /usr -type l -name "z*" -ls +# 超过250000k的文件,当然+改成-就是小于了 +find /home/admin -size +250000k +# 按照权限查询文件 +find /home/admin -f -perm 777 -exec ls -l {} \; + +# 1天内访问过的文件 +find /home/admin -atime -1 +# 1天内状态改变过的文件 +find /home/admin -ctime -1 +# 1天内修改过的文件 +find /home/admin -mtime -1 + +# 1分钟内访问过的文件 +find /home/admin -amin -1 +# 1分钟内状态改变过的文件 +find /home/admin -cmin -1 +# 1分钟内修改过的文件 +find /home/admin -mmin -1 + +# 删除大于50M的文件 +find /var/mail/ -size +50M -exec rm {} \ ``` @@ -890,7 +1482,16 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### netstat ```shell -# 查看当前连接,注意CLOSE_WAIT偏高的情况netstat -nat|awk '{print $6}'|sort|uniq -c|sort -rn# 显示所有tcp连接,并包括pid和程序名netstat -atnp# 统计所有tcp状态的数量并排序netstat -atn | awk '{print $6}' | sort | uniq -c | sort -rn# 每隔1s显示网络信息(-c参数)netstat -ctn | grep "ESTABLISHED"# 列出所有处于连接状态的ip并按数量排序netstat -an | grep ESTABLISHED | awk '/^tcp/ {print $5}' | awk -F: '{print $1}' | sort | uniq -c | sort -nr +# 查看当前连接,注意CLOSE_WAIT偏高的情况 +netstat -nat|awk '{print $6}'|sort|uniq -c|sort -rn +# 显示所有tcp连接,并包括pid和程序名 +netstat -atnp +# 统计所有tcp状态的数量并排序 +netstat -atn | awk '{print $6}' | sort | uniq -c | sort -rn +# 每隔1s显示网络信息(-c参数) +netstat -ctn | grep "ESTABLISHED" +# 列出所有处于连接状态的ip并按数量排序 +netstat -an | grep ESTABLISHED | awk '/^tcp/ {print $5}' | awk -F: '{print $1}' | sort | uniq -c | sort -nr ``` @@ -898,7 +1499,10 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### echo ```shell -# 创建test.txt文件,并向该文件写入“内容”echo "内容" > test.txt# 输出环境变量$JAVA_HOME的值echo $JAVA_HOME +# 创建test.txt文件,并向该文件写入“内容” +echo "内容" > test.txt +# 输出环境变量$JAVA_HOME的值 +echo $JAVA_HOME ``` @@ -906,7 +1510,8 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### telnet ```shell -# 查看端口是否通畅:telnet IP 端口号telnet 10.150.159.71 5516 +# 查看端口是否通畅:telnet IP 端口号 +telnet 10.150.159.71 5516 ``` @@ -914,7 +1519,10 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### rpm ```shell -# 安装一个rpm包rpm -ivh package.rpm# 安装一个rpm包而忽略依赖关系警告rpm -ivh --nodeeps package.rpm +# 安装一个rpm包 +rpm -ivh package.rpm +# 安装一个rpm包而忽略依赖关系警告 +rpm -ivh --nodeeps package.rpm ``` @@ -922,7 +1530,42 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar ### yum ```shell -# 下载并安装一个rpm包yum install package_name# 将安装一个rpm包,使用你自己的软件仓库为你解决所有依赖关系yum localinstall package_name.rpm# 更新当前系统中所有安装的rpm包yum update package_name.rpm# 更新一个rpm包yum update package_name# 删除一个rpm包yum remove package_name# 列出当前系统中安装的所有包yum list# 在rpm仓库中搜寻软件包yum search package_name# 删除所有缓存的包和头文件yum clean all +# 下载并安装一个rpm包 +yum install package_name +# 将安装一个rpm包,使用你自己的软件仓库为你解决所有依赖关系 +yum localinstall package_name.rpm +# 更新当前系统中所有安装的rpm包 +yum update package_name.rpm +# 更新一个rpm包 +yum update package_name +# 删除一个rpm包 +yum remove package_name +# 列出当前系统中安装的所有包 +yum list +# 在rpm仓库中搜寻软件包 +yum search package_name +# 删除所有缓存的包和头文件 +yum clean all +``` + + + +### whereis + +Linux whereis命令用于查找文件。 + +```shell +whereis [-bfmsu][-B <目录>...][-M <目录>...][-S <目录>...][文件...] + +# 参数: +# -b:只查找二进制文件 +# -B<目录>:只在设置的目录下查找二进制文件 +# -f:不显示文件名前的路径名称 +# -m:只查找说明文件 +# -M<目录>:只在设置的目录下查找说明文件 +# -s:只查找原始代码文件 +# -S<目录>:只在设置的目录下查找原始代码文件 +# -u:查找不包含指定类型的文件 ``` @@ -936,13 +1579,30 @@ java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar `free`是查看内存使用情况,包括物理内存、交换内存(swap)和内核缓冲区内存。 ```shell -# 语法free [-bkmhotV][-s <间隔秒数>]# 参数说明:# -b  以Byte为单位显示内存使用情况# -k  以KB为单位显示内存使用情况# -m 以MB为单位显示内存使用情况# -h  以合适的单位显示内存使用情况,最大为三位数,自动计算对应单位值。单位:B=bytes, K=kilos, M=megas, G=gigas, T=teras# -o  不显示缓冲区调节列# -s <间隔秒数>  持续观察内存使用状况# -t  显示内存总和列# -V  显示版本信息 +# 语法 +free [-bkmhotV][-s <间隔秒数>] + +# 参数说明: +# -b  以Byte为单位显示内存使用情况 +# -k  以KB为单位显示内存使用情况 +# -m 以MB为单位显示内存使用情况 +# -h  以合适的单位显示内存使用情况,最大为三位数,自动计算对应单位值。单位:B=bytes, K=kilos, M=megas, G=gigas, T=teras +# -o  不显示缓冲区调节列 +# -s <间隔秒数>  持续观察内存使用状况 +# -t  显示内存总和列 +# -V  显示版本信息 ``` `free -h -s 3`表示每隔三秒输出一次内存情况,命令如下: ```shell -[1014154@cc69dd4c5-4tdb5 ~]$ free -h -s 3 total used free shared buff/cache availableMem: 114G 41G 43G 4.1G 29G 67GSwap: 0B 0B 0B total used free shared buff/cache availableMem: 114G 41G 43G 4.1G 29G 67GSwap: 0B 0B 0B +[1014154@cc69dd4c5-4tdb5 ~]$ free -h -s 3 + total used free shared buff/cache available +Mem: 114G 41G 43G 4.1G 29G 67G +Swap: 0B 0B 0B + total used free shared buff/cache available +Mem: 114G 41G 43G 4.1G 29G 67G +Swap: 0B 0B 0B ``` - `Mem`:是内存的使用情况 @@ -965,7 +1625,13 @@ swap space 是磁盘上的一块区域,当系统物理内存吃紧时,Linux vmstat(Virtual Meomory Statistics,虚拟内存统计)是Linux中监控内存的常用工具,它收集和显示关于**内存**,**进程**,**终端**和**分页**和**I/O阻塞**的概括信息。 ```shell -# 每隔1秒打印一次,一共打印3次。-S指定显示单位, M代表Mb, 默认为Kb[root@localhost ~]# vmstat -SM 1 3procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 1 0 0 2433 2 657 0 0 132 8 77 95 1 2 97 1 0 0 0 0 2433 2 657 0 0 0 0 47 71 0 0 100 0 0 0 0 0 2433 2 657 0 0 0 0 54 72 0 0 100 0 0 +# 每隔1秒打印一次,一共打印3次。-S指定显示单位, M代表Mb, 默认为Kb +[root@localhost ~]# vmstat -SM 1 3 +procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- + r b swpd free buff cache si so bi bo in cs us sy id wa st + 1 0 0 2433 2 657 0 0 132 8 77 95 1 2 97 1 0 + 0 0 0 2433 2 657 0 0 0 0 47 71 0 0 100 0 0 + 0 0 0 2433 2 657 0 0 0 0 54 72 0 0 100 0 0 ``` - **procs** @@ -1022,7 +1688,18 @@ top可以查看CPU总体消耗,包括分项消耗,如User,System,Idle, - `Shift + T` 按照CPU累积使用时间排序 ```shell -top - 15:24:11 up 8 days, 7:52, 1 user, load average: 5.73, 6.85, 7.33Tasks: 17 total, 1 running, 16 sleeping, 0 stopped, 0 zombie%Cpu(s): 13.9 us, 9.2 sy, 0.0 ni, 76.1 id, 0.1 wa, 0.0 hi, 0.1 si, 0.7 stKiB Mem : 11962365+total, 50086832 free, 38312808 used, 31224016 buff/cacheKiB Swap: 0 total, 0 free, 0 used. 75402760 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 300 ymmapp 20 0 17.242g 1.234g 14732 S 2.3 1.1 9:40.38 java 1 root 20 0 15376 1988 1392 S 0.0 0.0 0:00.06 sh 11 root 20 0 120660 11416 1132 S 0.0 0.0 0:04.94 python 54 root 20 0 85328 2240 1652 S 0.0 0.0 0:00.00 su...... +top - 15:24:11 up 8 days, 7:52, 1 user, load average: 5.73, 6.85, 7.33 +Tasks: 17 total, 1 running, 16 sleeping, 0 stopped, 0 zombie +%Cpu(s): 13.9 us, 9.2 sy, 0.0 ni, 76.1 id, 0.1 wa, 0.0 hi, 0.1 si, 0.7 st +KiB Mem : 11962365+total, 50086832 free, 38312808 used, 31224016 buff/cache +KiB Swap: 0 total, 0 free, 0 used. 75402760 avail Mem + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND + 300 ymmapp 20 0 17.242g 1.234g 14732 S 2.3 1.1 9:40.38 java + 1 root 20 0 15376 1988 1392 S 0.0 0.0 0:00.06 sh + 11 root 20 0 120660 11416 1132 S 0.0 0.0 0:04.94 python + 54 root 20 0 85328 2240 1652 S 0.0 0.0 0:00.00 su +...... ``` 第三行:`%Cpu(s): 13.9 us, 9.2 sy, 0.0 ni, 76.1 id, 0.1 wa, 0.0 hi, 0.1 si, 0.7 st`:**用户空间CPU占比13.9%** ,内核空间CPU占比9.2%,改变过优先级的进程CPU占比0%,**空闲CPU占比76.1** ,**IO等待占用CPU占比0.1%** ,硬中断占用CPU占比0%,软中断占用CPU占比0.1%,当前VM中的cpu 时钟被虚拟化偷走的比例0.7%。其中: @@ -1052,7 +1729,14 @@ htop基本上是一个top改善版本,它能够以更加多彩的方式显示 ① 通过`sar -u 1 3`可以查看CUP总体消耗占比,每间隔1秒钟统计1次总共统计3次: ```shell -[root@localhost ~]# sar -u 1 3Linux 3.10.0-1062.el7.x86_64 (localhost.localdomain) 2020年05月01日 _x86_64_ (2 CPU)15时18分03秒 CPU %user %nice %system %iowait %steal %idle15时18分06秒 all 0.00 0.00 0.17 0.00 0.00 99.8315时18分09秒 all 0.00 0.00 0.17 0.00 0.00 99.8315时18分12秒 all 0.17 0.00 0.17 0.00 0.00 99.6615时18分15秒 all 0.00 0.00 0.00 0.00 0.00 100.0015时18分18秒 all 0.00 0.00 0.00 0.00 0.00 100.00 +[root@localhost ~]# sar -u 1 3 +Linux 3.10.0-1062.el7.x86_64 (localhost.localdomain) 2020年05月01日 _x86_64_ (2 CPU) +15时18分03秒 CPU %user %nice %system %iowait %steal %idle +15时18分06秒 all 0.00 0.00 0.17 0.00 0.00 99.83 +15时18分09秒 all 0.00 0.00 0.17 0.00 0.00 99.83 +15时18分12秒 all 0.17 0.00 0.17 0.00 0.00 99.66 +15时18分15秒 all 0.00 0.00 0.00 0.00 0.00 100.00 +15时18分18秒 all 0.00 0.00 0.00 0.00 0.00 100.00 ``` - `%user`:用户空间的CPU使用 @@ -1089,7 +1773,10 @@ htop基本上是一个top改善版本,它能够以更加多彩的方式显示 iostat用于报告中央处理器(CPU)统计信息和整个系统、适配器、tty 设备、磁盘和 CD-ROM 的输入/输出统计信息,默认显示了与vmstat相同的cpu使用信息,使用以下命令显示扩展的设备统计: ```shell -# 每隔一秒打印一次磁盘的详细信息iostat -dx 1# 每秒打印一次统计信息,打印30次后退出iostat 1 30 +# 每隔一秒打印一次磁盘的详细信息 +iostat -dx 1 +# 每秒打印一次统计信息,打印30次后退出 +iostat 1 30 ``` ![iostat](images/DevOps/iostat.png) @@ -1112,7 +1799,26 @@ pidstat主要用于监控全部或指定进程占用系统资源的情况。如C ![pidstat](images/DevOps/pidstat.jpg) ```shell -# 统计IO使用信息pidstat –d # 统计CPU使用信息pidstat –u # 统计内存使用信息pidstat –r # 查看特定进程的cpu统计信息pidstat –p # 查看特定进程的CPU使用情况pidstat –u –p {pid} {interval} [count]# 作用:以1秒为信息采集周期,采集10次程序“admin”的CPU统计信息,最后一行会输出10次统计信息的平均值 pidstat -u -p `pgrep admin` 1 10# 查看特定进程的内存使用情况pidstat –r –p {pid} {interval} [count]# 查看特定进程的IO使用情况pidstat –d –p {pid} {interval} [count] +# 统计IO使用信息 +pidstat –d +# 统计CPU使用信息 +pidstat –u +# 统计内存使用信息 +pidstat –r +# 查看特定进程的cpu统计信息 +pidstat –p + +# 查看特定进程的CPU使用情况 +pidstat –u –p {pid} {interval} [count] + +# 作用:以1秒为信息采集周期,采集10次程序“admin”的CPU统计信息,最后一行会输出10次统计信息的平均值 +pidstat -u -p `pgrep admin` 1 10 + +# 查看特定进程的内存使用情况 +pidstat –r –p {pid} {interval} [count] + +# 查看特定进程的IO使用情况 +pidstat –d –p {pid} {interval} [count] ``` @@ -1122,7 +1828,12 @@ pidstat主要用于监控全部或指定进程占用系统资源的情况。如C iotop命令是专门显示硬盘IO的命令,界面风格类似top命令,可以显示IO负载具体是由哪个进程产生的。是一个用来监视磁盘I/O使用状况的top类工具,具有与top相似的UI,其中包括PID、用户、I/O、进程等相关信息。可以以非交互的方式使用:iotop –bod interval。查看每个进程的I/O,可以使用pidstat,pidstat –d instat。 ```shell -# Linux安装iotopyum install iotop# Ubuntu安装iotopsudo apt-get install iotop # 实时监控IO读写iotop +# Linux安装iotop +yum install iotop +# Ubuntu安装iotop +sudo apt-get install iotop +# 实时监控IO读写 +iotop ``` @@ -1136,13 +1847,36 @@ netstat 是一个内置工具,用于显示IP、TCP、UDP和ICMP协议相关的 ![netstat](images/DevOps/netstat.png) ```shell -# 查看当前连接,注意CLOSE_WAIT偏高的情况netstat -nat|awk '{print $6}'|sort|uniq -c|sort -rn# 显示所有tcp连接,并包括pid和程序名netstat -atnp# 统计所有tcp状态的数量并排序netstat -atn | awk '{print $6}' | sort | uniq -c | sort -rn# 每隔1s显示网络信息(-c参数)netstat -ctn | grep "ESTABLISHED"# 列出所有处于连接状态的ip并按数量排序netstat -an | grep ESTABLISHED | awk '/^tcp/ {print $5}' | awk -F: '{print $1}' | sort | uniq -c | sort -nr +# 查看当前连接,注意CLOSE_WAIT偏高的情况 +netstat -nat|awk '{print $6}'|sort|uniq -c|sort -rn +# 显示所有tcp连接,并包括pid和程序名 +netstat -atnp +# 统计所有tcp状态的数量并排序 +netstat -atn | awk '{print $6}' | sort | uniq -c | sort -rn +# 每隔1s显示网络信息(-c参数) +netstat -ctn | grep "ESTABLISHED" +# 列出所有处于连接状态的ip并按数量排序 +netstat -an | grep ESTABLISHED | awk '/^tcp/ {print $5}' | awk -F: '{print $1}' | sort | uniq -c | sort -nr ``` **案例分析** ```shell -[root@localhost ~]# netstatActive Internet connections (w/o servers)Proto Recv-Q Send-Q Local Address Foreign Address Statetcp 0 2 210.34.6.89:telnet 210.34.6.96:2873 ESTABLISHEDtcp 296 0 210.34.6.89:1165 210.34.6.84:netbios-ssn ESTABLISHEDtcp 0 0 localhost.localdom:9001 localhost.localdom:1162 ESTABLISHEDtcp 0 0 localhost.localdom:1162 localhost.localdom:9001 ESTABLISHEDtcp 0 80 210.34.6.89:1161 210.34.6.10:netbios-ssn CLOSEActive UNIX domain sockets (w/o servers)Proto RefCnt Flags Type State I-Node Pathunix 1 [ ] STREAM CONNECTED 16178 @000000ddunix 1 [ ] STREAM CONNECTED 16176 @000000dcunix 9 [ ] DGRAM 5292 /dev/logunix 1 [ ] STREAM CONNECTED 16182 @000000df +[root@localhost ~]# netstat +Active Internet connections (w/o servers) +Proto Recv-Q Send-Q Local Address Foreign Address State +tcp 0 2 210.34.6.89:telnet 210.34.6.96:2873 ESTABLISHED +tcp 296 0 210.34.6.89:1165 210.34.6.84:netbios-ssn ESTABLISHED +tcp 0 0 localhost.localdom:9001 localhost.localdom:1162 ESTABLISHED +tcp 0 0 localhost.localdom:1162 localhost.localdom:9001 ESTABLISHED +tcp 0 80 210.34.6.89:1161 210.34.6.10:netbios-ssn CLOSE + +Active UNIX domain sockets (w/o servers) +Proto RefCnt Flags Type State I-Node Path +unix 1 [ ] STREAM CONNECTED 16178 @000000dd +unix 1 [ ] STREAM CONNECTED 16176 @000000dc +unix 9 [ ] DGRAM 5292 /dev/log +unix 1 [ ] STREAM CONNECTED 16182 @000000df ``` 其中"Recv-Q"和"Send-Q"指%0A的是接收队列和发送队列,这些数字一般都应该是0。如果不是则表示软件包正在队列中堆积,这种情况只能在非常少的情况见到。 @@ -1154,7 +1888,19 @@ netstat 是一个内置工具,用于显示IP、TCP、UDP和ICMP协议相关的 iftop可用来监控网卡的实时流量(可以指定网段)、反向解析IP、显示端口信息等,详细的将会在后面的使用参数中说明。 ```shell -# 安装命令yum install iftop# 开始监控iftop# 结果参数说明:# 1.中间的<= =>这两个左右箭头,表示的是流量的方向。# 2.TX:发送流量# 3.RX:接收流量# 4.TOTAL:总流量# 5.Cumm:运行iftop到目前时间的总流量# 6.peak:流量峰值# 7.rates:分别表示过去 2s 10s 40s 的平均流量 +# 安装命令 +yum install iftop +# 开始监控 +iftop + +# 结果参数说明: +# 1.中间的<= =>这两个左右箭头,表示的是流量的方向。 +# 2.TX:发送流量 +# 3.RX:接收流量 +# 4.TOTAL:总流量 +# 5.Cumm:运行iftop到目前时间的总流量 +# 6.peak:流量峰值 +# 7.rates:分别表示过去 2s 10s 40s 的平均流量 ``` ![iftop](images/DevOps/iftop.png) @@ -1168,7 +1914,46 @@ tcpdump可以用来查看网络连接的封包内容。它显示了传输过程 ![tcpdump](images/DevOps/tcpdump.jpg) ```shell -# 过滤主机--------# 抓取所有经过 eth1,目的或源地址是 192.168.1.1 的网络数据tcpdump -i eth1 host 192.168.1.1# 源地址tcpdump -i eth1 src host 192.168.1.1# 目的地址tcpdump -i eth1 dst host 192.168.1.1# 过滤端口--------# 抓取所有经过 eth1,目的或源端口是 25 的网络数据tcpdump -i eth1 port 25# 源端口tcpdump -i eth1 src port 25# 目的端口tcpdump -i eth1 dst port 25# 网络过滤--------tcpdump -i eth1 net 192.168tcpdump -i eth1 src net 192.168tcpdump -i eth1 dst net 192.168# 协议过滤--------tcpdump -i eth1 arptcpdump -i eth1 iptcpdump -i eth1 tcptcpdump -i eth1 udp# 常用表达式----------# 非 : ! or "not" (去掉双引号)# 且 : && or "and"# 或 : || or "or"# 抓取所有经过 eth1,目的地址是 192.168.1.254 或 192.168.1.200 端口是 80 的 TCP 数据tcpdump -i eth1 '((tcp) and (port 80) and ((dst host 192.168.1.254) or (dst host 192.168.1.200)))'# 抓取所有经过 eth1,目标 MAC 地址是 00:01:02:03:04:05 的 ICMP 数据tcpdump -i eth1 '((icmp) and ((ether dst host 00:01:02:03:04:05)))'# 抓取所有经过 eth1,目的网络是 192.168,但目的主机不是 192.168.1.200 的 TCP 数据tcpdump -i eth1 '((tcp) and ((dst net 192.168) and (not dst host 192.168.1.200)))'# 实时抓取端口号8000的GET包,然后写入GET.logtcpdump -i eth0 '((port 8000) and (tcp[(tcp[12]>>2):4]=0x47455420))' -nnAl -w /tmp/GET.log +# 过滤主机-------- +# 抓取所有经过 eth1,目的或源地址是 192.168.1.1 的网络数据 +tcpdump -i eth1 host 192.168.1.1 +# 源地址 +tcpdump -i eth1 src host 192.168.1.1 +# 目的地址 +tcpdump -i eth1 dst host 192.168.1.1 + +# 过滤端口-------- +# 抓取所有经过 eth1,目的或源端口是 25 的网络数据 +tcpdump -i eth1 port 25 +# 源端口 +tcpdump -i eth1 src port 25 +# 目的端口 +tcpdump -i eth1 dst port 25 + +# 网络过滤-------- +tcpdump -i eth1 net 192.168 +tcpdump -i eth1 src net 192.168 +tcpdump -i eth1 dst net 192.168 + +# 协议过滤-------- +tcpdump -i eth1 arp +tcpdump -i eth1 ip +tcpdump -i eth1 tcp +tcpdump -i eth1 udp + +# 常用表达式---------- +# 非 : ! or "not" (去掉双引号) +# 且 : && or "and" +# 或 : || or "or" +# 抓取所有经过 eth1,目的地址是 192.168.1.254 或 192.168.1.200 端口是 80 的 TCP 数据 +tcpdump -i eth1 '((tcp) and (port 80) and ((dst host 192.168.1.254) or (dst host 192.168.1.200)))' +# 抓取所有经过 eth1,目标 MAC 地址是 00:01:02:03:04:05 的 ICMP 数据 +tcpdump -i eth1 '((icmp) and ((ether dst host 00:01:02:03:04:05)))' +# 抓取所有经过 eth1,目的网络是 192.168,但目的主机不是 192.168.1.200 的 TCP 数据 +tcpdump -i eth1 '((tcp) and ((dst net 192.168) and (not dst host 192.168.1.200)))' + +# 实时抓取端口号8000的GET包,然后写入GET.log +tcpdump -i eth0 '((port 8000) and (tcp[(tcp[12]>>2):4]=0x47455420))' -nnAl -w /tmp/GET.log ``` @@ -1182,19 +1967,60 @@ tcpdump可以用来查看网络连接的封包内容。它显示了传输过程 **安装** ```shell -# Ubuntu安装方法sudo apy-get install dstat# Centos安装方法yum install dstat# ArchLinux系统pacman -S dstat +# Ubuntu安装方法 +sudo apy-get install dstat +# Centos安装方法 +yum install dstat +# ArchLinux系统 +pacman -S dstat ``` **使用参数** ```shell -# 参数说明:# -l :显示负载统计量# -m :显示内存使用率(包括used,buffer,cache,free值)# -r :显示I/O统计# -s :显示交换分区使用情况# -t :将当前时间显示在第一行# –fs :显示文件系统统计数据(包括文件总数量和inodes值)# –nocolor :不显示颜色(有时候有用)# –socket :显示网络统计数据# –tcp :显示常用的TCP统计# –udp :显示监听的UDP接口及其当前用量的一些动态数据# 插件库:# -–disk-util :显示某一时间磁盘的忙碌状况# -–freespace :显示当前磁盘空间使用率# -–proc-count :显示正在运行的程序数量# -–top-bio :指出块I/O最大的进程# -–top-cpu :图形化显示CPU占用最大的进程# -–top-io :显示正常I/O最大的进程# -–top-mem :显示占用最多内存的进程# eg:# 查看全部内存都有谁在占用dstat -g -l -m -s --top-mem# 显示一些关于CPU资源损耗的数据dstat -c -y -l --proc-count --top-cpu# 想输出一个csv格式的文件用于以后,可以通过下面的命令dstat –output /tmp/sampleoutput.csv -cdn +# 参数说明: +# -l :显示负载统计量 +# -m :显示内存使用率(包括used,buffer,cache,free值) +# -r :显示I/O统计 +# -s :显示交换分区使用情况 +# -t :将当前时间显示在第一行 +# –fs :显示文件系统统计数据(包括文件总数量和inodes值) +# –nocolor :不显示颜色(有时候有用) +# –socket :显示网络统计数据 +# –tcp :显示常用的TCP统计 +# –udp :显示监听的UDP接口及其当前用量的一些动态数据 + +# 插件库: +# -–disk-util :显示某一时间磁盘的忙碌状况 +# -–freespace :显示当前磁盘空间使用率 +# -–proc-count :显示正在运行的程序数量 +# -–top-bio :指出块I/O最大的进程 +# -–top-cpu :图形化显示CPU占用最大的进程 +# -–top-io :显示正常I/O最大的进程 +# -–top-mem :显示占用最多内存的进程 + +# eg: +# 查看全部内存都有谁在占用 +dstat -g -l -m -s --top-mem +# 显示一些关于CPU资源损耗的数据 +dstat -c -y -l --proc-count --top-cpu +# 想输出一个csv格式的文件用于以后,可以通过下面的命令 +dstat –output /tmp/sampleoutput.csv -cdn ``` **监控分析** ```shell -dstat# 结果参数说明:# 1.CPU状态:CPU的使用率。这项报告更有趣的部分是显示了用户,系统和空闲部分,这更好地分析了CPU当前的使用状况。如果你看到"wait"一栏中,CPU的状态是一个高使用率值,那说明系统存在一些其它问题。当CPU的状态处在"waits"时,那是因为它正在等待I/O设备(例如内存,磁盘或者网络)的响应而且还没有收到。# 2.磁盘统计:磁盘的读写操作,这一栏显示磁盘的读、写总数。# 3.网络统计:网络设备发送和接受的数据,这一栏显示的网络收、发数据总数。# 4.分页统计:系统的分页活动。分页指的是一种内存管理技术用于查找系统场景,一个较大的分页表明系统正在使用大量的交换空间,或者说内存非常分散,大多数情况下你都希望看到page in(换入)和page out(换出)的值是0 0。# 5.系统统计:这一项显示的是中断(int)和上下文切换(csw)。这项统计仅在有比较基线时才有意义。这一栏中较高的统计值通常表示大量的进程造成拥塞,需要对CPU进行关注。你的服务器一般情况下都会运行运行一些程序,所以这项总是显示一些数值。# 默认情况下,dstat每秒都会刷新数据。如果想退出dstat,你可以按"CTRL-C"键。 +dstat + +# 结果参数说明: +# 1.CPU状态:CPU的使用率。这项报告更有趣的部分是显示了用户,系统和空闲部分,这更好地分析了CPU当前的使用状况。如果你看到"wait"一栏中,CPU的状态是一个高使用率值,那说明系统存在一些其它问题。当CPU的状态处在"waits"时,那是因为它正在等待I/O设备(例如内存,磁盘或者网络)的响应而且还没有收到。 +# 2.磁盘统计:磁盘的读写操作,这一栏显示磁盘的读、写总数。 +# 3.网络统计:网络设备发送和接受的数据,这一栏显示的网络收、发数据总数。 +# 4.分页统计:系统的分页活动。分页指的是一种内存管理技术用于查找系统场景,一个较大的分页表明系统正在使用大量的交换空间,或者说内存非常分散,大多数情况下你都希望看到page in(换入)和page out(换出)的值是0 0。 +# 5.系统统计:这一项显示的是中断(int)和上下文切换(csw)。这项统计仅在有比较基线时才有意义。这一栏中较高的统计值通常表示大量的进程造成拥塞,需要对CPU进行关注。你的服务器一般情况下都会运行运行一些程序,所以这项总是显示一些数值。 + +# 默认情况下,dstat每秒都会刷新数据。如果想退出dstat,你可以按"CTRL-C"键。 ``` ![dstat](images/DevOps/dstat.png) @@ -1206,7 +2032,11 @@ dstat# 结果参数说明:# 1.CPU状态:CPU的使用率。这项报告更有 saidar是一个简单且轻量的系统信息监控工具。虽然它无法提供大多性能报表,但是它能够通过一个简单明了的方式显示最有用的系统运行状况数据。可以容易地看到运行时间、平均负载、CPU、内存、进程、磁盘和网络接口统计信息。 ```powershell -# Usage: saidar [-d delay] [-c] [-v] [-h]# -d 设置更新时间(秒)# -c 彩色显示# -v 显示版本号# -h 显示本帮助 +# Usage: saidar [-d delay] [-c] [-v] [-h] +# -d 设置更新时间(秒) +# -c 彩色显示 +# -v 显示版本号 +# -h 显示本帮助 ``` ![saidar](images/DevOps/saidar.png) @@ -1222,13 +2052,22 @@ saidar是一个简单且轻量的系统信息监控工具。虽然它无法提 **在 Linux/Unix 系统中安装 Glances** ```powershell -# 对于 RHEL/CentOS/Fedora 发行版# yum install -y glances#对于 Debian/Ubuntu/Linux Mint 发行版# sudo apt-add-repository ppa:arnaud-hartmann/glances-stable# sudo apt-get update# sudo apt-get install glances +# 对于 RHEL/CentOS/Fedora 发行版 +# yum install -y glances + +#对于 Debian/Ubuntu/Linux Mint 发行版 +# sudo apt-add-repository ppa:arnaud-hartmann/glances-stable +# sudo apt-get update +# sudo apt-get install glances ``` **使用 Glances** ```powershell -# Glances 的默认刷新频率是 1 (秒),但是你可以通过在终端指定参数来手动定义其刷新频率# glances -t 2# 按下 ‘**q**‘ (‘**ESC**‘ 和 ‘**Ctrl-C**‘ 也可以) 退出 Glances 终端 +# Glances 的默认刷新频率是 1 (秒),但是你可以通过在终端指定参数来手动定义其刷新频率 +# glances -t 2 + +# 按下 ‘**q**‘ (‘**ESC**‘ 和 ‘**Ctrl-C**‘ 也可以) 退出 Glances 终端 ``` **Glances 的选项** @@ -1256,7 +2095,29 @@ saidar是一个简单且轻量的系统信息监控工具。虽然它无法提 ## Statistics ```shell -# 查看某个进程的PIDps -ef | grep arthas-demo.jar# 查看java关键词的进程的数量ps -ef | grep java| wc -l# 查看线程是否存在死锁jstack -l # 统计某个进程的线程数量ps -efL | grep [pid] | wc -l# 查看某个进制有哪些线程ps -Lp [pid] cu# 统计所有的log文件中,包含Error字符的行find / -type f -name "*.log" | xargs grep "ERROR"# 统计日志文件中包含特定异常数量cat xxx.log | grep ** *Exception| wc -l# 统计log中301、302状态码的行数,$8表示第八列是状态码,可以根据实际情况更改awk'{print $8}' 2017-05-22-access_log|egrep '301|302'| wc -l +# 查看某个进程的PID +ps -ef | grep arthas-demo.jar + +# 查看java关键词的进程的数量 +ps -ef | grep java| wc -l + +# 查看线程是否存在死锁 +jstack -l + +# 统计某个进程的线程数量 +ps -efL | grep [pid] | wc -l + +# 查看某个进制有哪些线程 +ps -Lp [pid] cu + +# 统计所有的log文件中,包含Error字符的行 +find / -type f -name "*.log" | xargs grep "ERROR" + +# 统计日志文件中包含特定异常数量 +cat xxx.log | grep ** *Exception| wc -l + +# 统计log中301、302状态码的行数,$8表示第八列是状态码,可以根据实际情况更改 +awk'{print $8}' 2017-05-22-access_log|egrep '301|302'| wc -l ``` ### GoAccess @@ -1268,7 +2129,12 @@ saidar是一个简单且轻量的系统信息监控工具。虽然它无法提 **下载与安装** ```powershell -# wget https://tar.goaccess.io/goaccess-1.3.tar.gz# tar -xzvf goaccess-1.3.tar.gz# cd goaccess-1.3/# ./configure --enable-utf8 --enable-geoip=legacy# make# make install +# wget https://tar.goaccess.io/goaccess-1.3.tar.gz +# tar -xzvf goaccess-1.3.tar.gz +# cd goaccess-1.3/ +# ./configure --enable-utf8 --enable-geoip=legacy +# make +# make install ``` @@ -1280,7 +2146,19 @@ saidar是一个简单且轻量的系统信息监控工具。虽然它无法提 **第一步:通过 `top` 命令找到最耗时 ( `Shift + P` ) 的进程** ```shell -[root@localhost ~]# toptop - 11:11:05 up 20:02, 3 users, load average: 0.09, 0.07, 0.05Tasks: 225 total, 1 running, 224 sleeping, 0 stopped, 0 zombie%Cpu(s): 0.0 us, 0.7 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 stKiB Mem : 1421760 total, 135868 free, 758508 used, 527384 buff/cacheKiB Swap: 2097148 total, 2070640 free, 26508 used. 475852 avail MemChange delay from 3.0 to PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 98344 root 20 0 2422552 23508 12108 S 0.7 1.7 0:00.32 java 1 root 20 0 194100 6244 3184 S 0.0 0.4 0:20.41 systemd 2 root 20 0 0 0 0 S 0.0 0.0 0:00.12 kthreadd 4 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0H 6 root 20 0 0 0 0 S 0.0 0.0 0:20.25 ksoftirqd/0 +[root@localhost ~]# top +top - 11:11:05 up 20:02, 3 users, load average: 0.09, 0.07, 0.05 +Tasks: 225 total, 1 running, 224 sleeping, 0 stopped, 0 zombie +%Cpu(s): 0.0 us, 0.7 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +KiB Mem : 1421760 total, 135868 free, 758508 used, 527384 buff/cache +KiB Swap: 2097148 total, 2070640 free, 26508 used. 475852 avail Mem +Change delay from 3.0 to + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND + 98344 root 20 0 2422552 23508 12108 S 0.7 1.7 0:00.32 java + 1 root 20 0 194100 6244 3184 S 0.0 0.4 0:20.41 systemd + 2 root 20 0 0 0 0 S 0.0 0.0 0:00.12 kthreadd + 4 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0H + 6 root 20 0 0 0 0 S 0.0 0.0 0:20.25 ksoftirqd/0 ``` 找到进程号是98344。 @@ -1290,7 +2168,18 @@ saidar是一个简单且轻量的系统信息监控工具。虽然它无法提 使用`ps -Lp cu`命令,查看某个进程中的线程CPU消耗排序: ```shell -[root@localhost ~]# ps -Lp 98344 cuUSER PID LWP %CPU NLWP %MEM VSZ RSS TTY STAT START TIME COMMANDroot 98344 98344 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:00 javaroot 98344 98345 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:04 javaroot 98344 98346 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:01 VM Threadroot 98344 98347 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:00 Reference Handlroot 98344 98348 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:00 Finalizerroot 98344 98349 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:00 Signal Dispatchroot 98344 98350 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:05 C2 CompilerThreroot 98344 98351 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:00 C1 CompilerThreroot 98344 98352 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:00 Service Threadroot 98344 98353 0.1 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:19 VM Periodic Tas +[root@localhost ~]# ps -Lp 98344 cu +USER PID LWP %CPU NLWP %MEM VSZ RSS TTY STAT START TIME COMMAND +root 98344 98344 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:00 java +root 98344 98345 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:04 java +root 98344 98346 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:01 VM Thread +root 98344 98347 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:00 Reference Handl +root 98344 98348 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:00 Finalizer +root 98344 98349 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:00 Signal Dispatch +root 98344 98350 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:05 C2 CompilerThre +root 98344 98351 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:00 C1 CompilerThre +root 98344 98352 0.0 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:00 Service Thread +root 98344 98353 0.1 10 4.1 2422552 59060 pts/0 Sl+ 11:09 0:19 VM Periodic Tas ``` 看`TIME`列可看出哪个线程耗费CUP多,`LWP`列可以看到线程的ID号,但需要转换成16进制才可以查询线程堆栈信息。 @@ -1300,7 +2189,8 @@ saidar是一个简单且轻量的系统信息监控工具。虽然它无法提 使用`printf '%x\n' `命令做进制转换: ```shell -[root@localhost ~]# printf '%x\n' 9834518029 +[root@localhost ~]# printf '%x\n' 98345 +18029 ``` **第四步:查看线程堆栈信息** @@ -1308,7 +2198,16 @@ saidar是一个简单且轻量的系统信息监控工具。虽然它无法提 使用jstack获取堆栈信息`jstack | grep -A 10 <16进制LWP>`: ```shell -[root@localhost ~]# jstack 98344 | grep -A 10 0x18029"main" #1 prio=5 os_prio=0 tid=0x00007fb88404b800 nid=0x18029 waiting on condition [0x00007fb88caab000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) at demo.MathGame.main(MathGame.java:17)"VM Thread" os_prio=0 tid=0x00007fb8840f2800 nid=0x1802a runnable"VM Periodic Task Thread" os_prio=0 tid=0x00007fb884154000 nid=0x18031 waiting on condition +[root@localhost ~]# jstack 98344 | grep -A 10 0x18029 +"main" #1 prio=5 os_prio=0 tid=0x00007fb88404b800 nid=0x18029 waiting on condition [0x00007fb88caab000] + java.lang.Thread.State: TIMED_WAITING (sleeping) + at java.lang.Thread.sleep(Native Method) + at java.lang.Thread.sleep(Thread.java:340) + at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) + at demo.MathGame.main(MathGame.java:17) + +"VM Thread" os_prio=0 tid=0x00007fb8840f2800 nid=0x1802a runnable +"VM Periodic Task Thread" os_prio=0 tid=0x00007fb884154000 nid=0x18031 waiting on condition ``` 通过命令我们可以看到这个线程的对应的耗时代码是在 `demo.MathGame.main(MathGame.java:17)` @@ -1320,7 +2219,12 @@ saidar是一个简单且轻量的系统信息监控工具。虽然它无法提 `watch more /proc/net/dev`用于定位丢包,错包情况,以便看网络瓶颈,重点关注drop(包被丢弃)和网络包传送的总量,不要超过网络上限: ```shell -[root@localhost ~]# watch -n 2 more /proc/net/devEvery 2.0s: more /proc/net/dev Fri May 1 17:16:55 2020Inter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed lo: 10025 130 0 0 0 0 0 0 10025 130 0 0 0 0 0 0 ens33: 759098071 569661 0 0 0 0 0 0 19335572 225551 0 0 0 0 0 0 +[root@localhost ~]# watch -n 2 more /proc/net/dev +Every 2.0s: more /proc/net/dev Fri May 1 17:16:55 2020 +Inter-| Receive | Transmit + face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed + lo: 10025 130 0 0 0 0 0 0 10025 130 0 0 0 0 0 0 + ens33: 759098071 569661 0 0 0 0 0 0 19335572 225551 0 0 0 0 0 0 ``` - 最左边的表示接口的名字,Receive表示收包,Transmit表示发送包 @@ -1336,7 +2240,24 @@ saidar是一个简单且轻量的系统信息监控工具。虽然它无法提 `traceroute ip`可以查看路由经过的地址,常用来统计网络在各个路由区段的耗时,如: ```shell -[root@localhost ~]# traceroute 14.215.177.38traceroute to 14.215.177.38 (14.215.177.38), 30 hops max, 60 byte packets 1 CD-HZTK5H2.mshome.net (192.168.137.1) 0.126 ms * * 2 * * * 3 10.250.112.3 (10.250.112.3) 12.587 ms 12.408 ms 12.317 ms 4 172.16.227.230 (172.16.227.230) 2.152 ms 2.040 ms 1.956 ms 5 172.16.227.202 (172.16.227.202) 11.884 ms 11.746 ms 12.692 ms 6 172.16.227.65 (172.16.227.65) 2.665 ms 3.143 ms 2.923 ms 7 171.223.206.217 (171.223.206.217) 2.834 ms 2.752 ms 2.654 ms 8 182.150.18.205 (182.150.18.205) 5.145 ms 5.815 ms 5.542 ms 9 110.188.6.33 (110.188.6.33) 3.514 ms 171.208.199.185 (171.208.199.185) 3.431 ms 171.208.199.181 (171.208.199.181) 10.768 ms10 202.97.29.17 (202.97.29.17) 29.574 ms 202.97.30.146 (202.97.30.146) 32.619 ms *11 113.96.5.126 (113.96.5.126) 36.062 ms 113.96.5.70 (113.96.5.70) 35.940 ms 113.96.4.42 (113.96.4.42) 45.859 ms12 90.96.135.219.broad.fs.gd.dynamic.163data.com.cn (219.135.96.90) 35.680 ms 35.468 ms 35.304 ms13 14.215.32.102 (14.215.32.102) 35.135 ms 14.215.32.110 (14.215.32.110) 35.613 ms 14.29.117.242 (14.29.117.242) 54.712 ms14 * 14.215.32.134 (14.215.32.134) 49.518 ms 14.215.32.122 (14.215.32.122) 47.652 ms15 * * *... +[root@localhost ~]# traceroute 14.215.177.38 +traceroute to 14.215.177.38 (14.215.177.38), 30 hops max, 60 byte packets + 1 CD-HZTK5H2.mshome.net (192.168.137.1) 0.126 ms * * + 2 * * * + 3 10.250.112.3 (10.250.112.3) 12.587 ms 12.408 ms 12.317 ms + 4 172.16.227.230 (172.16.227.230) 2.152 ms 2.040 ms 1.956 ms + 5 172.16.227.202 (172.16.227.202) 11.884 ms 11.746 ms 12.692 ms + 6 172.16.227.65 (172.16.227.65) 2.665 ms 3.143 ms 2.923 ms + 7 171.223.206.217 (171.223.206.217) 2.834 ms 2.752 ms 2.654 ms + 8 182.150.18.205 (182.150.18.205) 5.145 ms 5.815 ms 5.542 ms + 9 110.188.6.33 (110.188.6.33) 3.514 ms 171.208.199.185 (171.208.199.185) 3.431 ms 171.208.199.181 (171.208.199.181) 10.768 ms +10 202.97.29.17 (202.97.29.17) 29.574 ms 202.97.30.146 (202.97.30.146) 32.619 ms * +11 113.96.5.126 (113.96.5.126) 36.062 ms 113.96.5.70 (113.96.5.70) 35.940 ms 113.96.4.42 (113.96.4.42) 45.859 ms +12 90.96.135.219.broad.fs.gd.dynamic.163data.com.cn (219.135.96.90) 35.680 ms 35.468 ms 35.304 ms +13 14.215.32.102 (14.215.32.102) 35.135 ms 14.215.32.110 (14.215.32.110) 35.613 ms 14.29.117.242 (14.29.117.242) 54.712 ms +14 * 14.215.32.134 (14.215.32.134) 49.518 ms 14.215.32.122 (14.215.32.122) 47.652 ms +15 * * * +... ``` @@ -1346,7 +2267,11 @@ saidar是一个简单且轻量的系统信息监控工具。虽然它无法提 `netstat -i`可以查看网络错误: ```shell -[root@localhost ~]# netstat -iKernel Interface tableIface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flgens33 1500 570291 0 0 0 225897 0 0 0 BMRUlo 65536 130 0 0 0 130 0 0 0 LRU +[root@localhost ~]# netstat -i +Kernel Interface table +Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg +ens33 1500 570291 0 0 0 225897 0 0 0 BMRU +lo 65536 130 0 0 0 130 0 0 0 LRU ``` - `Iface`: 网络接口名称 @@ -1370,7 +2295,19 @@ saidar是一个简单且轻量的系统信息监控工具。虽然它无法提 **TCP重传率 =(RetransSegs ÷ OutSegs)× 100%** ```shell -[root@localhost ~]# cat /proc/net/snmpIp: Forwarding DefaultTTL InReceives InHdrErrors InAddrErrors ForwDatagrams InUnknownProtos InDiscards InDelivers OutRequests OutDiscards OutNoRoutes ReasmTimeout ReasmReqds ReasmOKs ReasmFails FragOKs FragFails FragCreatesIp: 1 64 241708 0 0 0 0 0 238724 225517 15 0 0 0 0 0 0 0 0Icmp: InMsgs InErrors InCsumErrors InDestUnreachs InTimeExcds InParmProbs InSrcQuenchs InRedirects InEchos InEchoReps InTimestamps InTimestampReps InAddrMasks InAddrMaskReps OutMsgs OutErrors OutDestUnreachs OutTimeExcds OutParmProbs OutSrcQuenchs OutRedirects OutEchos OutEchoReps OutTimestamps OutTimestampReps OutAddrMasks OutAddrMaskRepsIcmp: 149 0 0 50 99 0 0 0 0 0 0 0 0 0 147 0 147 0 0 0 0 0 0 0 0 0 0IcmpMsg: InType3 InType11 OutType3IcmpMsg: 50 99 147Tcp: RtoAlgorithm RtoMin RtoMax MaxConn ActiveOpens PassiveOpens AttemptFails EstabResets CurrEstab InSegs OutSegs RetransSegs InErrs OutRsts InCsumErrorsTcp: 1 200 120000 -1 376 6 0 0 4 236711 223186 292 0 4 0Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrorsUdp: 1405 438 0 1896 0 0 0UdpLite: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrorsUdpLite: 0 0 0 0 0 0 0 +[root@localhost ~]# cat /proc/net/snmp +Ip: Forwarding DefaultTTL InReceives InHdrErrors InAddrErrors ForwDatagrams InUnknownProtos InDiscards InDelivers OutRequests OutDiscards OutNoRoutes ReasmTimeout ReasmReqds ReasmOKs ReasmFails FragOKs FragFails FragCreates +Ip: 1 64 241708 0 0 0 0 0 238724 225517 15 0 0 0 0 0 0 0 0 +Icmp: InMsgs InErrors InCsumErrors InDestUnreachs InTimeExcds InParmProbs InSrcQuenchs InRedirects InEchos InEchoReps InTimestamps InTimestampReps InAddrMasks InAddrMaskReps OutMsgs OutErrors OutDestUnreachs OutTimeExcds OutParmProbs OutSrcQuenchs OutRedirects OutEchos OutEchoReps OutTimestamps OutTimestampReps OutAddrMasks OutAddrMaskReps +Icmp: 149 0 0 50 99 0 0 0 0 0 0 0 0 0 147 0 147 0 0 0 0 0 0 0 0 0 0 +IcmpMsg: InType3 InType11 OutType3 +IcmpMsg: 50 99 147 +Tcp: RtoAlgorithm RtoMin RtoMax MaxConn ActiveOpens PassiveOpens AttemptFails EstabResets CurrEstab InSegs OutSegs RetransSegs InErrs OutRsts InCsumErrors +Tcp: 1 200 120000 -1 376 6 0 0 4 236711 223186 292 0 4 0 +Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors +Udp: 1405 438 0 1896 0 0 0 +UdpLite: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors +UdpLite: 0 0 0 0 0 0 0 ``` TCP重传率为:(292÷223186) × 100% = 0.13% @@ -1410,7 +2347,9 @@ TCP重传率为:(292÷223186) × 100% = 0.13% 脚本文件“copy.sh”,其内容如下: ```shell -m=$1n=$2echo $m-$n +m=$1 +n=$2 +echo $m-$n ``` 执行命令:“sh copy.sh 111 222”;输出 111-222 @@ -1420,7 +2359,8 @@ m=$1n=$2echo $m-$n ### 格式化输出日期 ```shell -curdate="`date +%Y%m%d%H%M%S`"echo $curdate +curdate="`date +%Y%m%d%H%M%S`" +echo $curdate ``` 执行结果:20210504175817 @@ -1432,7 +2372,8 @@ curdate="`date +%Y%m%d%H%M%S`"echo $curdate 退出当前shell脚本,一般来说,返回0表示执行成功,其他值表示没有执行成功。 ```shell -exist 0 # 返回0exist 1 # 返回1 +exist 0 # 返回0 +exist 1 # 返回1 ``` @@ -1464,7 +2405,9 @@ p_name='kang' 使用单变量: ```shell -echo $p_name'.js' # 输出kang.jsecho $p_name.js # 输出kang.jscp $p_name.js copy.js; +echo $p_name'.js' # 输出kang.js +echo $p_name.js # 输出kang.js +cp $p_name.js copy.js; ``` @@ -1472,13 +2415,17 @@ echo $p_name'.js' # 输出kang.jsecho $p_name.js # 输出kang.jscp $p ### 系统变量 ```shell -pwd=$PWD # 当前目录user=$USER # 当前用户echo $pwdecho $user +pwd=$PWD # 当前目录 +user=$USER # 当前用户 +echo $pwd +echo $user ``` 运行脚本后输出: ```shell -/home/rainman/testrainman +/home/rainman/test +rainman ``` @@ -1492,7 +2439,13 @@ pwd=$PWD # 当前目录user=$USER # 当前用户echo $pwdecho $user - 常用的 Bash Shell 只支持一维数组,不支持多维数组 ```shell - #!/bin/bashnums=(29 100 13 8 91 44)echo ${nums[@]} # 输出所有数组元素nums[10]=66 # 给第10个元素赋值(此时会增加数组长度)echo ${nums[*]} # 输出所有数组元素echo ${nums[4]} # 输出第4个元素 + #!/bin/bash + +nums=(29 100 13 8 91 44) +echo ${nums[@]} # 输出所有数组元素 +nums[10]=66 # 给第10个元素赋值(此时会增加数组长度) +echo ${nums[*]} # 输出所有数组元素 +echo ${nums[4]} # 输出第4个元素 ``` @@ -1502,13 +2455,21 @@ pwd=$PWD # 当前目录user=$USER # 当前用户echo $pwdecho $user 利用`@`或`*`,可以将数组扩展成列表,然后使用`#`来获取数组元素的个数,格式如下: ```shell -${#array_name[@]}${#array_name[*]} +${#array_name[@]} +${#array_name[*]} ``` 其中 array_name 表示数组名。两种形式是等价的,选择其一即可。示例如下: ```shell - #!/bin/bashnums=(29 100 13)echo ${#nums[*]} # 输出3# 向数组中添加元素nums[10]="http://c.biancheng.net/shell/"echo ${#nums[@]} # 输出4 + #!/bin/bash + +nums=(29 100 13) +echo ${#nums[*]} # 输出3 + +# 向数组中添加元素 +nums[10]="http://c.biancheng.net/shell/" +echo ${#nums[@]} # 输出4 ``` @@ -1518,13 +2479,19 @@ ${#array_name[@]}${#array_name[*]} 拼接数组的思路是:先利用`@`或`*`,将数组扩展成列表,然后再合并到一起。具体格式如下: ```shell -array_new=(${array1[@]} ${array2[@]})array_new=(${array1[*]} ${array2[*]}) +array_new=(${array1[@]} ${array2[@]}) +array_new=(${array1[*]} ${array2[*]}) ``` 两种方式是等价的,选择其一即可。其中,array1 和 array2 是需要拼接的数组,array_new 是拼接后形成的新数组。完整示例如下: ```shell - #!/bin/basharray1=(23 56)array2=(99 "https://www.baidu.com/")array_new=(${array1[@]} ${array2[*]})echo ${array_new[@]} # 也可以写作 ${array_new[*]} + #!/bin/bash + +array1=(23 56) +array2=(99 "https://www.baidu.com/") +array_new=(${array1[@]} ${array2[*]}) +echo ${array_new[@]} # 也可以写作 ${array_new[*]} ``` 运行结果:`23 56 99 https://www.baidu.com/` @@ -1548,7 +2515,14 @@ unset array_name 那么就是删除整个数组,所有元素都会消失。 ```shell - #!/bin/bash arr=(23 56 99 "https://www.baidu.com/")unset arr[1]echo ${arr[@]}unset arrecho ${arr[*]} + #!/bin/bash + +arr=(23 56 99 "https://www.baidu.com/") +unset arr[1] +echo ${arr[@]} + +unset arr +echo ${arr[*]} ``` 运行结果:`23 99 https://www.baidu.com/` @@ -1580,7 +2554,15 @@ unset array_name 注意:在 expr 命令所支持的操作符中,“`|`、`&`、`<`、`<=`、`>`、`>=`、 `\*` ” 这几个需要用 `\` 符进行转义再使用。此外,表达式的各字符之间需要用空格隔开。使用方法如下: ```shell - #!/bin/bash a=5;b=6;c=0 echo $(expr $a \| $c) # 输出 5 echo $(expr $b \& $c) # 输出 0 echo $(expr $a \& $b) # 输出 5 echo $(expr $a \<= $b) # 输出 1 echo $(expr $a \* $b) # 输出 30 echo $(expr $a = 2) # 输出 1 exit 0 + #!/bin/bash + + a=5;b=6;c=0 + echo $(expr $a \| $c) # 输出 5 + echo $(expr $b \& $c) # 输出 0 + echo $(expr $a \& $b) # 输出 5 + echo $(expr $a \<= $b) # 输出 1 + echo $(expr $a \* $b) # 输出 30 + echo $(expr $a = 2) # 输出 1 exit 0 ``` @@ -1600,7 +2582,18 @@ unset array_name expr 虽然功能强大,但是上面已经提到,在进行一些运算的时候,需要使用 `\` 符来进行转义,这对于阅读代码的人来说并不友好。另一方面,expr 命令执行起来其实很慢,因为它需要调用一个新的 shell 来处理 expr 命令。更新更好的一种做法是使用 `$((...))` 扩展的方式。只需要将准备求值的表达式放在 `$((...))` 的括号中即可进行简单的算术求值。且,所有支持 `$(( ... ))` 的 shell,都可以让用户在提供变量名称时,无须前置 `$` 符。用一段代码演示一下用法: ```shell - #!/bin/bash a=5;b=6 echo $(($a + $b))  # 输出 11 。在变量名前加上 $,这在shell中一般是取变量值的意思 echo $((a + b)) # 输出 11 。可见,变量前不加 $ 也是可以的,为了简便,后面的代码就不加 $ 了 echo $((a | b)) # 输出 7 。这里的 | 是按位或操作符 echo $((a || b)) # 输出 1 。这里的 || 是逻辑或操作符 echo $((a & b)) # 输出 4 。这里的 & 是按位与操作符 echo $((a && b)) # 输出 1 。这里的 && 是逻辑与操作符 echo $((a * b)) # 输出 30 echo $((a == b)) # 输出 0 exit 0 + #!/bin/bash + + a=5;b=6 + + echo $(($a + $b))  # 输出 11 。在变量名前加上 $,这在shell中一般是取变量值的意思 + echo $((a + b)) # 输出 11 。可见,变量前不加 $ 也是可以的,为了简便,后面的代码就不加 $ 了 + echo $((a | b)) # 输出 7 。这里的 | 是按位或操作符 + echo $((a || b)) # 输出 1 。这里的 || 是逻辑或操作符 + echo $((a & b)) # 输出 4 。这里的 & 是按位与操作符 + echo $((a && b)) # 输出 1 。这里的 && 是逻辑与操作符 + echo $((a * b)) # 输出 30 + echo $((a == b)) # 输出 0 exit 0 ``` @@ -1628,7 +2621,22 @@ expr 虽然功能强大,但是上面已经提到,在进行一些运算的时 字符串拼接连接、合并。 ```shell - #!/bin/bashname="Shell"url="https://www.baidu.com/"str1=$name$url # 中间不能有空格str2="$name $url" # 如果被双引号包围,那么中间可以有空格str3=$name": "$url # 中间可以出现别的字符串str4="$name: $url" # 这样写也可以str5="${name}Script: ${url}index.html" # 这个时候需要给变量名加上大括号echo $str1echo $str2echo $str3echo $str4echo $str5 + #!/bin/bash + +name="Shell" +url="https://www.baidu.com/" + +str1=$name$url # 中间不能有空格 +str2="$name $url" # 如果被双引号包围,那么中间可以有空格 +str3=$name": "$url # 中间可以出现别的字符串 +str4="$name: $url" # 这样写也可以 +str5="${name}Script: ${url}index.html" # 这个时候需要给变量名加上大括号 + +echo $str1 +echo $str2 +echo $str3 +echo $str4 +echo $str5 ``` @@ -1666,7 +2674,18 @@ test 命令可以使用的条件类型有三类:字符串比较、算术比较 使用方法如下: ```shell -str1="tongye"str2="ttyezi"# 用 test 命令,test 语句的结果将作为 if 的判断条件,结果为真即条件为真,则执行 if 下面的语句if test "$str1" = "$str2" ; then ....fi# 用 [ 命令的话,可以这样,注意 [ 与表达式之间要有空格if [ "$str1" != "$str2" ] ; then ....fi if [ -n "$str1" ] ; then ....fi +str1="tongye" +str2="ttyezi" + +# 用 test 命令,test 语句的结果将作为 if 的判断条件,结果为真即条件为真,则执行 if 下面的语句 +if test "$str1" = "$str2" ; then + .... +fi + +# 用 [ 命令的话,可以这样,注意 [ 与表达式之间要有空格 +if [ "$str1" != "$str2" ] ; then + .... +fi if [ -n "$str1" ] ; then ....fi ``` 使用字符串比较的时候,必须给变量加上引号 " " ,避免因为空字符或字符串中的空格导致一些问题。实际上,对于条件测试语句里的变量,都建议加上双引号,能做字符串比较的时候,不要用数值比较。 @@ -1688,13 +2707,38 @@ str1="tongye"str2="ttyezi"# 用 test 命令,test 语句的结果将作为 if 使用方法如下: ```shell -num1=2num2=3if [ "$num1" -eq "$num2" ] ; then ...fiif [ "$num1" -le "$num2" ] ; then ....fi +num1=2 +num2=3 + +if [ "$num1" -eq "$num2" ] ; then + ... +fi + +if [ "$num1" -le "$num2" ] ; then + .... +fi ``` 注意算术比较和字符串比较之间的不同之处,字符串比较比较的是两个字符串,数字也是能组成字符串的,因此,当我们使用字符串比较的方式和数字比较的方式来比较两串数字的时候,结果会有些不同。案例如下: ```shell - #!/bin/bash val1="1" val2="001" val3="1 " # 字符串 val3 在 1 的后面还有一个空格    [ "$val1" = "$val2" ] echo $?        # 使用字符串比较,退出码为 1,说明两个字符串不相等 [ "$val1" -eq "$val2" ] echo $?        # 使用数值比较,退出码为 0,说明两个数值相等 [ "$val1" = "$val3" ] echo $?        # 退出码为 1 [ "$val1" -eq "$val3" ] echo $?        # 退出码为 0 exit 0 + #!/bin/bash + + val1="1" + val2="001" + val3="1 " # 字符串 val3 在 1 的后面还有一个空格    + + [ "$val1" = "$val2" ] + echo $?        # 使用字符串比较,退出码为 1,说明两个字符串不相等 + + [ "$val1" -eq "$val2" ] + echo $?        # 使用数值比较,退出码为 0,说明两个数值相等 + + [ "$val1" = "$val3" ] + echo $?        # 退出码为 1 + + [ "$val1" -eq "$val3" ] + echo $?        # 退出码为 0 exit 0 ``` 需要注意的是,如果在编写代码时,变量没有加上双引号,上述程序的结果又会不同,仅对 val3 进行取值,将会忽略该字符串中的空格,则第三个表达式的退出码将为 0 。这也说明了在变量两边加上双引号的重要性。 @@ -1718,7 +2762,17 @@ num1=2num2=3if [ "$num1" -eq "$num2" ] ; then ...fiif [ "$num1" -le "$num2" ] 用一个例子演示一下: ```shell -#!/bin/bashif [ -f /bin/bash ] ; then echo "file /bin/bash exists"fiif [ -d /bin/bash ] ; then  echo "/bin/bash is a directory"else  echo "/bin/bash is not a directory"fiexit 0 +#!/bin/bash + +if [ -f /bin/bash ] ; then + echo "file /bin/bash exists" +fi + +if [ -d /bin/bash ] ; then +  echo "/bin/bash is a directory" +else +  echo "/bin/bash is not a directory" +fiexit 0 ``` @@ -1730,13 +2784,22 @@ num1=2num2=3if [ "$num1" -eq "$num2" ] ; then ...fiif [ "$num1" -le "$num2" ] "["和"]"前后的空格必须有,否则提示错误。 ```shell -m="kang2"if [ "$m" == 'kang' ]; then echo 'kang'elif [ $m == 'kang2' ]; then echo 'kang2'else echo 'no'fi +m="kang2" +if [ "$m" == 'kang' ]; then + echo 'kang' +elif [ $m == 'kang2' ]; then + echo 'kang2' +else + echo 'no' +fi ``` 示例:判断文件夹 ```shell -if [ -d './js' ]; then echo 'js是文件夹'fi +if [ -d './js' ]; then + echo 'js是文件夹' +fi ``` @@ -1746,7 +2809,11 @@ if [ -d './js' ]; then echo 'js是文件夹'fi 与其他编程语言中的 case 语句类似, shell 中的 case 语句也可以用来进行模式匹配,语法如下: ```shell -case variable in pattern [ | pattern ] ... ) statements;; pattern [ | pattern ] ... ) statements;; ...esac +case variable in + pattern [ | pattern ] ... ) statements;; + pattern [ | pattern ] ... ) statements;; + ... +esac ``` 关于 case 的语法,有以下几点需要说明一下: @@ -1758,7 +2825,17 @@ case variable in pattern [ | pattern ] ... ) statements;; pattern [ | patt - case 语句支持使用正则表达式作为匹配项,这使得 case 语句的功能更为强大 ```shell -#!/bin/bashread -p "please keyin a word:" -t 5 wordcase $word in [a-z] | [A-Z] ) echo "You have keyin a letter";; [1-9] ) echo "You have keyin a number";; * ) echo "Unknow input"esacexit 0 +#!/bin/bash + +read -p "please keyin a word:" -t 5 word + +case $word in + [a-z] | [A-Z] ) echo "You have keyin a letter";; + [1-9] ) echo "You have keyin a number";; + * ) echo "Unknow input" +esac + +exit 0 ``` 这段代码从键盘输入一个字符,然后进行匹配,判断这个字符是字母还是数字,都不是的话返回未知输入。 @@ -1772,17 +2849,37 @@ case variable in pattern [ | pattern ] ... ) statements;; pattern [ | patt **foreach形式**: ```shell -name="rain man's blog"for loop in $name; do echo $loop;done +name="rain man's blog" +for loop in $name; do + echo $loop; +done ``` **自定义步长循环**: ```shell -for ((初始值; 限定值; 执行步长 ))do # 程序段done# 例如for (( i = 1; i < ${number}; i = i + 1 ))do # 程序段done +for ((初始值; 限定值; 执行步长 )) +do + # 程序段 +done + +# 例如 +for (( i = 1; i < ${number}; i = i + 1 )) +do + # 程序段 +done ``` ```shell - #!/bin/bash for name in tongye wuhen xiaodong wufei laowang do echo $name done exit 0# 依次输出:tongye wuhen xiaodong wufei laowang + #!/bin/bash + + for name in tongye wuhen xiaodong wufei laowang + do + echo $name + done + + exit 0 +# 依次输出:tongye wuhen xiaodong wufei laowang ``` @@ -1792,19 +2889,36 @@ for ((初始值; 限定值; 执行步长 ))do # 程序段done# 例如for (( i 如果你需要进行循环操作而是先不知道需要循环的次数,可以使用 while 循环,while 循环的语法如下: ```shell -while conditiondo statementsdone +while condition +do + statements +done ``` until 循环语句的功能与 while 一样,不同的是对于条件判断结果的处理上。until 循环的语法如下: ```shell -until conditiondo statementsdone +until condition +do + statements +done ``` 在 while 和 until 语句中,condition 是判断条件,不同的是,while 语句中,若判断条件为真,则执行循环体;until 语句中,若判断条件为真,则停止执行循环体。 ```shell - #!/bin/bash i=1 while [ "$i" -le 10 ] do read -p "please keyin a number:" i done 9 10 echo "$i" 11 12 exit 0 + #!/bin/bash + + i=1 + + while [ "$i" -le 10 ] + do + read -p "please keyin a number:" i + done + 9 + 10 echo "$i" + 11 + 12 exit 0 ``` 这段代码从键盘中输入一个数字,直到输入数值大于 10,退出循环并打印最后输入的那个值。 @@ -1830,11 +2944,17 @@ until conditiondo statementsdone 示例: ```shell - #!/bin/bash for str in "test1" "test2" "test3"do echo $str >>demo.txt # 将输入结果以追加的方式重定向到文件done + #!/bin/bash + +for str in "test1" "test2" "test3" +do + echo $str >>demo.txt # 将输入结果以追加的方式重定向到文件 +done ``` ```shell -[localhost]$ ls -l >demo.txt # 重定向[localhost]$ cat demo.txt # 查看文件内容 +[localhost]$ ls -l >demo.txt # 重定向 +[localhost]$ cat demo.txt # 查看文件内容 ``` @@ -1844,7 +2964,15 @@ until conditiondo statementsdone `$?` 获取函数的返回值。 ```shell - #!/bin/bash # 得到两个数相加的和function add(){ return `expr $1 + $2`}add 23 50 # 调用函数echo $? # 获取函数返回值 + #!/bin/bash + +# 得到两个数相加的和 +function add(){ + return `expr $1 + $2` +} + +add 23 50 # 调用函数 +echo $? # 获取函数返回值 ``` @@ -1858,31 +2986,115 @@ until conditiondo statementsdone **检测两台服务器指定目录下的文件一致性** ```shell -#!/bin/bash######################################检测两台服务器指定目录下的文件一致性######################################通过对比两台服务器上文件的md5值,达到检测一致性的目的dir=/data/webb_ip=192.168.88.10#将指定目录下的文件全部遍历出来并作为md5sum命令的参数,进而得到所有文件的md5值,并写入到指定文件中find $dir -type f|xargs md5sum > /tmp/md5_a.txtssh $b_ip "find $dir -type f|xargs md5sum > /tmp/md5_b.txt"scp $b_ip:/tmp/md5_b.txt /tmp#将文件名作为遍历对象进行一一比对for f in `awk '{print 2} /tmp/md5_a.txt'`do#以a机器为标准,当b机器不存在遍历对象中的文件时直接输出不存在的结果if grep -qw "$f" /tmp/md5_b.txtthenmd5_a=`grep -w "$f" /tmp/md5_a.txt|awk '{print 1}'`md5_b=`grep -w "$f" /tmp/md5_b.txt|awk '{print 1}'`#当文件存在时,如果md5值不一致则输出文件改变的结果if [ $md5_a != $md5_b ]thenecho "$f changed."fielseecho "$f deleted."fidone +#!/bin/bash +##################################### +#检测两台服务器指定目录下的文件一致性 +##################################### +#通过对比两台服务器上文件的md5值,达到检测一致性的目的 +dir=/data/web +b_ip=192.168.88.10 +#将指定目录下的文件全部遍历出来并作为md5sum命令的参数,进而得到所有文件的md5值,并写入到指定文件中 +find $dir -type f|xargs md5sum > /tmp/md5_a.txt +ssh $b_ip "find $dir -type f|xargs md5sum > /tmp/md5_b.txt" +scp $b_ip:/tmp/md5_b.txt /tmp +#将文件名作为遍历对象进行一一比对 +for f in `awk '{print 2} /tmp/md5_a.txt'` +do +#以a机器为标准,当b机器不存在遍历对象中的文件时直接输出不存在的结果 +if grep -qw "$f" /tmp/md5_b.txt +then +md5_a=`grep -w "$f" /tmp/md5_a.txt|awk '{print 1}'` +md5_b=`grep -w "$f" /tmp/md5_b.txt|awk '{print 1}'` +#当文件存在时,如果md5值不一致则输出文件改变的结果 +if [ $md5_a != $md5_b ] +then +echo "$f changed." +fi +else +echo "$f deleted." +fi +done ``` **定时清空文件内容,定时记录文件大小** ```shell -#!/bin/bash#################################################################每小时执行一次脚本(任务计划),当时间为0点或12点时,将目标目录下的所有文件内#容清空,但不删除文件,其他时间则只统计各个文件的大小,一个文件一行,输出到以时#间和日期命名的文件中,需要考虑目标目录下二级、三级等子目录的文件################################################################logfile=/tmp/`date +%H-%F`.logn=`date +%H`if [ $n -eq 00 ] || [ $n -eq 12 ]then#通过for循环,以find命令作为遍历条件,将目标目录下的所有文件进行遍历并做相应操作for i in `find /data/log/ -type f`dotrue > $idoneelsefor i in `find /data/log/ -type f`dodu -sh $i >> $logfiledonefi +#!/bin/bash +################################################################ +#每小时执行一次脚本(任务计划),当时间为0点或12点时,将目标目录下的所有文件内 +#容清空,但不删除文件,其他时间则只统计各个文件的大小,一个文件一行,输出到以时#间和日期命名的文件中,需要考虑目标目录下二级、三级等子目录的文件 +################################################################ +logfile=/tmp/`date +%H-%F`.log +n=`date +%H` +if [ $n -eq 00 ] || [ $n -eq 12 ] +then +#通过for循环,以find命令作为遍历条件,将目标目录下的所有文件进行遍历并做相应操作 +for i in `find /data/log/ -type f` +do +true > $i +done +else +for i in `find /data/log/ -type f` +do +du -sh $i >> $logfile +done +fi ``` **检测网卡流量,并按规定格式记录在日志中** ```shell -#!/bin/bash########################################################检测网卡流量,并按规定格式记录在日志中#规定一分钟记录一次#日志格式如下所示:#2019-08-12 20:40#ens33 input: 1234bps#ens33 output: 1235bps######################################################3while :do#设置语言为英文,保障输出结果是英文,否则会出现bugLANG=enlogfile=/tmp/`date +%d`.log#将下面执行的命令结果输出重定向到logfile日志中exec >> $logfiledate +"%F %H:%M"#sar命令统计的流量单位为kb/s,日志格式为bps,因此要*1000*8sar -n DEV 1 59|grep Average|grep ens33|awk '{print $2,"\t","input:","\t",$5*1000*8,"bps","\n",$2,"\t","output:","\t",$6*1000*8,"bps"}'echo "####################"#因为执行sar命令需要59秒,因此不需要sleepdone +#!/bin/bash +####################################################### +#检测网卡流量,并按规定格式记录在日志中 +#规定一分钟记录一次 +#日志格式如下所示: +#2019-08-12 20:40 +#ens33 input: 1234bps +#ens33 output: 1235bps +######################################################3 +while : +do +#设置语言为英文,保障输出结果是英文,否则会出现bug +LANG=en +logfile=/tmp/`date +%d`.log +#将下面执行的命令结果输出重定向到logfile日志中 +exec >> $logfile +date +"%F %H:%M" +#sar命令统计的流量单位为kb/s,日志格式为bps,因此要*1000*8 +sar -n DEV 1 59|grep Average|grep ens33|awk '{print $2,"\t","input:","\t",$5*1000*8,"bps","\n",$2,"\t","output:","\t",$6*1000*8,"bps"}' +echo "####################" +#因为执行sar命令需要59秒,因此不需要sleep +done ``` **杀死所有脚本** ```shell -#!/bin/bash#################################################################有一些脚本加入到了cron之中,存在脚本尚未运行完毕又有新任务需要执行的情况,#导致系统负载升高,因此可通过编写脚本,筛选出影响负载的进程一次性全部杀死。################################################################ps aux|grep 指定进程名|grep -v grep|awk '{print $2}'|xargs kill -9 +#!/bin/bash +################################################################ +#有一些脚本加入到了cron之中,存在脚本尚未运行完毕又有新任务需要执行的情况, +#导致系统负载升高,因此可通过编写脚本,筛选出影响负载的进程一次性全部杀死。 +################################################################ +ps aux|grep 指定进程名|grep -v grep|awk '{print $2}'|xargs kill -9 ``` **从FTP服务器下载文件** ```shell -#!/bin/bashif [ $# -ne 1 ]; then echo "Usage: $0 filename"fidir=$(dirname $1)file=$(basename $1)ftp -n -v << EOF # -n 自动登录open 192.168.1.10 # ftp服务器user admin passwordbinary # 设置ftp传输模式为二进制,避免MD5值不同或.tar.gz压缩包格式错误cd $dirget "$file"EOF +#!/bin/bash +if [ $# -ne 1 ]; then + echo "Usage: $0 filename" +fi +dir=$(dirname $1) +file=$(basename $1) +ftp -n -v << EOF # -n 自动登录 +open 192.168.1.10 # ftp服务器 +user admin password +binary # 设置ftp传输模式为二进制,避免MD5值不同或.tar.gz压缩包格式错误 +cd $dir +get "$file" +EOF ``` **监测Nginx访问日志502情况,并做相应动作** @@ -1890,31 +3102,127 @@ until conditiondo statementsdone 假设服务器环境为lnmp,近期访问经常出现502现象,且502错误在重启php-fpm服务后消失,因此需要编写监控脚本,一旦出现502,则自动重启php-fpm服务。 ```shell -#场景:#1.访问日志文件的路径:/data/log/access.log#2.脚本死循环,每10秒检测一次,10秒的日志条数为300条,出现502的比例不低于10%(30条)则需要重启php-fpm服务#3.重启命令为:/etc/init.d/php-fpm restart#!/bin/bash############################################################监测Nginx访问日志502情况,并做相应动作###########################################################log=/data/log/access.logN=30 #设定阈值while :do #查看访问日志的最新300条,并统计502的次数 err=`tail -n 300 $log |grep -c '502" '` if [ $err -ge $N ] then /etc/init.d/php-fpm restart 2> /dev/null #设定60s延迟防止脚本bug导致无限重启php-fpm服务 sleep 60 fi sleep 10done +#场景: +#1.访问日志文件的路径:/data/log/access.log +#2.脚本死循环,每10秒检测一次,10秒的日志条数为300条,出现502的比例不低于10%(30条)则需要重启php-fpm服务 +#3.重启命令为:/etc/init.d/php-fpm restart +#!/bin/bash +########################################################### +#监测Nginx访问日志502情况,并做相应动作 +########################################################### +log=/data/log/access.log +N=30 #设定阈值 +while : +do + #查看访问日志的最新300条,并统计502的次数 + err=`tail -n 300 $log |grep -c '502" '` + if [ $err -ge $N ] + then + /etc/init.d/php-fpm restart 2> /dev/null + #设定60s延迟防止脚本bug导致无限重启php-fpm服务 + sleep 60 + fi + sleep 10 +done ``` **批量修改文件名** ```shell -# touch article_{1..3}.html# lsarticle_1.html article_2.html article_3.html# 目的:把article改为bbs# 方法1for file in $(ls *html); do mv $file bbs_${file#*_} # mv $file $(echo $file |sed -r 's/.*(_.*)/bbs\1/') # mv $file $(echo $file |echo bbs_$(cut -d_ -f2)done# 方法2for file in $(find . -maxdepth 1 -name "*html"); do mv $file bbs_${file#*_}done# 方法3rename article bbs *.html +# touch article_{1..3}.html +# ls +article_1.html article_2.html article_3.html + +# 目的:把article改为bbs +# 方法1 +for file in $(ls *html); do + mv $file bbs_${file#*_} + # mv $file $(echo $file |sed -r 's/.*(_.*)/bbs\1/') + # mv $file $(echo $file |echo bbs_$(cut -d_ -f2) +done + +# 方法2 +for file in $(find . -maxdepth 1 -name "*html"); do + mv $file bbs_${file#*_} +done + +# 方法3 +rename article bbs *.html ``` **统计当前目录中以.html结尾的文件总大** ```shell -# 方法1find . -name "*.html" -exec du -k {} \; |awk '{sum+=$1}END{print sum}'# 方法2for size in $(ls -l *.html |awk '{print $5}'); do sum=$(($sum+$size))doneecho $sum +# 方法1 +find . -name "*.html" -exec du -k {} \; |awk '{sum+=$1}END{print sum}' + +# 方法2 +for size in $(ls -l *.html |awk '{print $5}'); do + sum=$(($sum+$size)) +done +echo $sum ``` **扫描主机端口状态** ```shell -#!/bin/bashHOST=$1PORT="22 25 80 8080"for PORT in $PORT; do if echo &>/dev/null > /dev/tcp/$HOST/$PORT; then echo "$PORT open" else echo "$PORT close" fidone +#!/bin/bash +HOST=$1 +PORT="22 25 80 8080" +for PORT in $PORT; do + if echo &>/dev/null > /dev/tcp/$HOST/$PORT; then + echo "$PORT open" + else + echo "$PORT close" + fi +done ``` **输入数字运行相应命令** ```shell -#!/bin/bash###############################################################输入数字运行相应命令##############################################################echo "*cmd menu* 1-date 2-ls 3-who 4-pwd 0-exit "while :do#捕获用户键入值 read -p "please input number :" n n1=`echo $n|sed s'/[0-9]//'g`#空输入检测 if [ -z "$n" ] then continue fi#非数字输入检测 if [ -n "$n1" ] then exit 0 fi breakdonecase $n in 1) date ;; 2) ls ;; 3) who ;; 4) pwd ;; 0) break ;; #输入数字非1-4的提示 *) echo "please input number is [1-4]"esac +#!/bin/bash +############################################################## +#输入数字运行相应命令 +############################################################## +echo "*cmd menu* 1-date 2-ls 3-who 4-pwd 0-exit " +while : +do +#捕获用户键入值 + read -p "please input number :" n + n1=`echo $n|sed s'/[0-9]//'g` +#空输入检测 + if [ -z "$n" ] + then + continue + fi +#非数字输入检测 + if [ -n "$n1" ] + then + exit 0 + fi + break +done +case $n in + 1) + date + ;; + 2) + ls + ;; + 3) + who + ;; + 4) + pwd + ;; + 0) + break + ;; + #输入数字非1-4的提示 + *) + echo "please input number is [1-4]" +esac ``` **Expect实现SSH免交互执行命令** @@ -1922,71 +3230,327 @@ until conditiondo statementsdone Expect是一个自动交互式应用程序的工具,如telnet,ftp,passwd等。需先安装expect软件包。 ```shell -# 将expect脚本独立出来为登录脚本# cat login.exp#!/usr/bin/expectset ip [lindex $argv 0]set user [lindex $argv 1]set passwd [lindex $argv 2]set cmd [lindex $argv 3]if { $argc != 4 } {puts "Usage: expect login.exp ip user passwd"exit 1}set timeout 30spawn ssh $user@$ipexpect { "(yes/no)" {send "yes\r"; exp_continue} "password:" {send "$passwd\r"}}expect "$user@*" {send "$cmd\r"}expect "$user@*" {send "exit\r"}expect eof# 执行命令脚本:写个循环可以批量操作多台服务器#!/bin/bashHOST_INFO=user_info.txtfor ip in $(awk '{print $1}' $HOST_INFO)do user=$(awk -v I="$ip" 'I==$1{print $2}' $HOST_INFO) pass=$(awk -v I="$ip" 'I==$1{print $3}' $HOST_INFO) expect login.exp $ip $user $pass $1done# Linux主机SSH连接信息:# cat user_info.txt192.168.1.120 root 123456 +# 将expect脚本独立出来为登录脚本 +# cat login.exp +#!/usr/bin/expect +set ip [lindex $argv 0] +set user [lindex $argv 1] +set passwd [lindex $argv 2] +set cmd [lindex $argv 3] +if { $argc != 4 } { +puts "Usage: expect login.exp ip user passwd" +exit 1 +} +set timeout 30 +spawn ssh $user@$ip +expect { + "(yes/no)" {send "yes\r"; exp_continue} + "password:" {send "$passwd\r"} +} +expect "$user@*" {send "$cmd\r"} +expect "$user@*" {send "exit\r"} +expect eof + +# 执行命令脚本:写个循环可以批量操作多台服务器 +#!/bin/bash +HOST_INFO=user_info.txt +for ip in $(awk '{print $1}' $HOST_INFO) +do + user=$(awk -v I="$ip" 'I==$1{print $2}' $HOST_INFO) + pass=$(awk -v I="$ip" 'I==$1{print $3}' $HOST_INFO) + expect login.exp $ip $user $pass $1 +done + +# Linux主机SSH连接信息: +# cat user_info.txt +192.168.1.120 root 123456 ``` **监控httpd的进程数,根据监控情况做相应处理** ```sh -#!/bin/bash################################################################################################################################需求:#1.每隔10s监控httpd的进程数,若进程数大于等于500,则自动重启Apache服务,并检测服务是否重启成功#2.若未成功则需要再次启动,若重启5次依旧没有成功,则向管理员发送告警邮件,并退出检测#3.如果启动成功,则等待1分钟后再次检测httpd进程数,若进程数正常,则恢复正常检测(10s一次),否则放弃重启并向管理员发送告警邮件,并退出检测################################################################################################################################计数器函数check_service(){ j=0 for i in `seq 1 5` do #重启Apache的命令 /usr/local/apache2/bin/apachectl restart 2> /var/log/httpderr.log #判断服务是否重启成功 if [ $? -eq 0 ] then break else j=$[$j+1] fi #判断服务是否已尝试重启5次 if [ $j -eq 5 ] then mail.py exit fi done }while :do n=`pgrep -l httpd|wc -l` #判断httpd服务进程数是否超过500 if [ $n -gt 500 ] then /usr/local/apache2/bin/apachectl restart if [ $? -ne 0 ] then check_service else sleep 60 n2=`pgrep -l httpd|wc -l` #判断重启后是否依旧超过500 if [ $n2 -gt 500 ] then mail.py exit fi fi fi #每隔10s检测一次 sleep 10done +#!/bin/bash +############################################################################################################################### +#需求: +#1.每隔10s监控httpd的进程数,若进程数大于等于500,则自动重启Apache服务,并检测服务是否重启成功 +#2.若未成功则需要再次启动,若重启5次依旧没有成功,则向管理员发送告警邮件,并退出检测 +#3.如果启动成功,则等待1分钟后再次检测httpd进程数,若进程数正常,则恢复正常检测(10s一次),否则放弃重启并向管理员发送告警邮件,并退出检测 +############################################################################################################################### +#计数器函数 +check_service() +{ + j=0 + for i in `seq 1 5` + do + #重启Apache的命令 + /usr/local/apache2/bin/apachectl restart 2> /var/log/httpderr.log + #判断服务是否重启成功 + if [ $? -eq 0 ] + then + break + else + j=$[$j+1] + fi + #判断服务是否已尝试重启5次 + if [ $j -eq 5 ] + then + mail.py + exit + fi + done +} +while : +do + n=`pgrep -l httpd|wc -l` + #判断httpd服务进程数是否超过500 + if [ $n -gt 500 ] + then + /usr/local/apache2/bin/apachectl restart + if [ $? -ne 0 ] + then + check_service + else + sleep 60 + n2=`pgrep -l httpd|wc -l` + #判断重启后是否依旧超过500 + if [ $n2 -gt 500 ] + then + mail.py + exit + fi + fi + fi + #每隔10s检测一次 + sleep 10 +done ``` **iptables自动屏蔽访问网站频繁的IP** ```shell -#场景:恶意访问,安全防范#1)屏蔽每分钟访问超过200的IP#方法1:根据访问日志(Nginx为例)#!/bin/bashDATE=$(date +%d/%b/%Y:%H:%M)ABNORMAL_IP=$(tail -n5000 access.log |grep $DATE |awk '{a[$1]++}END{for(i in a)if(a[i]>100)print i}')#先tail防止文件过大,读取慢,数字可调整每分钟最大的访问量。awk不能直接过滤日志,因为包含特殊字符。for IP in $ABNORMAL_IP; do if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then iptables -I INPUT -s $IP -j DROP fidone#方法2:通过TCP建立的连接#!/bin/bashABNORMAL_IP=$(netstat -an |awk '$4~/:80$/ && $6~/ESTABLISHED/{gsub(/:[0-9]+/,"",$5);{a[$5]++}}END{for(i in a)if(a[i]>100)print i}')#gsub是将第五列(客户端IP)的冒号和端口去掉for IP in $ABNORMAL_IP; do if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then iptables -I INPUT -s $IP -j DROP fidone#2)屏蔽每分钟SSH尝试登录超过10次的IP#方法1:通过lastb获取登录状态:#!/bin/bashDATE=$(date +"%a %b %e %H:%M") #星期月天时分 %e单数字时显示7,而%d显示07ABNORMAL_IP=$(lastb |grep "$DATE" |awk '{a[$3]++}END{for(i in a)if(a[i]>10)print i}')for IP in $ABNORMAL_IP; do if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then iptables -I INPUT -s $IP -j DROP fidone#方法2:通过日志获取登录状态#!/bin/bashDATE=$(date +"%b %d %H")ABNORMAL_IP="$(tail -n10000 /var/log/auth.log |grep "$DATE" |awk '/Failed/{a[$(NF-3)]++}END{for(i in a)if(a[i]>5)print i}')"for IP in $ABNORMAL_IP; do if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then iptables -A INPUT -s $IP -j DROP echo "$(date +"%F %T") - iptables -A INPUT -s $IP -j DROP" >>~/ssh-login-limit.log fidone +#场景:恶意访问,安全防范 +#1)屏蔽每分钟访问超过200的IP +#方法1:根据访问日志(Nginx为例) +#!/bin/bash +DATE=$(date +%d/%b/%Y:%H:%M) +ABNORMAL_IP=$(tail -n5000 access.log |grep $DATE |awk '{a[$1]++}END{for(i in a)if(a[i]>100)print i}') +#先tail防止文件过大,读取慢,数字可调整每分钟最大的访问量。awk不能直接过滤日志,因为包含特殊字符。 +for IP in $ABNORMAL_IP; do + if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then + iptables -I INPUT -s $IP -j DROP + fi +done + +#方法2:通过TCP建立的连接 +#!/bin/bash +ABNORMAL_IP=$(netstat -an |awk '$4~/:80$/ && $6~/ESTABLISHED/{gsub(/:[0-9]+/,"",$5);{a[$5]++}}END{for(i in a)if(a[i]>100)print i}') +#gsub是将第五列(客户端IP)的冒号和端口去掉 +for IP in $ABNORMAL_IP; do + if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then + iptables -I INPUT -s $IP -j DROP + fi +done + +#2)屏蔽每分钟SSH尝试登录超过10次的IP +#方法1:通过lastb获取登录状态: +#!/bin/bash +DATE=$(date +"%a %b %e %H:%M") #星期月天时分 %e单数字时显示7,而%d显示07 +ABNORMAL_IP=$(lastb |grep "$DATE" |awk '{a[$3]++}END{for(i in a)if(a[i]>10)print i}') +for IP in $ABNORMAL_IP; do + if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then + iptables -I INPUT -s $IP -j DROP + fi +done + +#方法2:通过日志获取登录状态 +#!/bin/bash +DATE=$(date +"%b %d %H") +ABNORMAL_IP="$(tail -n10000 /var/log/auth.log |grep "$DATE" |awk '/Failed/{a[$(NF-3)]++}END{for(i in a)if(a[i]>5)print i}')" +for IP in $ABNORMAL_IP; do + if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then + iptables -A INPUT -s $IP -j DROP + echo "$(date +"%F %T") - iptables -A INPUT -s $IP -j DROP" >>~/ssh-login-limit.log + fi +done ``` **根据web访问日志,封禁请求量异常的IP,如IP在半小时后恢复正常,则解除封禁** ```shell -#!/bin/bash#####################################################################################根据web访问日志,封禁请求量异常的IP,如IP在半小时后恢复正常,则解除封禁####################################################################################logfile=/data/log/access.log#显示一分钟前的小时和分钟d1=`date -d "-1 minute" +%H%M`d2=`date +%M`ipt=/sbin/iptablesips=/tmp/ips.txtblock(){ #将一分钟前的日志全部过滤出来并提取IP以及统计访问次数 grep '$d1:' $logfile|awk '{print $1}'|sort -n|uniq -c|sort -n > $ips #利用for循环将次数超过100的IP依次遍历出来并予以封禁 for i in `awk '$1>100 {print $2}' $ips` do $ipt -I INPUT -p tcp --dport 80 -s $i -j REJECT echo "`date +%F-%T` $i" >> /tmp/badip.log done}unblock(){ #将封禁后所产生的pkts数量小于10的IP依次遍历予以解封 for a in `$ipt -nvL INPUT --line-numbers |grep '0.0.0.0/0'|awk '$2<10 {print $1}'|sort -nr` do $ipt -D INPUT $a done $ipt -Z}#当时间在00分以及30分时执行解封函数if [ $d2 -eq "00" ] || [ $d2 -eq "30" ] then #要先解再封,因为刚刚封禁时产生的pkts数量很少 unblock block else blockfi +#!/bin/bash +#################################################################################### +#根据web访问日志,封禁请求量异常的IP,如IP在半小时后恢复正常,则解除封禁 +#################################################################################### +logfile=/data/log/access.log +#显示一分钟前的小时和分钟 +d1=`date -d "-1 minute" +%H%M` +d2=`date +%M` +ipt=/sbin/iptables +ips=/tmp/ips.txt +block() +{ + #将一分钟前的日志全部过滤出来并提取IP以及统计访问次数 + grep '$d1:' $logfile|awk '{print $1}'|sort -n|uniq -c|sort -n > $ips + #利用for循环将次数超过100的IP依次遍历出来并予以封禁 + for i in `awk '$1>100 {print $2}' $ips` + do + $ipt -I INPUT -p tcp --dport 80 -s $i -j REJECT + echo "`date +%F-%T` $i" >> /tmp/badip.log + done +} +unblock() +{ + #将封禁后所产生的pkts数量小于10的IP依次遍历予以解封 + for a in `$ipt -nvL INPUT --line-numbers |grep '0.0.0.0/0'|awk '$2<10 {print $1}'|sort -nr` + do + $ipt -D INPUT $a + done + $ipt -Z +} +#当时间在00分以及30分时执行解封函数 +if [ $d2 -eq "00" ] || [ $d2 -eq "30" ] + then + #要先解再封,因为刚刚封禁时产生的pkts数量很少 + unblock + block + else + block +fi ``` -**添加脚本开机自启动** +**添加脚本开机自启动** + +```shell +# 将脚本移动到/etc/rc.d/init.d目录 +mv test.sh /etc/rc.d/init.d/test.sh + +# 赋予可执行权限 +chmod +x /etc/rc.d/init.d/test.sh + +# 添加脚本到开机自动启动项目中 +cd /etc/rc.d/init.d +chkconfig --add test.sh +chkconfig test.sh on +``` + + + +# Git + +![SVN集中式](images/DevOps/SVN集中式.png) + +![Git仿集中式](images/DevOps/Git仿集中式.png) + +## 工作流程 + +### Git Flow + +- 主干分支 +- 稳定分支 +- 开发分支 +- 补丁分支 +- 修改分支 + +![GitFlow](images/DevOps/GitFlow.jpg) + + + +### Github Flow + +- 创建分支 +- 添加提交 +- 提交 PR 请求 +- 讨论和评估代码 +- 部署检测 +- 合并代码 + +![Github-Flow](images/DevOps/Github-Flow.jpg) + + + +### Gitlab Flow + +- 带生产分支 +- 带环境分支 +- 带发布分支 + +![GitlabFlow](images/DevOps/GitlabFlow.jpg) + + + +## GitFlow工作流 + +Gitflow 工作流是目前非常成熟的一个方案,它定义了一个围绕项目发布的严格分支模型,通过为代码研发、项目发布以及维护分配独立的分支来让项目的迭代过程更加地顺畅,不同于之前的集中式工作流以及功能分支工作流,Gitflow 工作流常驻的分支有两个:主干分支 master、开发分支 develop。和功能分支工作流相比,Gitflow工作流没有增加任何新的概念或命令,它给不同的分支指定了特定的角色,定义它们应该如何、什么时候交互。除了功能分支之外,还为准备发布、维护发布、记录发布分别使用了单独的分支。 + + + +**Gitflow常见分支** + +- 开发主分支:master 分支 + + master 分支的代码是可以直接部署到生成环境的,为了保持稳定性一般不会直接在这个分支上修改代码,都是通过其他分支合并过来的。 + +- 开发主分支:develop分支 + + develop 分支是主开发分支,包含所有要发布到下一个release的代码,主要是由feature分支合并过来的。 + +- 临时分支:feature 分支 + + feature 分支主要是用来开发一个新特性,一旦开发完成会合入 develop 分支,feature 分支也随即删除掉。 + +- 临时分支:release 分支 + + 当需要一个发布一个新release版本时,会基于develop分支创建一个release分支,经过测试人员充分测试后再合入 master 分支和 develop 分支。 + +- 临时分支:hotfix 分支 + + 当在生成环境发现新的Bug时候,如果需要紧急修复,会创建一个hotfix分支, 充分测试后合入master和develop分支,随后删除该分支。 + + + +**分支命名规范** + +团队内部可以约定每个分支的命名样式,这里举个例子,大家可以参考: + +- feature分支:以feature_开头,如 feature_order + +- release分支:以release_开头,如 release_v1.0 + +- hotfix分支:以hotfix_开头,如hotfix_20210117 + +- tag标记:如果是release分支合并,则以release\_开头,如果是hotfix分支合并,则以hotfix\_开头。 + -```shell -# 将脚本移动到/etc/rc.d/init.d目录mv test.sh /etc/rc.d/init.d/test.sh# 赋予可执行权限chmod +x /etc/rc.d/init.d/test.sh# 添加脚本到开机自动启动项目中cd /etc/rc.d/init.dchkconfig --add test.shchkconfig test.sh on -``` +### master与develop分支 +原则上master分支上所有的commit 都应该打上Tag,因为一般情况下master不存在直接commit。devlop分支是基于 master分支创建的,与 master 分支一样都是主分支,不会被删除。develop 从 master 拉出来之后会独立发展,不会与 master 直接产生联系。 -# Git +![master与develop分支](images/DevOps/master与develop分支.png) -## 工作流程 -**① Git Flow** -- 主干分支 -- 稳定分支 -- 开发分支 -- 补丁分支 -- 修改分支 +### feature分支 -![GitFlow](images/DevOps/GitFlow.jpg) +通常一个独立的特性都会基于develop拉出一个feature分支,feature 分支之间没有任何交互,互不影响。feature 分支一旦开发完成后会立马合入 develop 分支(采用 merge request 或者 pull request),feature 分支的生命周期也随之结束。 +![feature分支](images/DevOps/feature分支.png) -**② Github Flow** -- 创建分支 -- 添加提交 -- 提交 PR 请求 -- 讨论和评估代码 -- 部署检测 -- 合并代码 +### release分支 -![Github-Flow](images/DevOps/Github-Flow.jpg) +通常一个迭代上线会拉一个release 分支,开发人员开发完毕所有的代码都已合入 develop 分支,这时候会基于 develop 分支拉出一个 release 分支,测试人员基于该分支进行测试。 +![release分支](images/DevOps/release分支.png) -**③ Gitlab Flow** -- 带生产分支 -- 带环境分支 -- 带发布分支 +### hotfix分支 -![GitlabFlow](images/DevOps/GitlabFlow.jpg) +hotfix分支基于master分支创建,开发完后需要同时回合到master和develop分支,同时在master上打一个tag。 + +![hotfix分支](images/DevOps/hotfix分支.png) @@ -1995,7 +3559,12 @@ Expect是一个自动交互式应用程序的工具,如telnet,ftp,passwd ### 新建代码库 ```shell -# 在当前目录新建一个Git代码库$ git init# 新建一个目录,将其初始化为Git代码库$ git init [project-name]# 下载一个项目和它的整个代码历史$ git clone [url] +# 在当前目录新建一个Git代码库 +$ git init +# 新建一个目录,将其初始化为Git代码库 +$ git init [project-name] +# 下载一个项目和它的整个代码历史 +$ git clone [url] ``` @@ -2005,7 +3574,24 @@ Expect是一个自动交互式应用程序的工具,如telnet,ftp,passwd Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置) ```shell -# 显示当前的Git配置$ git config --list# 编辑Git配置文件$ git config -e [--global]# 设置提交代码时的用户信息$ git config [--global] user.name "[name]"$ git config [--global] user.email "[email address]"# 颜色设置git config --global color.ui true # git status等命令自动着色git config --global color.status autogit config --global color.diff autogit config --global color.branch autogit config --global color.interactive autogit config --global --unset http.proxy # remove proxy configuration on git +# 显示当前的Git配置 +$ git config --list + +# 编辑Git配置文件 +$ git config -e [--global] + +# 设置提交代码时的用户信息 +$ git config [--global] user.name "[name]" +$ git config [--global] user.email "[email address]" + + +# 颜色设置 +git config --global color.ui true # git status等命令自动着色 +git config --global color.status auto +git config --global color.diff auto +git config --global color.branch auto +git config --global color.interactive auto +git config --global --unset http.proxy # remove proxy configuration on git ``` @@ -2013,7 +3599,10 @@ Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置) ### 增加/删除文件 ```shell -# 添加指定文件到暂存区$ git add [file] [dir] ...# 删除工作区文件,并且将这次删除放入暂存区$ git rm [file1] [file2] ... +# 添加指定文件到暂存区 +$ git add [file] [dir] ... +# 删除工作区文件,并且将这次删除放入暂存区 +$ git rm [file1] [file2] ... ``` @@ -2021,7 +3610,10 @@ Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置) ### 代码提交 ```shell -# 提交暂存区到仓库区$ git commit -m [message]# 提交暂存区的指定文件到仓库区$ git commit [file1] [file2] ... -m [message] +# 提交暂存区到仓库区 +$ git commit -m [message] +# 提交暂存区的指定文件到仓库区 +$ git commit [file1] [file2] ... -m [message] ``` @@ -2029,7 +3621,28 @@ Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置) ### 分支 ```shell -# 列出所有本地分支和远程分支$ git branch -a# 新建一个分支,但依然停留在当前分支$ git branch [branch-name]# 新建一个分支,并切换到该分支$ git checkout -b [branch]# 从远程分支develop创建新本地分支devel并检出$ git checkout -b devel origin/develop# 切换到指定分支,并更新工作区$ git checkout [branch-name]# 合并指定分支到当前分支$ git merge [branch]# 选择一个commit,合并进当前分支$ git cherry-pick [commit]# 删除分支$ git branch -d [branch-name]# 删除远程分支$ git push origin --delete [branch-name] +# 列出所有本地分支和远程分支 +$ git branch -a + +# 新建一个分支,但依然停留在当前分支 +$ git branch [branch-name] +# 新建一个分支,并切换到该分支 +$ git checkout -b [branch] +# 从远程分支develop创建新本地分支devel并检出 +$ git checkout -b devel origin/develop + +# 切换到指定分支,并更新工作区 +$ git checkout [branch-name] + +# 合并指定分支到当前分支 +$ git merge [branch] +# 选择一个commit,合并进当前分支 +$ git cherry-pick [commit] + +# 删除分支 +$ git branch -d [branch-name] +# 删除远程分支 +$ git push origin --delete [branch-name] ``` @@ -2037,7 +3650,23 @@ Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置) ### 标签 ```shell -# 列出所有tag$ git tag# 新建一个tag在当前commit$ git tag [tag]# 删除本地tag$ git tag -d [tag]# 删除远程tag$ git push origin :refs/tags/[tagName]# 查看tag信息$ git show [tag]# 提交指定tag$ git push [remote] [tag]# 提交所有tag$ git push [remote] --tags +# 列出所有tag +$ git tag +# 新建一个tag在当前commit +$ git tag [tag] + +# 删除本地tag +$ git tag -d [tag] +# 删除远程tag +$ git push origin :refs/tags/[tagName] + +# 查看tag信息 +$ git show [tag] + +# 提交指定tag +$ git push [remote] [tag] +# 提交所有tag +$ git push [remote] --tags ``` @@ -2045,7 +3674,53 @@ Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置) ### 查看信息 ```shell -# 显示有变更的文件$ git status# 显示当前分支的版本历史$ git log# 显示commit历史,以及每次commit发生变更的文件$ git log --stat# 搜索提交历史,根据关键词$ git log -S [keyword]# 显示某个commit之后的所有变动,每个commit占据一行$ git log [tag] HEAD --pretty=format:%s# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件$ git log [tag] HEAD --grep feature# 显示某个文件的版本历史,包括文件改名$ git log --follow [file]# 显示指定文件相关的每一次diff$ git log -p [file]# 显示过去5次提交$ git log -5 --pretty --oneline# 显示所有提交过的用户,按提交次数排序$ git shortlog -sn# 显示指定文件是什么人在什么时间修改过$ git blame [file]# 显示暂存区和工作区的差异$ git diff# 显示暂存区和上一个commit的差异$ git diff --cached [file]# 显示工作区与当前分支最新commit之间的差异$ git diff HEAD# 显示两次提交之间的差异$ git diff [first-branch]...[second-branch]# 显示今天你写了多少行代码$ git diff --shortstat "@{0 day ago}"# 显示某次提交的元数据和内容变化$ git show [commit]# 显示某次提交发生变化的文件$ git show --name-only [commit]# 显示某次提交时,某个文件的内容$ git show [commit]:[filename]# 显示当前分支的最近几次提交$ git reflog +# 显示有变更的文件 +$ git status + +# 显示当前分支的版本历史 +$ git log +# 显示commit历史,以及每次commit发生变更的文件 +$ git log --stat +# 搜索提交历史,根据关键词 +$ git log -S [keyword] +# 显示某个commit之后的所有变动,每个commit占据一行 +$ git log [tag] HEAD --pretty=format:%s +# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件 +$ git log [tag] HEAD --grep feature +# 显示某个文件的版本历史,包括文件改名 +$ git log --follow [file] + +# 显示指定文件相关的每一次diff +$ git log -p [file] +# 显示过去5次提交 +$ git log -5 --pretty --oneline + +# 显示所有提交过的用户,按提交次数排序 +$ git shortlog -sn + +# 显示指定文件是什么人在什么时间修改过 +$ git blame [file] + +# 显示暂存区和工作区的差异 +$ git diff +# 显示暂存区和上一个commit的差异 +$ git diff --cached [file] +# 显示工作区与当前分支最新commit之间的差异 +$ git diff HEAD +# 显示两次提交之间的差异 +$ git diff [first-branch]...[second-branch] +# 显示今天你写了多少行代码 +$ git diff --shortstat "@{0 day ago}" + +# 显示某次提交的元数据和内容变化 +$ git show [commit] +# 显示某次提交发生变化的文件 +$ git show --name-only [commit] +# 显示某次提交时,某个文件的内容 +$ git show [commit]:[filename] + +# 显示当前分支的最近几次提交 +$ git reflog ``` @@ -2053,7 +3728,21 @@ Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置) ### 远程同步 ```shell -# 下载远程仓库的所有变动$ git fetch [remote]# 显示所有远程仓库$ git remote -v# 显示某个远程仓库的信息$ git remote show [remote]# 增加一个新的远程仓库,并命名$ git remote add [shortname] [url]# 取回远程仓库的变化,并与本地分支合并$ git pull [remote] [branch]# 上传本地指定分支到远程仓库$ git push [remote] [branch] +# 下载远程仓库的所有变动 +$ git fetch [remote] + +# 显示所有远程仓库 +$ git remote -v +# 显示某个远程仓库的信息 +$ git remote show [remote] +# 增加一个新的远程仓库,并命名 +$ git remote add [shortname] [url] + +# 取回远程仓库的变化,并与本地分支合并 +$ git pull [remote] [branch] + +# 上传本地指定分支到远程仓库 +$ git push [remote] [branch] ``` @@ -2061,7 +3750,14 @@ Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置) ### 撤销 ```shell -# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变$ git reset [file]# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变$ git reset [commit]# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致$ git reset --hard [commit]# 后者的所有变化都将被前者抵消,并且应用到当前分支$ git revert [commit] +# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变 +$ git reset [file] +# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变 +$ git reset [commit] +# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致 +$ git reset --hard [commit] +# 后者的所有变化都将被前者抵消,并且应用到当前分支 +$ git revert [commit] ``` @@ -2075,7 +3771,36 @@ Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置) ## AB ```shell -# 参数说明:# -n:即requests,用于指定压力测试总共的执行次数# -c:即concurrency,用于指定压力测试的并发数# -t:即timelimit,等待响应的最大时间(单位:秒)# -b:即windowsize,TCP发送/接收的缓冲大小(单位:字节)# -p:即postfile,发送POST请求时需要上传的文件,此外还必须设置-T参数# -u:即putfile,发送PUT请求时需要上传的文件,此外还必须设置-T参数# -T:即content-type,用于设置Content-Type请求头信息,例如:application/json,默认值为text/plain# -w:以HTML表格形式打印结果# -i:使用HEAD请求代替GET请求# -x:插入字符串作为table标签的属性# -y:插入字符串作为tr标签的属性# -z:插入字符串作为td标签的属性# -C:添加cookie信息,例如:"Apache=1234"(可以重复该参数选项以添加多个)# -H:添加任意的请求头,如:"Accept-Encoding: gzip",可以重复该参数选项以添加多个# -A:添加一个基本的网络认证信息,用户名和密码之间用英文冒号隔开# -P:添加一个基本的代理认证信息,用户名和密码之间用英文冒号隔开# -X:指定使用的代理服务器和端口号,例如:"126.10.10.3:88"# -k:使用HTTP的KeepAlive特性# -e:输出结果信息到CSV格式的文件中。# -r:指定接收到错误信息时不退出程序。# 在任意目录下执行该命令yum -y install httpd-tools# 添加请求头参数ab -n100 -c10 -H "Cookie: Key1=Value1; Key2=Value2" http://127.0.0.1:8080/get# ab测试简单HTTP GET接口ab -n30000 -c1000 http://127.0.0.1:8080/get# ab测试HTTP POST接口,img.json为符合接口格式的字符串ab -n400 -c20 -p "img.json" -T "application/x-www-form-urlencoded" http://127.0.0.1:8080/add +# 参数说明: +# -n:即requests,用于指定压力测试总共的执行次数 +# -c:即concurrency,用于指定压力测试的并发数 +# -t:即timelimit,等待响应的最大时间(单位:秒) +# -b:即windowsize,TCP发送/接收的缓冲大小(单位:字节) +# -p:即postfile,发送POST请求时需要上传的文件,此外还必须设置-T参数 +# -u:即putfile,发送PUT请求时需要上传的文件,此外还必须设置-T参数 +# -T:即content-type,用于设置Content-Type请求头信息,例如:application/json,默认值为text/plain +# -w:以HTML表格形式打印结果 +# -i:使用HEAD请求代替GET请求 +# -x:插入字符串作为table标签的属性 +# -y:插入字符串作为tr标签的属性 +# -z:插入字符串作为td标签的属性 +# -C:添加cookie信息,例如:"Apache=1234"(可以重复该参数选项以添加多个) +# -H:添加任意的请求头,如:"Accept-Encoding: gzip",可以重复该参数选项以添加多个 +# -A:添加一个基本的网络认证信息,用户名和密码之间用英文冒号隔开 +# -P:添加一个基本的代理认证信息,用户名和密码之间用英文冒号隔开 +# -X:指定使用的代理服务器和端口号,例如:"126.10.10.3:88" +# -k:使用HTTP的KeepAlive特性 +# -e:输出结果信息到CSV格式的文件中。 +# -r:指定接收到错误信息时不退出程序。 + +# 在任意目录下执行该命令 +yum -y install httpd-tools +# 添加请求头参数 +ab -n100 -c10 -H "Cookie: Key1=Value1; Key2=Value2" http://127.0.0.1:8080/get +# ab测试简单HTTP GET接口 +ab -n30000 -c1000 http://127.0.0.1:8080/get +# ab测试HTTP POST接口,img.json为符合接口格式的字符串 +ab -n400 -c20 -p "img.json" -T "application/x-www-form-urlencoded" http://127.0.0.1:8080/add ``` @@ -2287,20 +4012,29 @@ HTTP Mirror Server可以在本地临时搭建一个HTTP服务器,该服务器 Non-GUI Mode (Command Line mode) ```shell --n 指定JMeter以非GUI模式运行-t 包含测试计划的jmx文件的名称(.jmx测试脚本)-l 记录样本结果日志的jtl文件名称-j JMeter运行日志文件名称-r 在JMeter属性"remote_hosts"所指定的服务器中运行测试-R [远程服务器列表]在指定的远程服务器上运行测试-g [CSV文件路径]只生成报表-e 负载测试后生成报表-o 负载测试完成后,用于存放所生成报表的文件夹(文件夹必须不存在or文件夹内为空) +-n 指定JMeter以非GUI模式运行 +-t 包含测试计划的jmx文件的名称(.jmx测试脚本) +-l 记录样本结果日志的jtl文件名称 +-j JMeter运行日志文件名称 +-r 在JMeter属性"remote_hosts"所指定的服务器中运行测试 +-R [远程服务器列表]在指定的远程服务器上运行测试 +-g [CSV文件路径]只生成报表 +-e 负载测试后生成报表 +-o 负载测试完成后,用于存放所生成报表的文件夹(文件夹必须不存在or文件夹内为空) ``` e.g: ```powershell -[root@localhost ~]# jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder][root@localhost ~]# jmeter -n -t [jmx file] -l [results file] -R host1,host2 -e -o [Path to web report folder] +[root@localhost ~]# jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder] +[root@localhost ~]# jmeter -n -t [jmx file] -l [results file] -R host1,host2 -e -o [Path to web report folder] ``` # Pack -## nohup +## 其它 当写完一个Spring boot Maven 工程,使用 mvn clean package 打包成可运行的jar文件后,可使用如下命令开始执行: @@ -2310,10 +4044,6 @@ nohup java -Xloggc:${logging_file_location}gc.log -XX:+PrintGCDetails -jar app.j -## jsw - - - # Docker ## 安装 @@ -2328,8 +4058,10 @@ nohup java -Xloggc:${logging_file_location}gc.log -XX:+PrintGCDetails -jar app.j # 列出 Docker 本地镜像列表 $ docker images $ docker image ls -a + # 运行 Docker 镜像(守护态方式) $ docker run -d {镜像名} + # 删除指定 Docker 镜像 $ docker image rm {镜像名} ``` @@ -2338,13 +4070,24 @@ $ docker image rm {镜像名} ### 容器命令 + + ```bash # 列出正在运行的容器 $ docker ps -a + # 列出所有容器(包括已停止容器) $ docker ps -l ``` + + +```bash + +``` + +: + ```bash $ docker exec -it {容器ID} /bin/bash ``` @@ -2404,7 +4147,8 @@ $ docker volume rm {数据卷名} 删除未关联(失效) Docker 数据卷: ```bash -$ docker volume prune$ docker volume rm $(docker volume ls -qf dangling=true) +$ docker volume prune +$ docker volume rm $(docker volume ls -qf dangling=true) ``` @@ -2425,6 +4169,602 @@ $ sudo docker cp {容器ID}:{容器内文件路径} {主机内文件存储路径 +# Nginx + +## 常用配置 + +### 侦听端口 + +```nginx +server { + # Standard HTTP Protocol + listen 80; + # Standard HTTPS Protocol + listen 443 ssl; + # For http2 + listen 443 ssl http2; + # Listen on 80 using IPv6 + listen [::]:80; + # Listen only on using IPv6 + listen [::]:80 ipv6only=on; +} +``` + + + +### 访问日志 + +```nginx +server { + # Relative or full path to log file + access_log /path/to/file.log; + # Turn 'on' or 'off' + access_log on; +} +``` + + + +### 域名 + +```nginx +server { + # Listen to yourdomain.com + server_name yourdomain.com; + # Listen to multiple domains server_name yourdomain.com www.yourdomain.com; + # Listen to all domains + server_name *.yourdomain.com; + # Listen to all top-level domains + server_name yourdomain.*; + # Listen to unspecified Hostnames (Listens to IP address itself) + server_name ""; +} +``` + + + +### 静态资产 + +```nginx +server { + listen 80; + server_name yourdomain.com; + location / { + root /path/to/website; + } +} +``` + + + +### 重定向 + +```nginx +server { + listen 80; + server_name www.yourdomain.com; + return 301 http://yourdomain.com$request_uri; +} +server { + listen 80; + server_name www.yourdomain.com; + location /redirect-url { + return 301 http://otherdomain.com; + } +} +``` + + + +### 反向代理 + +```nginx +server { + listen 80; + server_name yourdomain.com; + location / { + proxy_pass http://0.0.0.0:3000; + # where 0.0.0.0:3000 is your application server (Ex: node.js) bound on 0.0.0.0 listening on port 3000 + } +} +``` + + + +### 负载均衡 + +```nginx +upstream node_js { + server 0.0.0.0:3000; + server 0.0.0.0:4000; + server 123.131.121.122; +} +server { + listen 80; + server_name yourdomain.com; + location / { + proxy_pass http://node_js; + } +} +``` + + + +### SSL 协议 + +```nginx +server { + listen 443 ssl; + server_name yourdomain.com; + ssl on; + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/privatekey.pem; + ssl_stapling on; + ssl_stapling_verify on; + ssl_trusted_certificate /path/to/fullchain.pem; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_session_timeout 1h; + ssl_session_cache shared:SSL:50m; + add_header Strict-Transport-Security max-age=15768000; +} +# Permanent Redirect for HTTP to HTTPS +server { + listen 80; + server_name yourdomain.com; + return 301 https://$host$request_uri; +} +``` + +其实可以采用可视化的方式对 Nginx 进行配置,我在 GitHub 上发现了一款可以一键生成 Nginx 配置的神器,相当给力。 + +先来看看它都支持什么功能的配置:反向代理、HTTPS、HTTP/2、IPv6, 缓存、WordPress、CDN、Node.js 支持、 Python (Django) 服务器等等。 + +如果你想在线进行配置,只需要打开网站:**https://nginxconfig.io/** + + + +## 应用场景 + +### HTTP服务器 + +Nginx本身也是一个静态资源的服务器,当只有静态资源的时候,就可以使用Nginx来做服务器,如果一个网站只是静态页面的话,那么就可以通过这种方式来实现部署。 + +1、 首先在文档根目录`Docroot(/usr/local/var/www)`下创建html目录, 然后在html中放一个test.html; + +2、 配置`nginx.conf`中的server + +```nginx +user mengday staff; + +http { + server { + listen 80; + server_name localhost; + client_max_body_size 1024M; + + # 默认location + location / { + root /usr/local/var/www/html; + index index.html index.htm; + } + } +} +``` + +3、访问测试 + +- `http://localhost/` 指向`/usr/local/var/www/index.html`, index.html是安装nginx自带的html +- `http://localhost/test.html` 指向`/usr/local/var/www/html/test.html` + +> 注意:如果访问图片出现403 Forbidden错误,可能是因为nginx.conf 的第一行user配置不对,默认是#user nobody;是注释的,linux下改成user root; macos下改成user 用户名 所在组; 然后重新加载配置文件或者重启,再试一下就可以了, 用户名可以通过who am i 命令来查看。 + +4、指令简介 + +- server : 用于定义服务,http中可以有多个server块 +- listen : 指定服务器侦听请求的IP地址和端口,如果省略地址,服务器将侦听所有地址,如果省略端口,则使用标准端口 +- server_name : 服务名称,用于配置域名 +- location : 用于配置映射路径uri对应的配置,一个server中可以有多个location, location后面跟一个uri,可以是一个正则表达式, / 表示匹配任意路径, 当客户端访问的路径满足这个uri时就会执行location块里面的代码 +- root : 根路径,当访问`http://localhost/test.html`,“/test.html”会匹配到”/”uri, 找到root为`/usr/local/var/www/html`,用户访问的资源物理地址=`root + uri = /usr/local/var/www/html + /test.html=/usr/local/var/www/html/test.html` +- index : 设置首页,当只访问`server_name`时后面不跟任何路径是不走root直接走index指令的;如果访问路径中没有指定具体的文件,则返回index设置的资源,如果访问`http://localhost/html/` 则默认返回index.html + +5、location uri正则表达式 + +- `.` :匹配除换行符以外的任意字符 +- `?` :重复0次或1次 +- `+` :重复1次或更多次 +- `*` :重复0次或更多次 +- `\d` :匹配数字 +- `^` :匹配字符串的开始 +- `$` :匹配字符串的结束 +- `{n}` :重复n次 +- `{n,}` :重复n次或更多次 +- `[c]` :匹配单个字符c +- `[a-z]` :匹配a-z小写字母的任意一个 +- `(a|b|c)` : 属线表示匹配任意一种情况,每种情况使用竖线分隔,一般使用小括号括括住,匹配符合a字符 或是b字符 或是c字符的字符串 +- `\` 反斜杠:用于转义特殊字符 + +小括号()之间匹配的内容,可以在后面通过`$1`来引用,`$2`表示的是前面第二个()里的内容。正则里面容易让人困惑的是`\`转义特殊字符。 + + + +### 静态服务器 + +在公司中经常会遇到静态服务器,通常会提供一个上传的功能,其他应用如果需要静态资源就从该静态服务器中获取。 + +在`/usr/local/var/www` 下分别创建images和img目录,分别在每个目录下放一张`test.jpg` + +```nginx +http { + server { + listen 80; + server_name localhost; + + + set $doc_root /usr/local/var/www; + + # 默认location + location / { + root /usr/local/var/www/html; + index index.html index.htm; + } + + location ^~ /images/ { + root $doc_root; + } + + location ~* \.(gif|jpg|jpeg|png|bmp|ico|swf|css|js)$ { + root $doc_root/img; + } + } +} +``` + +自定义变量使用set指令,语法 set 变量名值;引用使用变量名值;引用使用变量名; 这里自定义了doc_root变量。 + +静态服务器location的映射一般有两种方式: + +- 使用路径,如 /images/ 一般图片都会放在某个图片目录下, +- 使用后缀,如 .jpg、.png 等后缀匹配模式 + +访问`http://localhost/test.jpg` 会映射到 `$doc_root/img` + +访问`http://localhost/images/test.jpg` 当同一个路径满足多个location时,优先匹配优先级高的location,由于`^~` 的优先级大于 `~`, 所以会走`/images/`对应的location + +常见的location路径映射路径有以下几种: + +- `=` 进行普通字符精确匹配。也就是完全匹配。 +- `^~` 前缀匹配。如果匹配成功,则不再匹配其他location。 +- `~` 表示执行一个正则匹配,区分大小写 +- `~*` 表示执行一个正则匹配,不区分大小写 +- `/xxx/` 常规字符串路径匹配 +- `/` 通用匹配,任何请求都会匹配到 + + + +**location优先级** + +当一个路径匹配多个location时究竟哪个location能匹配到时有优先级顺序的,而优先级的顺序于location值的表达式类型有关,和在配置文件中的先后顺序无关。相同类型的表达式,字符串长的会优先匹配。推荐:[Java面试题大全](http://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247504489&idx=1&sn=afd92248113146b086652ad7f89c7a7c&chksm=ebd5ed45dca26453c0cf91265d669711a4e2b4ea52f3ff4a00b063a9de46d69fcc599f151210&scene=21#wechat_redirect) + +以下是按优先级排列说明: + +- 等号类型(=)的优先级最高。一旦匹配成功,则不再查找其他匹配项,停止搜索。 +- `^~`类型表达式,不属于正则表达式。一旦匹配成功,则不再查找其他匹配项,停止搜索。 +- 正则表达式类型(`~ ~*`)的优先级次之。如果有多个location的正则能匹配的话,则使用正则表达式最长的那个。 +- 常规字符串匹配类型。按前缀匹配。 +- / 通用匹配,如果没有匹配到,就匹配通用的 + +优先级搜索问题:不同类型的location映射决定是否继续向下搜索 + +- 等号类型、`^~`类型:一旦匹配上就停止搜索了,不会再匹配其他location了 +- 正则表达式类型(`~ ~*`),常规字符串匹配类型`/xxx/` : 匹配到之后,还会继续搜索其他其它location,直到找到优先级最高的,或者找到第一种情况而停止搜索 + +location优先级从高到底: + +(`location =`) > (`location 完整路径`) > (`location ^~ 路径`) > (`location ~,~* 正则顺序`) > (`location 部分起始路径`) > (`/`) + +```nginx +location = / { + # 精确匹配/,主机名后面不能带任何字符串 / + [ configuration A ] +} +location / { + # 匹配所有以 / 开头的请求。 + # 但是如果有更长的同类型的表达式,则选择更长的表达式。 + # 如果有正则表达式可以匹配,则优先匹配正则表达式。 + [ configuration B ] +} +location /documents/ { + # 匹配所有以 /documents/ 开头的请求,匹配符合以后,还要继续往下搜索。 + # 但是如果有更长的同类型的表达式,则选择更长的表达式。 + # 如果有正则表达式可以匹配,则优先匹配正则表达式。 + [ configuration C ] +} +location ^~ /images/ { + # 匹配所有以 /images/ 开头的表达式,如果匹配成功,则停止匹配查找,停止搜索。 + # 所以,即便有符合的正则表达式location,也不会被使用 + [ configuration D ] +} + +location ~* \.(gif|jpg|jpeg)$ { + # 匹配所有以 gif jpg jpeg结尾的请求。 + # 但是 以 /images/开头的请求,将使用 Configuration D,D具有更高的优先级 + [ configuration E ] +} + +location /images/ { + # 字符匹配到 /images/,还会继续往下搜索 + [ configuration F ] +} + + +location = /test.htm { + root /usr/local/var/www/htm; + index index.htm; +} +``` + +注意:location的优先级与location配置的位置无关 + + + +### 反向代理 + +反向代理应该是Nginx使用最多的功能了,反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。 + +简单来说就是真实的服务器不能直接被外部网络访问,所以需要一台代理服务器,而代理服务器能被外部网络访问的同时又跟真实服务器在同一个网络环境,当然也可能是同一台服务器,端口不同而已。 + +反向代理通过`proxy_pass`指令来实现。 + +启动一个Java Web项目,端口号为8081 + +```nginx +server { + listen 80; + server_name localhost; + + location / { + proxy_pass http://localhost:8081; + proxy_set_header Host $host:$server_port; + # 设置用户ip地址 + proxy_set_header X-Forwarded-For $remote_addr; + # 当请求服务器出错去寻找其他服务器 + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; + } + +} +``` + +当我们访问localhost的时候,就相当于访问 `localhost:8081`了 + + + +### 负载均衡 + +负载均衡也是Nginx常用的一个功能,负载均衡其意思就是分摊到多个操作单元上进行执行,例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。 + +简单而言就是当有2台或以上服务器时,根据规则随机的将请求分发到指定的服务器上处理,负载均衡配置一般都需要同时配置反向代理,通过反向代理跳转到负载均衡。而Nginx目前支持自带3种负载均衡策略,还有2种常用的第三方策略。负载均衡通过upstream指令来实现。 + +#### RR(round robin :轮询 默认) + +每个请求按时间顺序逐一分配到不同的后端服务器,也就是说第一次请求分配到第一台服务器上,第二次请求分配到第二台服务器上,如果只有两台服务器,第三次请求继续分配到第一台上,这样循环轮询下去,也就是服务器接收请求的比例是 1:1, 如果后端服务器down掉,能自动剔除。轮询是默认配置,不需要太多的配置,同一个项目分别使用8081和8082端口启动项目: + +```nginx +upstream web_servers { + server localhost:8081; + server localhost:8082; +} + +server { + listen 80; + server_name localhost; + #access_log logs/host.access.log main; + + + location / { + proxy_pass http://web_servers; + # 必须指定Header Host + proxy_set_header Host $host:$server_port; + } + } +``` + +访问地址仍然可以获得响应 `http://localhost/api/user/login?username=zhangsan&password=111111` ,这种方式是轮询的 + + + +#### 权重 + +指定轮询几率,weight和访问比率成正比, 也就是服务器接收请求的比例就是各自配置的weight的比例,用于后端服务器性能不均的情况,比如服务器性能差点就少接收点请求,服务器性能好点就多处理点请求。 + +```nginx +upstream test { + server localhost:8081 weight=1; + server localhost:8082 weight=3; + server localhost:8083 weight=4 backup; +} +``` + +示例是4次请求只有一次被分配到8081上,其他3次分配到8082上。backup是指热备,只有当8081和8082都宕机的情况下才走8083 + + + +#### ip_hash + +上面的2种方式都有一个问题,那就是下一个请求来的时候请求可能分发到另外一个服务器,当我们的程序不是无状态的时候(采用了session保存数据),这时候就有一个很大的很问题了,比如把登录信息保存到了session中,那么跳转到另外一台服务器的时候就需要重新登录了,所以很多时候我们需要一个客户只访问一个服务器,那么就需要用iphash了,iphash的每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。 + +```nginx +upstream test { + ip_hash; + server localhost:8080; + server localhost:8081; +} +``` + + + +#### fair(第三方) + +按后端服务器的响应时间来分配请求,响应时间短的优先分配。这个配置是为了更快的给用户响应 + +```nginx +upstream backend { + fair; + server localhost:8080; + server localhost:8081; +} +``` + +#### url_hash(第三方) + +按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。在upstream中加入hash语句,server语句中不能写入weight等其他的参数,`hash_method`是使用的hash算法 + +```nginx +upstream backend { + hash $request_uri; + hash_method crc32; + server localhost:8080; + server localhost:8081; +} +``` + +以上5种负载均衡各自适用不同情况下使用,所以可以根据实际情况选择使用哪种策略模式,不过fair和url_hash需要安装第三方模块才能使用。 + + + +### 动静分离 + +动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路。 + +```nginx +upstream web_servers { + server localhost:8081; + server localhost:8082; +} + +server { + listen 80; + server_name localhost; + + set $doc_root /usr/local/var/www; + + location ~* \.(gif|jpg|jpeg|png|bmp|ico|swf|css|js)$ { + root $doc_root/img; + } + + location / { + proxy_pass http://web_servers; + # 必须指定Header Host + proxy_set_header Host $host:$server_port; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root $doc_root; + } + + } +``` + + + +### 其他 + +#### return指令 + +返回http状态码 和 可选的第二个参数可以是重定向的URL + +```nginx +location /permanently/moved/url { + return 301 http://www.example.com/moved/here; +} +``` + + + +#### rewrite指令 + +重写URI请求 rewrite,通过使用rewrite指令在请求处理期间多次修改请求URI,该指令具有一个可选参数和两个必需参数。 + +第一个(必需)参数是请求URI必须匹配的正则表达式。 + +第二个参数是用于替换匹配URI的URI。 + +可选的第三个参数是可以停止进一步重写指令的处理或发送重定向(代码301或302)的标志 + +```nginx +location /users/ { + rewrite ^/users/(.*)$ /show?user=$1 break; +} +``` + + + +#### error_page指令 + +使用error_page指令,您可以配置NGINX返回自定义页面以及错误代码,替换响应中的其他错误代码,或将浏览器重定向到其他URI。在以下示例中,`error_page`指令指定要返回404页面错误代码的页面(/404.html)。 + +```nginx +error_page 404 /404.html; +``` + + + +#### 日志 + +访问日志:需要开启压缩 gzip on; 否则不生成日志文件,打开`log_format`、`access_log`注释 + +```nginx +log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + +access_log /usr/local/etc/nginx/logs/host.access.log main; + +gzip on; +``` + + + +#### deny 指令 + +```nginx +# 禁止访问某个目录 +location ~* \.(txt|doc)${ + root $doc_root; + deny all; +} +``` + + + +#### 内置变量 + +nginx的配置文件中可以使用的内置变量以美元符`$`开始,也有人叫全局变量。其中,部分预定义的变量的值是可以改变的。另外,关注Java知音公众号,回复“后端面试”,送你一份面试题宝典! + +- `$args` :`#`这个变量等于请求行中的参数,同`$query_string` +- `$content_length` :请求头中的Content-length字段。 +- `$content_type` :请求头中的Content-Type字段。 +- `$document_root` :当前请求在root指令中指定的值。 +- `$host` :请求主机头字段,否则为服务器名称。 +- `$http_user_agent` :客户端agent信息 +- `$http_cookie` :客户端cookie信息 +- `$limit_rate` :这个变量可以限制连接速率。 +- `$request_method` :客户端请求的动作,通常为GET或POST。 +- `$remote_addr` :客户端的IP地址。 +- `$remote_port` :客户端的端口。 +- `$remote_user` :已经经过Auth Basic Module验证的用户名。 +- `$request_filename` :当前请求的文件路径,由root或alias指令与URI请求生成。 +- `$scheme` :HTTP方法(如http,https)。 +- `$server_protocol` :请求使用的协议,通常是HTTP/1.0或HTTP/1.1。 +- `$server_addr` :服务器地址,在完成一次系统调用后可以确定这个值。 +- `$server_name` :服务器名称。 +- `$server_port` :请求到达服务器的端口号。 +- `$request_uri` :包含请求参数的原始URI,不包含主机名,如:”`/foo/bar.php?arg=baz`”。 +- `$uri` :不带请求参数的当前URI,`$uri`不包含主机名,如”`/foo/bar.html`”。 +- `$document_uri` :与`$uri`相同 + + + # Optimize ## MySQL @@ -2445,12 +4785,12 @@ MySQL不像Oracle那样支持函数索引,即时是函数内的字段有索引 MySQL对IN做了优化,即将IN中的常量全部存储在数组里,且排好序。但IN中的数值较多时,产生的消耗也比较大。连续数值可以用BETWEEN,而非IN。 - - ### 分页 ```mysql -select id,name from table_name limit 866613, 20;# 改为select id,name from table_name where id> 866612 limit 20; +select id,name from table_name limit 866613, 20; +# 改为 +select id,name from table_name where id> 866612 limit 20; ``` diff --git a/JAVA.md b/JAVA.md new file mode 100644 index 0000000..b2b578a --- /dev/null +++ b/JAVA.md @@ -0,0 +1,5222 @@ +
JAVA
+ +Author:李茹钰(`echo`) + +Introduction:收纳技术相关的JAVA知识 `JUC`、`Thread`、`Lock`、`I/O` 等总结! + +[TOC] + +# J.U.C + +## 并发特性 + +JAVA里面进行多线程通信的主要方式就是 `共享内存` 的方式,共享内存主要的关注点有两个:`可见性` 和 `有序性`。加上复合操作的 `原子性`,可以认为JAVA的线程安全性问题主要关注点有3个(JAVA内存模型JMM解决了可见性和有序性的问题,而锁解决了原子性的问题):`可见性`、`有序性`、`原子性` + +- **原子性(Atomicity)**:在Java中原子性指的是一个或多个操作要么全部执行成功要么全部执行失败 +- **有序性(Ordering)**:程序执行的顺序按照代码的先后顺序执行(处理器可能会对指令进行重排序) +- **可见性(Visibility)**:指在多线程环境下,当一个线程修改了某一个共享变量的值,其它线程能够立刻知道这个修改 + + + +**① 重排序** + +指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。从JAVA源码到最终实际执行的指令序列,会经历下面3种重排序(主要流程): + +![源码指令](images/JAVA/源码指令.png) + +**指令重排序分类** + +- 编译器重排序 + - 编译器优化重排序:编译器在不改变单线程程序语义(as-if-serial )的前提下,可以重新安排语句的执行顺序 +- 处理器重排序 + - 内存系统重排序:由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行 + - 指令级并行重排序:现代处理器采用了指令级并行技术(Instruction Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对机器指令的执行顺序 + +**② 顺序一致性** + +顺序一致性内存模型是一个理论参考模型,在设计的时候,处理器的内存模型和编程语言的内存模型都会以顺序一致性内存模型作为参照。顺序一致性特征如下: + +- 一个线程中的所有操作必须按照程序的顺序来执行 +- (不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性的内存模型中,每个操作必须原子执行并且立刻对所有线程可见 + + + +## Unsafe + +Java不能直接访问操作系统底层,而是通过本地方法来访问。**Unsafe类提供了硬件级别的原子操作**,主要提供以下功能: + +- **通过Unsafe类可以分配内存,可以释放内存** + + 类中提供的3个本地方法allocateMemory(申请)、reallocateMemory(扩展)、freeMemory(销毁)分别用于分配内存,扩充内存和释放内存,与C语言中的3个方法对应。 + +- **可以定位对象某字段的内存位置,也可以修改对象的字段值,即使它是私有的** + + - 字段的定位 + - 数组元素定位 + +- **挂起与恢复** + + 将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。 + +- **CAS操作** + + 是通过compareAndSwapXXX方法实现的 + + + +## LockSupport + +LockSupport 和 CAS 是Java并发包中很多并发工具控制机制的基础,它们底层其实都是依赖Unsafe实现。LockSupport 提供park()和unpark()方法实现阻塞线程和解除线程阻塞。 + +LockSupport和每个使用它的线程都与一个许可(permit)关联。permit相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park会消费permit, 也就是将1变成0,同时park立即返回。再次调用park会变成block(因为permit为0了,会阻塞在这里,直到permit变为1), 这时调用unpark会把permit置为1。每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累。 + +park()和unpark()不会有 `Thread.suspend` 和 `Thread.resume` 所可能引发的死锁问题,由于许可的存在,调用 park 的线程和另一个试图将其 unpark 的线程之间的竞争将保持活性。 + + + +## CAS机制 + +CAS(`Compare And Swap`,即比较并交换),是解决多线程并行情况下使用锁造成性能损耗的一种机制。其原理是利用`sun.misc.Unsafe.java` 类通过JNI来调用硬件级别的原子操作来实现CAS(即CAS是借助C来调用CPU底层指令实现的)。 + + + +**CAS机制=比较并交换+乐观锁机制+锁自旋** + +**设计思想**:如果`内存位置` 的值与 `预期原值` 相匹配,那么处理器会自动将该位置值更新为新值,否则处理器不做任何操作。 + +ReentrantLock、ReentrantReadWriteLock 都是基于 AbstractQueuedSynchronizer (AQS),而 AQS 又是基于 CAS。CAS 的全称是 Compare And Swap(比较与交换),它是一种无锁算法。synchronized和Lock都采用了悲观锁的机制,而CAS是一种乐观锁的实现。乐观锁的原理就是每次不加锁去执行某项操作,如果发生冲突则失败并重试,直到成功为止,其实本质上不算锁,所以很多地方也称之为**自旋**。乐观锁用到的主要机制就是**CAS**(Compare And Swap)。 + + + +**CAS特性** + +- 通过JNI借助C来调用CPU底层指令实现 +- 非阻塞算法 +- 非独占锁 + + + +**CAS缺陷** + +- **ABA问题**:X线程读到为A;Y线程立刻改为B,又改为A;X线程发现值还是A,此时CAS比较值相等,自旋成功 + - 使用数据乐观锁的方式给它加一个版本号或者时间戳,如使用 `AtomicStampedReference` 解决 +- **自旋消耗资源**:多个线程争夺同一个资源时,如果自旋一直不成功,将会一直占用CPU + - 破坏掉死循环,当超过一定时间或者一定次数时,return退出 +- **只能保证一个共享变量的原子操作** + - 可以加锁来解决 + - 封装成对象类解决,如使用 `AtomicReference` 解决 + + + +## AQS框架 + +**什么是AQS?** + +`AQS` 的全称是 `AbstractQueuedSynchronizer`,即`抽象队列同步器`。是Java并发工具的基础,采用乐观锁,通过CAS与自旋轻量级的获取锁。维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。很多JUC包,比如ReentrantLock、Semaphore、CountDownLatch等并发类均是继承AQS,通过AQS的模板方法,来实现的。 + +![AQS](images/JAVA/AQS.png) + +**核心思想** + +- 若请求的共享资源空闲,则将当前请求的线程设置为有效的工作线程,并将共享资源设置为锁定状态 +- 若共享资源被占用,则需要阻塞等待唤醒机制保证锁的分配 + + + +**工作原理** + +**AQS = `同步状态(volatile int state)` + `同步队列(即等待队列,FIFO的CLH队列)` + `条件队列(ConditionObject)`** + +- **state**:代表共享资源。`volatile` 保证并发读,`CAS` 保证并发写 +- **同步队列(即等待队列,CLH队列)**:是CLH变体的虚拟双向队列(先进先出FIFO)来等待获取共享资源。当前线程可以通过signal和signalAll将条件队列中的节点转移到同步队列中 +- **条件队列(ConditionObject)**:当前线程存在于同步队列的头节点,可以通过await从同步队列转移到条件队列中 + + + +**实现原理** + +- 通过CLH队列的变体:FIFO双向队列实现的 +- 每个请求资源的线程被包装成一个节点来实现锁的分配 +- 通过`volatile`的`int`类型的成员变量`state`表示同步状态 +- 通过FIFO队列完成资源获取的排队工作 +- 通过CAS完成对`state`的修改 + + + +### 共享方式 + +AQS定义两种资源共享方式。无论是独占锁还是共享锁,本质上都是对AQS内部的一个变量`state`的获取。`state`是一个原子的int变量,用来表示锁状态、资源数等。 + +**① 独占锁(`Exclusive`)模式**:只能被一个线程获取到(`Reentrantlock`)。 + +![独占锁(Exclusive)模式](images/JAVA/独占锁(Exclusive)模式.png) + + + +**② 共享锁(`Share`)模式**:可以被多个线程同时获取(`Semaphore/CountDownLatch/ReadWriteLock`)。 + +![共享锁(Share)模式](images/JAVA/共享锁(Share)模式.png) + + + +### state机制 + +提供`volatile`变量`state`,用于同步线程之间的共享状态。通过 `CAS` 和 `volatile` 保证其原子性和可见性。核心要点: + +- state 用 `volatile` 修饰,保证多线程中的可见性 +- `getState()` 和 `setState()` 方法**采用final修饰**,限制AQS的子类重写它们两 +- `compareAndSetState()` 方法采用乐观锁思想的CAS算法,也是采用final修饰的,不允许子类重写 + + + +**state应用案例**: + +| 案例 | 描述 | +| ------------------------ | ------------------------------------------------------------ | +| `Semaphore` | 使用AQS同步状态来保存信号量的当前计数。tryRelease会增加计数,acquireShared会减少计数 | +| `CountDownLatch` | 使用AQS同步状态来表示计数。计数为0时,所有的Acquire操作(CountDownLatch的await方法)才可以通过 | +| `ReentrantReadWriteLock` | 使用AQS同步状态中的16位保存写锁持有的次数,剩下的16位用于保存读锁的持有次数 | +| `ThreadPoolExecutor` | Worker利用AQS同步状态实现对独占线程变量的设置(tryAcquire和tryRelease) | +| `ReentrantLock` | 使用AQS保存锁重复持有的次数。当一个线程获取锁时,ReentrantLock记录当前获得锁的线程标识,用于检测是否重复获取,以及错误线程试图解锁操作时异常情况的处理 | + + + +### 双队列 + +![AQS同步队列与条件队列](images/JAVA/AQS同步队列与条件队列.png) + +![AQS-Lock-Condition](images/JAVA/AQS-Lock-Condition.png) + +- **同步队列(syncQueue):管理多个线程的休眠与唤醒** +- **条件队列(waitQueue):类似wait与signal作用,实现在使用锁时对线程管理** + + + +**注意** + +- 同步队列与条件队列节点可相互转化 +- 一个线程只能存在于两个队列中的一个 + + + +#### 同步队列 + +**同步队列是用来管理多个线程的休眠与唤醒**。 + +同步队列依赖一个双向链表(CHL)来完成同步状态的管理,当前线程获取同步状态失败后,同步器会将线程构建成一个节点,并将其加入同步队列中。通过`signal`或`signalAll`将条件队列中的节点转移到同步队列。(由条件队列转化为同步队列) + +![同步队列(syncQueue)结构](images/JAVA/同步队列(syncQueue)结构.png) + +如果没有锁竞争,线程可以直接获取到锁,就不会进入同步队列。即没有锁竞争时,同步队列(syncQueue)是空的,当存在锁竞争时,线程会进入到同步队列中。一旦进入到同步队列中,就会有线程切换。标准的 CHL 无锁队列是单向链表,同步队列(syncQueue) 在 CHL 基础上做了改进: + +- **同步队列是双向链表**。和二叉树一样,双向链表目前也没有无锁算法的实现。双向链表需要同时设置前驱和后继结点,这两次操作只能保证一个是原子性的 + +- **node.pre一定可以遍历所有结点,是线程安全的**。而后继结点 node.next 则是线程不安全的。也就是说,node.pre 一定可以遍历整个链表,而 node.next 则不一定 + + + +#### 条件队列 + +**条件队列是类似wait与signal作用,实现在使用锁时对线程管理。且由于实现了Condition,对线程的管理可更加细化**。 + +当线程存在于同步队列的头结点时,调用 `await` 方法进行阻塞(从同步队列转化到条件队列)。Condition条件队列(`waitQueue`)要比Lock同步队列(`syncQueue`)简单很多,最重要的原因是 `waitQueue` 的操作都是在获取锁的线程中执行,不存在数据竞争的问题。 + +![Condition等待队列结构](images/JAVA/Condition等待队列结构.png) + +ConditionObject重要的方法说明: + +- **await**:阻塞线程并放弃锁,加入到等待队列中 +- **signal**:唤醒等待线程,没有特殊的要求,尽量使用 signalAll +- **addConditionWaiter**:将结点(状态为 CONDITION)添加到等待队列 waitQueue 中,不存在锁竞争 +- **fullyRelease**:释放锁,并唤醒后继等待线程 +- **isOnSyncQueue**:根据结点是否在同步队列上,判断等待线程是否已经被唤醒 +- **acquireQueued**:Lock 接口中的方法,通过同步队列方法竞争锁 +- **unlinkCancelledWaiters**:清理取消等待的线程 + + + +### 框架架构图 + +![AQS框架架构图](images/JAVA/AQS框架架构图.png) + + + +**常见问题** + +**问题1:state为什么要提供setState和compareAndSetState两种修改状态的方法?** + +这个问题,关键是修改状态时是否存在数据竞争,如果有则必须使用 compareAndSetState。 + +- lock.lock() 获取锁时会发生数据竞争,必须使用 CAS 来保障线程安全,也就是 compareAndSetState 方法 +- lock.unlock() 释放锁时,线程已经获取到锁,没有数据竞争,也就可以直接使用 setState 修改锁的状态 + +**问题2:AQS为什么选择node.prev前驱结点的原子性,而node.next后继结点则是辅助结点?** + +- next 域:需要修改二处来保证原子性,一是 tail.next;二是 tail 指针 +- prev 域:只需要修改一处来保证原子性,就是 tail 指针。你可能会说不需要修改 node.prev 吗?当然需要,但 node 还没添加到链表中,其 node.prev 修改并没有锁竞争的问题,将 tail 指针指向 node 时,如果失败会通过自旋不断尝试 + +**问题3:AQS明知道node.next有可见性问题,为什么还要设计成双向链表?** + +唤醒同步线程时,如果有后继结点,那么时间复杂为 O(1)。否则只能只反向遍历,时间复杂度为 O(n)。以下两种情况,则认为 node.next 不可靠,需要从 tail 反向遍历。 + +- node.next=null:可能结点刚刚插入链表中,node.next 仍为空。此时有其它线程通过 unparkSuccessor 来唤醒该线程 +- node.next.waitStatus>0:结点已经取消,next 值可能已经改变 + + + +## Condition + +Condition的作用是对锁进行更精确的控制。Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。 + + + +## volatile + +Java 语言提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程。volatile 变量具备两种特性,volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取 volatile 类型的变量时总会返回最新写入的值。 + + + +**volatile特性与原理** + +- **可见性**:volatile修饰的变量,JVM保证了每次都跳过工作内存和缓存行(CPU Cache)来读取主内存中的最新值 +- **有序性**:即JVM使用内存屏障来禁止了该变量操作的指令重排序优化 +- **volatile性能**:volatile的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行 +- **内存屏障**:加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令相当于一个内存屏障(也称内存栅栏) +- **轻量级同步机制**:Java提供了一种稍弱同步机制,即volatile变量,是一种比sychronized关键字更轻量级的同步机制 +- **禁止重排序**:volatile 禁止了指令重排 + + + +**使用场景** + +- 状态标记量 + +- DCL(Double Check Lock) + + + +**CPU缓存** + +CPU缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快得多。按照读取顺序与CPU结合的紧密程度,CPU缓存可分为: + +- **一级缓存**:简称L1 Cache,位于CPU内核的旁边,是与CPU结合最为紧密的CPU缓存 +- **二级缓存**:简称L2 Cache,分内部和外部两种芯片,内部芯片二级缓存运行速度与主频相同,外部芯片二级缓存运行速度则只有主频的一半 +- **三级缓存**:简称L3 Cache,部分高端CPU才有 + + + +**volatile的特性** + +- 保证了线程可见性,不保证原子性,保证一定的有序性(禁止指令重排序) +- 在JVM底层volatile是采用“内存屏障”来实现的 +- volatile常用场景:状态标记、DCL(双重检查锁,Double Check) +- volatile不会引起线程上下文的切换和调度 +- 基于lock前缀指令,相当于内存屏障(内存栅栏) + + + +## lambda + +### 函数式接口 + +函数接口是只有一个抽象方法的接口,用作 Lambda 表达式的类型。使用@FunctionalInterface注解修饰的类,编译器会检测该类是否只有一个抽象方法或接口,否则,会报错。可以有多个默认方法,静态方法。JAVA8自带的常用函数式接口: + +| 函数接口 | 抽象方法 | 功能 | 参数 | 返回类型 | 示例 | +| -------------- | --------------- | ---------------------- | ----- | -------- | ------------------- | +| Predicate | test(T t) | 判断真假 | T | boolean | 身高大于185cm吗? | +| Consumer | accept(T t) | 消费消息 | T | void | 输出一个值 | +| Function | R apply(T t) | 将T映射为R(转换功能) | T | R | 取student对象的名字 | +| Supplier | T get() | 生产消息 | None | T | 工厂方法 | +| UnaryOperator | T apply(T t) | 一元操作 | T | T | 逻辑非(!) | +| BinaryOperator | apply(T t, U u) | 二元操作 | (T,T) | (T) | 求两个数的乘积(*) | + + + +### 常用的流 + +#### collect + +**将流转换为集合。有toList()、toSet()、toMap()等,及早求值**。 + +```java +public class TestCase { + public static void main(String[] args) { + List studentList = Stream.of(new Student("路飞", 22, 175), + new Student("红发", 40, 180), new Student("白胡子", 50, 185)).collect(Collectors.toList()); + System.out.println(studentList); + } +} + +// 输出结果 +// [Student{name='路飞', age=22, stature=175, specialities=null},  +// Student{name='红发', age=40, stature=180, specialities=null},  +// Student{name='白胡子', age=50, stature=185, specialities=null}] +``` + + + +#### filter + +顾名思义,起**过滤筛选**的作用。**内部就是Predicate接口。惰性求值。** + +![lambda-filter](images/JAVA/lambda-filter.jpg) + +```java +public class TestCase { + public static void main(String[] args) { + List students = new ArrayList<>(3); + students.add(new Student("路飞", 22, 175)); + students.add(new Student("红发", 40, 180)); + students.add(new Student("白胡子", 50, 185)); + + List list = students.stream() + .filter(stu -> stu.getStature() < 180) + .collect(Collectors.toList()); + System.out.println(list); + } +} + +// 输出结果 +// [Student{name='路飞', age=22, stature=175, specialities=null}] +``` + + + +#### map + +**转换功能,内部就是Function接口。惰性求值。** +![lambda-map](images/JAVA/lambda-map.jpg) + +```java +public class TestCase { + public static void main(String[] args) { + List students = new ArrayList<>(3); + students.add(new Student("路飞", 22, 175)); + students.add(new Student("红发", 40, 180)); + students.add(new Student("白胡子", 50, 185)); + + List names = students.stream().map(student -> student.getName()) + .collect(Collectors.toList()); + System.out.println(names); + } +} + +// 输出结果 +// [路飞, 红发, 白胡子] +``` + +例子中将student对象转换为String对象,获取student的名字。 + + + +#### flatMap + +**将多个Stream合并为一个Stream。惰性求值。** + +![lambda-flatMap](images/JAVA/lambda-flatMap.jpg) + +```java +public class TestCase { + public static void main(String[] args) { + List students = new ArrayList<>(3); + students.add(new Student("路飞", 22, 175)); + students.add(new Student("红发", 40, 180)); + students.add(new Student("白胡子", 50, 185)); + + List studentList = Stream.of(students, + asList(new Student("艾斯", 25, 183), + new Student("雷利", 48, 176))) + .flatMap(students1 -> students1.stream()).collect(Collectors.toList()); + System.out.println(studentList); + } +} + +// 输出结果 +// [Student{name='路飞', age=22, stature=175, specialities=null}, +// Student{name='红发', age=40, stature=180, specialities=null}, +// Student{name='白胡子', age=50, stature=185, specialities=null}, +// Student{name='艾斯', age=25, stature=183, specialities=null}, +// Student{name='雷利', age=48, stature=176, specialities=null}] +``` + +调用Stream.of的静态方法将两个list转换为Stream,再通过flatMap将两个流合并为一个。 + + + +#### max和min + +我们经常会在集合中**求最大或最小值**,使用流就很方便。**及早求值。** + +```java +public class TestCase { + public static void main(String[] args) { + List students = new ArrayList<>(3); + students.add(new Student("路飞", 22, 175)); + students.add(new Student("红发", 40, 180)); + students.add(new Student("白胡子", 50, 185)); + + Optional max = students.stream() + .max(Comparator.comparing(stu -> stu.getAge())); + Optional min = students.stream() + .min(Comparator.comparing(stu -> stu.getAge())); + //判断是否有值 + if (max.isPresent()) { + System.out.println(max.get()); + } + if (min.isPresent()) { + System.out.println(min.get()); + } + } +} + +// 输出结果 +// Student{name='白胡子', age=50, stature=185, specialities=null} +// Student{name='路飞', age=22, stature=175, specialities=null} +``` + +**max、min接收一个Comparator**(例子中使用java8自带的静态函数,只需要传进需要比较值即可),并且返回一个Optional对象,该对象是java8新增的类,专门为了防止null引发的空指针异常。可以使用max.isPresent()判断是否有值;可以使用max.orElse(new Student()),当值为null时就使用给定值;也可以使用max.orElseGet(() -> new Student());这需要传入一个Supplier的lambda表达式。 + + + +#### count + +**统计功能,一般都是结合filter使用,因为先筛选出我们需要的再统计即可。及早求值。** + +```java +public class TestCase { + public static void main(String[] args) { + List students = new ArrayList<>(3); + students.add(new Student("路飞", 22, 175)); + students.add(new Student("红发", 40, 180)); + students.add(new Student("白胡子", 50, 185)); + + long count = students.stream().filter(s1 -> s1.getAge() < 45).count(); + System.out.println("年龄小于45岁的人数是:" + count); + } +} + +// 输出结果 +// 年龄小于45岁的人数是:2 +``` + + + +#### reduce + +**reduce 操作可以实现从一组值中生成一个值**。在上述例子中用到的 count 、 min 和 max 方法,因为常用而被纳入标准库中。事实上,这些方法都是 reduce 操作。**及早求值。** + +![lambda-reduce](images/JAVA/lambda-reduce.jpg) + +```java +public class TestCase { + public static void main(String[] args) { + Integer reduce = Stream.of(1, 2, 3, 4).reduce(0, (acc, x) -> acc+ x); + System.out.println(reduce); + } +} + +// 输出结果 +// 10 +``` + +我们看得reduce接收了一个初始值为0的累加器,依次取出值与累加器相加,最后累加器的值就是最终的结果。 + + + +### 高级集合类及收集器 + +#### 转换成值 + +**收集器,一种通用的、从流生成复杂值的结构。**只要将它传给 collect 方法,所有的流就都可以使用它了。标准类库已经提供了一些有用的收集器,**以下示例代码中的收集器都是从 java.util.stream.Collectors 类中静态导入的。** + +```java +public class CollectorsTest { + public static void main(String[] args) { + List students1 = new ArrayList<>(3); + students1.add(new Student("路飞", 23, 175)); + students1.add(new Student("红发", 40, 180)); + students1.add(new Student("白胡子", 50, 185)); + + OutstandingClass ostClass1 = new OutstandingClass("一班", students1); + //复制students1,并移除一个学生 + List students2 = new ArrayList<>(students1); + students2.remove(1); + OutstandingClass ostClass2 = new OutstandingClass("二班", students2); + //将ostClass1、ostClass2转换为Stream + Stream classStream = Stream.of(ostClass1, ostClass2); + OutstandingClass outstandingClass = biggestGroup(classStream); + System.out.println("人数最多的班级是:" + outstandingClass.getName()); + + System.out.println("一班平均年龄是:" + averageNumberOfStudent(students1)); + } + + /** + * 获取人数最多的班级 + */ + private static OutstandingClass biggestGroup(Stream outstandingClasses) { + return outstandingClasses.collect( + maxBy(comparing(ostClass -> ostClass.getStudents().size()))) + .orElseGet(OutstandingClass::new); + } + + /** + * 计算平均年龄 + */ + private static double averageNumberOfStudent(List students) { + return students.stream().collect(averagingInt(Student::getAge)); + } +} + +// 输出结果 +// 人数最多的班级是:一班 +// 一班平均年龄是:37.666666666666664 +``` + +maxBy或者minBy就是求最大值与最小值。 + + + +#### 转换成块 + +**常用的流操作是将其分解成两个集合,Collectors.partitioningBy帮我们实现了,接收一个Predicate函数式接口。** + +![lambda-partitioningBy](images/JAVA/lambda-partitioningBy.jpg) + +将示例学生分为会唱歌与不会唱歌的两个集合。 + +```java +public class PartitioningByTest { + public static void main(String[] args) { + // 省略List students的初始化 + Map> listMap = students.stream().collect( + Collectors.partitioningBy(student -> student.getSpecialities(). + contains(SpecialityEnum.SING))); + } +} +``` + + + +#### 数据分组 + +数据分组是一种更自然的分割数据操作,与将数据分成 ture 和 false 两部分不同,**可以使用任意值对数据分组。Collectors.groupingBy接收一个Function做转换。** + +![lambda-groupingBy](images/JAVA/lambda-groupingBy.jpg) + +**如图,使用groupingBy将根据进行分组为圆形一组,三角形一组,正方形一组。**例子:根据学生第一个特长进行分组 + +```java +public class GroupingByTest { + public static void main(String[] args) { + //省略List students的初始化 + Map> listMap = + students.stream().collect( + Collectors.groupingBy(student -> student.getSpecialities().get(0))); + } +} +``` + +Collectors.groupingBy与SQL 中的 group by 操作是一样的。 + + + +#### 字符串拼接 + +如果将所有学生的名字拼接起来,怎么做呢?通常只能创建一个StringBuilder,循环拼接。使用Stream,使用Collectors.joining()简单容易。 + +```java +public class JoiningTest { + public static void main(String[] args) { + List students = new ArrayList<>(3); + students.add(new Student("路飞", 22, 175)); + students.add(new Student("红发", 40, 180)); + students.add(new Student("白胡子", 50, 185)); + + String names = students.stream() + .map(Student::getName).collect(Collectors.joining(",","[","]")); + System.out.println(names); + } +} +//输出结果 +//[路飞,红发,白胡子] +``` + +joining接收三个参数,第一个是分界符,第二个是前缀符,第三个是结束符。也可以不传入参数Collectors.joining(),这样就是直接拼接。 + + + +## Striped64 + +Striped64的设计思路是在竞争激烈的时候尽量分散竞争。 + + + +**Striping(条带化)** + +大多数磁盘系统都对访问次数(每秒的 I/O 操作,IOPS)和数据传输率(每秒传输的数据量,TPS)有限制。当达到这些限制时,后续需要访问磁盘的进程就需要等待,这就是所谓的磁盘冲突。当多个进程同时访问一个磁盘时,可能会出现磁盘冲突。因此,避免磁盘冲突是优化 I/O 性能的一个重要目标。 + +条带(strip)是把连续的数据分割成相同大小的数据块,把每段数据分别写入到阵列中的不同磁盘上的方法。使用条带化技术使得多个进程同时访问数据的多个不同部分而不会造成磁盘冲突,而且在需要对这种数据进行顺序访问的时候可以获得最大程度上的 I/O 并行能力,从而获得非常好的性能。 + + + +**Striped64设计** + +Striped64通过维护一个原子更新Cell表和一个base字段,并使用每个线程的探针字段作为哈希码映射到表的指定Cell。当竞争激烈时,将多线程的更新分散到不同Cell进行,有效降低了高并发下CAS更新的竞争,从而最大限度地提高了Striped64的吞吐量。Striped64为实现高吞吐量的并发计数组件奠定了基础,其中LongAdder就是基于Striped64实现,此外Java8中ConcurrentHashMap实现的并发计数功能也是基于Striped64的设计理念,还有hystrix、guava等实现的并发计数组件也离不开Striped64。 + + + +## LongAdder + +LongAdder是JDK1.8开始出现的,所提供的API基本上可替换掉原先AtomicLong。LongAdder所使用思想就是**热点分离**,这点可以类比一下ConcurrentHashMap的设计思想。就是将value值分离成一个数组,当多线程访问时,通过hash算法映射到其中的一个数字进行计数。而最终的结果,就是这些数组的求和累加。这样一来,就减小了锁的粒度。如下图所示: + +![LongAdder原理](images/JAVA/LongAdder原理.png) + +LonAdder和AtomicLong性能测试对比: + +![LongAdder和AtomicLong性能对比](images/JAVA/LongAdder和AtomicLong性能对比.png) + +LongAdder就是基于Striped64实现,用于并发计数时,若不存在竞争或竞争比较低时,LongAdder具有和AtomicLong差不多的效率。但是,高并发环境下,竞争比较严重时,LongAdder的cells表发挥作用,将并发更新分散到不同Cell进行,有效降低了CAS更新的竞争,从而极大提高了LongAdder的并发计数能力。因此,高并发场景下,要实现吞吐量更高的计数器,推荐使用LongAdder。 + + + +## Semaphore + +Semaphore是一个计数信号量,它的本质是一个"共享锁"。信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。 + + + +**数据结构** + +![Semaphore数据结构](images/JAVA/Semaphore数据结构.jpg) + +- 和"ReentrantLock"一样,Semaphore也包含sync对象,sync是Sync类型;而且Sync是一个继承于AQS的抽象类 +- Sync包括两个子类:"公平信号量"FairSync 和 "非公平信号量"NonfairSync。sync是"FairSync的实例",或者"NonfairSync的实例";默认情况下,sync是NonfairSync(即,默认是非公平信号量) + + + +## CyclicBarrier + +CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。 + + + +**比较CountDownLatch和CyclicBarrier** + +- CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待 +- CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier + + + +**数据结构** + +![CyclicBarrier数据结构](images/JAVA/CyclicBarrier数据结构.jpg) + +CyclicBarrier是包含了"ReentrantLock对象lock"和"Condition对象trip",它是通过独占锁实现的。下面通过源码去分析到底是如何实现的。 + + + +## CountDownLatch + +CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 + + + +**CountDownLatch和CyclicBarrier的区别** + +- CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待 +- CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier + + + +**数据结构** + +![CountDownLatch数据结构](images/JAVA/CountDownLatch数据结构.jpg) + +CountDownLatch的数据结构很简单,它是通过"共享锁"实现的。它包含了sync对象,sync是Sync类型。Sync是实例类,它继承于AQS。 + + + +## CompletableFuture + +CompletableFuture是Java 8新增的一个类,用于异步编程,继承了Future和CompletionStage。Future主要具备对请求结果独立处理的功能,CompletionStage用于实现流式处理,实现异步请求的各个阶段组合或链式处理,因此CompletableFuture能实现整个异步调用接口的扁平化和流式处理,解决原有Future处理一系列链式异步请求时的复杂编码: + +![img](images/JAVA/CompletableFuture.jpg) + +**Future的局限性** + +- **Future 的结果在非阻塞的情况下,不能执行更进一步的操作** + + 我们知道,使用Future时只能通过isDone()方法判断任务是否完成,或者通过get()方法阻塞线程等待结果返回,它不能非阻塞的情况下,执行更进一步的操作 + +- **不能组合多个Future的结果** + + 假设你有多个Future异步任务,你希望最快的任务执行完时,或者所有任务都执行完后,进行一些其他操作 + +- **多个Future不能组成链式调用** + + 当异步任务之间有依赖关系时,Future不能将一个任务的结果传给另一个异步任务,多个Future无法创建链式的工作流 + +- **没有异常处理** + + + +**注意事项** + +- **CompletableFuture默认线程池是否满足使用** + + 前面提到创建CompletableFuture异步任务的静态方法runAsync和supplyAsync等,可以指定使用的线程池,不指定则用CompletableFuture的默认线程池: + + ```java + private static final Executor asyncPool = useCommonPool ? ForkJoinPool.commonPool() : new ThreadPerTaskExecutor(); + ``` + + 可以看到,CompletableFuture默认线程池是调用ForkJoinPool的commonPool()方法创建,这个默认线程池的核心线程数量根据CPU核数而定,公式为`Runtime.getRuntime().availableProcessors() - 1`,以4核双槽CPU为例,核心线程数量就是`4*2-1=7`个。这样的设置满足CPU密集型的应用,但对于业务都是IO密集型的应用来说,是有风险的,当qps较高时,线程数量可能就设的太少了,会导致线上故障。所以可以根据业务情况自定义线程池使用。 + +- **get设置超时时间不能串行get,不然会导致接口延时`线程数量\*超时时间`** + + + +**① 创建异步任务** + +通常可以使用下面几个CompletableFuture的静态方法创建一个异步任务 + +```java +// 创建无返回值的异步任务 +public static CompletableFuture runAsync(Runnable runnable); +// 无返回值,可指定线程池(默认使用ForkJoinPool.commonPool) +public static CompletableFuture runAsync(Runnable runnable, Executor executor); +// 创建有返回值的异步任务 +public static CompletableFuture supplyAsync(Supplier supplier); +// 有返回值,可指定线程池 +public static CompletableFuture supplyAsync(Supplier supplier, Executor executor); +``` + +使用示例: + +```java +Executor executor = Executors.newFixedThreadPool(10); +CompletableFuture future = CompletableFuture.runAsync(() -> { + //do something +}, executor); + +int poiId = 111; +CompletableFuture future = CompletableFuture.supplyAsync(() -> { + PoiDTO poi = poiService.loadById(poiId); + return poi.getName(); +}); +// Block and get the result of the Future +String poiName = future.get(); +``` + + + +**② 使用回调方法** + +通过`future.get()`方法获取异步任务的结果,还是会阻塞的等待任务完成 + +CompletableFuture提供了几个回调方法,可以不阻塞主线程,在异步任务完成后自动执行回调方法中的代码 + +```java +// 无参数、无返回值 +public CompletableFuture thenRun(Runnable runnable); +// 接受参数,无返回值 +public CompletableFuture thenAccept(Consumer action); +// 接受参数T,有返回值U +public CompletableFuture thenApply(Function fn); +``` + +使用示例: + +```java +CompletableFuture future = CompletableFuture.supplyAsync(() -> "Hello") + .thenRun(() -> System.out.println("do other things. 比如异步打印日志或发送消息")); +// 如果只想在一个CompletableFuture任务执行完后,进行一些后续的处理,不需要返回值,那么可以用thenRun回调方法来完成。 +// 如果主线程不依赖thenRun中的代码执行完成,也不需要使用get()方法阻塞主线程。 + +CompletableFuture future = CompletableFuture.supplyAsync(() -> "Hello") + .thenAccept((s) -> System.out.println(s + " world")); +// 输出:Hello world +// 回调方法希望使用异步任务的结果,并不需要返回值,那么可以使用thenAccept方法 + +CompletableFuture future = CompletableFuture.supplyAsync(() -> { + PoiDTO poi = poiService.loadById(poiId); + return poi.getMainCategory(); +}).thenApply((s) -> isMainPoi(s)); // boolean isMainPoi(int poiId); +future.get(); +// 希望将异步任务的结果做进一步处理,并需要返回值,则使用thenApply方法。 +// 如果主线程要获取回调方法的返回,还是要用get()方法阻塞得到 +``` + + + +**③ 组合两个异步任务** + +```java +// thenCompose方法中的异步任务依赖调用该方法的异步任务 +public CompletableFuture thenCompose(Function> fn); +// 用于两个独立的异步任务都完成的时候 +public CompletableFuture thenCombine(CompletionStage other, BiFunction fn); +``` + +使用示例: + +```java +CompletableFuture> poiFuture = CompletableFuture.supplyAsync( + () -> poiService.queryPoiIds(cityId, poiId) +); +// 第二个任务是返回CompletableFuture的异步方法 +CompletableFuture> getDeal(List poiIds){ + return CompletableFuture.supplyAsync(() -> poiService.queryPoiIds(poiIds)); +} +// thenCompose +CompletableFuture> resultFuture = poiFuture.thenCompose(poiIds -> getDeal(poiIds)); +resultFuture.get(); +``` + +thenCompose和thenApply的功能类似,两者区别在于thenCompose接受一个返回`CompletableFuture`的Function,当想从回调方法返回的`CompletableFuture`中直接获取结果U时,就用thenCompose。如果使用thenApply,返回结果resultFuture的类型是`CompletableFuture>>`,而不是`CompletableFuture>` + +```java +CompletableFuture future = CompletableFuture.supplyAsync(() -> "Hello") + .thenCombine(CompletableFuture.supplyAsync(() -> "world"), (s1, s2) -> s1 + s2); +// future.get() +``` + + + +**④ 组合多个CompletableFuture** + +当需要多个异步任务都完成时,再进行后续处理,可以使用**allOf**方法: + +```java +CompletableFuture poiIDTOFuture = CompletableFuture + .supplyAsync(() -> poiService.loadPoi(poiId)) + .thenAccept(poi -> { + model.setModelTitle(poi.getShopName()); + // do more thing + }); + +CompletableFuture productFuture = CompletableFuture + .supplyAsync(() -> productService.findAllByPoiIdOrderByUpdateTimeDesc(poiId)) + .thenAccept(list -> { + model.setDefaultCount(list.size()); + model.setMoreDesc("more"); + }); +// future3等更多异步任务,这里就不一一写出来了 + +// allOf组合所有异步任务,并使用join获取结果 +CompletableFuture.allOf(poiIDTOFuture, productFuture, future3, ...).join(); +``` + +该方法挺适合C端的业务,通过poiId异步的从多个服务拿门店信息,然后组装成自己需要的模型,最后所有门店信息都填充完后返回。这里使用了join方法获取结果,它和get方法一样阻塞的等待任务完成。多个异步任务有任意一个完成时就返回结果,可以使用**anyOf**方法: + +```java +CompletableFuture future1 = CompletableFuture.supplyAsync(() -> { + try { + TimeUnit.SECONDS.sleep(2); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + return "Result of Future 1"; +}); + +CompletableFuture future2 = CompletableFuture.supplyAsync(() -> { + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + return "Result of Future 2"; +}); + +CompletableFuture future3 = CompletableFuture.supplyAsync(() -> { + try { + TimeUnit.SECONDS.sleep(3); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + return "Result of Future 3"; +}); + +CompletableFuture anyOfFuture = CompletableFuture.anyOf(future1, future2, future3); + +System.out.println(anyOfFuture.get()); // Result of Future 2 +``` + + + +**⑤ 异常处理** + +```java +Integer age = -1; + +CompletableFuture maturityFuture = CompletableFuture.supplyAsync(() -> { + if(age < 0) { + throw new IllegalArgumentException("Age can not be negative"); + } + if(age > 18) { + return "Adult"; + } else { + return "Child"; + } +}).exceptionally(ex -> { + System.out.println("Oops! We have an exception - " + ex.getMessage()); + return "Unknown!"; +}).thenAccept(s -> System.out.print(s)); +// Unkown! +``` + +exceptionally方法可以处理异步任务的异常,在出现异常时,给异步任务链一个从错误中恢复的机会,可以在这里记录异常或返回一个默认值。使用handler方法也可以处理异常,并且无论是否发生异常它都会被调用: + +```java +Integer age = -1; + +CompletableFuture maturityFuture = CompletableFuture.supplyAsync(() -> { + if(age < 0) { + throw new IllegalArgumentException("Age can not be negative"); + } + if(age > 18) { + return "Adult"; + } else { + return "Child"; + } +}).handle((res, ex) -> { + if(ex != null) { + System.out.println("Oops! We have an exception - " + ex.getMessage()); + return "Unknown!"; + } + return res; +}); +``` + + + +**⑥ 分片处理** + +分片和并行处理:分片借助stream实现,然后通过CompletableFuture实现并行执行,最后做数据聚合(其实也是stream的方法)。CompletableFuture并不提供单独的分片api,但可以借助stream的分片聚合功能实现。举个例子: + +```java +// 请求商品数量过多时,做分批异步处理 +List> skuBaseIdsList = ListUtils.partition(skuIdList, 10);//分片 +// 并行 +List>> futureList = Lists.newArrayList(); +for (List skuId : skuBaseIdsList) { + CompletableFuture> tmpFuture = getSkuSales(skuId); + futureList.add(tmpFuture); +} +// 聚合 +futureList.stream().map(CompletalbleFuture::join).collent(Collectors.toList()); +``` + + + +**⑦ 应用案例** + +首先还是需要先完成分工方案,在下面的程序中,我们分了3个任务: + +- 任务1负责洗水壶、烧开水 +- 任务2负责洗茶壶、洗茶杯和拿茶叶 +- 任务3负责泡茶。其中任务3要等待任务1和任务2都完成后才能开始 + +```java +// 任务1:洗水壶->烧开水 +CompletableFuture f1 = + CompletableFuture.runAsync(()->{ + System.out.println("T1:洗水壶..."); + sleep(1, TimeUnit.SECONDS); + + System.out.println("T1:烧开水..."); + sleep(15, TimeUnit.SECONDS); +}); + +// 任务2:洗茶壶->洗茶杯->拿茶叶 +CompletableFuture f2 = + CompletableFuture.supplyAsync(()->{ + System.out.println("T2:洗茶壶..."); + sleep(1, TimeUnit.SECONDS); + + System.out.println("T2:洗茶杯..."); + sleep(2, TimeUnit.SECONDS); + + System.out.println("T2:拿茶叶..."); + sleep(1, TimeUnit.SECONDS); + return "龙井"; +}); + +// 任务3:任务1和任务2完成后执行:泡茶 +CompletableFuture f3 = + f1.thenCombine(f2, (__, tf)->{ + System.out.println("T3:拿到茶叶:" + tf); + System.out.println("T3:泡茶..."); + return "上茶:" + tf; + }); + +// 等待任务3执行结果 +System.out.println(f3.join()); +void sleep(int t, TimeUnit u) { + try { + u.sleep(t); + }catch(InterruptedException e){} +} + +// 一次执行结果: +T1:洗水壶... +T2:洗茶壶... +T1:烧开水... +T2:洗茶杯... +T2:拿茶叶... +T3:拿到茶叶:龙井 +T3:泡茶... +上茶:龙井 +``` + + + +# 集合 + +## List + +### ArrayList(数组) + +ArrayList 是一个**数组队列**,相当于 **动态数组**。与Java中的数组相比,它的容量能动态增长。 + +它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。 + + + +### LinkedList(链表) + +LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。 + +- LinkedList是有序的双向链表 +- LinkedList在内存中开辟的内存不连续【ArrayList开辟的内存是连续的】 +- LinkedList插入和删除元素很快,但是查询很慢【相对于ArrayList】 + + + +### Vector(数组实现、线程同步) + +Vector 与 ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写 Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问 ArrayList 慢。 + + + +### CopyOnWriteArrayList + +**Copy-On-Write是什么?** + +顾名思义,在计算机中就是当你想要对一块内存进行修改时,我们不在原有内存块中进行`写`操作,而是将内存拷贝一份,在新的内存中进行`写`操作,`写`完之后呢,就将指向原来内存指针指向新的内存,原来的内存就可以被回收掉。 + + + +CopyOnWriteArrayList相当于线程安全的ArrayList,它实现了List接口,支持高并发。和ArrayList一样,它是个可变数组,但是和ArrayList不同的时,它具有以下特性: + +- 最适合于具有以下特征的应用:List大小通常保持很小,只读操作远多于可变操作,需在遍历期间防止线程间的冲突 +- 它是线程安全的 +- 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大 +- 迭代器支持hasNext(), next()等不可变操作,但不支持可变 remove()等操作 +- 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照 + + + +**CopyOnWriteArrayList数据结构** + +![CopyOnWriteArrayList数据结构](images/JAVA/CopyOnWriteArrayList数据结构.jpg) + +- CopyOnWriteArrayList实现了List接口,因此它是一个队列 +- CopyOnWriteArrayList包含了成员lock。每一个CopyOnWriteArrayList都和一个互斥锁lock绑定,通过lock,实现了对CopyOnWriteArrayList的互斥访问 +- CopyOnWriteArrayList包含了成员array数组,这说明CopyOnWriteArrayList本质上通过数组实现的 + + + +**CopyOnWriteArrayList原理** + +下面从“动态数组”和“线程安全”两个方面进一步对CopyOnWriteArrayList的原理进行说明。 + +- **CopyOnWriteArrayList的“动态数组”机制** + + 它内部有个“volatile数组”(array)来保持数据。 + + 在“添加/修改/删除”数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile数组”。这就是它叫做CopyOnWriteArrayList的原因。CopyOnWriteArrayList就是通过这种方式实现的动态数组,不过正由于它在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的操作,CopyOnWriteArrayList效率很 + 低。但是单单只是进行遍历查找的话,效率比较高 + +- **CopyOnWriteArrayList的“线程安全”机制** + + 是通过volatile和互斥锁来实现的。 + + - **CopyOnWriteArrayList是通过“volatile数组”来保存数据的**。一个线程读取volatile数组时,总能看到其它线程对该volatile变量最后的写入。就这样,通过volatile提供了“读取到的数据总是最新的”这个机制的保证 + - **CopyOnWriteArrayList通过互斥锁来保护数据**。在“添加、修改、删除”数据时,会先“获取互斥锁”,再修改完毕之后,先将数据更新到“volatile数组”中,然后再“释放互斥锁”;这样,就达到了保护数据的目的 + + + +## Set + +### HashSet(Hash表) + +**HashSet 基于 HashMap,底层是通过 HashMap 的API来实现的。**哈希表边存放的是哈希值。HashSet 存储元素的顺序并不是按照存入时的顺序(和 List 显然不同) 而是按照哈希值来存的所以取数据也是按照哈希值取得。元素的哈希值是通过元素的hashcode 方法来获取的, HashSet 首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals方法,如果 equls 结果为 true ,HashSet 就视为同一个元素;如果 equals 为 false 就不是同一个元素。hashcode相同,equals不相等,则使用链表存储。 + + + +### TreeSet(二叉树) + +底层通过TreeMap来实现,非线程安全,具有排序功能(自然排序(默认)和自定义排序)。它继承AbstractSet,实现NavigableSet(搜索功能), Cloneable(克隆), Serializable(序列化,可用hessian协议来传输)接口。 + +- TreeSet()是使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置 +- Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的,自己定义的类必须实现 Comparable 接口,并且覆写相应的 compareTo()函数,才可以正常使用 +- 在覆写 compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序 +- 比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数 + + + +### LinkHashSet(HashSet+LinkedHashMap) + +对于 LinkedHashSet 而言,它继承与 HashSet、又基于 LinkedHashMap 来实现的。LinkedHashSet 底层使用LinkedHashMap 来保存所有元素,它继承与 HashSet,其所有的方法操作上又与 HashSet 相同,因此 LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数,调用父类的构造器,底层构造一个 LinkedHashMap 来实现,在相关操作上与父类 HashSet 的操作相同,直接调用父类 HashSet 的方法即可。 + + + +### ConcurrentSkipListSet + +ConcurrentSkipListSet是线程安全的有序的集合(相当于线程安全的TreeSet);它继承于AbstractSet,并实现了NavigableSet接口。ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,它也支持并发。 + + + +### CopyOnWriteArraySet + +CopyOnWriteArraySet是线程安全的无序的集合,可以将它理解成线程安全的HashSet。CopyOnWriteArraySet内部包含一个CopyOnWriteArrayList对象,它是通过CopyOnWriteArrayList实现的。 + +- **通过“动态数组(CopyOnWriteArrayList)”实现(HashSet是通过“散列表(HashMap)”实现的)** +- **线程安全是通过volatile和互斥锁来实现** +- **无序的不能重复集合** + + + +**CopyOnWriteArraySet特性** + +- 它最适合于具有以下特征的应用程序:Set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突 +- 它是线程安全的 +- 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大 +- 迭代器支持hasNext(), next()等不可变操作,但不支持可变 remove()等 操作 +- 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照 + + + +**数据结构** + +![CopyOnWriteArraySet数据结构](images/JAVA/CopyOnWriteArraySet数据结构.jpg) + +- CopyOnWriteArraySet继承于AbstractSet,这就意味着它是一个集合 +- CopyOnWriteArraySet包含CopyOnWriteArrayList对象,它是通过CopyOnWriteArrayList实现的。而CopyOnWriteArrayList本质是个动态数组队列,所以CopyOnWriteArraySet相当于通过通过动态数组实现的“集合”! CopyOnWriteArrayList中允许有重复的元素;但是,CopyOnWriteArraySet是一个集合,所以它不能有重复集合。因此,CopyOnWriteArrayList额外提供了addIfAbsent()和addAllAbsent()这两个添加元素的API,通过这些API来添加元素时,只有当元素不存在时才执行添加操作 + + + +### ConcurrentSkipListSet + +ConcurrentSkipListSet是线程安全的有序的集合,适用于高并发的场景。ConcurrentSkipListSet和TreeSet,它们虽然都是有序的集合。但是,第一,它们的线程安全机制不同,TreeSet是非线程安全的,而ConcurrentSkipListSet是线程安全的。第二,ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,而TreeSet是通过TreeMap实现的。 + + + +**数据结构** + + ![ConcurrentSkipListSet数据结构](images/JAVA/ConcurrentSkipListSet数据结构.jpg) + +- ConcurrentSkipListSet继承于AbstractSet。因此,它本质上是一个集合 +- ConcurrentSkipListSet实现了NavigableSet接口。因此,ConcurrentSkipListSet是一个有序的集合 +- ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的。它包含一个ConcurrentNavigableMap对象m,而m对象实际上是ConcurrentNavigableMap的实现类ConcurrentSkipListMap的实例。ConcurrentSkipListMap中的元素是key-value键值对;而ConcurrentSkipListSet是集合,它只用到了ConcurrentSkipListMap中的key + + + +## Map + +### HashMap(数组+链表+红黑树) + +**工作原理** + +**HashMap(数组+链表+红黑树)**。HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。我们用下面这张图来介绍HashMap 的结构。 + + + +hashCode 是定位的,**存储位置**;equals是定性的,**比较两者是否相等**。 + +**put()** + +- 第一步:调用 hash(K) 方法**计算 K 的 hash 值**,然后结合数组长度,计算得数组下标 +- 第二步:**调整数组大小**(当容器中的元素个数大于 capacity * loadfactor 时,容器会进行扩容resize 为 2n) +- 第三步: + - 如果 **K 的 hash 值**在 HashMap 中**不存在**,则执行**插入**,若存在,则发生**碰撞** + - 如果 K 的 hash 值在 HashMap 中**存在**,且它们两者 **equals 返回 true**,则**更新键值对** + - 如果 K 的 hash 值在 HashMap 中**存在**,且它们两者 **equals 返回 false**,则**插入链表的尾部(尾插法)或者红黑树中(树的添加方式)** + +**get()** + +- 第一步:调用 hash(K) 方法(**计算 K 的 hash 值**)从而**获取该键值所在链表的数组下标** +- 第二步:顺序遍历链表,equals()方法查找**相同 Node 链表中 K 值**对应的 V 值 + + + +![Java7HashMap结构](images/JAVA/Java7HashMap结构.png) + +大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。上图中,每个绿色的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。 + +- capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍 +- loadFactor:负载因子,默认为 0.75 +- threshold:扩容的阈值,等于 capacity * loadFactor + + + +![Java8HashMap结构](images/JAVA/Java8HashMap结构.png) + +Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。 + + + +HashMap具有如下特性: + +- HashMap 的存取是没有顺序的 +- KV 均允许为 NULL +- 多线程情况下该类不安全,可以考虑用 HashTable +- JDk8底层是数组 + 链表 + 红黑树,JDK7底层是数组 + 链表 +- 初始容量和装载因子是决定整个类性能的关键点,轻易不要动 +- HashMap是**懒汉式**创建的,只有在你put数据时候才会 build +- 单向链表转换为红黑树的时候会先变化为**双向链表**最终转换为**红黑树**,切记双向链表跟红黑树是`共存`的 +- 对于传入的两个`key`,会强制性的判别出个高低,目的是为了决定向左还是向右放置数据 +- 链表转红黑树后会努力将红黑树的`root`节点和链表的头节点 跟`table[i]`节点融合成一个 +- 在删除时候是先判断删除节点红黑树个数是否需要转链表,不转链表就跟`RBT`类似,找个合适的节点来填充已删除的节点 +- 红黑树的`root`节点`不一定`跟`table[i]`也就是链表的头节点是同一个,三者同步是靠`MoveRootToFront`实现的。而`HashIterator.remove()`会在调用`removeNode`的时候`movable=false` + + + +### TreeMap(可排序) + +TreeMap 实现 SortedMap 接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。如果使用排序的映射,建议使用 TreeMap。在使用 TreeMap 时,key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException 类型的异常。 + + + +### HashTable(线程安全) + +Hashtable 是遗留类,很多映射的常用功能与 HashMap 类似,不同的是它承自 Dictionary 类,并且是线程安全的,任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁。Hashtable 不建议在新代码中使用,不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换。 + + + +### LinkHashMap(记录插入顺序) + +LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。 + + + +### ConcurrentHashMap + +ConcurrentHashMap是线程安全的哈希表(相当于线程安全的HashMap);它继承于AbstractMap类,并且实现ConcurrentMap接口。ConcurrentHashMap是通过“锁分段”来实现的,它支持并发。 + + + +**Segment** **段** + +ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些。整个ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分“或”一段“的意思,所以很多地方都会将其描述为分段锁。注意,行文中,我很多地方用了“槽”来代表一个segment。 + + + +**线程安全(Segment继承ReentrantLock加锁)** + +简单理解就是,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。 + +![Java7ConcurrentHashMap结构](images/JAVA/Java7ConcurrentHashMap结构.png) + + + +**并行度(默认16)** + +concurrencyLevel:并行级别、并发数、Segment 数,怎么翻译不重要,理解它。默认是 16,也就是说ConcurrentHashMap 有 16 个 Segments,所以理论上,这个时候,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。这个值可以在初始化的时候设置为其他值,但是一旦初始化以后,它是不可以扩容的。再具体到每个 Segment 内部,其实每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安全,所以处理起来要麻烦些。 + + + +**Java8** **实现 (引入了红黑树)** + +Java8 对 ConcurrentHashMap 进行了比较大的改动,Java8 也引入了红黑树。 + +![Java8ConcurrentHashMap结构](images/JAVA/Java8ConcurrentHashMap结构.png) + + + +**ConcurrentHashMap并发** + +- **减小锁粒度** +- **ConcurrentHashMap 分段锁(Segment)** + + + +### ConcurrentSkipListMap + +ConcurrentSkipListMap是线程安全的有序的哈希表(相当于线程安全的TreeMap)。它继承于AbstractMap类,并且实现ConcurrentNavigableMap接口。ConcurrentSkipListMap是通过“跳表”来实现的,它支持并发。 + + + +**数据结构** + +![ConcurrentSkipListMap数据结构](images/JAVA/ConcurrentSkipListMap数据结构.jpg) + +ConcurrentSkipListMap是线程安全的有序的哈希表,适用于高并发的场景。ConcurrentSkipListMap和TreeMap,它们虽然都是有序的哈希表。但是,第一,它们的线程安全机制不同,TreeMap是非线程安全的,而ConcurrentSkipListMap是线程安全的。第二,ConcurrentSkipListMap是通过跳表实现的,而TreeMap是通过红黑树实现的。关于跳表(Skip List),它是平衡树的一种替代的数据结构,但是和红黑树不相同的是,跳表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳表的插入和删除的工作是比较简单的。 + + + +# Queue + +**Queue(队列)是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,只允许在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。** + +每个元素总是从队列的rear端进入队列,然后等待该元素之前的所有元素出队之后,当前元素才能出对,遵循先进先出(FIFO)原则。下面是Queue类的继承关系图: + +![队列类图](images/JAVA/队列类图.png) + +图中我们可以看到,最上层是Collection接口,Queue满足集合类的所有方法: + +- **add(E e):增加元素** +- **remove(Object o):删除元素** +- **clear():清除集合中所有元素** +- **size():集合元素的大小** +- **isEmpty():集合是否没有元素** +- **contains(Object o):集合是否包含元素o** + + + +## 队列 + +### Queue + +Queue:队列的上层接口,提供了插入、删除、获取元素这3种类型的方法,而且对每一种类型都提供了两种方式。 + +**插入方法** + +- **add(E e)**:插入元素到队尾,插入成功返回true,没有可用空间抛出异常 IllegalStateException +- **offer(E e)**: 插入元素到队尾,插入成功返回true,否则返回false + +add和offer作为插入方法的唯一不同就在于队列满了之后的处理方式。add抛出异常,而offer返回false。 + + + +**删除和获取元素方法(和插入方法类似)** + +- **remove()**:获取并移除队首的元素,该方法和poll方法的不同之处在于,如果队列为空该方法会抛出异常,而poll不会 +- **poll()**:获取并移除队首的元素,如果队列为空,返回null +- **element()**:获取队列首的元素,该方法和peek方法的不同之处在于,如果队列为空该方法会抛出异常,而peek不会 +- **peek()**:获取队列首的元素,如果队列为空,返回null + +如果队列是空,remove和element方法会抛出异常,而poll和peek返回null。当然,Queue只是单向队列,为了提供更强大的功能,JDK在1.6的时候新增了一个双向队列Deque,用来实现更灵活的队列操作。 + + + +### Deque + +Deque在Queue的基础上,增加了以下几个方法: + +- **addFirst(E e)**:在前端插入元素,异常处理和add一样 +- **addLast(E e)**:在后端插入元素,和add一样的效果 +- **offerFirst(E e)**:在前端插入元素,异常处理和offer一样 +- **offerLast(E e)**:在后端插入元素,和offer一样的效果 +- **removeFirst()**:移除前端的一个元素,异常处理和remove一样 +- **removeLast()**:移除后端的一个元素,和remove一样的效果 +- **pollFirst()**:移除前端的一个元素,和poll一样的效果 +- **pollLast()**:移除后端的一个元素,异常处理和poll一样 +- **getFirst()**:获取前端的一个元素,和element一样的效果 +- **getLast()**:获取后端的一个元素,异常处理和element一样 +- **peekFirst()**:获取前端的一个元素,和peek一样的效果 +- **peekLast()**:获取后端的一个元素,异常处理和peek一样 +- **removeFirstOccurrence(Object o)**:从前端开始移除第一个是o的元素 +- **removeLastOccurrence(Object o)**:从后端开始移除第一个是o的元素 +- **push(E e)**:和addFirst一样的效果 +- **pop()**:和removeFirst一样的效果 + +其实很多方法的效果都是一样的,只不过名字不同。比如Deque为了实现Stack的语义,定义了push和pop两个方法。 + + + +## 阻塞队列 + +### BlockingQueue + +**BlockingQueue(阻塞队列)**,在Queue的基础上实现了阻塞等待的功能。它是JDK 1.5中加入的接口,它是指这样的一个队列:当生产者向队列添加元素但队列已满时,生产者会被阻塞;当消费者从队列移除元素但队列为空时,消费者会被阻塞。 + +先给出BlockingQueue新增的方法: + +- put(E e):向队尾插入元素。如果队列满了,阻塞等待,直到被中断为止。 +- boolean offer(E e, long timeout, TimeUnit unit):向队尾插入元素。如果队列满了,阻塞等待timeout个时长,如果到了超时时间还没有空间,抛弃该元素。 +- take():获取并移除队首的元素。如果队列为空,阻塞等待,直到被中断为止。 +- poll(long timeout, TimeUnit unit):获取并移除队首的元素。如果队列为空,阻塞等待timeout个时长,如果到了超时时间还没有元素,则返回null。 +- remainingCapacity():返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的元素数量,如果该队列是无界队列,返回Integer.MAX_VALUE。 +- drainTo(Collection c):移除此队列中所有可用的元素,并将它们添加到给定 collection 中。 +- drainTo(Collection c, int maxElements):最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 collection 中。 + +**BlockingQueue**最重要的也就是关于阻塞等待的几个方法,而这几个方法正好可以用来实现**生产-消费的模型**。 + +从图中我们可以知道实现了BlockingQueue的类有以下几个: + +- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。 +- LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。 +- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。 +- SynchronousQueue:一个不存储元素的阻塞队列。 +- DelayQueue:一个使用优先级队列实现的无界阻塞队列。 + + + +#### ArrayBlockingQueue、 + +**ArrayBlockingQueue是一个用数组实现的有界阻塞队列**。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素。通常情况下为了保证公平性会降低吞吐量。 + +**特性** + +- **内部使用循环数组进行存储** +- **内部使用ReentrantLock来保证线程安全** +- **由Condition的await和signal来实现等待唤醒功能** +- **支持对生产者线程和消费者线程进行公平的调度**。默认情况下是不保证公平性的。公平性通常会降低吞吐量,但是减少了可变性和避免了线程饥饿问题 + + + +**数据结构 —— 数组** + +通常,队列的实现方式有数组和链表两种方式。对于数组这种实现方式来说,我们可以通过维护一个队尾指针,使得在入队的时候可以在O(1)的时间内完成。但是对于出队操作,在删除队头元素之后,必须将数组中的所有元素都往前移动一个位置,这个操作的复杂度达到了O(n),效果并不是很好。如下图所示: + +![数据结构—数组](images/JAVA/数据结构—数组.png) + + + +**数据结构 —— 环型结构** + +为了解决这个问题,我们可以使用另外一种逻辑结构来处理数组中各个位置之间的关系。假设现在我们有一个数组A[1…n],我们可以把它想象成一个环型结构,即A[n]之后是A[1],相信了解过一致性Hash算法的应该很容易能够理解。如下图所示:![数据结构—环型结构](images/JAVA/数据结构—环型结构.png) + +我们可以使用两个指针,分别维护队头和队尾两个位置,使入队和出队操作都可以在O(1)的时间内完成。当然,这个环形结构只是逻辑上的结构,实际的物理结构还是一个普通的数据。 + + + +**入队方法** + +ArrayBlockingQueue 提供了多种入队操作的实现来满足不同情况下的需求,入队操作有如下几种: + +- **boolean add(E e)**:其调用的是父类,即AbstractQueue的add方法,实际上调用的就是offer方法 + +- **void put(E e)**:在count等于items长度时,一直等待,直到被其他线程唤醒。唤醒后调用enqueue方法放入队列 + +- **boolean offer(E e)**:offer方法在队列满了的时候返回false,否则调用enqueue方法插入元素,并返回true。 + + **enqueue**:方法首先把元素放在items的putIndex位置,接着判断在putIndex+1等于队列的长度时把putIndex设置为0,也就是上面提到的圆环的index操作。最后唤醒等待获取元素的线程。 + +- **boolean offer(E e, long timeout, TimeUnit unit)**:只是在offer(E e)的基础上增加了超时时间的概念 + + + +**出队方法** + +ArrayBlockingQueue提供了多种出队操作的实现来满足不同情况下的需求,如下: + +- **E poll()**:poll方法是非阻塞方法,如果队列没有元素返回null,否则调用dequeue把队首的元素出队列。 + + **dequeue**:会根据takeIndex获取到该位置的元素,并把该位置置为null,接着利用圆环原理,在takeIndex到达列表长度时设置为0,最后唤醒等待元素放入队列的线程。 + +- **E poll(long timeout, TimeUnit unit)**:该方法是poll()的可配置超时等待方法,和上面的offer一样,使用while循环+Condition的awaitNanos来进行等待,等待时间到后执行dequeue获取元素 + +- **E take()**: + + + +**获取元素方法** + +- **peek()**:这里获取元素时上锁是为了避免脏数据的产生 + + + +**删除元素方法** + +- **remove(Object o)**:从takeIndex一直遍历到putIndex,直到找到和元素o相同的元素,调用removeAt进行删除。removeAt(): + - 当removeIndex == takeIndex时就不需要后面的元素整体往前移了,而只需要把takeIndex的指向下一个元素即可(还记得前面说的ArrayBlockingQueue可以类比为圆环吗) + - 当removeIndex != takeIndex时,通过putIndex将removeIndex后的元素往前移一位 + + + +#### LinkedBlockingQueue + +**LinkedBlockingQueue是一个用链表实现的有界阻塞队列**。此队列的默认和最大长度为`Integer.MAX_VALUE`,也就是无界队列,所以为了避免队列过大造成机器负载或者内存爆满的情况出现,我们在使用的时候建议手动传一个队列的大小。此队列按照先进先出的原则对元素进行排序。 + +LinkedBlockingQueue是一个阻塞队列,内部由两个ReentrantLock来实现出入队列的线程安全,由各自的Condition对象的await和signal来实现等待和唤醒功能。 + + + +**LinkedBlockingQueue和ArrayBlockingQueue的不同点** + +- 队列大小有所不同,ArrayBlockingQueue是有界的初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题 +- 数据存储容器不同,ArrayBlockingQueue采用的是数组作为数据存储容器,而LinkedBlockingQueue采用的则是以Node节点作为连接对象的链表 +- 由于ArrayBlockingQueue采用的是数组的存储容器,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象。这可能在长时间内需要高效并发地处理大批量数据的时,对于GC可能存在较大影响 +- 两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReenterLock锁,而LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能 + + + +**入队方法** + +LinkedBlockingQueue提供了多种入队操作的实现来满足不同情况下的需求,入队操作有如下几种: + +- **void put(E e)**: + - 队列已满,阻塞等待 + - 队列未满,创建一个node节点放入队列中,如果放完以后队列还有剩余空间,继续唤醒下一个添加线程进行添加。如果放之前队列中没有元素,放完以后要唤醒消费线程进行消费 +- **boolean offer(E e)**:offer仅仅对put方法改动了一点点,当队列没有可用元素的时候,不同于put方法的阻塞等待,offer方法直接方法false +- **boolean offer(E e, long timeout, TimeUnit unit)**:该方法只是对offer方法进行了阻塞超时处理,使用了Condition的awaitNanos来进行超时等待。为什么要用while循环?因为awaitNanos方法是可中断的,为了防止在等待过程中线程被中断,这里使用while循环进行等待过程中中断的处理,继续等待剩下需等待的时间 + + + +**出队方法** + +入队列的方法说完后,我们来说说出队列的方法。LinkedBlockingQueue提供了多种出队操作的实现来满足不同情况下的需求,如下: + +- **E take()**: + - 队列为空,阻塞等待 + - 队列不为空,从队首获取并移除一个元素,如果消费后还有元素在队列中,继续唤醒下一个消费线程进行元素移除。如果放之前队列是满元素的情况,移除完后要唤醒生产线程进行添加元素 +- **E poll()**:poll方法去除了take方法中元素为空后阻塞等待 +- **E poll(long timeout, TimeUnit unit)**:利用了Condition的awaitNanos方法来进行阻塞等待直至超时 + + + +**获取元素方法** + +- **peek()**:加锁获取。枷锁后获取到head节点的next节点,如果为空返回null,如果不为空,返回next节点的item值 + + + +**删除元素方法** + +- **remove(Object o)**:因为remove方法使用两个锁(put锁和take锁)全部上锁,所以其它操作都需要等待它完成,而该方法需要从head节点遍历到尾节点,所以时间复杂度为O(n) + + + +#### PriorityBlockingQueue + +**PriorityBlockingQueue是一个支持优先级的无界队列**。默认情况下元素采取自然顺序排列,也可以通过比较器comparator来指定元素的排序规则。元素按照升序排列。 + + + +#### SynchronousQueue + +**SynchronousQueue是一个不存储元素的阻塞队列**。每一个put操作必须等待一个take操作,否则不能继续添加元素。SynchronousQueue可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素,非常适合于传递性场景,比如在一个线程中使用的数据,传递给另外一个线程使用,SynchronousQueue的吞吐量高于LinkedBlockingQueue 和 ArrayBlockingQueue。 + + + +#### DelayQueue + +**DelayQueue是一个支持延时获取元素的无界阻塞队列**。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。我们可以将DelayQueue运用在以下应用场景: + +- 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。 +- 定时任务调度。使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,从比如TimerQueue就是使用DelayQueue实现的。 + + + +### BlockingDeque + +**BlockingDeque(阻塞双端队列)**在Deque的基础上实现了双端阻塞等待的功能。和第2节说的类似,BlockingDeque也提供了双端队列该有的阻塞等待方法: + +- putFirst(E e):在队首插入元素,如果队列满了,阻塞等待,直到被中断为止。 +- putLast(E e):在队尾插入元素,如果队列满了,阻塞等待,直到被中断为止。 +- offerFirst(E e, long timeout, TimeUnit unit):向队首插入元素。如果队列满了,阻塞等待timeout个时长,如果到了超时时间还没有空间,抛弃该元素。 +- offerLast(E e, long timeout, TimeUnit unit):向队尾插入元素。如果队列满了,阻塞等待timeout个时长,如果到了超时时间还没有空间,抛弃该元素。 +- takeFirst():获取并移除队首的元素。如果队列为空,阻塞等待,直到被中断为止。 +- takeLast():获取并移除队尾的元素。如果队列为空,阻塞等待,直到被中断为止。 +- pollFirst(long timeout, TimeUnit unit):获取并移除队首的元素。如果队列为空,阻塞等待timeout个时长,如果到了超时时间还没有元素,则返回null。 +- pollLast(long timeout, TimeUnit unit):获取并移除队尾的元素。如果队列为空,阻塞等待timeout个时长,如果到了超时时间还没有元素,则返回null。 +- removeFirstOccurrence(Object o):从队首开始移除第一个和o相等的元素。 +- removeLastOccurrence(Object o):从队尾开始移除第一个和o相等的元素。 + + + +#### LinkedBlockingDeque + +**LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列**,即可以从队列的两端插入和移除元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。`LinkedBlockingDeque`是可选容量的,在初始化时可以设置容量防止其过度膨胀,如果不设置,默认容量大小为`Integer.MAX_VALUE`。 + +相比于其它阻塞队列,LinkedBlockingDeque多了addFirst、addLast、peekFirst、peekLast等方法,以first结尾的方法,表示插入、获取获移除双端队列的第一个元素。以last结尾的方法,表示插入、获取获移除双端队列的最后一个元素。 + + + +**LinkedBlockingDeque和LinkedBlockingQueue的相同点** + +- 基于链表 +- 容量可选,不设置的话,就是Int的最大值 + + + +**LinkedBlockingDeque和LinkedBlockingQueue的不同点** + +- 双端链表和单链表 +- 不存在头节点 +- 一把锁+两个条件 + + + +**入队方法** + +LinkedBlockingDeque提供了多种入队操作的实现来满足不同情况下的需求,入队操作有如下几种: + +- add(E e)、addFirst(E e)、addLast(E e) +- offer(E e)、offerFirst(E e)、offerLast(E e) +- offer(E e, long timeout, TimeUnit unit)、offerFirst(E e, long timeout, TimeUnit unit)、offerLast(E e, long timeout, TimeUnit unit) +- put(E e)、putFirst(E e)、putLast(E e) + + + +**出队方法** + +入队列的方法说完后,我们来说说出队列的方法。LinkedBlockingDeque提供了多种出队操作的实现来满足不同情况下的需求,如下: + +- **remove()、removeFirst()、removeLast()** +- **poll()、pollFirst()、pollLast()** +- **take()、takeFirst()、takeLast()** +- **poll(long timeout, TimeUnit unit)、pollFirst(long timeout, TimeUnit unit)、pollLast(long timeout, TimeUnit unit)** + + + +**获取元素方法** + +获取元素前加锁,防止并发问题导致数据不一致。利用first和last节点直接可以获得元素。 + +- **element()** +- **peek()** + + + +**删除元素方法** + +删除元素是从头/尾向两边进行遍历比较,故时间复杂度为O(n),最后调用unlink把要移除元素的prev和next进行关联,把要移除的元素从链中脱离,等待下次GC回收。 + +- **remove(Object o)**: + + + +### TransferQueue + +TransferQueue是JDK 1.7对于并发类库新增加的一个接口,它扩展自BlockingQueue,所以保持着阻塞队列的所有特性。 + +TransferQueue对比与BlockingQueue更强大的一点是,生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费(不仅仅是添加到队列里就完事)。新添加的transfer方法用来实现这种约束。顾名思义,阻塞就是发生在元素从一个线程transfer到另一个线程的过程中,它有效地实现了元素在线程之间的传递(以建立Java内存模型中的happens-before关系的方式)。 + + + +该接口提供的标准方法: + +- tryTransfer(E e):若当前存在一个正在等待获取的消费者线程(使用take()或者poll()函数),使用该方法会即刻转移/传输对象元素e并立即返回true;**若不存在,则返回false,并且不进入队列。这是一个不阻塞的操作** +- transfer(E e):若当前存在一个正在等待获取的消费者线程,即立刻移交之;**否则,会插入当前元素e到队列尾部,并且等待进入阻塞状态,到有消费者线程取走该元素** +- tryTransfer(E e, long timeout, TimeUnit unit):若当前存在一个正在等待获取的消费者线程,会立即传输给它;**否则将插入元素e到队列尾部,并且等待被消费者线程获取消费掉;若在指定的时间内元素e无法被消费者线程获取,则返回false,同时该元素被移除** +- hasWaitingConsumer():判断是否存在消费者线程 +- getWaitingConsumerCount():获取所有等待获取元素的消费线程数量 + + + +#### LinkedTransferQueue + +LinkedTransferQueue 是**单向链表结构的无界阻塞队列**。 + +LinkedTransferQueue(LTQ) 相比 BlockingQueue 更进一步,**生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费(不仅仅是添加到队列里就完事)**。新添加的 transfer 方法用来实现这种约束。顾名思义,阻塞就是发生在元素从一个线程 transfer 到另一个线程的过程中,它有效地实现了元素在线程之间的传递(以建立 Java 内存模型中的 happens-before 关系的方式)。**Doug Lea 说从功能角度来讲,LinkedTransferQueue 实际上是 ConcurrentLinkedQueue、SynchronousQueue(公平模式)和 LinkedBlockingQueue 的超集。**而且 LinkedTransferQueue 更好用,因为它不仅仅综合了这几个类的功能,同时也提供了更高效的实现。 + + + +# Thread + +**什么是进程 ?** + +进程是**资源分配的最小单位**。(资源包括各种表格、内存空间、磁盘空间) 同一进程中的多条线程将共享该进程中的全部系统资源。 + +**什么是线程 ?** + +线程是**CPU调度的最小单位**。线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表组成。 而寄存器可被用来存储线程内的局部变量。 + +**什么是并行和并发 ?** + +- **并行运行**:总线程数≤CPU数量×核心数 +- **并发运行**:总线程数>CPU数量×核心数(如:有的操作系统CPU线程切换之间用的时间片轮转进程调度算法) + + + +**线程优缺点** + +- **优点** + - 创建一个新线程的代价要比创建一个新进程小的多 + - 线程之间的切换相较于进程之间的切换需要操作系统做的工作很少 + - 线程占用的资源要比进程少很多 + - 能充分利用多处理器的可并行数量 + - 等待慢速IO操作结束以后,程序可以继续执行其他的计算任务 + - 计算(CPU)密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现 + - IO密集型应用,为了提高性能,将IO操作重叠,线程可以等待不同的IO操作 + +- **缺点** + - 性能损失 + - 健壮性降低 + - 缺乏访问控制 + - 编程难度提高 + + + +## 线程实现方式 + +**实现线程**只有一种方式: + +- **new Thread()** + + + +**实现线程执行内容**有两种方式: + +- **继续Thread类**:Thread实现了Runable接口 +- **实现Runable接口**:new Thread(new Runable(){……}),本质是通过Thread的run()进行调用触发 + + + +更多实现线程执行内容的方式,只需在此基础上进行封装: + +- **线程池创建线程**:本质是通过 new Thread() 的方式实现 +- **有返回值的Callable创建线程**:需要提交到线程池中执行。本质是通过实例化Thread的方式实现 +- **定时器Timer**:本质是继承自 Thread 类实现 + + + +**Thread、Runnable和Callable的区别** + +- Runnable相对于Thread的优势是:**避免单继承**的局限,适合于**资源共享**场景 +- Thread使用JNI调用(native修饰的start0方法)系统函数来完成start,Runnable则由JVM来实现start,Callable也需要调用Thread.start()启动线程 +- 一般情况,多线程中优先选择实现Runnable接口 +- Callable能返回任务线程执行结果,而Runable不能返回 +- Callable的call方法允许抛出异常,而Runable异常只能在run方法内部消化 + + + +**Thread.sleep(0)的作用是什么?** + +由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。 + + + +**wait()和sleep()的区别?** + +- wait()来自Object ,sleep()来自Thread +- 调用wait()时线程会释放锁,调用sleep()时线程不会释放对象锁(只是暂时让出CPU的执行权) +- wait()只能在同步控制方法或者同步控制块中使用,sleep()可以在任何地方使用 +- wait()可以通过notify()或notifyAll()被结束 ,sleep()只能等待休眠时间到期后才结束 + + + +## 线程创建方式 + +- 继承Thread类 +- 实现Runnable接口 +- ExecutorService、Callable、Future有返回值线程 +- 基于线程池的方式 + + + +## 线程生命周期 + +就像生物从出生到长大、最终死亡的过程一样,线程也有自己的生命周期,在 Java 中线程的生命周期中一共有 6 种状态。 + +- New(新创建) +- Runnable(可运行) +- Blocked(被阻塞) +- Waiting(等待) +- Timed Waiting(计时等待) +- Terminated(被终止) + +如果想要确定线程当前的状态,可以通过 getState() 方法,并且线程在任何时刻只可能处于 1 种状态。线程状态切换: + +![线程状态切换](images/JAVA/线程状态切换.jpg) + + + +### 新建状态(NEW) + +![Thread-NEW](images/JAVA/Thread-NEW.png) + +New 表示线程被创建但尚未启动的状态:当我们用 new Thread() 新建一个线程时,如果线程没有开始运行 start() 方法,所以也没有开始执行 run() 方法里面的代码,那么此时它的状态就是 New。而一旦线程调用了 start(),它的状态就会从 New 变成 Runnable,也就是状态转换图中中间的这个大方框里的内容。 + + + +### 可运行状态(RUNNABLE) + +![Thread-RUNNABLE](images/JAVA/Thread-RUNNABLE.png) + +Java 中的 Runable 状态对应操作系统线程状态中的两种状态,分别是 Running 和 Ready,也就是说,Java 中处于 Runnable 状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配 CPU 资源。 + +所以,如果一个正在运行的线程是 Runnable 状态,当它运行到任务的一半时,执行该线程的 CPU 被调度去做其他事情,导致该线程暂时不运行,它的状态依然不变,还是 Runnable,因为它有可能随时被调度回来继续执行任务。 + + + +### 被阻塞状态(BLOCKED) + +![Thread-BLOCKED](images/JAVA/Thread-BLOCKED.png) + +首先来看最简单的 Blocked,从箭头的流转方向可以看出,从 Runnable 状态进入 Blocked 状态只有一种可能,就是进入 synchronized 保护的代码时没有抢到 monitor 锁,无论是进入 synchronized 代码块,还是 synchronized 方法,都是一样。当处于 Blocked 的线程抢到 monitor 锁,就会从 Blocked 状态回到Runnable 状态。 + + + +### 等待状态(WAITING) + +![Thread-WAITING](images/JAVA/Thread-WAITING.png) + +线程进入 Waiting 状态有三种可能性: + +- 没有设置 Timeout 参数的 Object.wait() 方法 +- 没有设置 Timeout 参数的 Thread.join() 方法 +- LockSupport.park() 方法 + +Blocked 仅仅针对 synchronized monitor 锁,可是在 Java 中还有很多其他的锁,比如 ReentrantLock,如果线程在获取这种锁时没有抢到该锁就会进入 Waiting 状态,因为本质上它执行了 LockSupport.park() 方法,所以会进入 Waiting 状态。同样,Object.wait() 和 Thread.join() 也会让线程进入 Waiting 状态。 + +Blocked 与 Waiting 的区别是 Blocked 在等待其他线程释放 monitor 锁,而 Waiting 则是在等待某个条件,比如 join 的线程执行完毕,或者是 notify()/notifyAll() 。 + + + +### 计时等待状态(TIMED_WAITING) + +![Thread-TIMED_WAITING](images/JAVA/Thread-TIMED_WAITING.png) + +在 Waiting 上面是 Timed Waiting 状态,这两个状态是非常相似的,区别仅在于有没有时间限制,Timed Waiting 会等待超时,由系统自动唤醒,或者在超时前被唤醒信号唤醒。以下情况会让线程进入 Timed Waiting 状态。 + +- 设置了时间参数的 Thread.sleep(long millis) 方法 +- 设置了时间参数的 Object.wait(long timeout) 方法 +- 设置了时间参数的 Thread.join(long millis) 方法 +- 设置了时间参数的 LockSupport.parkNanos(long nanos) 方法和 LockSupport.parkUntil(long deadline) 方法 + + + +### 已终止状态(TERMINATED) + +![Thread-TERMINATED](images/JAVA/Thread-TERMINATED.png) + +线程会以下面三种方式结束,结束后就是终止状态: + +- **正常结束**:run()或 call()方法执行完成,线程正常结束 + +- **异常结束**:线程抛出一个未捕获的 Exception 或 Error + +- **调用** **stop**:直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用 + + + +## JDK线程池 + +### newCachedThreadPool + +创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。 + + + +### newFixedThreadPool + +创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。 + + + +### newScheduledThreadPool + +创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 + + + +### newSingleThreadExecutor + +Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去。 + + + +## 线程方法 + +### 线程等待(wait) + +调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,需要注意的是调用 wait()方法后,会释放对象的锁。因此,wait 方法一般用在同步方法或同步代码块中。 + + + +### 线程睡眠(sleep) + +sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占有的锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法会导致当前线程进入 WATING 状态。 + + + +### 线程让步(yield) + +yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对线程优先级并不敏感。 + + + +### 线程中(interrupt) + +中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)。 + + + +### 等待其他线程终止(join) + +join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu 的宠幸。 + + + +### 线程唤醒(notify) + +Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。 + + + +## 线程安全 + +线程安全是指当多个线程访问某个类时,该类始终都表现正确行为,则称该类是线程安全的。 + + + +**出现线程安全问题的原因?** + +在多个线程并发环境下,多个线程共同访问同一共享内存资源时,其中一个线程对资源进行写操作的中途(写⼊入已经开始,但还没结束),其他线程对这个写了一半的资源进⾏了读操作,或者对这个写了一半的资源进⾏了写操作,导致此资源出现数据错误。 + + + +**如何避免线程安全问题?** + +- 保证共享资源在同一时间只能由一个线程进行操作(原子性,有序性) +- 将线程操作的结果及时刷新,保证其他线程可以立即获取到修改后的最新数据(可见性) + + + +## 线程同步 + +多个线程操作一个资源的情况下,导致资源数据前后不一致。这样就需要协调线程的调度,即线程同步。线程同步的方式: + +- **同步方法(synchronized )** +- **同步代码块(synchronized )** +- **使用特殊域变量(volatile)实现线程同步** +- **使用重入锁(ReentrantLock)实现线程同步** +- **使用局部变量(ThreadLocal)实现线程同步** +- **使用阻塞队列(LinkedBlockingQueue)实现线程同步** +- **使用原子变量(AtomicXxx)实现线程同步** + + + +## 多线程通信 + +多线程通讯的方式主要包括以下几种: + +- **使用volatile关键词:基于共享内存的思想** +- **使用Synchronized+Object类的wait()/notify()/notifyAll()方法** +- **使用JUC工具类CountDownLatch:基于共享变量state实现** +- **使用Lock(ReentrantLock)结合Condition** +- **基于LockSupport实现线程间的阻塞和唤醒** + + + +## 线程协作 + +### sleep/yield/join + +**sleep()** + + - 让当前线程暂停指定的时间(毫秒) + - wait方法依赖于同步,而sleep方法可以直接调用 + - sleep方法只是暂时让出CPU的执行权,并不释放锁,而wait方法则需要释放锁 + +**yield()** + + - 暂停当前线程,让出当前CPU的使用权,以便其它线程有机会执行 + - 不能指定暂停的时间,并且也不能保证当前线程马上停止 + - 会让当前线程从运行状态转变为就绪状态 + - yield只能使同优先级或更高优先级的线程有执行的机会 + +**join()** + + - 等待调用 join 方法的线程执行结束,才执行后面的代码 + - 其调用一定要在 start 方法之后(看源码可知) + - 作用是父线程等待子线程执行完成后再执行(即将异步执行的线程合并为同步的线程) + + + +### wait/notify/notifyAll + +一般需要配合**synchronized**一起使用。**Object**的主要方法如下: + +- **wait()**:阻塞当前线程,直到 notify 或者 notifyAll 来唤醒 +- **notify()**:只能唤醒一个处于 wait 的线程 +- **notifyAll()**:唤醒全部处于 wait 的线程 + + + +### await/signal/signalAll + +使用显式的 **Lock** 和 **Condition** 对象: + +- **await()**:当前线程进入等待状态,直到被通知(signal/signalAll)、中断或超时 +- **signal()**:唤醒一个等待在Condition上的线程,将该线程从**等待队列**中转移到**同步队列**中 + +- **signalAll()**:能够唤醒所有等待在Condition上的线程 + + + +## 线程死锁 + +**造成死锁的原因?** + +多个线程竞争共享资源,由于资源被占用、资源不足或进程推进顺序不当等原因造成线程处于永久阻塞状态,从而引发死锁。死锁形成的原因: + +- 系统资源不足 +- 进程(线程)推进的顺序不恰当 +- 资源分配不当 + + + +**死锁的解决办法?** + +- 让程序每次至多只能获得一个锁(多线程环境中并不现实) +- 设计时考虑清楚锁的顺序,尽量减少嵌套加锁和交互数量 +- 设置锁等待时间上限 + + + +## 守护线程 + +守护线程(daemon=true):当线程只剩下守护线程的时候,JVM就会退出;但是如果还有其它的任意一个用户线程还在,JVM就不会退出。在Java中有两类线程:**User Thread(用户线程)、Daemon Thread(守护线程)** + +- thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常 +- 在Daemon线程中产生的新线程也是Daemon的 +- 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑 + + + +## 常见问题 + +**sleep与wait区别** + +- 来源不同:**wait** 来自**Object**,**sleep** 来自 **Thread** +- 是否释放锁:**wait** 释放锁,**sleep** 不释放 +- 使用范围:**wait** 必须在同步代码块中,**sleep** 可以任意使用 +- 捕捉异常:**wait** 不需要捕获异常,**sleep** 需捕获异常 + + + +**start与run区别** + +- start()方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码 +- 通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行 +- 方法 run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行 run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程 + + + +**yield跟sleep区别** + +- **yield** 跟 **sleep** 都能暂停当前线程,都**不会释放锁资源**,**sleep** 可以指定具体休眠的时间,而 **yield** 则依赖 **CPU** 的时间片划分 +- **sleep**方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会。**yield**方法只会给相同优先级或更高优先级的线程以运行的机会 +- 调用 **sleep** 方法使线程进入**等待状态**,等待休眠时间达到,而调用我们的 **yield**方法,线程会进入**就绪状态**,也就是**sleep**需要等待设置的时间后才会进行**就绪状态**,而**yield**会立即进入**就绪状态** +- **sleep**方法声明会抛出 **InterruptedException**,而 **yield** 方法没有声明任何异常 +- **yield** 不能被中断,而 **sleep** 则可以接受中断 +- **sleep**方法比**yield**方法具有更好的移植性(跟操作系统CPU调度相关) + + + +## ThreadLocal + +把ThreadLocal看成一个全局Map,每个线程获取ThreadLocal变量时,总是**使用Thread自身作为key**。相当于给每个线程都开辟了一个独立的存储空间,**各个线程的ThreadLocal关联的实例互不干扰**。 + +- ThreadLocal表示线程的"局部变量",它确保每个线程的ThreadLocal变量都是各自独立的 +- ThreadLocal适合在一个线程的处理流程中保持上下文(避免了同一参数在所有方法中传递) +- 使用ThreadLocal要用try ... finally结构,并在finally中清除 + + + +ThreadLocal常用的方法 + +- set:为当前线程设置变量,当前ThreadLocal作为索引 +- get:获取当前线程变量,当前ThreadLocal作为索引 +- initialValue(钩子方法需要子类实现):赖加载形式初始化线程本地变量,执行get时,发现线程本地变量为null,就会执行initialValue的内容 +- remove:清空当前线程的ThreadLocal索引与映射的元素 + + + +### InheritableThreadLocal + +`InheritableThreadLocal` 是 JDK 本身自带的一种线程传递解决方案。顾名思义,由当前线程创建的线程,将会继承当前线程里 ThreadLocal 保存的值。 + +`JDK`的`InheritableThreadLocal`类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的`ThreadLocal`值传递已经没有意义,应用需要的实际上是把 **任务提交给线程池时**的`ThreadLocal`值传递到 **任务执行时**。 + + + +### TransmittableThreadLocal + +**TransmittableThreadLocal** 是Alibaba开源的、用于解决 **“在使用线程池等会缓存线程的组件情况下传递ThreadLocal”** 问题的 InheritableThreadLocal 扩展。若希望 TransmittableThreadLocal 在线程池与主线程间传递,需配合 TtlRunnable 和 TtlCallable使用。 + + + +**使用场景** + +- 分布式跟踪系统 +- 应用容器或上层框架跨应用代码给下层SDK传递信息 +- 日志收集记录系统上下文 + + + +## ThreadPoolExecutor + +ThreadPoolExecutor是如何运行,如何同时维护线程和执行任务的呢?其运行机制如下图所示: + +![ThreadPoolExecutor运行流程](images/JAVA/ThreadPoolExecutor运行流程.png) + +线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。线程池的运行主要分成两部分:任务管理、线程管理。任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转:(1)直接申请线程执行该任务;(2)缓冲到队列中等待线程执行;(3)拒绝该任务。线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。 + + + +**线程池(Thread Pool)** + +线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中。 + +线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。当然,使用线程池可以带来一系列好处: + +- **降低资源消耗**:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗 +- **提高响应速度**:任务到达时,无需等待线程创建即可立即执行 +- **提高线程的可管理性**:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控 +- **提供更多更强大的功能**:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行 + + + +**线程池要解决的两个问题** + +- **解决频繁创建和销毁线程所产生的开销**。减少在创建和销毁线程上所花的时间以及系统资源的开销 +- **解决无限制创建线程引起系统崩溃**。不使用线程池,可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换” + + + +**多线程优缺点** + +- **多线程的优点** + - 资源利用率更好 + - 程序设计在某些情况下更简单 + - 程序响应更快 +- **多线程的缺点** + - 设计更复杂 + - 上下文切换的开销 + - 增加资源消耗 + + + +**线程池类型** + +JDK默认提供四种线程池: + +- **newCachedThreadPool**:可变尺寸的线程池 +- **newFixedThreadPool**:可重用的固定线程数的线程池 +- **newScheduledThreadPool**:定时以及周期性执行任务的线程池 +- **newSingleThreadExecutor**:单任务的线程池 + + + +### 重要参数 + +- **corePoolSize**(核心线程数,默认值为1) + + - 核心线程会一直存活,即使没有任务需要执行 + - 当线程数<核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理 + - 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭 + +- **maximumPoolSize**(最大线程数,默认值为Integer.MAX_VALUE) + + - 当线程数≥corePoolSize,且任务队列已满时,线程池会创建新线程来处理任务 + - 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常 + +- **keepAliveTime**(线程空闲时间,Single和Fixed默认值为0ms,Cached默认值为60s) + + - 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize + - 如果allowCoreThreadTimeout=true,则会直到线程数量=0 + +- **unit**(时间单位):用于设置keepAliveTime的时间单位 + +- **workQueue**(任务队列容量,阻塞队列,默认值为Integer.MAX_VALUE) + + - 当核心线程数达到最大时,新任务会放在队列中排队等待执行 + +- **threadFactory**( 新建线程工厂) + + - 通常用于线程命名 + - 设置线程守护(daemon) + +- **allowCoreThreadTimeout**:允许核心线程超时,默认值为false + +- **handler**(任务拒绝处理器,默认值为策略为AbortPolicy) + + 以下两种情况会拒绝处理任务: + + - 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务 + - 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务 + + + +### 线程池状态转换 + +下图为线程池的状态转换过程: +![ThreadPoolExecutor状态转换](images/JAVA/ThreadPoolExecutor状态转换.png) + +线程池最重要的5种状态: + +- **RUNNING**:能接受新提交的任务,并且也能处理阻塞队列中的任务 +- **SHUTDOWN**:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态) +- **STOP**:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态 +- **TIDYING**:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态 +- **TERMINATED**:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做 + + + +### execute销毁流程 + +**execute到线程销毁的整个流程图** +![ThreadPoolExecutor线程销毁流程](images/JAVA/ThreadPoolExecutor线程销毁流程.png) + + + +### 线程池的监控 + +通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用 + +- **getTaskCount**:线程池已经执行的和未执行的任务总数 +- **getCompletedTaskCount**:线程池已完成的任务数量,该值小于等于taskCount +- **getLargestPoolSize**:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过,也就是达到了maximumPoolSize +- **getPoolSize**:线程池当前的线程数量 +- **getActiveCount**:当前线程池中正在执行任务的线程数量 + + + +### 生命周期 + +hreadPoolExecutor的运行状态有5种,分别为: + +![生命周期状态](images/JAVA/生命周期状态.png) + +其生命周期转换如下入所示: + +![线程池生命周期](images/JAVA/线程池生命周期.png) + + + +### 任务调度 + +![ThreadPoolExecutor任务调度流程](images/JAVA/ThreadPoolExecutor任务调度流程.png) + +线程池分配线程时,其执行过程如下: + +- 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务 +- **当线程数<核心线程数(corePoolSize)**时,每次都创建新线程 +- **当线程数 ≥ 核心线程数(corePoolSize)**时 + - **任务队列(queueCapacity)未满** + - 将任务放入任务队列 + - **任务队列(queueCapacity)已满** + - **若线程数<最大线程数(maxPoolSize)**,则创建线程 + - **若线程数 = 最大线程数(maxPoolSize)**,则抛出异常而拒绝任务 + + + +### 任务缓冲 + +任务缓冲模块是线程池能够管理任务的核心部分。线程池的本质是对任务和线程的管理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。使用不同的队列可以实现不一样的任务存取策略。以下是阻塞队列的成员: + +![任务缓冲策略](images/JAVA/任务缓冲策略.png) + + + +### 任务申请 + +由上文的任务分配部分可知,任务的执行有两种可能:一种是任务直接由新创建的线程执行。另一种是线程从任务队列中获取任务然后执行,执行完任务的空闲线程会再次去从队列中申请任务再去执行。第一种情况仅出现在线程初始创建的时候,第二种是线程获取任务绝大多数的情况。 + +线程需要从任务缓存模块中不断地取任务执行,帮助线程从阻塞队列中获取任务,实现线程管理模块和任务管理模块之间的通信。这部分策略由getTask方法实现,其执行流程如下图所示: + +![获取任务流程图](images/JAVA/获取任务流程图.png) + +getTask这部分进行了多次判断,为的是控制线程的数量,使其符合线程池的状态。如果线程池现在不应该持有那么多线程,则会返回null值。工作线程Worker会不断接收新任务去执行,而当工作线程Worker接收不到任务的时候,就会开始被回收。 + + + +### 任务拒绝 + +任务拒绝模块是线程池的保护部分,线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。拒绝策略是一个接口,其设计如下: + +![任务拒绝](images/JAVA/任务拒绝.png) + + + +### 并发类型 + +**CPU密集型(CPU-bound)** + +CPU密集型(又称计算密集型),是指任务需要进行大量复杂的运算,几乎没有阻塞,需要CPU长时间高速运行。在多核CPU时代,我们要让每一个CPU核心都参与计算,将CPU的性能充分利用起来,这样才算是没有浪费服务器配置,如果在非常好的服务器配置上还运行着单线程程序那将是多么重大的浪费。对于计算密集型的应用,完全是靠CPU的核数来工作,所以为了让它的优势完全发挥出来,**避免过多的线程上下文切换**,比较理想方案是: + +`线程数 = CPU核数 + 1` + +JVM可运行的CPU核数可以通过`Runtime.getRuntime().availableProcessors()`查看。也可设置成CPU核数×2 ,这还是要看JDK的使用版本以及CPU配置(服务器的CPU有超线程)。对于JDK1.8来说,里面增加了一个并行计算,因此计算密集型的较理想: + +`线程数 = CPU内核线程数×2` + + + +**IO密集型(IO-bound)** + +对于IO密集型的应用,我们现在做的开发大部分都是WEB应用,涉及到大量的网络传输或磁盘读写,线程花费更多的时间在IO阻塞上,而不是CPU运算。如与数据库和缓存之间的交互也涉及到IO,一旦发生IO,线程就会处于等待状态,当IO结束且数据准备好后,线程才会继续执行。因此对于IO密集型的应用,我们可以多设置一些线程池中线程的数量(但不宜过多,因为线程上下文切换是有代价的),这样就能让在等待IO的这段时间内,线程可以去做其它事,提高并发处理效率。对于IO密集型应用: + +`线程数=CPU数/(1-阻塞系数)` + + `阻塞系数=线程等待时间/(线程等待时间+CPU处理时间) ` + +或 `线程数=CPU核数×2 + 1` + +这个阻塞系数一般为 **0.8~0.9** 之间,也可以取0.8或者0.9。套用公式,对于双核CPU来说,它比较理想的线程数就是:2÷(1-0.9)=20,当然这都不是绝对的,需要根据实际情况以及实际业务来调整。 + + + +### 参数计算 + +**目标假设** + +- **tasks** :每秒的任务数假设为**500~1000** +- **taskCost**:每个任务花费时间假设为**0.1s**,则 + - **corePoolSize**=TPS÷平均耗时 + - **1÷taskCost**:表示单个线程每秒的处理能力(处理数量) +- **responseTime**:系统允许容忍的最大响应时间,假设为**1s** + - **queueCapacity**=corePoolSize÷平均耗时×最大容忍耗时 + + + +**参数计算** + +- **每秒需要多少个线程处理(corePoolSize) = tasks ÷ (1 ÷ taskCost)** + + ① corePoolSize = tasks ÷ (1 ÷ taskCost) = (500~1000) ÷ (1 ÷ 0.1) = 50~100个线程 + + ② 根据2/8原则,如果每秒任务数80%都小于800,那么corePoolSize设置为80即可 + +- **线程池缓冲队列大小(queueCapacity) = (coreSizePool ÷ taskCost) × responseTime** + + ① queueCapacity = 80÷0.1×1 = 80,即队列里的线程可以等待1s,超过了的需要新开线程来执行 + + ② 禁止设置为Integer.MAX_VALUE,否则CPU Load会飙满,耗时会变长,内存也会OOM + +- **最大线程数(maximumPoolSize )= (Max(tasks) - queueCapacity) ÷ (1÷taskCost)** + + ① 计算可得 maximumPoolSize = (1000 - 80) ÷ 10 = 92 + +- rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则利用一些缓冲机制来处理 + +- keepAliveTime和allowCoreThreadTimeout采用默认通常能满足 + +- prestartAllCoreThreads:调用线程池的prestartAllCoreThreads方法,可以实现提前创建并启动好所有基本线程 + +**注意:** 以上都是理想值,实际情况下要根据机器性能来决定。如果在未达到最大线程数的情况机器CPU Load已经满了,则需要通过升级硬件和优化代码,降低taskCost来处理。 + + + +# Lock + +## synchronized + +synchronized是Java中的关键字,是利用锁的机制来实现同步的。synchronized是一种 **互斥锁**,它通过字节码指令monitorenter和monitorexist隐式的来使用lock和unlock操作,synchronized 具有 **原子性** 和 **可见性** 。 + +共享资源代码段又称为**临界区**(`critical section`),保证**临界区互斥**,是指执行**临界区**(`critical section`)的只能有一个线程执行,其他线程阻塞等待,达到排队效果。 + +![synchronized](images/JAVA/synchronized.png) + + + +**synchronize缺点** + +- 性能较低 +- 无法中断一个正在等候获得锁的线程 +- 无法通过投票得到锁,如果不想等下去,也就没办法得到锁 + + + +**synchronized和lock的区别** + +| compare | synchronized | lock | +| :------ | :----------------------- | :---------------------------------------- | +| 哪层面 | 虚拟机层面 | 代码层面 | +| 锁类型 | 可重入、不可中断、非公平 | 可重入、可中断、可公平 | +| 锁获取 | A线程获取锁,B线程等待 | 可以尝试获取锁,不需要一直等待 | +| 锁释放 | 由JVM 释放锁 | 在finally中手动释放。如不释放,会造成死锁 | +| 锁状态 | 无法判断 | 可以判断 | + + + +### Monitor + +在多线程的 JAVA程序中,实现线程之间的同步,就要说说 Monitor。 Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。下 面这个图,描述了线程和 Monitor之间关系,以 及线程的状态转换图: +![JAVA_Monitor](images/JAVA/JAVA_Monitor.jpg) + +- **进入区(Entrt Set)**:表示线程通过synchronized要求获取对象的锁。如果对象未被锁住,则进入拥有者;否则在进入等待区。一旦对象锁被其他线程释放,立即参与竞争 +- **拥有者(The Owner)**:表示某一线程成功竞争到对象锁 +- **等待区(Wait Set)** :表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒 + +从图中可以看出,一个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”,而在 “Wait Set”中等待的线程状态是 “in Object.wait()”。 先看 “Entry Set”里面的线程。我们称被 synchronized保护起来的代码段为临界区。当一个线程申请进入临界区时,它就进入了 “Entry Set”队列。 + + + +### 使用方式 + +`Synchronized` 的使用方式有三种: + +- **修饰普通函数**。监视器锁(`monitor`)便是对象实例(`this`) +- **修饰静态函数**。视器锁(`monitor`)便是对象的`Class`实例(每个对象只有一个`Class`实例) +- **修饰代码块**。监视器锁(`monitor`)是指定对象实例 + + + +**修饰普通函数** + +![synchronized-修饰普通函数](images/JAVA/synchronized-修饰普通函数.png) + +**修饰静态函数** + +![synchronized-修饰静态函数](images/JAVA/synchronized-修饰静态函数.png) + +**修饰代码块** + +![synchronized-修饰代码块](images/JAVA/synchronized-修饰代码块.png) + + + +### synchronized原理 + +![synchronized原理](images/JAVA/synchronized原理.png) + +![synchronized](images/JAVA/synchronized.jpg) + + + +### synchronized优化 + +`Jdk 1.5`以后对`Synchronized`关键字做了各种的优化,经过优化后`Synchronized`已经变得原来越快了,这也是为什么官方建议使用`Synchronized`的原因,具体的优化点如下: + +- **轻量级锁和偏向锁**:引入轻量级锁和偏向锁来减少重量级锁的使用 +- **适应性自旋(Adaptive Spinning)**:自旋成功,则下次自旋次数增加;自旋失败,则下次自旋次数减少 +- **锁粗化(Lock Coarsening)**:将多次连接在一起的加锁、解锁操作合并为一次,将多个连续锁扩展成一个范围更大的锁 +- **锁消除(Lock Elimination)**:锁消除即删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁 + + + +### 锁升级 + +`Java`中每个对象都拥有对象头,对象头由`Mark World` 、指向类的指针、以及数组长度三部分组成,本文,我们只需要关心`Mark World` 即可,`Mark World` 记录了对象的`HashCode`、分代年龄和锁标志位信息。 + +**Mark World简化结构** + +| 锁状态 | 存储内容 | 锁标记 | +| :------- | :------------------------------------------------------ | :----- | +| 无锁 | 对象的hashCode、对象分代年龄、是否是偏向锁(0) | 01 | +| 偏向锁 | 偏向线程ID、偏向时间戳、对象分代年龄、是否是偏向锁(1) | 01 | +| 轻量级锁 | 指向栈中锁记录的指针 | 00 | +| 重量级锁 | 指向互斥量(重量级锁)的指针 | 10 | + +锁的升级变化,体现在锁对象的对象头`Mark World`部分,也就是说`Mark World`的内容会随着锁升级而改变。`Java1.5`以后为了减少获取锁和释放锁带来的性能消耗,引入了**偏向锁**和**轻量级锁**,`Synchronized`的升级顺序是 「**无锁-->偏向锁-->轻量级锁-->重量级锁,只会升级不会降级**」 + + + +#### 偏向锁 +在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁,其目标就是在只有一个线程执行同步代码块时,降低获取锁带来的消耗,提高性能(可以通过J V M参数关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态)。线程执行同步代码或方法前,线程只需要判断对象头的Mark Word中线程ID与当前线程ID是否一致,如果一致直接执行同步代码或方法,具体流程如下 + +![synchronized-偏向锁](images/JAVA/synchronized-偏向锁.png) + +- 无锁状态,存储内容「是否为偏向锁(`0`)」,锁标识位`01` + - `CAS`设置当前线程ID到`Mark Word`存储内容中 + - 是否为偏向锁`0` => 是否为偏向锁`1` + - 执行同步代码或方法 +- 偏向锁状态,存储内容「是否为偏向锁(`1`)、线程ID」,锁标识位`01` + - 对比线程`ID`是否一致,如果一致执行同步代码或方法,否则进入下面的流程 + - 若不一致,`CAS`将`Mark Word`的线程`ID`设置为当前线程`ID`,设置成功,执行同步代码或方法,否则进入下面的流程 + - `CAS`设置失败,证明存在多线程竞争情况,触发撤销偏向锁,当到达全局安全点,偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后在安全点的位置恢复继续往下执行 + + + +#### 轻量级锁 +轻量级锁考虑的是竞争锁对象的线程不多,持有锁时间也不长的场景。因为阻塞线程需要C P U从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失,所以干脆不阻塞这个线程,让它自旋一段时间等待锁释放。 + +当前线程持有的锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。轻量级锁的获取主要有两种情况:① 当关闭偏向锁功能时;② 多个线程竞争偏向锁导致偏向锁升级为轻量级锁。 + +![synchronized-轻量级锁](images/JAVA/synchronized-轻量级锁.png) + +- 无锁状态,存储内容「是否为偏向锁(`0`)」,锁标识位`01` + - 关闭偏向锁功能时 + - `CAS`设置当前线程栈中锁记录的指针到`Mark Word`存储内容 + - 锁标识位设置为`00` + - 执行同步代码或方法 + - 释放锁时,还原来`Mark Word`内容 +- 轻量级锁状态,存储内容「线程栈中锁记录的指针」,锁标识位`00`(存储内容的线程是指"持有轻量级锁的线程") + - `CAS`设置当前线程栈中锁记录的指针到`Mark Word`存储内容,设置成功获取轻量级锁,执行同步块代码或方法,否则执行下面的逻辑 + - 设置失败,证明多线程存在一定竞争,线程自旋上一步的操作,自旋一定次数后还是失败,轻量级锁升级为重量级锁 + - `Mark Word`存储内容替换成重量级锁指针,锁标记位`10` + + + +#### 重量级锁 + +轻量级锁膨胀之后,就升级为重量级锁,重量级锁是依赖操作系统的MutexLock(互斥锁)来实现的,需要从用户态转到内核态,这个成本非常高,这就是为什么Java1.6之前Synchronized效率低的原因。 + +升级为重量级锁时,锁标志位的状态值变为10,此时Mark Word中存储内容的是重量级锁的指针,等待锁的线程都会进入阻塞状态,下面是简化版的锁升级过程。 + +![synchronized-重量级锁](images/JAVA/synchronized-重量级锁.png) + + + +## ReentrantLock + +ReentrantLock的底层是借助AbstractQueuedSynchronizer实现,所以其数据结构依附于AbstractQueuedSynchronizer的数据结构,关于AQS的数据结构,在前一篇已经介绍过,不再累赘。 + +- ReentrantLock是一个可重入的互斥锁,又被称为“独占锁” +- ReentrantLock锁在同一个时间点只能被一个线程锁持有;可重入表示,ReentrantLock锁可以被同一个线程多次获取 +- ReentraantLock是通过一个FIFO的等待队列来管理获取该锁所有线程的。在“公平锁”的机制下,线程依次排队获取锁;而“非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁 + + + +**ReentrantLock和synchronized比较** + +- synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活 +- synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁 +- synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断 + + + +## ReentrantReadWriteLock + +**数据结构** + +![ReentrantReadWriteLock数据结构](images/JAVA/ReentrantReadWriteLock数据结构.jpg) + +- ReentrantReadWriteLock实现了ReadWriteLock接口。ReadWriteLock是一个读写锁的接口,提供了"获取读锁的readLock()函数" 和 "获取写锁的writeLock()函数" +- ReentrantReadWriteLock中包含:sync对象,读锁readerLock和写锁writerLock。读锁ReadLock和写锁WriteLock都实现了Lock接口。读锁ReadLock和写锁WriteLock中也都分别包含了"Sync对象",它们的Sync对象和ReentrantReadWriteLock的Sync对象 是一样的,就是通过sync,读锁和写锁实现了对同一个对象的访问 +- 和"ReentrantLock"一样,sync是Sync类型;而且,Sync也是一个继承于AQS的抽象类。Sync也包括"公平锁"FairSync和"非公平锁"NonfairSync。sync对象是"FairSync"和"NonfairSync"中的一个,默认是"NonfairSync" + + + +## 锁的状态 + +- 锁的4种状态:无锁、偏向锁、轻量级锁和重量级锁 +- 锁状态只能升级不能降级 +- 锁的状态是通过对象监视器在对象头中的字段来表明的 + + + +**锁的升级流程** + +- **偏向锁:** 指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价 +- **轻量级锁:** 指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其它线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能 +- **重量级锁:** 指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其它申请的线程进入阻塞,性能降低 + + + +**Java对象头** + +Hotspot虚拟机的对象头主要包括两部分数据(synchronized的锁就是存在Java对象头中): + +- **Mark Word(标记字段)**:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化 +- **Klass Point(类型指针)**:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例 + + + +**Monitor** + +- Monitor可以理解为一个同步工具或一种同步机制 +- synchronized通过Monitor来实现线程同步 +- Monitor是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的线程同步 +- 每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁 +- Monitor是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表 + + + +**锁状态升级流程** + +![锁状态升级流程](images/JAVA/锁状态升级流程.png) + +- **偏向锁**:通过对比Mark Word解决加锁问题,避免执行CAS操作 +- **轻量级锁**:通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能 +- **重量级锁**:将除了拥有锁的线程以外的线程都阻塞 + + + +**优缺点对比** + +| 锁 | 优点 | 缺点 | 适用场景 | +| :------- | :----------------------------------------------------------- | :--------------------------------------------- | :--------------------------------- | +| 偏向锁 | 加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距 | 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 | 适用于只有一个线程访问同步块的场景 | +| 轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度 | 如果始终得不到锁竞争的线程,使用自旋会消耗CPU | 追求响应时间同步块执行速度非常快 | +| 重量级锁 | 线程竞争不使用自旋,不会消耗CPU | 线程阻塞,响应时间缓慢 | 追求吞吐量,同步块执行速度较长 | + + + +### 无锁 + +- 无锁是指没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功 +- 修改操作在循环内进行,线程会不断的尝试修改共享资源 +- CAS即是无锁的实现 +- 无锁无法全面代替有锁,但无锁在某些场合下的性能是非常高的 + + + +### 偏向锁 + +指同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。适用于只有一个线程访问同步块场景。 + +- **优点**:加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距 +- **缺点**:如果线程间存在锁竞争,会带来额外的锁撤销的消耗 + + + +### 轻量级锁 + +指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其它线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。适用于追求响应时间,同步块执行速度非常快。 + +- **优点**:竞争的线程不会阻塞,提高了程序的响应速度 +- **缺点**:如果始终得不到锁竞争的线程使用自旋会消耗CPU + + + +### 重量级锁 + +指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其它申请的线程进入阻塞,性能降低。适用于追求吞吐量,同步块执行速度较长。 + +- **优点**:线程竞争不使用自旋,不会消耗CPU +- **缺点**:线程阻塞,响应时间缓慢 + + + +## 自旋锁(SpinLock) + +**指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环**。普通自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。其特点如下: + +- 自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短 +- 阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间 +- 如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长 + +![自旋锁(SpinLock)](images/JAVA/自旋锁(SpinLock).png) + +简单代码实现: + +```java +public class SpinLock { + private AtomicReference owner = new AtomicReference(); + public void lock() { + Thread currentThread = Thread.currentThread(); + // 如果锁未被占用,则设置当前线程为锁的拥有者 + while (owner.compareAndSet(null, currentThread)) { + } + } + + public void unlock() { + Thread currentThread = Thread.currentThread(); + // 只有锁的拥有者才能释放锁 + owner.compareAndSet(currentThread, null); + } +} +``` + +**缺点** + +- CAS操作需要硬件的配合 +- 保证各个CPU的缓存(L1、L2、L3、跨CPU Socket、主存)的数据一致性,通讯开销很大,在多处理器系统上更严重 +- 没法保证公平性,不保证等待进程/线程按照FIFO顺序获得锁 + + + +### Ticket Lock + +TicketLock即为排队自旋锁。思路:类似银行办业务,先取一个号,然后等待叫号叫到自己。好处:保证FIFO,先取号的肯定先进入。而普通的SpinLock,大家都在转圈,锁释放后谁刚好转到这谁进入。简单的实现: + +```java +public class TicketLock { + private AtomicInteger serviceNum = new AtomicInteger(); // 服务号 + private AtomicInteger ticketNum = new AtomicInteger(); // 排队号 + + public int lock() { + // 首先原子性地获得一个排队号 + int myTicketNum = ticketNum.getAndIncrement(); + + // 只要当前服务号不是自己的就不断轮询 + while (serviceNum.get() != myTicketNum) { + } + + return myTicketNum; + } + + public void unlock(int myTicket) { + // 只有当前线程拥有者才能释放锁 + int next = myTicket + 1; + serviceNum.compareAndSet(myTicket, next); + } +} +``` + +**缺点** + +Ticket Lock 虽然解决了公平性的问题,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量,每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能。 + + + +### MCS Lock + +MCS SpinLock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。 + +```java +public class MCSLock { + public static class MCSNode { + MCSNode next; + boolean isLocked = true; // 默认是在等待锁 + } + + volatile MCSNode queue ;// 指向最后一个申请锁的MCSNode + private static final AtomicReferenceFieldUpdater UPDATER = + AtomicReferenceFieldUpdater.newUpdater(MCSLock.class, MCSNode. class, "queue" ); + + public void lock(MCSNode currentThread) { + MCSNode predecessor = UPDATER.getAndSet(this, currentThread);// step 1 + if (predecessor != null) { + predecessor.next = currentThread;// step 2 + while (currentThread.isLocked ) {// step 3 + } + } + } + + public void unlock(MCSNode currentThread) { + if ( UPDATER.get( this ) == currentThread) {// 锁拥有者进行释放锁才有意义 + if (currentThread.next == null) {// 检查是否有人排在自己后面 + if (UPDATER.compareAndSet(this, currentThread, null)) {// step 4 + // compareAndSet返回true表示确实没有人排在自己后面 + return; + } else { + // 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者 + // 这里之所以要忙等是因为:step 1执行完后,step 2可能还没执行完 + while (currentThread.next == null) { // step 5 + } + } + } + + currentThread.next.isLocked = false; + currentThread.next = null;// for GC + } + } +} +``` + + + +### CLH Lock + +CLH(Craig, Landin, and Hagersten Locks)锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。 + +```java +public class CLHLock { + public static class CLHNode { + private boolean isLocked = true; // 默认是在等待锁 + } + + @SuppressWarnings("unused" ) + private volatile CLHNode tail ; + private static final AtomicReferenceFieldUpdater UPDATER = AtomicReferenceFieldUpdater + . newUpdater(CLHLock.class, CLHNode .class , "tail" ); + + public void lock(CLHNode currentThread) { + CLHNode preNode = UPDATER.getAndSet( this, currentThread); + if(preNode != null) {//已有线程占用了锁,进入自旋 + while(preNode.isLocked ) { + } + } + } + + public void unlock(CLHNode currentThread) { + // 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。 + if (!UPDATER .compareAndSet(this, currentThread, null)) { + // 还有后续线程 + currentThread. isLocked = false ;// 改变状态,让后续线程结束自旋 + } + } +} +``` + +**CLH优势** + +- 公平、FIFO、先来后到的顺序进入锁 +- 且没有竞争同一个变量,因为每个线程只要等待自己的前继释放即可 + +**CLH锁与MCS锁的比较** + +- 从代码实现来看,CLH比MCS要简单得多 +- 从自旋的条件来看,CLH是在本地变量上自旋,MCS是自旋在其他对象的属性 +- 从链表队列来看,CLH的队列是隐式的,CLHNode并不实际持有下一个节点;MCS的队列是物理存在的 +- CLH锁释放时只需要改变自己的属性,MCS锁释放则需要改变后继节点的属性 + + + +## 常见锁 + +### 乐观锁/悲观锁 + +**悲观锁** +当线程去操作数据的时候,总认为别的线程会去修改数据,所以它每次拿数据的时候总会上锁,别的线程去拿数据的时候就会阻塞,比如synchronized。 + + + +**乐观锁** +每次去拿数据的时候都认为别人不会修改,更新的时候会判断是别人是否回去更新数据,通过版本来判断,如果数据被修改了就拒绝更新,比如CAS是乐观锁,但严格来说并不是锁,通过原子性来保证数据的同步,比如说数据库的乐观锁,通过版本控制来实现,CAS不会保证线程同步,乐观的认为在数据更新期间没有其他线程影响 + + + +**小结**:悲观锁适合写操作多的场景,乐观锁适合读操作多的场景,乐观锁的吞吐量会比悲观锁大。 + + + +### 公平锁/非公平锁 +**公平锁** +指多个线程按照申请锁的顺序来获取锁,简单来说 如果一个线程组里,能保证每个线程都能拿到锁 比如ReentrantLock(底层是同步队列FIFO: First Input First Output来实现) + + + +**非公平锁** +获取锁的方式是随机获取的,保证不了每个线程都能拿到锁,也就是存在有线程饿死,一直拿不到锁,比如synchronized、ReentrantLock。 + + + +**小结**:非公平锁性能高于公平锁,更能重复利用CPU的时间。ReentrantLock中可以通过构造方法指定是否为公平锁,默认为非公平锁!synchronized无法指定为公平锁,一直都是非公平锁。 + + + +### 可重入锁/不可重入锁 +**可重入锁** +也叫递归锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁。一个线程获取锁之后再尝试获取锁时会自动获取锁,可重入锁的优点是避免死锁。 + + + +**不可重入锁** +若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞。 + + + +**小结**:可重入锁能一定程度的避免死锁 synchronized、ReentrantLock都是可重入锁。 + + + +### 独占锁/共享锁 +**独享锁** + +指锁一次只能被一个线程持有。也叫X锁/排它锁/写锁/独享锁:该锁每一次只能被一个线程所持有,加锁后任何线程试图再次加锁的线程会被阻塞,直到当前线程解锁。例子:如果 线程A 对 data1 加上排他锁后,则其他线程不能再对 data1 加任何类型的锁,获得独享锁的线程即能读数据又能修改数据! + + + +**共享锁** + +指锁一次可以被多个线程持有。也叫S锁/读锁,能查看数据,但无法修改和删除数据的一种锁,加锁后其它用户可以并发读取、查询数据,但不能修改,增加,删除数据,该锁可被多个线程所持有,用于资源数据共享! + + + +**小结**:ReentrantLock和synchronized都是独享锁,ReadWriteLock的读锁是共享锁,写锁是独享锁。 + + + +### 互斥锁/读写锁 +与独享锁/共享锁的概念差不多,是独享锁/共享锁的具体实现。 + +ReentrantLock和synchronized都是互斥锁,ReadWriteLock是读写锁 + + + +### 自旋锁 +**自旋锁** + +- 一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环,任何时刻最多只能有一个执行单元获得锁。 +- 不会发生线程状态的切换,一直处于用户态,减少了线程上下文切换的消耗,缺点是循环会消耗CPU。 + + + +**常见的自旋锁**:TicketLock,CLHLock,MSCLock + + + +## 锁优化 + +### 偏向锁/轻量级锁 + +**偏向锁** + +大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了 `让线程获得锁的代价更低` 而引入了偏向锁,让该线程会自动获取锁,减少不必要的CAS操作。 + +**轻量级锁** + +对于轻量级锁,其性能提升依据:“`对于绝大部分锁,在整个生命周期内都是不会存在竞争的`”。轻量级锁的目标:`减少无实际竞争情况下,使用重量级锁产生的性能消耗`,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。 + + + +### 自旋锁 + +**背景** + +线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,势必会给系统的并发性能带来很大的压力。同时我们发现在许多应用上面,对象锁的锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的。所以引入自旋锁。 + +**定义** +自旋锁就是 `让该线程等待(即执行一段无意义的循环为自旋)固定的一段时间`,不会被立即挂起,看持有锁的线程是否会很快释放锁。 + +**弊端** + +自旋可以避免线程切换带来的开销,但它占用了处理器(CPU)的时间。长时间的自旋而不处理任何事,就会浪费资源,所以需要设置自旋等待时间(即自旋次数)。自旋的次数虽然可以通过参数-XX:PreBlockSpin来调整(默认为10次),但固定的自旋次数,`会对部分场景(如只需要自旋一两次就可获得锁)造成浪费`,因此JDK1.6引入了自适应自旋锁。 + + + +### 适应性自旋锁 + +所谓自适应就意味着 `自旋的次数不再是固定的`,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。 + +- **自旋成功,次数增加**:因为虚拟机认为既然上次成功,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多 +- **自旋失败,次数减少**:如果对于某个锁,很少有自旋能够成功的,那么在以后要获得这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源 + + + +### 锁消除 + +为了保证数据的完整性,我们在进行操作时需要对这部分操作进行同步控制,但是在有些情况下,JVM检测到不可能存在共享数据竞争,这时JVM会对这些同步锁进行锁消除,锁消除的依据是 `逃逸分析` 的数据支持。 + +锁消除主要是解决我们使用JDK内置API时存在的 `隐形加锁操作`。如StringBuffer、Vector、HashTable等,StringBuffer的append()方法,Vector的add()方法: + +```java +public void vectorTest(){ + Vector vector = new Vector(); + for(int i = 0 ; i < 10 ; i++){ + vector.add(i + ""); + } + System.out.println(vector); +} +``` + +在运行这段代码时,JVM可以明显检测到变量vector没有逃逸出方法vectorTest()之外,所以JVM可以大胆地将vector内部的加锁操作消除。 + + + +### 锁粗化 + +在使用同步锁的时候,需要让同步块的作用范围尽可能小,仅在共享数据的实际作用域中才进行同步,这样做的目的是为了使需要同步的操作数量尽可能缩小,如果存在锁竞争,那么等待锁的线程也能尽快拿到锁。 +在大多数情况下,上述观点是正确的。但如果 `一系列的连续加锁解锁操作,可能会导致不必要的性能损耗`,所以引入锁粗化的概念。锁粗化概念比较好理解,就是 `将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁`。 + +如上面实例:vector每次add的时候都需要加锁操作,JVM检测到对同一个对象(vector)连续加锁、解锁操作,会合并一个更大范围的加锁、解锁操作,即加锁解锁操作会移到for循环之外。 + + + +### 分段锁 + +分段锁其实是一种锁的设计,并不是具体的一种锁,对于 `ConcurrentHashMap` 而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。 + + + +### 锁细化 + +- **减少锁的时间**:不需要同步执行的代码,能不放在同步快里面执行就不要放在同步快内,可以让锁尽快释放 +- **减少锁的粒度**:将物理上的一个锁,拆成逻辑上的多个锁,增加并行度,从而降低锁竞争。其思想是用空间来换时间 + + + + +# I/O + +Linux/Unix常见IO模型:**阻塞(Blocking I/O)**、**非阻塞(Non-Blocking I/O)**、**IO多路复用(I/O Multiplexing)**、 **信号驱动 I/O(Signal Driven I/O)**(不常用)和**异步(Asynchronous I/O)**。网络IO操作主要涉及到**内核**和**进程**,其主要分为两个过程: + +- 内核等待数据可操作(可读或可写)——阻塞与非阻塞 +- 内核与进程之间数据的拷贝——同步与异步 + + + +## 基础概念 + +**① 阻塞(Blocking)和非阻塞(Non-blocking)** + +阻塞和非阻塞发生在内核等待数据可操作(可读或可写)时,指做事时是否需要等待应答。 + +- **阻塞:** 内核检查数据不可操作,则不立即返回 +- **非阻塞:** 内核检查数据不可操作,则立即返回 + +**② 同步(Synchronous)和异步(Asynchronous)** + +同步和异步发生在内核与进程交互时,进程触发IO操作后是否需要等待或轮询查看结果。 + +- **同步:** 触发IO操作 → 等待或轮询查看结果 +- **异步:** 触发IO操作 → 直接返回去做其它事,IO处理完后内核主动通知进程 + + + +### 阻塞I/O + +当用户程序执行 `read` ,线程会被阻塞,一直等到内核数据准备好,并把数据从内核缓冲区拷贝到应用程序的缓冲区中,当拷贝过程完成,`read` 才会返回。阻塞等待的是 **内核数据准备好** 和 **数据从内核态拷贝到用户态** 两个过程。过程如下图: + +![阻塞IO](images/JAVA/阻塞IO.png) + + + +### 非阻塞I/O + +非阻塞的 `read` 请求在数据未准备好的情况下立即返回,可以继续往下执行,此时应用程序不断轮询内核,直到数据准备好,内核将数据拷贝到应用程序缓冲区,`read` 调用才可以获取到结果。过程如下图: + +![非阻塞IO](images/JAVA/非阻塞IO.png) + +注意,**这里最后一次 read 调用,获取数据的过程,是一个同步的过程,是需要等待的过程。这里的同步指的是内核态的数据拷贝到用户程序的缓存区这个过程。** + + + +### 同步I/O + +无论 `read` 和 `send` 是 `阻塞I/O`,还是 `非阻塞I/O` 都是同步调用。因为在 `read` 调用时,内核将数据从内核空间拷贝到用户空间的过程都是需要等待的,即这个过程是同步的,如果内核实现的拷贝效率不高,`read` 调用就会在这个同步过程中等待比较长的时间。 + + + +### 异步I/O + +真正的异步 I/O 是`内核数据准备好` 和 `数据从内核态拷贝到用户态` 这两个过程都不用等待。 + +当我们发起 `aio_read` (异步 I/O) 之后,就立即返回,内核自动将数据从内核空间拷贝到用户空间,这个拷贝过程同样是异步的,内核自动完成的,和前面的同步操作不一样,应用程序并不需要主动发起拷贝动作。过程如下图: + +![异步IO](images/JAVA/异步IO.png) + + + +## Reactor模式 + +`Reactor 模式` 即 I/O 多路复用监听事件,收到事件后根据事件类型分配(Dispatch)给某个进程/线程。其主要由 `Reactor` 和 `处理资源池` 两个核心部分组成: + +- **Reactor**:负责监听和分发事件。事件类型包含连接事件、读写事件 +- **处理资源池**:负责处理事件。如:read -> 业务逻辑 -> send + +Reactor 模式是灵活多变的,可以应对不同的业务场景,灵活在于: + +- Reactor 的数量可以只有一个,也可以有多个 +- 处理资源池可以是单个进程/线程,也可以是多个进程/线程 + + + +将上面的两个因素排列组设一下,理论上就可以有 4 种方案选择: + +- **单 Reactor 单进程/线程** +- **单 Reactor 多进程/线程** +- **多 Reactor 单进程/线程**:相比 `单Reactor单进程/线程` 方案不仅复杂而且没有性能优势,因此可以忽略 +- **多 Reactor 多进程/线程** + + + +### 单Reactor单进程/单线程 + +一般来说,C 语言实现的是`单Reactor单进程`的方案,因为 C 语编写完的程序,运行后就是一个独立的进程,不需要在进程中再创建线程。而 Java 语言实现的是「单 Reactor 单线程」的方案,因为 Java 程序是跑在 Java 虚拟机这个进程上面的,虚拟机中有很多线程,我们写的 Java 程序只是其中的一个线程而已。以下是「`单 Reactor单进程`」的方案示意图: + + +![单Reactor单进程线程](images/JAVA/单Reactor单进程线程.png) + +可以看到进程里有 `Reactor`、`Acceptor`、`Handler` 这三个对象: + +- `Reactor` 对象的作用是监听和分发事件 +- `Acceptor` 对象的作用是获取连接 +- `Handler` 对象的作用是处理业务 + +对象里的 `select`、`accept`、`read`、`send` 是系统调用函数,`dispatch` 和 `业务处理` 是需要完成的操作,其中 `dispatch` 是分发事件操作。 + + + +**工作流程** + +- `Reactor` 对象通过 `select` (IO多路复用接口) 监听事件,收到事件后通过 `dispatch` 进行分发,具体分发给 `Acceptor` 对象还是 `Handler` 对象,还要看收到的事件类型 +- 如果是连接建立的事件,则交由 `Acceptor` 对象进行处理,`Acceptor` 对象会通过 `accept` 方法 获取连接,并创建一个 `Handler` 对象来处理后续的响应事件 +- 如果不是连接建立事件, 则交由当前连接对应的 `Handler` 对象来进行响应 +- `Handler` 对象通过 `read` -> 业务处理 -> `send` 的流程来完成完整的业务流程 + + + +**优缺点** + +- **优点** + - 因为全部工作都在同一个进程内完成,所以实现起来比较简单 + - 不需要考虑进程间通信,也不用担心多进程竞争 + +- **缺点** + - 因为只有一个进程,无法充分利用 多核 `CPU` 的性能 + - `Handler`对象在业务处理时,整个进程是无法处理其它连接事件,如果业务处理耗时比较长,那么就造成响应的延迟 + + + +**使用场景** + +单Reactor单进程的方案`不适用计算机密集型的场景`,`只适用于业务处理非常快速的场景`。如:Redis 是由 C 语言实现的,它采用的正是「单Reactor单进程」的方案,因为 Redis 业务处理主要是在内存中完成,操作的速度是很快的,性能瓶颈不在 CPU 上,所以 Redis 对于命令的处理是单进程的方案。 + + + +### 单Reactor多线程/多进程 + +如果要克服`单 Reactor 单线程/单进程`方案的缺点,那么就需要引入多线程/多进程,这样就产生了**单Reactor多线程/多进程**的方案。具体方案的示意图如下: + +![单Reactor多线程多进程](images/JAVA/单Reactor多线程多进程.png) + +**工作流程** + +- `Reactor` 对象通过 `select` (IO 多路复用接口) 监听事件,收到事件后通过 `dispatch` 进行分发,具体分发给 `Acceptor` 对象还是 `Handler` 对象,还要看收到的事件类型 +- 如果是连接建立的事件,则交由 `Acceptor` 对象进行处理,`Acceptor` 对象会通过 `accept` 方法获取连接,并创建一个 `Handler` 对象来处理后续的响应事件 +- 如果不是连接建立事件, 则交由当前连接对应的 `Handler` 对象来进行响应 +- `Handler` 对象不再负责业务处理,只负责数据的接收和发送,`Handler` 对象通过 `read` 读取到数据后,会将数据发给子线程里的 `Processor` 对象进行业务处理 +- 子线程里的 `Processor` 对象就进行业务处理,处理完后,将结果发给主线程中的 `Handler` 对象,接着由 `Handler` 通过 `send` 方法将响应结果发送给 `client` + + + +**单Reator多线程** + +- **优势**:能够充分利用多核 `CPU` 的能力 + +- **缺点**:带来了多线程竞争资源问题(如需加互斥锁解决) + +**单Reactor多进程** + +- **缺点** + - 需要考虑子进程和父进程的双向通信 + - 进程间通信远比线程间通信复杂 + + + +另外,`单Reactor` 的模式还有个问题,因为一个 `Reactor` 对象承担所有事件的 `监听` 和 `响应` ,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能瓶颈。 + + + +### 多Reactor多进程/多线程 + +要解决 `单Reactor` 的问题,就是将 `单Reactor` 实现成 `多Reactor`,这样就产生了 **多Reactor多进程/线程** 方案。其方案的示意图如下(以线程为例): + +![多Reactor多进程线程](images/JAVA/多Reactor多进程线程.png) + +**工作流程** + +- 主线程中的 `MainReactor` 对象通过 `select` 监控连接建立事件,收到事件后通过 `Acceptor` 对象中的 `accept` 获取连接,将新的连接分配给某个子线程 +- 子线程中的 `SubReactor` 对象将 `MainReactor` 对象分配的连接加入 `select` 继续进行监听,并创建一个 `Handler` 用于处理连接的响应事件 +- 如果有新的事件发生时,`SubReactor` 对象会调用当前连接对应的 `Handler` 对象来进行响应 +- `Handler` 对象通过 `read` -> 业务处理 -> `send` 的流程来完成完整的业务流程 + + + +**方案优势** + +`多Reactor多线程` 的方案虽然看起来复杂的,但是实际实现时比 `单Reactor多线程`的方案要简单的多,原因如下: + +- **分工明确**:主线程只负责接收新连接,子线程负责完成后续的业务处理 +- **主线程和子线程的交互很简单**:主线程只需要把新连接传给子线程,子线程无须返回数据,直接就可以在子线程将处理结果发送给客户端 + + + +**应用场景** + +- `多Reactor多线程`:开源软件 `Netty`、`Memcache` + +- `多Reactor多进程`:开源软件 `Nginx`。不过 Nginx 方案与标准的多Reactor多进程有些差异,具体差异: + - 主进程仅用来初始化 socket,并没有创建 mainReactor 来 accept 连接,而由子进程的 Reactor 来 accept 连接 + - 通过锁来控制一次只有一个子进程进行 accept(防止出现惊群现象),子进程 accept 新连接后就放到自己的 Reactor 进行处理,不会再分配给其他子进程 + + + +## Proactor模式 + +**Reactor 和 Proactor 的区别** + +- **Reactor 是非阻塞同步网络模式,感知的是就绪可读写事件** + - 在每次感知到有事件发生(比如可读就绪事件)后,就需要应用进程主动调用 `read` 方法来完成数据的读取,也就是要应用进程主动将 `socket` 接收缓存中的数据读到应用进程内存中,这个过程是同步的,读取完数据后应用进程才能处理数据 + - 简单理解:**来了事件**(有新连接、有数据可读、有数据可写)**操作系统通知应用进程,让应用进程来处理**(从驱动读取到内核以及从内核读取到用户空间) +- **Proactor 是异步网络模式, 感知的是已完成的读写事件** + - 在发起异步读写请求时,需要传入数据缓冲区的地址(用来存放结果数据)等信息,这样系统内核才可以自动帮我们把数据的读写工作完成,这里的读写工作全程由操作系统来做,并不需要像 Reactor 那样还需要应用进程主动发起 read/write 来读写数据,操作系统完成读写工作后,就会通知应用进程直接处理数据 + - 简单理解:**来了事件**(有新连接、有数据可读、有数据可写)**操作系统来处理**(从驱动读取到内核,从内核读取到用户空间),**处理完再通知应用进程** + +无论是 Reactor,还是 Proactor,都是一种基于「事件分发」的网络编程模式,区别在于 **Reactor 模式是基于「待完成」的 I/O 事件,而 Proactor 模式则是基于「已完成」的 I/O 事件**。 + + + +Proactor 模式的示意图如下: + +![Proactor模式](images/JAVA/Proactor模式.png) + +**工作流程** + +- Proactor Initiator 负责创建 Proactor 和 Handler 对象,并将 Proactor 和 Handler 都通过 +- Asynchronous Operation Processor 注册到内核 +- Asynchronous Operation Processor 负责处理注册请求,并处理 I/O 操作; +- Asynchronous Operation Processor 完成 I/O 操作后通知 Proactor +- Proactor 根据不同的事件类型回调不同的 Handler 进行业务处理 +- Handler 完成业务处理 + + + +**平台支持** + +- **Linux**:在 `Linux` 下的 `异步I/O` 是不完善的,`aio` 系列函数是由 `POSIX` 定义的异步操作接口,不是真正的操作系统级别支持的,而是在用户空间模拟出来的异步。并且仅仅支持基于本地文件的 `aio` 异步操作,网络编程中的 `socket` 是不支持的,这也使得基于 `Linux` 的高性能网络程序都是使用 `Reactor` 方案 +- **Windows** :在 `Windows` 下实现了一套完整的支持 `socket` 的异步编程接口,这套接口就是 `IOCP`,是由操作系统级别实现的 `异步I/O`,真正意义上 `异步I/O`,因此在 `Windows` 里实现高性能网络程序可以使用效率更高的 `Proactor` 方案 + + + +## select/poll/epoll + +select/poll/epoll对比: + +![select、poll、epoll对比](images/JAVA/select、poll、epoll对比.png) + +**注意**:**遍历**相当于查看所有的位置,**回调**相当于查看对应的位置。 + + + +### select + +![select工作流程](images/JAVA/select工作流程.jpg) + +POSIX所规定,目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理 + +**缺点** + +- 单个进程可监视的fd数量被限制,即能监听端口的数量有限,数值存在如下文件里:`cat /proc/sys/fs/file-max` +- 对socket是线性扫描,即采用轮询的方法,效率较低 +- select采取了内存拷贝方法来实现内核将FD消息通知给用户空间,这样一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大 + + + +select是第一版IO复用,提出后暴漏了很多问题。 + +- select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的 +- select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但不会告诉是那个sock上有数据,只能自己遍历查找 +- select 只能监视1024个链接 +- select 不是线程安全的,如果你把一个sock加入到select, 然后突然另外一个线程发现这个sock不用,要收回,这个select 不支持的 + + + +### poll + +![poll工作流程](images/JAVA/poll工作流程.jpg) + +本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态 + +- 其没有最大连接数的限制,原因是它是基于链表来存储的 +- 大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义 +- poll特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd +- 边缘触发:只通知一次,epoll用的就是边缘触发 + + + +poll 修复了 select 的很多问题: + +- poll 去掉了1024个链接的限制 +- poll 从设计上来说不再修改传入数组 + +但是poll仍然不是线程安全的, 这就意味着不管服务器有多强悍,你也只能在一个线程里面处理一组 I/O 流。你当然可以拿多进程来配合了,不过然后你就有了多进程的各种问题。 + + + +### epoll + +![epoll工作流程](images/JAVA/epoll工作流程.jpg) + +在Linux2.6内核中提出的select和poll的增强版本 + +- 支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次 +- 使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知 + +**优点** + +- 没有最大并发连接的限制:能打开的FD的上限远大于1024(1G的内存能监听约10万个端口) +- 效率提升:非轮询的方式,不会随着FD数目的增加而效率下降;只有活跃可用的FD才会调用callback函数,即epoll最大的优点就在于它只管理“活跃”的连接,而跟连接总数无关 +- 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销 +- 文件映射内存直接通过地址空间访问,效率更高,把文件映射到内存中 + + + +epoll 可以说是 I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如: + +- epoll 现在是线程安全的 +- epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了 +- epoll 内核态管理了各种IO文件描述符, 以前用户态发送所有文件描述符到内核态,然后内核态负责筛选返回可用数组,现在epoll模式下所有文件描述符在内核态有存,查询时不用传文件描述符进去了 + + + +## BIO(同步阻塞I/O) + +用户需要等待read将socket中的数据读取到buffer后,才继续处理接收的数据。整个IO请求的过程中,用户线程是被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够。 + +![同步阻塞IO](images/JAVA/同步阻塞IO.png) + +**特点:**I/O执行的两个阶段进程都是阻塞的。 + +**优点** + +- 能够及时的返回数据,无延迟 +- 程序简单,进程挂起基本不会消耗CPU时间 + +**缺点** + +- I/O等待对性能影响较大 +- 每个连接需要独立的一个进程/线程处理,当并发请求量较大时为了维护程序,内存、线程和CPU上下文切换开销较大,因此较少在开发环境中使用 + + + +## NIO(同步非阻塞I/O) + +用户需要不断地调用read,尝试读取socket中的数据,直到读取成功后,才继续处理接收的数据。整个IO请求过程中,虽然用户线程每次发起IO请求后可以立即返回,但为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的CPU的资源。 + +![同步非阻塞IO](images/JAVA/同步非阻塞IO.png) + +**特点:**non-blocking I/O模式需要不断的主动询问kernel数据是否已准备好。 + +**优点** + +- 进程在等待当前任务完成时,可以同时执行其他任务进程不会被阻塞在内核等待数据过程,每次发起的I/O请求会立即返回,具有较好的实时性 + +**缺点** + +- 不断轮询将占用大量CPU时间,系统资源利用率大打折扣,影响性能,整体数据吞吐量下降 +- 该模型不适用web服务器 + + + +## IO多路复用(异步阻塞I/O) + +通过Reactor的方式,可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作。 + +![IO多路复用](images/JAVA/IO多路复用.png) + +**特点:**通过一种机制能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个变为可读就绪状态,select()/poll()函数就会返回。 + +**优点** + +- 可以基于一个阻塞对象,同时在多个描述符上可读就绪,而不是使用多个线程(每个描述符一个线程),即能处理更多的连接 +- 可以节省更多的系统资源 + +**缺点:** + +- 如果处理的连接数不是很多的话,使用select/poll的web server不一定比使用multi-threading + blocking I/O的web server性能更好 +- 可能延迟还更大,因为处理一个连接数需要发起两次system call + + + +## AIO(异步非阻塞I/O) + +AIO(异步非阻塞IO,即NIO.2)。异步IO模型中,用户线程直接使用内核提供的异步IO API发起read请求,且发起后立即返回,继续执行用户线程代码。不过此时用户线程已经将调用的AsynchronousOperation和CompletionHandler注册到内核,然后操作系统开启独立的内核线程去处理IO操作。当read请求的数据到达时,由内核负责读取socket中的数据,并写入用户指定的缓冲区中。最后内核将read的数据和用户线程注册的CompletionHandler分发给内部Proactor,Proactor将IO完成的信息通知给用户线程(一般通过调用用户线程注册的完成事件处理函数),完成异步IO。 + +![异步非阻塞IO](images/JAVA/异步非阻塞IO.png) + +**特点:**第一阶段和第二阶段都是有内核完成。 + +**优点** + +- 能充分利用DMA的特性,将I/O操作与计算重叠,提高性能、资源利用率与并发能力 + +**缺点** + +- 在程序的实现上比较困难 +- 要实现真正的异步 I/O,操作系统需要做大量的工作。目前 Windows 下通过 IOCP 实现了真正的异步 I/O。而在 Linux 系统下,Linux 2.6才引入,目前 AIO 并不完善,因此在 Linux 下实现高并发网络编程时都是以 复用式I/O模型为主 + + + +## 信号驱动式I/O + +信号驱动式I/O是指进程预先告知内核,使得某个文件描述符上发生了变化时,内核使用信号通知该进程。在信号驱动式I/O模型,进程使用socket进行信号驱动I/O,并建立一个SIGIO信号处理函数,当进程通过该信号处理函数向内核发起I/O调用时,内核并没有准备好数据报,而是返回一个信号给进程,此时进程可以继续发起其他I/O调用。也就是说,在第一阶段内核准备数据的过程中,进程并不会被阻塞,会继续执行。当数据报准备好之后,内核会递交SIGIO信号,通知用户空间的信号处理程序,数据已准备好;此时进程会发起recvfrom的系统调用,这一个阶段与阻塞式I/O无异。也就是说,在第二阶段内核复制数据到用户空间的过程中,进程同样是被阻塞的。 + +**信号驱动式I/O的整个过程图如下:** +![信号驱动式IO](images/JAVA/信号驱动式IO.png) + +**第一阶段(非阻塞):** + +- ①:进程使用socket进行信号驱动I/O,建立SIGIO信号处理函数,向内核发起系统调用,内核在未准备好数据报的情况下返回一个信号给进程,此时进程可以继续做其他事情 +- ②:内核将磁盘中的数据加载至内核缓冲区完成后,会递交SIGIO信号给用户空间的信号处理程序 + +**第二阶段(阻塞):** + +- ③:进程在收到SIGIO信号程序之后,进程向内核发起系统调用(recvfrom) +- ④:内核再将内核缓冲区中的数据复制到用户空间中的进程缓冲区中(真正执行IO过程的阶段),直到数据复制完成 +- ⑤:内核返回成功数据处理完成的指令给进程;进程在收到指令后再对数据包进程处理;处理完成后,此时的进程解除不可中断睡眠态,执行下一个I/O操作 + + + +**特点:**借助socket进行信号驱动I/O并建立SIGIO信号处理函数 + +**优点** + +- 线程并没有在第一阶段(数据等待)时被阻塞,提高了资源利用率; + +**缺点** + +- 在程序的实现上比较困难 +- 信号 I/O 在大量 IO 操作时可能会因为信号队列溢出导致没法通知。信号驱动 I/O 尽管对于处理 UDP 套接字来说有用,即这种信号通知意味着到达一个数据报,或者返回一个异步错误。但是,对于 TCP 而言,信号驱动的 I/O 方式近乎无用,因为导致这种通知的条件为数众多,每一个来进行判别会消耗很大资源,与前几种方式相比优势尽失 + + + +**信号通知机制** + +- **水平触发:**指数据报到内核缓冲区准备好之后,内核通知进程后,进程因繁忙未发起recvfrom系统调用;内核会再次发送通知信号,循环往复,直到进程来请求recvfrom系统调用。很明显,这种方式会频繁消耗过多的系统资源 +- **边缘触发:**内核只会发送一次通知信号 + + + +# Classloader + +## JVM类加载机制 + +JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。 + +![JVM类加载机制](images/JAVA/JVM类加载机制.png) + +### 加载 + +加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对 象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)。 + + + +### 验证 + +这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 + + + +### 准备 + +准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为: + +```java +public static int v = 8080; +``` + +实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080,将 v 赋值为 8080 的 put static 指令是程序被编译后,存放于类构造器方法之中。但是注意如果声明为: + +```java +public static final int v = 8080; +``` + +在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v 赋值为 8080。 + + + +### 解析 + +解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的: + +- CONSTANT_Class_info +- CONSTANT_Field_info +- CONSTANT_Method_info + +等类型的常量。 + + + +## 类加载器 + +虚拟机设计团队把加载动作放到 JVM 外部实现,以便让应用程序决定如何获取所需的类,JVM 提供了 3 种类加载器: + +![Classloader](images/JAVA/Classloader.png) + +- **启动类加载器(Bootstrap ClassLoader)** + + 负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath 参数指定路径中的,且被虚拟机认可(按文件名识别,如 rt.jar)的类。 + +- **扩展类加载器(Extension ClassLoader)** + + 负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类库。 + +- **应用程序类加载器(Application ClassLoader)** + + 负责加载用户路径(classpath)上的类库。JVM 通过双亲委派模型进行类的加载,当然我们也可以通过继承java.lang.ClassLoader 实现自定义的类加载器。 + + + +此外我们比较需要知道的几点: + +- 一个类是由 jvm 加载是通过类加载器+全限定类名确定唯一性的 +- 双亲委派,众所周知,子加载器会尽量委托给父加载器进行加载,父加载器找不到再自己加载 +- 线程上下文类加载,为了满足 spi 等需求突破双亲委派机制,当高层类加载器想加载底层类时通过 Thread.contextClassLoader 来获取当前线程的类加载器(往往是底层类加载器)去加载类 + + + +## 双亲委派 + +当一个.class文件要被加载时,不考虑我们自定义类加载器类,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载;如果没有会交到父加载器,然后调用父加载器的loadClass方法。父加载器同样也会先检查自己是否已经加载过,如果没有再往上,直到到达BootstrapClassLoader之前,都是在检查是否加载过,并不会选择自己去加载。到了根加载器时,才会开始检查是否能够加载当前类,能加载就结束,使用当前的加载器;否则就通知子加载器进行加载;子加载器重复该步骤。如果到最底层还不能加载,就抛出异常ClassNotFoundException。 + +**总结**:所有的加载请求都会传送到根加载器去加载,只有当父加载器无法加载时,子类加载器才会去加载 + +![双亲委派](images/JAVA/双亲委派.png) + +**作用** + +- 避免类的重复加载 +- 保证Java核心类库的安全 + + + +**如何打破双亲委派机制?** + + + + + +# Throwable + +![Throwable](images/JAVA/Throwable.png) + +## Error + +Error 类是指 java 运行时系统的内部错误和资源耗尽错误。应用程序不会抛出该类对象。如果出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。 + + + +## Exception + +### CheckedException + +检查异常(CheckedException)。一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch,该类异常一般包括几个方面: + +- 试图在文件尾部读取数据 +- 试图打开一个错误格式的 URL +- 试图根据给定的字符串查找 class 对象,而这个字符串表示的类并不存在 + + + +### RuntimeException + +运行时异常(RuntimeException)。如 :NullPointerException 、 ClassCastException ;一个是检查异常CheckedException,如I/O错误导致的IOException、SQLException。 RuntimeException 是那些可能在Java虚拟机正常运行期间抛出的异常的超类。 如果出现 RuntimeException,那么一定是程序员的错。 + + + +## 异常处理方式 + +抛出异常有三种形式: + +- throw +- throws +- 系统自动抛异常 + + + +**throw 和 throws 的区别** + +- throws 用在函数上,后面跟的是异常类,可以跟多个;而 throw 用在函数内,后面跟的是异常对象 +- throws 用来声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方式;throw 抛出具体的问题对象,执行到 throw,功能就已经结束了,跳转到调用者,并将具体的问题对象抛给调用者。也就是说 throw 语句独立存在时,下面不要定义其他语句,因为执行不到 +- throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw 则是抛出了异常,执行 throw 则一定抛出了某种异常对象 +- 两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理 + + + +# JAVA Others + +## Annotation + +![Annotation](images/JAVA/Annotation.png) + + + +## JAVA内部类 + +Java 类中不仅可以定义变量和方法,还可以定义类,这样定义在类内部的类就被称为内部类。根据定义的方式不同,内部类分为静态内部类、成员内部类、局部内部类和匿名内部类四种。 + +### 静态内部类 + +使用static修饰的内部类我们称之为静态内部类,不过我们更喜欢称之为嵌套内部类。静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着: + +- 它的创建是不需要依赖于外围类的 +- 它不能使用任何外围类的非static成员变量和方法 + +```java +public class OuterClass { + private String sex; + public static String name = "chenssy"; + + /** + *静态内部类 + */ + static class InnerClass1{ + /* 在静态内部类中可以存在静态成员 */ + public static String _name1 = "chenssy_static"; + + public void display(){ + /* + * 静态内部类只能访问外围类的静态成员变量和方法 + * 不能访问外围类的非静态成员变量和方法 + */ + System.out.println("OutClass name :" + name); + } + } + + /** + * 非静态内部类 + */ + class InnerClass2{ + /* 非静态内部类中不能存在静态成员 */ + public String _name2 = "chenssy_inner"; + /* 非静态内部类中可以调用外围类的任何成员,不管是静态的还是非静态的 */ + public void display(){ + System.out.println("OuterClass name:" + name); + } + } + + /** + * @desc 外围类方法 + * @author chenssy + * @data 2013-10-25 + * @return void + */ + public void display(){ + /* 外围类访问静态内部类:内部类. */ + System.out.println(InnerClass1._name1); + /* 静态内部类 可以直接创建实例不需要依赖于外围类 */ + new InnerClass1().display(); + + /* 非静态内部的创建需要依赖于外围类 */ + OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2(); + /* 方位非静态内部类的成员需要使用非静态内部类的实例 */ + System.out.println(inner2._name2); + inner2.display(); + } + + public static void main(String[] args) { + OuterClass outer = new OuterClass(); + outer.display(); + } +} +``` + + + +### 成员内部类 + +成员内部类也是最普通的内部类,它是外围类的一个成员,所以他是可以无限制的访问外围类的所有 成员属性和方法,尽管是private的,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。在成员内部类中要注意两点: + +- **成员内部类中不能存在任何static的变量和方法** +- **成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类** +- **推荐使用getxxx()来获取成员内部类,尤其是该内部类的构造函数无参数时** + +```java +public class OuterClass { + private String str; + + public class InnerClass{ + public void innerDisplay(){ + //使用外围内的属性 + str = "chenssy..."; + System.out.println(str); + //使用外围内的方法 + outerDisplay(); + } + } + + /* 推荐使用getxxx()来获取成员内部类,尤其是该内部类的构造函数无参数时 */ + public InnerClass getInnerClass(){ + return new InnerClass(); + } + + public static void main(String[] args) { + OuterClass outer = new OuterClass(); + OuterClass.InnerClass inner = outer.getInnerClass(); + inner.innerDisplay(); + } +} +``` + + + +### 局部内部类 + +有这样一种内部类,它是嵌套在方法和作用于内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。 + +- 定义在方法里: + + ```java + public class Parcel5 { + public Destionation destionation(String str){ + class PDestionation implements Destionation{ + private String label; + private PDestionation(String whereTo){ + label = whereTo; + } + public String readLabel(){ + return label; + } + } + return new PDestionation(str); + } + + public static void main(String[] args) { + Parcel5 parcel5 = new Parcel5(); + Destionation d = parcel5.destionation("chenssy"); + } + } + ``` + +- 定义在作用域内: + + ```java + public class Parcel6 { + private void internalTracking(boolean b){ + if(b){ + class TrackingSlip{ + private String id; + TrackingSlip(String s) { + id = s; + } + String getSlip(){ + return id; + } + } + TrackingSlip ts = new TrackingSlip("chenssy"); + String string = ts.getSlip(); + } + } + + public void track(){ + internalTracking(true); + } + + public static void main(String[] args) { + Parcel6 parcel6 = new Parcel6(); + parcel6.track(); + } + } + ``` + + + +### 匿名内部类 + +- 匿名内部类是没有访问修饰符的 +- new 匿名内部类,这个类首先是要存在的。如果我们将那个InnerClass接口注释掉,就会出现编译出错 +- 注意getInnerClass()方法的形参,第一个形参是用final修饰的,而第二个却没有。同时我们也发现第二个形参在匿名内部类中没有使用过,所以当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为final +- 匿名内部类是没有构造方法的。因为它连名字都没有何来构造方法 + +```java +button2.addActionListener( + new ActionListener(){ + public void actionPerformed(ActionEvent e) { + System.out.println("你按了按钮二"); + } + }); +``` + + + +## 泛型 + +### 获取类泛型类型 + +获取当前类上的泛型类型方式如下: + +```java +public class DefaultTargetType { + + private Type type; + private Class classType; + + @SuppressWarnings("unchecked") + public DefaultTargetType() { + Type superClass = getClass().getGenericSuperclass(); + this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; + if (this.type instanceof ParameterizedType) { + this.classType = (Class) ((ParameterizedType) this.type).getRawType(); + } else { + this.classType = (Class) this.type; + } + } + +} +``` + +获取到泛型中的类型方式: + +```java +Class> classType = new DefaultTargetType>() {}.getClassType(); +``` + + + +### 获取接口泛型类型 + +获取当前类的父类接口上的泛型类型方式如下: + +```java +public class DefaultTargetType implements TargetType { + + private Type type; + private Class classType; + + @SuppressWarnings("unchecked") + public DefaultTargetType() { + Type superClass = getClass().getGenericInterfaces()[0]; + this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; + if (this.type instanceof ParameterizedType) { + this.classType = (Class) ((ParameterizedType) this.type).getRawType(); + } else { + this.classType = (Class) this.type; + } + } + +} +``` + +获取到泛型中的类型方式: + +```java +Class> classType = new DefaultTargetType>() {}.getClassType(); +``` + + + +## JAVA复制 + +### 浅复制 + +被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。 + + + +### 深复制 + +被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。 + + + +# JVM + +## JVM常量池 + +- **字符串常量池**:存放在堆上,即执行intern方法后存的地方。class文件的静态常量池,如果是字符串,则也会被装到字符串常量池中 +- **运行时常量池**:存放在方法区,属于元空间,是类加载后的一些存储区域,大多数是类中 constant_pool 的内容 +- **类文件常量池**:也就是constant_pool,这个是概念性的,并没有什么实际存储区域 + + + +## JVM内存布局 + +JVM包含**堆**、**元空间**、**Java虚拟机栈**、**本地方法栈**、**程序计数器**等内存区域,其中**堆**是占用内存最大的,如下图所示: + +![JVM架构](images/JAVA/JVM架构.png) + + + +## JAVA内存模型(JMM) + +JVM试图定义一种统一的内存模型,能将各种底层硬件以及操作系统的内存访问差异进行封装,使Java程序在不同硬件以及操作系统上都能达到相同的并发效果。**它分为工作内存和主内存,线程无法对主存储器直接进行操作,如果一个线程要和另外一个线程通信,那么只能通过主存进行交换**。如下图所示: + +![JVM内存结构(JDK1.6)](images/JAVA/JVM内存结构(JDK1.6).png) + +![JVM内存结构(JDK1.7)](images/JAVA/JVM内存结构(JDK1.7).png) + +![JVM内存结构(JDK1.8)](images/JAVA/JVM内存结构(JDK1.8).png) + +**线程隔离数据区:** + +- **程序计数器:** 当前线程所执行字节码的行号指示器 +- **虚拟机栈:** 里面的元素叫栈帧,存储局部变量表、操作栈、动态链接、方法出口等,方法被调用到执行完成的过程对应一个栈帧在虚拟机栈中入栈到出栈的过程 +- **本地方法栈:** 和虚拟机栈的区别在于虚拟机栈为虚拟机执行Java方法,本地方法栈为虚拟机使用到的本地Native方法服务 + +**线程共享数据区:** + +- **方法区:** 可以描述为堆的一个逻辑部分,或者说使用永久代来实现方法区。存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 +- **堆:** 唯一目的就是存放对象的实例,是垃圾回收管理器的主要区域,分为Eden、From/To Survivor空间 + + + +### 程序计数器 + +程序计数器(Program Counter Register)。一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是 Native 方法,则为空。这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。 + +- 线程私有 +- 是一块很小的独立内存空间 +- 主要存储当前线程所执行的字节码行号指示器 +- 以一种数据结构的形式放置于内存中 +- 分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成 + +**注意**:此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。 + + + +### JAVA虚拟机栈 + +JAVA虚拟机栈(Java Virtual Machine Stacks)。是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。 + +- 线程私有 +- JAVA线程创建同时,会自动创建对应的JAVA栈 +- JAVA栈包含多个栈帧(运行每个方法,就会自动创建一个栈帧,用于存储局部变量、操作栈和返回值等) + +> **相关参数:** +> +> -Xss:设置方法栈的最大值 + + + +### 本地方法栈 + +本地方法栈(Native Method Stacks)。本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为Native 方法服务, 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。 + +- 线程私有 +- 与JAVA栈的作用相似 +- 主要为JVM使用本地方法(native)提供支持 +- 不是由Java实现的,而是由C实现的 + +与虚拟机栈一样,本地方法栈区域也会抛出**StackOverflowError**和**OutOfMemoryError**异常: + +```java +// 原因:对象不能被分配到堆内存中 +Exception in thread "main": java.lang.OutOfMemoryError: Java heap space +// 原因:类或者方法不能被加载到持久代。它可能出现在一个程序加载很多类的时候,比如引用了很多第三方的库 +Exception in thread "main": java.lang.OutOfMemoryError: PermGen space +// 原因:创建的数组大于堆内存的空间 +Exception in thread "main": java.lang.OutOfMemoryError: Requested array size exceeds VM limit +// 原因:分配本地分配失败。JNI、本地库或者Java虚拟机都会从本地堆中分配内存空间 +Exception in thread "main": java.lang.OutOfMemoryError: request bytes for . Out of swap space? +// 原因:同样是本地方法内存分配失败,只不过是JNI或者本地方法或者Java虚拟机发现 +Exception in thread "main": java.lang.OutOfMemoryError: (Native method) +``` + + + +### 方法区 + +方法区(Method Area)。即我们常说的**永久代(Permanent Generation)**, 用于存储**被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码**等数据,HotSpot VM把GC分代收集扩展至方法区, 即使用Java堆的永久代来实现方法区, 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型的卸载, 因此收益一般很小)。运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 Java 虚拟机对 Class 文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。 + +- 又称之为:**非堆(Non-Heap)**或 **永久区** +- 线程共享 +- 主要存储:类的类型信息、**常量池(Runtime Constant Pool)**、字段信息、方法信息、类变量和Class类的引用等 +- Java虚拟机规范规定:当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常 + +> **相关参数:** +> +> -XX:PermSize:设置Perm区的初始大小 +> +> -XX:MaxPermSize:设置Perm区的最大值 + + + +### 堆内存 + +堆内存(JAVA Heap)。是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行 + +垃圾收集的最重要的内存区域。由于现代 VM 采用**分代收集算法**, 因此 Java 堆从 GC 的角度还可以细分为: **新生代**(Eden 区、From Survivor 区和 To Survivor 区)和老年代。 + +- 线程共享 +- 主要用于存储JAVA实例或对象 +- GC发生的主要区域 +- 是Java虚拟机所管理的内存中最大的一块 +- 当堆中没有内存能完成实例分配,且堆也无法再扩展,则会抛出OutOfMemoryError异常 + +> **相关参数:** +> +> -Xms:设置堆内存初始大小 +> +> -Xmx:设置堆内存最大值 +> +> -XX:MaxTenuringThreshold:设置对象在新生代中存活的次数 +> +> -XX:PretenureSizeThreshold:设置超过指定大小的大对象直接分配在旧生代中 +> +> **新生代相关参数**(注意:当新生代设置得太小时,也可能引发大对象直接分配到旧生代): +> +> -Xmn:设置新生代内存大小 +> +> -XX:SurvivorRatio:设置Eden与Survivor空间的大小比例 + + + +## JVM运行时内存 + +JVM运行时内存又称堆内存(Heap)。Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。 + +![RuntimeDataArea](images/JAVA/RuntimeDataArea.png) + +![JVM堆内存划分](images/JAVA/JVM堆内存划分.png) + +当代主流虚拟机(Hotspot VM)的垃圾回收都采用“分代回收”的算法。“分代回收”是基于这样一个事实:对象的生命周期不同,所以针对不同生命周期的对象可以采取不同的回收方式,以便提高回收效率。Hotspot VM将内存划分为不同的物理区,就是“分代”思想的体现。 + + + +**一个对象从出生到消亡** + +![JVM对象申请空间流程](images/JAVA/JVM对象申请空间流程.png) + +一个对象产生之后首先进行栈上分配,栈上如果分配不下会进入伊甸区,伊甸区经过一次垃圾回收之后进入surivivor区,survivor区在经过一次垃圾回收之后又进入另外一个survivor,与此同时伊甸区的某些对象也跟着进入另外一个survivot,什么时候年龄够了就会进入old区,这是整个对象的一个逻辑上的移动过程。 + + + +### 新生代(Young Generation) + +**主要是用来存放新生的对象**。一般占据堆的1/3空间。由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。新生代又分为 Eden区、ServivorFrom、ServivorTo三个区。 + +- **Eden区**:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收 +- **ServivorTo**:保留了一次MinorGC过程中的幸存者 +- **ServivorFrom**:上一次GC的幸存者,作为这一次GC的被扫描者 + + + +**MinorGC流程** + +- **MinorGC采用复制算法** +- 首先把Eden和ServivorFrom区域中存活的对象复制到ServicorTo区域(如果有对象的年龄以及达到了老年的标准,则复制到老年代区),同时把这些对象的年龄+1(如果ServicorTo不够位置了就放到老年区) +- 然后清空Eden和ServicorFrom中的对象 +- 最后ServicorTo和ServicorFrom互换,原ServicorTo成为下一次GC时的ServicorFrom区 + + + +**为什么 Survivor 分区不能是 0 个?** + +如果 Survivor 是 0 的话,也就是说新生代只有一个 Eden 分区,每次垃圾回收之后,存活的对象都会进入老生代,这样老生代的内存空间很快就被占满了,从而触发最耗时的 Full GC ,显然这样的收集器的效率是我们完全不能接受的。 + +**为什么 Survivor 分区不能是 1 个?** + +如果 Survivor 分区是 1 个的话,假设我们把两个区域分为 1:1,那么任何时候都有一半的内存空间是闲置的,显然空间利用率太低不是最佳的方案。 + +但如果设置内存空间的比例是 8:2 ,只是看起来似乎“很好”,假设新生代的内存为 100 MB( Survivor 大小为 20 MB ),现在有 70 MB 对象进行垃圾回收之后,剩余活跃的对象为 15 MB 进入 Survivor 区,这个时候新生代可用的内存空间只剩了 5 MB,这样很快又要进行垃圾回收操作,显然这种垃圾回收器最大的问题就在于,需要频繁进行垃圾回收。 + +**为什么 Survivor 分区是 2 个?** + +如果Survivor分区有2个分区,我们就可以把 Eden、From Survivor、To Survivor 分区内存比例设置为 8:1:1 ,那么任何时候新生代内存的利用率都 90% ,这样空间利用率基本是符合预期的。再者就是虚拟机的大部分对象都符合“朝生夕死”的特性,所以每次新对象的产生都在空间占比比较大的Eden区,垃圾回收之后再把存活的对象方法存入Survivor区,如果是 Survivor区存活的对象,那么“年龄”就+1,当年龄增长到15(可通过 -XX:+MaxTenuringThreshold 设定)对象就升级到老生代。 + + + +**总结** + +根据上面的分析可以得知,当新生代的 Survivor 分区为 2 个的时候,不论是空间利用率还是程序运行的效率都是最优的,所以这也是为什么 Survivor 分区是 2 个的原因了。 + + + +### 老年代(Old Generation) + +**主要存放应用程序中生命周期长的内存对象**。老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。 + + + +**MajorGC流程** + +MajorGC采用标记—清除算法。首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常。 + + + +### 永久区(Perm Generation) + +指内存的永久保存区域,**主要存放元数据**,例如Class、Method的元信息,与垃圾回收要回收的Java对象关系不大。相对于新生代和年老代来说,该区域的划分对垃圾回收影响比较小。GC不会在主程序运行期对永久区域进行清理,所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。 + + + +**JAVA8与元数据** + +在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:**元空间并不在虚拟机中,而是使用本地内存**。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入Native Memory,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。 + + + +### 内存分配策略 + +堆内存常见的分配测试如下: + +- 对象优先在Eden区分配 +- 大对象直接进入老年代 +- 长期存活的对象将进入老年代 + +| **参数** | **说明信息** | +| :------------------------------ | ------------------------------------------------------------ | +| -Xms | 初始堆大小。如:-Xms256m | +| -Xmx | 最大堆大小。如:-Xmx512m | +| -Xmn | 新生代大小。通常为Xmx的1/3或1/4。新生代=Eden+2个Survivor空间。实际可用空间为=Eden+1个Survivor,即 90% | +| -Xss | JDK1.5+每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的 | +| -XX:NewRatio | 新生代与老年代的比例。如–XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3 | +| -XX:SurvivorRatio | 新生代中Eden与Survivor的比值。默认值为 8,即Eden占新生代空间的8/10,另外两个Survivor各占1/10 | +| -XX:PermSize | 永久代(方法区)的初始大小 | +| -XX:MaxPermSize | 永久代(方法区)的最大值 | +| -XX:+PrintGCDetails | 打印GC信息 | +| -XX:+HeapDumpOnOutOfMemoryError | 让虚拟机在发生内存溢出时Dump出当前的内存堆转储快照,以便分析用 | + + + +**参数基本策略** + +各分区的大小对GC的性能影响很大。如何将各分区调整到合适的大小,分析活跃数据的大小是很好的切入点。 + +**活跃数据的大小**:指应用程序稳定运行时长期存活对象在堆中占用的空间大小,即Full GC后堆中老年代占用空间的大小。 + +可以通过GC日志中Full GC之后老年代数据大小得出,比较准确的方法是在程序稳定后,多次获取GC数据,通过取平均值的方式计算活跃数据的大小。活跃数据和各分区之间的比例关系如下: + +| 空间 | 倍数 | +| ------ | --------------------------------------- | +| 总大小 | **3-4** 倍活跃数据的大小 | +| 新生代 | **1-1.5** 活跃数据的大小 | +| 老年代 | **2-3** 倍活跃数据的大小 | +| 永久代 | **1.2-1.5** 倍Full GC后的永久代空间占用 | + +例如,根据GC日志获得老年代的活跃数据大小为300M,那么各分区大小可以设为: + +> 总堆:1200MB = 300MB × 4 +> +> 新生代:450MB = 300MB × 1.5 +> +> 老年代: 750MB = 1200MB - 450MB + +这部分设置仅仅是堆大小的初始值,后面的优化中,可能会调整这些值,具体情况取决于应用程序的特性和需求。 + + + +## 引用级别 + +Java中4种引用的级别和强度由高到低依次为:**强引用→软引用→弱引用→虚引用** + +当**垃圾回收器**回收时,某些对象会被回收,某些不会被回收。垃圾回收器会从**根对象**`Object`来**标记**存活的对象,然后将某些不可达的对象和一些引用的对象进行回收。如下所示: + +| 引用类型 | 被垃圾回收时间 | 用途 | 生存时间 | +| -------- | -------------- | ------------------ | ----------------- | +| 强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 | +| 软引用 | 当内存不足时 | 对象缓存 | 内存不足时终止 | +| 弱引用 | 正常垃圾回收时 | 对象缓存 | 垃圾回收后终止 | +| 虚引用 | 正常垃圾回收时 | 跟踪对象的垃圾回收 | 垃圾回收后终止 | + +![引用级别](images/JAVA/引用级别.png) + + + +### 强引用(StrongReference) + +强引用是我们最常见的对象,它属于不可回收资源,垃圾回收器(后面简称GC)绝对不会回收它,即使是内存不足,JVM宁愿抛出 OutOfMemoryError 异常,使程序终止,也不会来回收强引用对象。如果一个对象具有强引用,那**垃圾回收器**绝不会回收它。如下: + +```java +Object strongReference = new Object(); +``` + +当**内存空间不足**时,`Java`虚拟机宁愿抛出`OutOfMemoryError`错误,使程序**异常终止**,也不会靠随意**回收**具有**强引用**的**对象**来解决内存不足的问题。 如果强引用对象**不使用时**,需要弱化从而使`GC`能够回收,如下: + +```java +strongReference = null; +``` + +显式地设置`strongReference`对象为`null`,或让其**超出**对象的**生命周期**范围,则`gc`认为该对象**不存在引用**,这时就可以回收这个对象。具体什么时候收集这要取决于`GC`算法。 + +```java +public void test() { + Object strongReference = new Object(); + // 省略其他操作 +} +``` + +在一个**方法的内部**有一个**强引用**,这个引用保存在`Java`**栈**中,而真正的引用内容(`Object`)保存在`Java`**堆**中。 当这个**方法运行完成**后,就会退出**方法栈**,则引用对象的**引用数**为`0`,这个对象会被回收。但是如果这个`strongReference`是**全局变量**时,就需要在不用这个对象时赋值为`null`,因为**强引用**不会被垃圾回收。 + + + +### 软引用(SoftReference) + +如果一个对象只具有**软引用**,则**内存空间充足**时,**垃圾回收器**就**不会**回收它;如果**内存空间不足**了,就会**回收**这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 + +```java +// 强引用 +String strongReference = new String("abc"); +// 软引用 +String str = new String("abc"); +SoftReference softReference = new SoftReference(str); +``` + +**软引用**可以和一个**引用队列**(`ReferenceQueue`)联合使用。如果**软引用**所引用对象被**垃圾回收**,`JAVA`虚拟机就会把这个**软引用**加入到与之关联的**引用队列**中。 + +```java +ReferenceQueue referenceQueue = new ReferenceQueue<>(); +String str = new String("abc"); +SoftReference softReference = new SoftReference<>(str, referenceQueue); +``` + +**注意**:软引用对象是在jvm内存不够时才会被回收,我们调用System.gc()方法只是起通知作用,JVM什么时候扫描回收对象是JVM自己的状态决定的。就算扫描到软引用对象也不一定会回收它,只有内存不够的时候才会回收。 + +**垃圾收集线程**会在虚拟机抛出`OutOfMemoryError`之前回**收软引用对象**,而**虚拟机**会尽可能优先回收**长时间闲置不用**的**软引用对象**。对那些**刚构建**的或刚使用过的**"较新的"**软对象会被虚拟机尽可能**保留**,这就是引入**引用队列**`ReferenceQueue`的原因。 + + + +### 弱引用(WeakReference) + +弱引用对象相对软引用对象具有更短暂的生命周期,只要 GC 发现它仅有弱引用,不管内存空间是否充足,都会回收它,不过 GC 是一个优先级很低的线程,因此不一定会很快发现那些仅有弱引用的对象。 + +**弱引用**与**软引用**的区别在于:只具有**弱引用**的对象拥有**更短暂**的**生命周期**。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有**弱引用**的对象,不管当前**内存空间足够与否**,都会**回收**它的内存。不过,由于垃圾回收器是一个**优先级很低的线程**,因此**不一定**会**很快**发现那些只具有**弱引用**的对象。 + +```java +String str = new String("abc"); +WeakReference weakReference = new WeakReference<>(str); +str = null; +``` + +`JVM`首先将**软引用**中的**对象**引用置为`null`,然后通知**垃圾回收器**进行回收: + +```java +str = null; +System.gc(); +``` + +**注意**:如果一个对象是偶尔(很少)的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用Weak Reference来记住此对象。 + +下面的代码会让一个**弱引用**再次变为一个**强引用**: + +```java +String str = new String("abc"); +WeakReference weakReference = new WeakReference<>(str); +// 弱引用转强引用 +String strongReference = weakReference.get(); +``` + +同样,**弱引用**可以和一个**引用队列**(`ReferenceQueue`)联合使用,如果**弱引用**所引用的**对象**被**垃圾回收**,`Java`虚拟机就会把这个**弱引用**加入到与之关联的**引用队列**中。 + + + +### 虚引用(PhantomReference) + +**虚引用**顾名思义,就是**形同虚设**。与其他几种引用都不同,**虚引用**并**不会**决定对象的**生命周期**。如果一个对象**仅持有虚引用**,那么它就和**没有任何引用**一样,在任何时候都可能被垃圾回收器回收。 + +**应用场景:** + +**虚引用**主要用来**跟踪对象**被垃圾回收器**回收**的活动。 **虚引用**与**软引用**和**弱引用**的一个区别在于: + +> 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。 + +```java +String str = new String("abc"); +ReferenceQueue queue = new ReferenceQueue(); +// 创建虚引用,要求必须与一个引用队列关联 +PhantomReference pr = new PhantomReference(str, queue); +``` + +程序可以通过判断引用**队列**中是否已经加入了**虚引用**,来了解被引用的对象是否将要进行**垃圾回收**。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的**内存被回收之前**采取必要的行动。 + + + +## OOM + +JVM发生OOM的九种场景如下: + +**场景一:Java heap space** + +> 当堆内存(Heap Space)没有足够空间存放新创建的对象时,就会抛出 `java.lang.OutOfMemoryError:Javaheap space` 错误(根据实际生产经验,可以对程序日志中的 OutOfMemoryError 配置关键字告警,一经发现,立即处理)。 +> +> **原因分析** +> +> `Javaheap space` 错误产生的常见原因可以分为以下几类: +> +> - 请求创建一个超大对象,通常是一个大数组 +> - 超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值 +> - 过度使用终结器(Finalizer),该对象没有立即被 GC +> - 内存泄漏(Memory Leak),大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收 +> +> **解决方案** +> +> 针对大部分情况,通常只需通过 `-Xmx` 参数调高 JVM 堆内存空间即可。如果仍然没有解决,可参考以下情况做进一步处理: +> +> - 如果是超大对象,可以检查其合理性,比如是否一次性查询了数据库全部结果,而没有做结果数限制 +> - 如果是业务峰值压力,可以考虑添加机器资源,或者做限流降级 +> - 如果是内存泄漏,需要找到持有的对象,修改代码设计,比如关闭没有释放的连接 + +**场景二:GC overhead limit exceeded** + +> 当 Java 进程花费 98% 以上的时间执行 GC,但只恢复了不到 2% 的内存,且该动作连续重复了 5 次,就会抛出 `java.lang.OutOfMemoryError:GC overhead limit exceeded` 错误。简单地说,就是应用程序已经基本耗尽了所有可用内存, GC 也无法回收。 +> +> 此类问题的原因与解决方案跟 `Javaheap space` 非常类似,可以参考上文。 + +**场景三:Permgen space** + +> 该错误表示永久代(Permanent Generation)已用满,通常是因为加载的 class 数目太多或体积太大。 +> +> **原因分析** +> +> 永久代存储对象主要包括以下几类: +> +> - 加载/缓存到内存中的 class 定义,包括类的名称,字段,方法和字节码 +> - 常量池 +> - 对象数组/类型数组所关联的 class +> - JIT 编译器优化后的 class 信息 +> +> PermGen 的使用量与加载到内存的 class 的数量/大小正相关。 +> +> **解决方案** +> +> 根据 Permgen space 报错的时机,可以采用不同的解决方案,如下所示: +> +> - 程序启动报错,修改 `-XX:MaxPermSize` 启动参数,调大永久代空间 +> - 应用重新部署时报错,很可能是没有应用没有重启,导致加载了多份 class 信息,只需重启 JVM 即可解决 +> - 运行时报错,应用程序可能会动态创建大量 class,而这些 class 的生命周期很短暂,但是 JVM 默认不会卸载 class,可以设置 `-XX:+CMSClassUnloadingEnabled` 和 `-XX:+UseConcMarkSweepGC` 这两个参数允许 JVM 卸载 class。 +> +> 如果上述方法无法解决,可以通过 jmap 命令 dump 内存对象 `jmap-dump:format=b,file=dump.hprof` ,然后利用 Eclipse MAT https://www.eclipse.org/mat 功能逐一分析开销最大的 classloader 和重复 class。 + +**场景四:Metaspace** + +> JDK 1.8 使用 Metaspace 替换了永久代(Permanent Generation),该错误表示 Metaspace 已被用满,通常是因为加载的 class 数目太多或体积太大。 +> +> 此类问题的原因与解决方法跟 `Permgenspace` 非常类似,可以参考上文。需要特别注意的是调整 Metaspace 空间大小的启动参数为 `-XX:MaxMetaspaceSize`。 + +**场景五:Unable to create new native thread** + +> 每个 Java 线程都需要占用一定的内存空间,当 JVM 向底层操作系统请求创建一个新的 native 线程时,如果没有足够的资源分配就会报此类错误。 +> +> **原因分析** +> +> JVM 向 OS 请求创建 native 线程失败,就会抛出 `Unableto createnewnativethread`,常见的原因包括以下几类: +> +> - 线程数超过操作系统最大线程数 ulimit 限制 +> - 线程数超过 kernel.pid_max(只能重启) +> - native 内存不足 +> +> 该问题发生的常见过程主要包括以下几步: +> +> - JVM 内部的应用程序请求创建一个新的 Java 线程 +> - JVM native 方法代理了该次请求,并向操作系统请求创建一个 native 线程 +> - 操作系统尝试创建一个新的 native 线程,并为其分配内存 +> - 如果操作系统的虚拟内存已耗尽,或是受到 32 位进程的地址空间限制,操作系统就会拒绝本次 native 内存分配 +> - JVM 将抛出 `java.lang.OutOfMemoryError:Unableto createnewnativethread`错误 +> +> **解决方案** +> +> - 升级配置,为机器提供更多的内存 +> - 降低 Java Heap Space 大小 +> - 修复应用程序的线程泄漏问题 +> - 限制线程池大小 +> - 使用 -Xss 参数减少线程栈的大小 +> - 调高 OS 层面的线程最大数:执行 `ulimia-a` 查看最大线程数限制,使用 `ulimit-u xxx` 调整最大线程数限制 + +**场景六:Out of swap space?** + +> 该错误表示所有可用的虚拟内存已被耗尽。虚拟内存(Virtual Memory)由物理内存(Physical Memory)和交换空间(Swap Space)两部分组成。当运行时程序请求的虚拟内存溢出时就会报 `Outof swap space?` 错误。 +> +> **原因分析** +> +> 该错误出现的常见原因包括以下几类: +> +> - 地址空间不足 +> - 物理内存已耗光 +> - 应用程序的本地内存泄漏(native leak),例如不断申请本地内存,却不释放 +> - 执行 `jmap-histo:live` 命令,强制执行 Full GC;如果几次执行后内存明显下降,则基本确认为 Direct ByteBuffer 问题 +> +> **解决方案** +> +> 根据错误原因可以采取如下解决方案: +> +> - 升级地址空间为 64 bit +> - 使用 Arthas 检查是否为 Inflater/Deflater 解压缩问题,如果是,则显式调用 end 方法 +> - Direct ByteBuffer 问题可以通过启动参数 `-XX:MaxDirectMemorySize` 调低阈值 +> - 升级服务器配置/隔离部署,避免争用 + +**场景七:Kill process or sacrifice child** + +> 有一种内核作业(Kernel Job)名为 Out of Memory Killer,它会在可用内存极低的情况下“杀死”(kill)某些进程。OOM Killer 会对所有进程进行打分,然后将评分较低的进程“杀死”,具体的评分规则可以参考 Surviving the Linux OOM Killer。不同于其它OOM错误, `Killprocessorsacrifice child` 错误不是由 JVM 层面触发的,而是由操作系统层面触发的。 +> +> **原因分析** +> +> 默认情况下,Linux 内核允许进程申请的内存总量大于系统可用内存,通过这种“错峰复用”的方式可以更有效的利用系统资源。然而,这种方式也会无可避免地带来一定的“超卖”风险。例如某些进程持续占用系统内存,然后导致其他进程没有可用内存。此时,系统将自动激活 OOM Killer,寻找评分低的进程,并将其“杀死”,释放内存资源。 +> +> **解决方案** +> +> - 升级服务器配置/隔离部署,避免争用 +> - OOM Killer 调优 + +**场景八:Requested array size exceeds VM limit** + +> JVM 限制了数组的最大长度,该错误表示程序请求创建的数组超过最大长度限制。JVM 在为数组分配内存前,会检查要分配的数据结构在系统中是否可寻址,通常为 `Integer.MAX_VALUE-2`。 +> +> 此类问题比较罕见,通常需要检查代码,确认业务是否需要创建如此大的数组,是否可以拆分为多个块,分批执行。 + +**场景九:Direct buffer memory** + +> Java 允许应用程序通过 Direct ByteBuffer 直接访问堆外内存,许多高性能程序通过 Direct ByteBuffer 结合内存映射文件(Memory Mapped File)实现高速 IO。 +> +> **原因分析** +> +> Direct ByteBuffer 的默认大小为 64 MB,一旦使用超出限制,就会抛出 `Directbuffer memory` 错误。 +> +> **解决方案** +> +> - Java 只能通过 ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer,因此,可以通过 Arthas 等在线诊断工具拦截该方法进行排查 +> - 检查是否直接或间接使用了 NIO,如 netty,jetty 等 +> - 通过启动参数 `-XX:MaxDirectMemorySize` 调整 Direct ByteBuffer 的上限值 +> - 检查 JVM 参数是否有 `-XX:+DisableExplicitGC` 选项,如果有就去掉,因为该参数会使 `System.gc()` 失效 +> - 检查堆外内存使用代码,确认是否存在内存泄漏;或者通过反射调用 `sun.misc.Cleaner` 的 `clean()` 方法来主动释放被 Direct ByteBuffer 持有的内存空间 +> - 内存容量确实不足,升级配置 + +**最佳实践** + +> ① OOM发生时输出堆dump: +> +> `-XX:+HeapDumpOnOutOfMemoryError` `-XX:HeapDumpPath=$CATALINA_HOME/logs` +> +> ② OOM发生后的执行动作: +> +> `-XX:OnOutOfMemoryError=$CATALINA_HOME/bin/stop.sh` +> +> `-XX:OnOutOfMemoryError=$CATALINA_HOME/bin/restart.sh` +> +> OOM之后除了保留堆dump外,根据管理策略选择合适的运行脚本。 + + + +# GC + +## 寻找垃圾算法 + +![寻找垃圾算法](images/JAVA/寻找垃圾算法.png) + +### 引用计数法 + +引用计数法(Reference Count)会给对象中添加一个引用计数器,每当有一个地方引用它的时候,计数器的值就 +1 ,当引用失效时,计数器值就 -1 ,计数器的值为 0 的对象不可能在被使用,这个时候就可以判定这个对象是垃圾。 + +![引用计数法](images/JAVA/引用计数法.png) + +当图中的数值变成0时,这个时候使用引用计数算法就可以判定它是垃圾了,但是引用计数法不能解决一个问题,就是当对象是循环引用的时候,计数器值都不为0,这个时候引用计数器无法通知GC收集器来回收他们,如下图所示: + +![引用计数法-问题](images/JAVA/引用计数法-问题.png) + +这个时候就需要使用到我们的根可达算法。 + + + +### 可达性分析 + +根可达算法(Root Searching)的意思是说从根上开始搜索,当一个程序启动后,马上需要的那些个对象就叫做根对象,所谓的根可达算法就是首先找到根对象,然后跟着这根线一直往外找到那些有用的。常见的GC roots如下: + +- **线程栈变量:** 线程里面会有线程栈和main栈帧,从这个main() 里面开始的这些对象都是我们的根对象 + +- **静态变量:** 一个class 它有一个静态的变量,load到内存之后马上就得对静态变量进行初始化,所以静态变量到的对象这个叫做根对象 + +- **常量池:** 如果你这个class会用到其他的class的那些个类的对象,这些就是根对象 + +- **JNI:** 如果我们调用了 C和C++ 写的那些本地方法所用到的那些个类或者对象 + +![根可达算法](images/JAVA/根可达算法.png) + +图中的 object5 和object6 虽然他们之间互相引用了,但是从根找不到它,所以就是垃圾,而object8没有任何引用自然而然也是垃圾,其他的Object对象都有可以从根找到的,所以是有用的,不会被垃圾回收掉。 + + + +**GC Root** + +GC Roots 是一组必须活跃的引用。用通俗的话来说,就是程序接下来通过直接引用或者间接引用,能够访问到的潜在被使用的对象。GC Roots 包括: + +- **Java 线程中,当前所有正在被调用的方法的引用类型参数、局部变量、临时值等。也就是与我们栈帧相关的各种引用** +- **所有当前被加载的 Java 类** +- **Java 类的引用类型静态变量** +- **运行时常量池里的引用类型常量(String 或 Class 类型)** +- **JVM 内部数据结构的一些引用,比如 sun.jvm.hotspot.memory.Universe 类** +- **用于同步的监控对象,比如调用了对象的 wait() 方法** +- **JNI handles,包括 global handles 和 local handles** + +这些 GC Roots 大体可以分为三大类: + +- **活动线程相关的各种引用** +- **类的静态变量的引用** +- **JNI 引用** + +有两个注意点: + +- **我们这里说的是活跃的引用,而不是对象,对象是不能作为 GC Roots 的** +- **GC 过程是找出所有活对象,并把其余空间认定为“无用”;而不是找出所有死掉的对象,并回收它们占用的空间。所以,哪怕 JVM 的堆非常的大,基于 tracing 的 GC 方式,回收速度也会非常快** + + + +## 清理垃圾算法 + +清理垃圾算法又叫内存回收算法。 + +### 标记(Mark) + +垃圾回收的第一步,就是找出活跃的对象。根据 GC Roots 遍历所有的可达对象,这个过程,就叫作标记。 + +![标记(Mark)](images/JAVA/标记(Mark).png) + +如图所示,圆圈代表的是对象。绿色的代表 GC Roots,红色的代表可以追溯到的对象。可以看到标记之后,仍然有多个灰色的圆圈,它们都是被回收的对象。 + + + +### 清除(Sweep) + +清除阶段就是把未被标记的对象回收掉。 + +![清除(Sweep)](images/JAVA/清除(Sweep).png) + +但是这种简单的清除方式,有一个明显的弊端,那就是碎片问题。比如我申请了 1k、2k、3k、4k、5k 的内存。 + +![清除(Sweep)-内存](images/JAVA/清除(Sweep)-内存.jpg) + +由于某种原因 ,2k 和 4k 的内存,我不再使用,就需要交给垃圾回收器回收。 + +![清除(Sweep)-回收](images/JAVA/清除(Sweep)-回收.jpg) + +这个时候,我应该有足足 6k 的空闲空间。接下来,我打算申请另外一个 5k 的空间,结果系统告诉我内存不足了。系统运行时间越长,这种碎片就越多。在很久之前使用 Windows 系统时,有一个非常有用的功能,就是内存整理和磁盘整理,运行之后有可能会显著提高系统性能。这个出发点是一样的。 + + + +### 复制(Copying) + +![复制(Copying)算法](images/JAVA/复制(Copying)算法.png) + +**优点** + +- 因为是对整个半区进行内存回收,内存分配时不用考虑内存碎片等情况。实现简单,效率较高 + +**不足之处** + +- 既然要复制,需要提前预留内存空间,有一定的浪费 +- 在对象存活率较高时,需要复制的对象较多,效率将会变低 + + + +### 整理(Compact) + +其实,不用分配一个对等的额外空间,也是可以完成内存的整理工作。可以把内存想象成一个非常大的数组,根据随机的 index 删除了一些数据。那么对整个数组的清理,其实是不需要另外一个数组来进行支持的,使用程序就可以实现。它的主要思路,就是移动所有存活的对象,且按照内存地址顺序依次排列,然后将末端内存地址以后的内存全部回收。 + +![整理(Compact)](images/JAVA/整理(Compact).png) + +但是需要注意,这只是一个理想状态。对象的引用关系一般都是非常复杂的,我们这里不对具体的算法进行描述。你只需要了解,从效率上来说,一般整理算法是要低于复制算法的。 + + + +### 扩展回收算法 + +目前JVM的垃圾回收器都是对几种朴素算法的发扬光大(没有最优的算法,只有最合适的算法): + +- **复制算法(Copying)**:复制算法是所有算法里面效率最高的,缺点是会造成一定的空间浪费 +- **标记-清除(Mark-Sweep)**:效率一般,缺点是会造成内存碎片问题 +- **标记-整理(Mark-Compact)**:效率比前两者要差,但没有空间浪费,也消除了内存碎片问题 + +![收集算法](images/JAVA/收集算法.png) + + + +#### 标记清除(Mark-Sweep) + +![标记清除(Mark-Sweep)算法](images/JAVA/标记清除(Mark-Sweep)算法.png) + +首先从 GC Root 开始遍历对象图,并标记(Mark)所遇到的每个对象,标识出所有要回收的对象。然后回收器检查堆中每一个对象,并将所有未被标记的对象进行回收。 + +**不足之处** + +- 标记、清除的效率都不高 +- 清除后产生大量的内存碎片,空间碎片太多会导致在分配大对象时无法找到足够大的连续内存,从而不得不触发另一次垃圾回收动作 + + + +#### 标记整理(Mark-Compact) + +![标记整理(Mark-Compact)算法](images/JAVA/标记整理(Mark-Compact)算法.png) + +与标记清除算法类似,但不是在标记完成后对可回收对象进行清理,而是将所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。 + +**优点** + +- 消除了标记清除导致的内存分散问题,也消除了复制算法中内存减半的高额代价 + +**不足之处** + +- 效率低下,需要标记所有存活对象,还要标记所有存活对象的引用地址。效率上低于复制算法 + + + +#### 分代收集(Generational Collection) + +研究表明大部分对象可以分为两类: + +- 大部分对象的生命周期都很短 +- 其他对象则很可能会存活很长时间 + +根据对象存活周期的不同将内存划分为几块。对不同周期的对象采取不同的收集算法: + +- **新生代**:每次垃圾收集会有大批对象回收,所以采取复制算法 +- **老年代**:对象存活率高,采取标记清理或者标记整理算法 + + + +**① 年轻代(Young Generation)** + +年轻代使用的垃圾回收算法是复制算法。因为年轻代发生 GC 后,只会有非常少的对象存活,复制这部分对象是非常高效的。但复制算法会造成一定的空间浪费,所以年轻代中间也会分很多区域。 + +![年轻代](images/JAVA/年轻代.jpg) + +如图所示,年轻代分为:**1个伊甸园空间(Eden )**,**2个幸存者空间(Survivor )**。当年轻代中的 Eden 区分配满的时候,就会触发年轻代的 GC(Minor GC)。具体过程如下: + +- 在 Eden 区执行了第一次 GC 之后,存活的对象会被移动到其中一个 Survivor 分区(以下简称from) +- Eden 区再次 GC,这时会采用复制算法,将 Eden 和 from 区一起清理。存活的对象会被复制到 to 区,然后只需要清空 from 区就可以了 + +在这个过程中,总会有1个 Survivor 分区是空置的。Eden、from、to 的默认比例是 8:1:1,所以只会造成 10% 的空间浪费。这个比例,是由参数 **-XX:SurvivorRatio** 进行配置的(默认为 8)。 + + + +**② 老年代(Old/Tenured Generation)** + +老年代一般使用“**标记-清除**”、“**标记-整理**”算法,因为老年代的对象存活率一般是比较高的,空间又比较大,拷贝起来并不划算,还不如采取就地收集的方式。对象进入老年代的途径如下: + +- **提升(Promotion)** + + 如果对象够老,会通过“提升”进入老年代 + +- **分配担保** + + 年轻代回收后存活的对象大于10%时,因Survivor空间不够存储,对象就会直接在老年代上分配 + +- **大对象直接在老年代分配** + + 超出某个大小的对象将直接在老年代分配 + +- **动态对象年龄判定** + + 有的垃圾回收算法,并不要求 age 必须达到 15 才能晋升到老年代,它会使用一些动态的计算方法。 + + 比如,如果幸存区中相同年龄对象大小的和,大于幸存区的一半,大于或等于 age 的对象将会直接进入老年代。 + + + +## GC垃圾收集器 + +![收集器](images/JAVA/收集器.jpg) + +GC垃圾收集器的JVM配置参数: + +- **-XX:+UseSerialGC**:年轻代和老年代都用串行收集器 +- **-XX:+UseParNewGC**:年轻代使用 ParNew,老年代使用 Serial Old +- **-XX:+UseParallelGC**:年轻代使用 ParallerGC,老年代使用 Serial Old +- **-XX:+UseParallelOldGC**:新生代和老年代都使用并行收集器 +- **-XX:+UseConcMarkSweepGC**:表示年轻代使用 ParNew,老年代的用 CMS +- **-XX:+UseG1GC**:使用 G1垃圾回收器 +- **-XX:+UseZGC**:使用 ZGC 垃圾回收器 + + + +### 年轻代收集器 + +#### Serial收集器 + +处理GC的只有一条线程,并且在垃圾回收的过程中暂停一切用户线程。最简单的垃圾回收器,但千万别以为它没有用武之地。因为简单,所以高效,它通常用在客户端应用上。因为客户端应用不会频繁创建很多对象,用户也不会感觉出明显的卡顿。相反,它使用的资源更少,也更轻量级。 + +![Serial收集器](images/JAVA/Serial收集器.jpg) + + + +#### ParNew收集器 + +ParNew是Serial的多线程版本。由多条GC线程并行地进行垃圾清理。清理过程依然要停止用户线程。ParNew 追求“低停顿时间”,与 Serial 唯一区别就是使用了多线程进行垃圾收集,在多 CPU 环境下性能比 Serial 会有一定程度的提升;但线程切换需要额外的开销,因此在单 CPU 环境中表现不如 Serial。 + +![ParNew收集器](images/JAVA/ParNew收集器.jpg) + + + +#### Parallel Scavenge收集器 + +另一个多线程版本的垃圾回收器。它与ParNew的主要区别是: + +- **Parallel Scavenge**:追求CPU吞吐量,能够在较短时间完成任务,适合没有交互的后台计算。弱交互强计算 +- **ParNew**:追求降低用户停顿时间,适合交互式应用。强交互弱计算 + + + +### 老年代收集器 + +#### Serial Old收集器 + +与年轻代的 Serial 垃圾收集器对应,都是单线程版本,同样适合客户端使用。年轻代的 Serial,使用复制算法。老年代的 Old Serial,使用标记-整理算法。 + +![SerialOld收集器](images/JAVA/SerialOld收集器.jpg) + + + +#### Parallel Old收集器 + +Parallel Old 收集器是 Parallel Scavenge 的老年代版本,追求 CPU 吞吐量。 + +![ParallelOld收集器](images/JAVA/ParallelOld收集器.jpg) + + + +#### CMS收集器 + +**并发标记清除(Concurrent Mark Sweep,CMS)垃圾回收器**,是一款致力于获取最短停顿时间的收集器,使用多个线程来扫描堆内存并标记可被清除的对象,然后清除标记的对象。在下面两种情形下会暂停工作线程: + +- 在老年代中标记引用对象的时候 + +- 在做垃圾回收的过程中堆内存中有变化发生 + + +对比与并行垃圾回收器,CMS回收器使用更多的CPU来保证更高的吞吐量。如果我们可以有更多的CPU用来提升性能,那么CMS垃圾回收器是比并行回收器更好的选择。使用 `-XX:+UseParNewGCJVM` 参数来开启使用CMS垃圾回收器。 + +![CMS收集器](images/JAVA/CMS收集器.png) + +**主要流程如下**: + +- 初始标记(CMS initial mark) +- 并发标记(CMS concurrenr mark) +- 重新标记(CMS remark) +- 并发清除(CMS concurrent sweep) + + + +**优点**: + +- 并发收集 +- 停顿时间最短 + +**缺点**: + +- 并发收集占据一定CPU资源,导致程序GC过程中变慢(吞吐量下降) +- 无法处理浮动垃圾,可能出现”Concurrent Mode Failure“失败而导致另一次Full GC +- 因为基于”标记-清除算法“导致空间碎片过多,可能因此在分配对象时引起另一次GC + + + +**作用内存区域**:老年代 + +**适用场景**:对停顿时间敏感的场合 + +**算法类型**:标记-清除 + + + +### 新生代和老年代收集 + +#### G1收集器 + +**G1垃圾回收器** 应用于大的堆内存空间。它将堆内存空间划分为不同的区域,对各个区域并行地做回收工作。G1在回收内存空间后还立即堆空闲空间做整合工作以减少碎片。CMS却是在全部停止(stop the world,STW)时执行内存整合工作。对于不同的区域G1根据垃圾的数量决定优先级。使用 `-XX:UseG1GCJVM` 参数来开启使用G1垃圾回收器。 + +![G1收集器](images/JAVA/G1收集器.jpg) + +**主要流程如下**: + +- 初始标记(Initial Marking) +- 并发标记(Concurrenr Marking) +- 最终标记(Final Marking) +- 筛选回收(Live Data Counting And Evacution) + + + +**优点**: + +- 并行与并发,充分发挥多核优势 +- 分代收集,所以不需要与其它收集器配合即可工作 +- 空间整合,整体来看基于”标记-整理算法“,局部采用”复制算法“都不会产生内存碎片 +- 可以指定GC最大停顿时长 + +**缺点**: + +- 需要记忆集来记录新生代和老年代之间的引用关系 +- 需要占用大量的内存,可能达到整个堆内存容量的20%甚至更多 + + + +**作用内存区域**:跨代 + +**适用场景**:作为关注停顿时间的场景的收集器备选方案 + +**算法类型**:整体来看基于”标记-整理算法“,局部采用"复制算法" + + + +#### ZGC收集器 + +Z Garbage Collector,简称 ZGC,是 JDK 11 中新加入的尚在实验阶段的低延迟垃圾收集器。它和 Shenandoah 同属于超低延迟的垃圾收集器,但在吞吐量上比 Shenandoah 有更优秀的表现,甚至超过了 G1,接近了“吞吐量优先”的 Parallel 收集器组合,可以说近乎实现了“鱼与熊掌兼得”。 + +与CMS中的ParNew和G1类似,ZGC也采用标记-复制算法,不过ZGC对该算法做了重大改进:ZGC在标记、转移和重定位阶段几乎都是并发的,这是ZGC实现停顿时间小于10ms目标的最关键原因。ZGC垃圾回收周期如下图所示: + +![ZGC收集器](images/JAVA/ZGC收集器.jpg) + +ZGC只有三个STW阶段:**初始标记**,**再标记**,**初始转移**。其中,初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎所有暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加。与ZGC对比,G1的转移阶段完全STW的,且停顿时间随存活对象的大小增加而增加。 + + + +**ZGC 的内存布局** + +与 Shenandoah 和 G1 一样,ZGC 也采用基于 Region 的堆内存布局,但与它们不同的是, ZGC 的 Region 具有动态性,也就是可以动态创建和销毁,容量大小也是动态的,有大、中、小三类容量: + +![ZGC内存布局](images/JAVA/ZGC内存布局.jpg) + +- 小型 Region (Small Region):容量固定为 2MB,用于放置小于 256KB 的小对象 +- 中型 Region (M edium Region):容量固定为 32MB,用于放置大于等于 256KB 但小于 4MB 的对象 +- 大型 Region (Large Region):容量不固定,可以动态变化,但必须为 2MB 的整数倍,用于放置 4MB 或以上的大对象。每个大型 Region 中只会存放一个大对象,这也预示着虽然名字叫作“大型 Region”,但它的实际容量完全有可能小于中型 Region,最小容量可低至 4MB + +在 JDK 11 及以上版本,可以通过以下参数开启 ZGC:`-XX:+UnlockExperimentalVMOptions -XX:+UseZGC` 。 + + + +#### Shenandoah收集器 + +Shenandoah 与 G1 有很多相似之处,比如都是基于 Region 的内存布局,都有用于存放大对象的 Humongous Region,默认回收策略也是优先处理回收价值最大的 Region。不过也有三个重大的区别: + +- Shenandoah支持并发的整理算法,G1整理阶段虽是多线程并行,但无法与用户程序并发执行 +- 默认不使用分代收集理论 +- 使用连接矩阵 (Connection Matrix)记录跨Region的引用关系,替换掉了G1中的记忆级(Remembered Set),内存和计算成本更低 + +Shenandoah 收集器的工作原理相比 G1 要复杂不少,其运行流程示意图如下: + +![Shenandoah收集器运行流程](images/JAVA/Shenandoah收集器运行流程.jpg) + +可见Shenandoah的并发程度明显比G1更高,只需要在初始标记、最终标记、初始引用更新和最终引用更新这几个阶段进行短暂的“Stop The World”,其他阶段皆可与用户程序并发执行,其中最重要的并发标记、并发回收和并发引用更新详情如下: + +- **并发标记( Concurrent Marking)** +- **并发回收( Concurrent Evacuation)** +- **并发引用更新( Concurrent Update Reference)** + +Shenandoah 的高并发度让它实现了超低的停顿时间,但是更高的复杂度也伴随着更高的系统开销,这在一定程度上会影响吞吐量,下图是 Shenandoah 与之前各种收集器在停顿时间维度和系统开销维度上的对比: + +![收集器停顿时间和系统开销对比](images/JAVA/收集器停顿时间和系统开销对比.png) + +OracleJDK 并不支持 Shenandoah,如果你用的是 OpenJDK 12 或某些支持 Shenandoah 移植版的 JDK 的话,可以通过以下参数开启 Shenandoah:`-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC` 。 + + + +## GC日志 + +### 日志格式 + +**ParallelGC YoungGC日志** + +![ParallelGCYoungGC日志](images/JAVA/ParallelGCYoungGC日志.jpg) + +**ParallelGC FullGC日志** + +![ParallelGCFullGC日志](images/JAVA/ParallelGCFullGC日志.jpg) + + + +### 最佳实践 + +在不同的 JVM 的不垃圾回收器上,看参数默认是什么,不要轻信别人的建议,命令行示例如下: + +```shell +java -XX:+PrintFlagsFinal -XX:+UseG1GC  2>&1 | grep UseAdaptiveSizePolicy +``` + +PrintCommandLineFlags:通过它,你能够查看当前所使用的垃圾回收器和一些默认的值。 + +```shell +# java -XX:+PrintCommandLineFlags -version +-XX:InitialHeapSize=127905216 -XX:MaxHeapSize=2046483456 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC +openjdk version "1.8.0_41" +OpenJDK Runtime Environment (build 1.8.0_41-b04) +OpenJDK 64-Bit Server VM (build 25.40-b25, mixed mode) +``` + + + +G1垃圾收集器JVM参数最佳实践: + +```shell +# 1.基本参数 +-server # 服务器模式 +-Xmx12g # 初始堆大小 +-Xms12g # 最大堆大小 +-Xss256k # 每个线程的栈内存大小 +-XX:+UseG1GC # 使用 G1 (Garbage First) 垃圾收集器 +-XX:MetaspaceSize=256m # 元空间初始大小 +-XX:MaxMetaspaceSize=1g # 元空间最大大小 +-XX:MaxGCPauseMillis=200 # 每次YGC / MixedGC 的最多停顿时间 (期望最长停顿时间) + +# 2.必备参数 +-XX:+PrintGCDetails # 输出详细GC日志 +-XX:+PrintGCDateStamps # 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800) +-XX:+PrintTenuringDistribution # 打印对象分布:为了分析GC时的晋升情况和晋升导致的高暂停,看对象年龄分布日志 +-XX:+PrintHeapAtGC # 在进行GC的前后打印出堆的信息 +-XX:+PrintReferenceGC # 打印Reference处理信息:强引用/弱引用/软引用/虚引用/finalize方法万一有问题 +-XX:+PrintGCApplicationStoppedTime # 打印STW时间 +-XX:+PrintGCApplicationConCurrentTime # 打印GC间隔的服务运行时长 + +# 3.日志分割参数 +-XX:+UseGCLogFileRotation # 开启日志文件分割 +-XX:NumberOfGCLogFiles=14 # 最多分割几个文件,超过之后从头文件开始写 +-XX:GCLogFileSize=32M # 每个文件上限大小,超过就触发分割 +-Xloggc:/path/to/gc-%t.log # GC日志输出的文件路径,使用%t作为日志文件名,即gc-2021-03-29_20-41-47.log +``` + +CMS垃圾收集器JVM参数最佳实践: + +```shell +# 1.基本参数 +-server # 服务器模式 +-Xmx4g # JVM最大允许分配的堆内存,按需分配 +-Xms4g # JVM初始分配的堆内存,一般和Xmx配置成一样以避免每次gc后JVM重新分配内存 +-Xmn256m # 年轻代内存大小,整个JVM内存=年轻代 + 年老代 + 持久代 +-Xss512k # 设置每个线程的堆栈大小 +-XX:+DisableExplicitGC # 忽略手动调用GC, System.gc()的调用就会变成一个空调用,完全不触发GC +-XX:+UseConcMarkSweepGC # 使用 CMS 垃圾收集器 +-XX:+CMSParallelRemarkEnabled # 降低标记停顿 +-XX:+UseCMSCompactAtFullCollection # 在FULL GC的时候对年老代的压缩 +-XX:+UseFastAccessorMethods # 原始类型的快速优化 +-XX:+UseCMSInitiatingOccupancyOnly # 使用手动定义初始化定义开始CMS收集 +-XX:LargePageSizeInBytes=128m # 内存页的大小 +-XX:CMSInitiatingOccupancyFraction=70 # 使用cms作为垃圾回收使用70%后开始CMS收集 + +# 2.必备参数 +-XX:+PrintGCDetails # 输出详细GC日志 +-XX:+PrintGCDateStamps # 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800) +-XX:+PrintTenuringDistribution # 打印对象分布:为分析GC时的晋升情况和晋升导致的高暂停,看对象年龄分布 +-XX:+PrintHeapAtGC # 在进行GC的前后打印出堆的信息 +-XX:+PrintReferenceGC # 打印Reference处理信息:强引用/弱引用/软引用/虚引用/finalize方法万一有问题 +-XX:+PrintGCApplicationStoppedTime # 打印STW时间 +-XX:+PrintGCApplicationConCurrentTime # 打印GC间隔的服务运行时长 + +# 3.日志分割参数 +-XX:+UseGCLogFileRotation # 开启日志文件分割 +-XX:NumberOfGCLogFiles=14 # 最多分割几个文件,超过之后从头文件开始写 +-XX:GCLogFileSize=32M # 每个文件上限大小,超过就触发分割 +-Xloggc:/path/to/gc-%t.log # GC日志输出的文件路径,使用%t作为日志文件名,即gc-2021-03-29_20-41-47.log +``` + + + +## GC场景 + +### Full GC场景 + +**场景一:System.gc()方法的调用** + +此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过 `-XX:+ DisableExplicitGC` 来禁止RMI调用System.gc()。 + + + +**场景二:老年代代空间不足** + +- 原因分析:新生代对象转入老年代、创建大对象或数组时,执行FullGC后仍空间不足 +- 抛出错误:`Java.lang.OutOfMemoryError: Java heap space` +- 解决办法: + - 尽量让对象在YoungGC时被回收 + - 让对象在新生代多存活一段时间 + - 不要创建过大的对象或数组 + + + +**场景三:永生区空间不足** + +- 原因分析:JVM方法区因系统中要加载的类、反射的类和调用的方法较多而可能会被占满 +- 抛出错误:`java.lang.OutOfMemoryError: PermGen space` +- 解决办法: + - 增大老年代空间大小 + - 使用CMS GC + + + +**场景四:CMS GC时出现promotion failed和concurrent mode failure** + +- 原因分析: + - `promotion failed`:是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成 + - `concurrent mode failure`:是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的 +- 抛出错误:GC日志中存在`promotion failed`和`concurrent mode` +- 解决办法:增大幸存区或老年代 + + + +**场景五:堆中分配很大的对象** + +- 原因分析:创建大对象或长数据时,此对象直接进入老年代,而老年代虽有很大剩余空间,但没有足够的连续空间来存储 +- 抛出错误:触发FullGC +- 解决办法:配置-XX:+UseCMSCompactAtFullCollection开关参数,用于享受用完FullGC后额外免费赠送的碎片整理过程,但同时停顿时间不得不变长。可以使用-XX:CMSFullGCsBeforeCompaction参数来指定执行多少次不压缩的FullGC后才执行一次压缩 + + + +### CMS GC场景 + +**场景一:动态扩容引起的空间震荡** + +- **现象** + + 服务**刚刚启动时 GC 次数较多**,最大空间剩余很多但是依然发生 GC,这种情况我们可以通过观察 GC 日志或者通过监控工具来观察堆的空间变化情况即可。GC Cause 一般为 Allocation Failure,且在 GC 日志中会观察到经历一次 GC ,堆内各个空间的大小会被调整,如下图所示: + + ![动态扩容引起的空间震荡](images/JAVA/动态扩容引起的空间震荡.png) + +- **原因** + + 在 JVM 的参数中 `-Xms` 和 `-Xmx` 设置的不一致,在初始化时只会初始 `-Xms` 大小的空间存储信息,每当空间不够用时再向操作系统申请,这样的话必然要进行一次 GC。另外,如果空间剩余很多时也会进行缩容操作,JVM 通过 `-XX:MinHeapFreeRatio` 和 `-XX:MaxHeapFreeRatio` 来控制扩容和缩容的比例,调节这两个值也可以控制伸缩的时机。整个伸缩的模型理解可以看这个图,当 committed 的空间大小超过了低水位/高水位的大小,capacity 也会随之调整: + + ![JVM内存伸缩模型](images/JAVA/JVM内存伸缩模型.png) + +- **策略** + + 观察 CMS GC 触发时间点 Old/MetaSpace 区的 committed 占比是不是一个固定的值,或者像上文提到的观察总的内存使用率也可以。尽量 **将成对出现的空间大小配置参数设置成固定的** ,如 `-Xms` 和 `-Xmx`,`-XX:MaxNewSize` 和 `-XX:NewSize`,`-XX:MetaSpaceSize` 和 `-XX:MaxMetaSpaceSize` 等。 + + + +**场景二:显式GC的去与留** + +- **现象** + + 除了扩容缩容会触发 CMS GC 之外,还有 Old 区达到回收阈值、MetaSpace 空间不足、Young 区晋升失败、大对象担保失败等几种触发条件,如果这些情况都没有发生却触发了 GC ?这种情况有可能是代码中手动调用了 System.gc 方法,此时可以找到 GC 日志中的 GC Cause 确认下。 + +- **原因** + + **保留 System.gc**:CMS中使用 Foreground Collector 时将会带来非常长的 STW,在应用程序中 System.gc 被频繁调用,那就非常危险。增加 `-XX:+DisableExplicitGC` 参数则可以禁用。**去掉 System.gc**:禁用掉后会带来另一个内存泄漏的问题,为 DirectByteBuffer 分配空间过程中会显式调用 System.gc ,希望通过 Full GC 来强迫已经无用的 DirectByteBuffer 对象释放掉它们关联的 Native Memory,如Netty等。 + +- **策略** + + 无论是保留还是去掉都会有一定的风险点,不过目前互联网中的 RPC 通信会大量使用 NIO,所以建议保留。此外 JVM 还提供了 `-XX:+ExplicitGCInvokesConcurrent` 和 `-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses` 参数来将 System.gc 的触发类型从 Foreground 改为 Background,同时 Background 也会做 Reference Processing,这样的话就能大幅降低了 STW 开销,同时也不会发生 NIO Direct Memory OOM。 + + + +**场景三:MetaSpace区OOM** + +- **现象** + + JVM 在启动后或者某个时间点开始, **MetaSpace 的已使用大小在持续增长,同时每次 GC 也无法释放,调大 MetaSpace 空间也无法彻底解决** 。 + +- **原因** + + 在讨论为什么会 OOM 之前,我们先来看一下这个区里面会存什么数据,Java 7 之前字符串常量池被放到了 Perm 区,所有被 intern 的 String 都会被存在这里,由于 String.intern 是不受控的,所以 `-XX:MaxPermSize` 的值也不太好设置,经常会出现 `java.lang.OutOfMemoryError: PermGen space` 异常,所以在 Java 7 之后常量池等字面量(Literal)、类静态变量(Class Static)、符号引用(Symbols Reference)等几项被移到 Heap 中。而 Java 8 之后 PermGen 也被移除,取而代之的是 MetaSpace。由场景一可知,为了避免弹性伸缩带来的额外 GC 消耗,我们会将 `-XX:MetaSpaceSize` 和 `-XX:MaxMetaSpaceSize` 两个值设置为固定的,但这样也会导致在空间不够的时候无法扩容,然后频繁地触发 GC,最终 OOM。所以关键原因是 **ClassLoader不停地在内存中load了新的Class ,一般这种问题都发生在动态类加载等情况上。** + +- **策略** + + 可以 dump 快照之后通过 JProfiler 或 MAT 观察 Classes 的 Histogram(直方图) 即可,或者直接通过命令即可定位, jcmd 打几次 Histogram 的图,看一下具体是哪个包下的 Class 增加较多就可以定位。如果无法从整体的角度定位,可以添加 `-XX:+TraceClassLoading` 和 `-XX:+TraceClassUnLoading` 参数观察详细的类加载和卸载信息。 + + + +**场景四:过早晋升** + +- **现象** + + 这种场景主要发生在分代的收集器上面,专业的术语称为“Premature Promotion”。90% 的对象朝生夕死,只有在 Young 区经历过几次 GC 的洗礼后才会晋升到 Old 区,每经历一次 GC 对象的 GC Age 就会增长 1,最大通过 `-XX:MaxTenuringThreshold` 来控制。过早晋升一般不会直接影响 GC,总会伴随着浮动垃圾、大对象担保失败等问题,但这些问题不是立刻发生的,我们可以观察以下几种现象来判断是否发生了过早晋升: + + - **分配速率接近于晋升速率** ,对象晋升年龄较小。 + + GC 日志中出现“Desired survivor size 107347968 bytes, **new threshold 1(max 6)** ”等信息,说明此时经历过一次 GC 就会放到 Old 区。 + + - **Full GC 比较频繁** ,且经历过一次 GC 之后 Old 区的 **变化比例非常大** 。 + + 如Old区触发回收阈值是80%,经历一次GC之后下降到了10%,这说明Old区70%的对象存活时间其实很短。 + + ![FullGC变化比例大](images/JAVA/FullGC变化比例大.png) + + 过早晋升的危害: + + - Young GC 频繁,总的吞吐量下降 + - Full GC 频繁,可能会有较大停顿 + +- **原因** + + 主要的原因有以下两点: + + - **Young/Eden 区过小**: 过小的直接后果就是 Eden 被装满的时间变短,本应该回收的对象参与了 GC 并晋升,Young GC 采用的是复制算法,由基础篇我们知道 copying 耗时远大于 mark,也就是 Young GC 耗时本质上就是 copy 的时间(CMS 扫描 Card Table 或 G1 扫描 Remember Set 出问题的情况另说),没来及回收的对象增大了回收的代价,所以 Young GC 时间增加,同时又无法快速释放空间,Young GC 次数也跟着增加 + - **分配速率过大**: 可以观察出问题前后 Mutator 的分配速率,如果有明显波动可以尝试观察网卡流量、存储类中间件慢查询日志等信息,看是否有大量数据被加载到内存中 + +- **策略** + - 如果是 **Young/Eden 区过小** ,可以在总的 Heap 内存不变的情况下适当增大Young区。一般情况下Old的大小应当为活跃对象的2~3倍左右,考虑到浮动垃圾问题最好在3倍左右,剩下的都可以分给Young区 + + - 过早晋升优化来看,原配置为Young 1.2G+Old 2.8G,通过观察CMS GC的情况找到存活对象大概为 300~400M,于是调整Old 1.5G左右,剩下2.5G分给Young 区。仅仅调了一个Young区大小参数(`-Xmn`),整个 JVM 一分钟Young GC从26次降低到了11次,单次时间也没有增加,总的GC时间从1100ms降低到了500ms,CMS GC次数也从40分钟左右一次降低到了7小时30分钟一次: + + ![过早晋升优化GC](images/JAVA/过早晋升优化GC.png) + + ![过早晋升优化Oldgen](images/JAVA/过早晋升优化Oldgen.png) + + 如果是分配速率过大: + + - **偶发较大** :通过内存分析工具找到问题代码,从业务逻辑上做一些优化 + - **一直较大** :当前的 Collector 已经不满足 Mutator 的期望了,这种情况要么扩容 Mutator 的 VM,要么调整 GC 收集器类型或加大空间 + +- **小结** + + 过早晋升问题一般不会特别明显,但日积月累之后可能会爆发一波收集器退化之类的问题,所以我们还是要提前避免掉的,可以看看自己系统里面是否有这些现象,如果比较匹配的话,可以尝试优化一下。一行代码优化的 ROI 还是很高的。如果在观察 Old 区前后比例变化的过程中,发现可以回收的比例非常小,如从 80% 只回收到了 60%,说明我们大部分对象都是存活的,Old 区的空间可以适当调大些。 + + + +**场景五:CMS Old GC频繁** + +- **现象** + + Old 区频繁的做 CMS GC,但是每次耗时不是特别长,整体最大 STW 也在可接受范围内,但由于 GC 太频繁导致吞吐下降比较多。 + +- **原因** + + 这种情况比较常见,基本都是一次 Young GC 完成后,负责处理 CMS GC 的一个后台线程 concurrentMarkSweepThread 会不断地轮询,使用 `shouldConcurrentCollect()` 方法做一次检测,判断是否达到了回收条件。如果达到条件,使用 `collect_in_background()` 启动一次 Background 模式 GC。轮询的判断是使用 `sleepBeforeNextCycle()` 方法,间隔周期为 `-XX:CMSWaitDuration` 决定,默认为 2s。 + +- **策略** + + 处理这种常规内存泄漏问题基本是一个思路,主要步骤如下: + + ![CMSOldGC频繁](images/JAVA/CMSOldGC频繁.png) + + Dump Diff 和 Leak Suspects 比较直观,这里说下其它几个关键点: + + - **内存 Dump**: 使用 jmap、arthas 等 dump 堆进行快照时记得摘掉流量,同时 **分别在 CMS GC 的发生前后分别 dump 一次** + - **分析 Top Component**: 要记得按照对象、类、类加载器、包等多个维度观察Histogram,同时使用 outgoing和incoming分析关联的对象,其次Soft Reference和Weak Reference、Finalizer 等也要看一下 + - **分析 Unreachable**: 重点看一下这个,关注下 Shallow 和 Retained 的大小。如下图所示的一次 GC 优化,就根据 Unreachable Objects 发现了 Hystrix 的滑动窗口问题。 + + ![分析Unreachable](images/JAVA/分析Unreachable.png) + + + +**场景六:单次CMS Old GC耗时长** + +- **现象** + + CMS GC 单次 STW 最大超过 1000ms,不会频繁发生,如下图所示最长达到了 8000ms。某些场景下会引起“雪崩效应”,这种场景非常危险,我们应该尽量避免出现。 + + ![CMSGC单次STW长](images/JAVA/CMSGC单次STW长.png) + +- **原因** + + CMS在回收的过程中,STW的阶段主要是 Init Mark 和 Final Remark 这两个阶段,也是导致CMS Old GC 最多的原因,另外有些情况就是在STW前等待Mutator的线程到达SafePoint也会导致时间过长,但这种情况较少。 + +- **策略** + + 知道了两个 STW 过程执行流程,我们分析解决就比较简单了,由于大部分问题都出在 Final Remark 过程,这里我们也拿这个场景来举例,主要步骤: + + - **【方向】** 观察详细 GC 日志,找到出问题时 Final Remark 日志,分析下 Reference 处理和元数据处理 real 耗时是否正常,详细信息需要通过 `-XX:+PrintReferenceGC` 参数开启。 **基本在日志里面就能定位到大概是哪个方向出了问题,耗时超过 10% 的就需要关注** 。 + + ```shell + 2019-02-27T19:55:37.920+0800: 516952.915: [GC (CMS Final Remark) 516952.915: [ParNew516952.939: [SoftReference, 0 refs, 0.0003857 secs]516952.939: [WeakReference, 1362 refs, 0.0002415 secs]516952.940: [FinalReference, 146 refs, 0.0001233 secs]516952.940: [PhantomReference, 0 refs, 57 refs, 0.0002369 secs]516952.940: [JNI Weak Reference, 0.0000662 secs] + [class unloading, 0.1770490 secs]516953.329: [scrub symbol table, 0.0442567 secs]516953.373: [scrub string table, 0.0036072 secs][1 CMS-remark: 1638504K(2048000K)] 1667558K(4352000K), 0.5269311 secs] [Times: user=1.20 sys=0.03, real=0.53 secs] + ``` + + - **【根因】** 有了具体的方向我们就可以进行深入的分析,一般来说最容易出问题的地方就是 Reference 中的 FinalReference 和元数据信息处理中的 scrub symbol table 两个阶段,想要找到具体问题代码就需要内存分析工具 MAT 或 JProfiler 了,注意要 dump 即将开始 CMS GC 的堆。在用 MAT 等工具前也可以先用命令行看下对象 Histogram,有可能直接就能定位问题。 + - 对 FinalReference 的分析主要观察 `java.lang.ref.Finalizer` 对象的 dominator tree,找到泄漏的来源。经常会出现问题的几个点有 Socket 的 `SocksSocketImpl` 、Jersey 的 `ClientRuntime`、MySQL 的 `ConnectionImpl` 等等 + - scrub symbol table 表示清理元数据符号引用耗时,符号引用是 Java 代码被编译成字节码时,方法在 JVM 中的表现形式,生命周期一般与 Class 一致,当 `_should_unload_classes` 被设置为 true 时在 `CMSCollector::refProcessingWork()` 中与 Class Unload、String Table 一起被处理 + + - **【策略】** 知道 GC 耗时的根因就比较好处理了,这种问题不会大面积同时爆发,不过有很多时候单台 STW 的时间会比较长,如果业务影响比较大,及时摘掉流量,具体后续优化策略如下: + - FinalReference:找到内存来源后通过优化代码的方式来解决,如果短时间无法定位可以增加 `-XX:+ParallelRefProcEnabled` 对 Reference 进行并行处理 + - symbol table:观察 MetaSpace 区的历史使用峰值,以及每次 GC 前后的回收情况,一般没有使用动态类加载或者 DSL 处理等,MetaSpace 的使用率上不会有什么变化,这种情况可以通过 `-XX:-CMSClassUnloadingEnabled` 来避免 MetaSpace 的处理,JDK8 会默认开启 CMSClassUnloadingEnabled,这会使得 CMS 在 CMS-Remark 阶段尝试进行类的卸载 + +- **小结** + + 正常情况进行的 Background CMS GC,出现问题基本都集中在 Reference 和 Class 等元数据处理上,在 Reference 类的问题处理方面,不管是 FinalReference,还是 SoftReference、WeakReference 核心的手段就是找准时机 dump快照,然后用内存分析工具来分析。Class处理方面目前除了关闭类卸载开关,没有太好的方法。在 G1 中同样有 Reference 的问题,可以观察日志中的 Ref Proc,处理方法与 CMS 类似。 + + + +**场景七:内存碎片&收集器退化** + +- **现象** + + 并发的 CMS GC 算法,退化为 Foreground 单线程串行 GC 模式,STW 时间超长,有时会长达十几秒。其中 CMS 收集器退化后单线程串行 GC 算法有两种: + + - 带压缩动作的算法,称为 MSC,上面我们介绍过,使用标记-清理-压缩,单线程全暂停的方式,对整个堆进行垃圾收集,也就是真正意义上的 Full GC,暂停时间要长于普通 CMS + - 不带压缩动作的算法,收集 Old 区,和普通的 CMS 算法比较相似,暂停时间相对 MSC 算法短一些 + +- **原因** + + CMS 发生收集器退化主要有以下几种情况: + + - **晋升失败(Promotion Failed)** + + - **增量收集担保失败** + + - **显式 GC** + + - **并发模式失败(Concurrent Mode Failure)** + +- **策略** + + 分析到具体原因后,我们就可以针对性解决了,具体思路还是从根因出发,具体解决策略: + + - **内存碎片**: 通过配置 `-XX:UseCMSCompactAtFullCollection=true` 来控制 Full GC 的过程中是否进行空间的整理(默认开启,注意是 Full GC,不是普通 CMS GC),以及 `-XX: CMSFullGCsBeforeCompaction=n` 来控制多少次 Full GC 后进行一次压缩 + - **增量收集**: 降低触发 CMS GC 的阈值,即参数 `-XX:CMSInitiatingOccupancyFraction` 的值,让 CMS GC 尽早执行,以保证有足够的连续空间,也减少 Old 区空间的使用大小,另外需要使用 `-XX:+UseCMSInitiatingOccupancyOnly` 来配合使用,不然 JVM 仅在第一次使用设定值,后续则自动调整 + - **浮动垃圾**: 视情况控制每次晋升对象的大小,或者缩短每次 CMS GC 的时间,必要时可调节 NewRatio 的值。另外使用 `-XX:+CMSScavengeBeforeRemark` 在过程中提前触发一次Young GC,防止后续晋升过多对象 + +- **小结** + + 正常情况下触发并发模式的 CMS GC,停顿非常短,对业务影响很小,但 CMS GC 退化后,影响会非常大,建议发现一次后就彻底根治。只要能定位到内存碎片、浮动垃圾、增量收集相关等具体产生原因,还是比较好解决的,关于内存碎片这块,如果 `-XX:CMSFullGCsBeforeCompaction` 的值不好选取的话,可以使用 `-XX:PrintFLSStatistics` 来观察内存碎片率情况,然后再设置具体的值。最后就是在编码的时候也要避免需要连续地址空间的大对象的产生,如过长的字符串,用于存放附件、序列化或反序列化的 byte 数组等,还有就是过早晋升问题尽量在爆发问题前就避免掉。 + + + +**场景八:堆外内存OOM** + +- **现象** + + 内存使用率不断上升,甚至开始使用 SWAP 内存,同时可能出现 GC 时间飙升,线程被 Block 等现象, **通过 top 命令发现 Java 进程的 RES 甚至超过了** `**-Xmx**` **的大小** 。出现这些现象时,基本可确定是出现堆外内存泄漏。 + +- **原因** + + JVM 的堆外内存泄漏,主要有两种的原因: + + - 通过 `UnSafe#allocateMemory`,`ByteBuffer#allocateDirect` 主动申请了堆外内存而没有释放,常见于 NIO、Netty 等相关组件 + - 代码中有通过 JNI 调用 Native Code 申请的内存没有释放 + +- **策略** + + **原因一:主动申请未释放** + + **原因二:通过 JNI 调用的 Native Code 申请的内存未释放** + + + +**场景九:JNI引发的GC问题** + +- **现象** + + 在 GC 日志中,出现 GC Cause 为 GCLocker Initiated GC。 + + ```shell + 2020-09-23T16:49:09.727+0800: 504426.742: [GC (GCLocker Initiated GC) 504426.742: [ParNew (promotion failed): 209716K->6042K(1887488K), 0.0843330 secs] 1449487K->1347626K(3984640K), 0.0848963 secs] [Times: user=0.19 sys=0.00, real=0.09 secs]2020-09-23T16:49:09.812+0800: 504426.827: [Full GC (GCLocker Initiated GC) 504426.827: [CMS: 1341583K->419699K(2097152K), 1.8482275 secs] 1347626K->419699K(3984640K), [Metaspace: 297780K->297780K(1329152K)], 1.8490564 secs] [Times: user=1.62 sys=0.20, real=1.85 secs] + ``` + +- **原因** + + JNI(Java Native Interface)意为 Java 本地调用,它允许 Java 代码和其他语言写的 Native 代码进行交互。JNI 如果需要获取 JVM 中的 String 或者数组,有两种方式: + + - 拷贝传递 + - 共享引用(指针),性能更高 + + 由于 Native 代码直接使用了 JVM 堆区的指针,如果这时发生 GC,就会导致数据错误。因此,在发生此类 JNI 调用时,禁止 GC 的发生,同时阻止其他线程进入 JNI 临界区,直到最后一个线程退出临界区时触发一次 GC。 + +- **策略** + - 添加 `-XX+PrintJNIGCStalls` 参数,可以打印出发生 JNI 调用时的线程,进一步分析,找到引发问题的 JNI 调用 + - JNI 调用需要谨慎,不一定可以提升性能,反而可能造成 GC 问题 + - 升级 JDK 版本到 14,避免 [JDK-8048556](https://bugs.openjdk.java.net/browse/JDK-8048556) 导致的重复 GC + diff --git a/Middleware.md b/Middleware.md new file mode 100644 index 0000000..51fb8eb --- /dev/null +++ b/Middleware.md @@ -0,0 +1,5467 @@ +
Middleware
+ +Author:李茹钰(`echo`) + +Introduction:收纳技术相关的基础知识 `Redis`、`RocketMQ`、`Zookeeper`、`Netty`、`Tomcat` 等总结! + +[TOC] + +# SPI + +SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名,配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类,正因为该特性,我们可以很容易的通过 SPI 机制为程序提供拓展功能。 + + + +## Java SPI + +JDK 中 提供了一个 `SPI` 的功能,核心类是 `java.util.ServiceLoader`。其作用就是,可以通过类名获取在 `META-INF/services/` 下的多个配置实现文件。为了解决上面的扩展问题,现在我们在`META-INF/services/`下创建一个`com.github.yu120.test.SuperLoggerConfiguration`文件(没有后缀)。文件中只有一行代码,那就是我们默认的`com.github.yu120.test.XMLConfiguration`(注意,一个文件里也可以写多个实现,回车分隔)。然后通过 ServiceLoader 获取我们的 SPI 机制配置的实现类: + +```java +// META-INF/services/com.github.test.test.SuperLoggerConfiguration: +com.github.yu120.test.XMLConfiguration + +ServiceLoader serviceLoader = ServiceLoader.load(SuperLoggerConfiguration.class); +Iterator iterator = serviceLoader.iterator(); +SuperLoggerConfiguration configuration; +while(iterator.hasNext()) { + // 加载并初始化实现类 + configuration = iterator.next(); +} +// 对最后一个configuration类调用configure方法 +configuration.configure(configFile); +``` + + + +## [Dubbo SPI](https://github.com/apache/dubbo/tree/master/dubbo-common/src/main/java/org/apache/dubbo/common/extension) + +Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader类中,它的getExtensionLoader方法用于从缓存中获取与接口对应的ExtensionLoader,若缓存未命中,则创建一个新的实例。Dubbo SPI的核心思想其实很简单: + +- 通过配置文件,解耦拓展接口和拓展实现类 +- 通过IOC自动注入依赖的拓展实现类对象 +- 通过URL参数,在运行时确认真正的自定义拓展类对象 + +Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下(以下demo来自dubbo官方文档)。 + +```properties +optimusPrime = org.apache.spi.OptimusPrime +bumblebee = org.apache.spi.Bumblebee +``` + +与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外在使用时还需要在接口上标注 @SPI 注解。 + + + +## [Motan SPI](https://github.com/weibocom/motan/tree/master/motan-core/src/main/java/com/weibo/api/motan/core/extension) + +Motan使用SPI机制来实现模块间的访问,基于接口和name来获取实现类,降低了模块间的耦合。 + +Motan的SPI的实现在 `motan-core/com/weibo/api/motan/core/extension` 中。组织结构如下: + +```tex +motan-core/com.weibo.api.motan.core.extension + |-Activation:SPI的扩展功能,例如过滤、排序 + |-ActivationComparator:排序比较器 + |-ExtensionLoader:核心,主要负责SPI的扫描和加载 + |-Scope:模式枚举,单例、多例 + |-Spi:注解,作用在接口上,表明这个接口的实现可以通过SPI的形式加载 + |-SpiMeta:注解,作用在具体的SPI接口的实现类上,标注该扩展的名称 +``` + + + +## SpringBoot SPI + +Spring 的 SPI 配置文件是一个固定的文件 - `META-INF/spring.factories`,功能上和 JDK 的类似,每个接口可以有多个扩展实现,使用起来非常简单: + +```java +// 获取所有factories文件中配置的LoggingSystemFactory +List> factories = SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class, classLoader); +``` + +下面是一段 Spring Boot 中 spring.factories 的配置 + +```properties +# Logging Systems +org.springframework.boot.logging.LoggingSystemFactory=\ +org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\ +org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory + +# PropertySource Loaders +org.springframework.boot.env.PropertySourceLoader=\ +org.springframework.boot.env.PropertiesPropertySourceLoader,\ +org.springframework.boot.env.YamlPropertySourceLoader +``` + + + +# Redis + +## 数据类型 + +在 Redis 中,常用的 5 种数据类型和应用场景如下: + +- **String:** 缓存、计数器、分布式锁等 +- **List:** 链表、队列、微博关注人时间轴列表等 +- **Hash:** 用户信息、Hash 表等 +- **Set:** 去重、赞、踩、共同好友等 +- **Zset:** 访问量排行榜、点击量排行榜等 + +当然是为了追求速度,不同数据类型使用不同的数据结构速度才得以提升。每种数据类型都有一种或者多种数据结构来支撑,底层数据结构有 6 种。 + +![Redis数据类型与底层数据结构关系](images/Middleware/Redis数据类型与底层数据结构关系.png) + +### Redis hash字典 + +Redis 整体就是一个 哈希表来保存所有的键值对,无论数据类型是 5 种的任意一种。哈希表,本质就是一个数组,每个元素被叫做哈希桶,不管什么数据类型,每个桶里面的 entry 保存着实际具体值的指针。 + +![img](images/Middleware/Redis全局hash字典.png) + +整个数据库就是一个全局哈希表,而哈希表的时间复杂度是 O(1),只需要计算每个键的哈希值,便知道对应的哈希桶位置,定位桶里面的 entry 找到对应数据,这个也是 Redis 快的原因之一。 + + + +**那 Hash 冲突怎么办?** + +当写入 Redis 的数据越来越多的时候,哈希冲突不可避免,会出现不同的 key 计算出一样的哈希值。Redis 通过链式哈希解决冲突:也就是同一个 桶里面的元素使用链表保存。但是当链表过长就会导致查找性能变差可能,所以 Redis 为了追求快,使用了两个全局哈希表。用于 rehash 操作,增加现有的哈希桶数量,减少哈希冲突。开始默认使用 hash 表 1 保存键值对数据,哈希表 2 此刻没有分配空间。当数据越来多触发 rehash 操作,则执行以下操作: + +1. 给 hash 表 2 分配更大的空间 +2. 将 hash 表 1 的数据重新映射拷贝到 hash 表 2 中 +3. 释放 hash 表 1 的空间 + +值得注意的是,将 hash 表 1 的数据重新映射到 hash 表 2 的过程中并不是一次性的,这样会造成 Redis 阻塞,无法提供服务。而是采用了渐进式 rehash,每次处理客户端请求的时候,先从 hash 表 1 中第一个索引开始,将这个位置的 所有数据拷贝到 hash 表 2 中,就这样将 rehash 分散到多次请求过程中,避免耗时阻塞。 + + + +### SDS简单动态字符 + +字符串结构使用最广泛,通常我们用于缓存登陆后的用户信息,key = userId,value = 用户信息 JSON 序列化成字符串。C 语言中字符串的获取 「MageByte」的长度,要从头开始遍历,直到 「\0」为止,Redis 作为唯快不破的男人是不能忍受的。C 语言字符串结构与 SDS 字符串结构对比图如下所示: + +![img](images/Middleware/SDS简单动态字符.png) + + + +### 双端列表 + +Redis List 数据类型通常被用于队列、微博关注人时间轴列表等场景。不管是先进先出的队列,还是先进后出的栈,双端列表都很好的支持这些特性。Redis 的链表实现的特性可以总结如下: + +- **双端**:链表节点带有 prev 和 next 指针,获取某个节点的前置节点和后置节点的复杂度都是 O(1) +- **无环**:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问以 NULL 为终点 +- **带表头指针和表尾指针**:通过 list 结构的 head 指针和 tail 指针,程序获取链表的表头节点和表尾节点的复杂度为 O(1) +- **带链表长度计数器**:程序使用 list 结构的 len 属性来对 list 持有的链表节点进行计数,程序获取链表中节点数量的复杂度为 O(1) +- **多态**:链表节点使用 void* 指针来保存节点值,并且可以通过 list 结构的 dup、free、match 三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值 + +后续版本对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist。quicklist 是 ziplist 和 linkedlist 的混合体,它将 linkedlist 按段切分,每一段使用 ziplist 来紧凑存储,多个 ziplist 之间使用双向指针串接起来。 + +![quicklist](images/Middleware/quicklist.png) + +这也是为何 Redis 快的原因,不放过任何一个可以提升性能的细节。 + + + +### zipList压缩列表 + +压缩列表是 List 、hash、 sorted Set 三种数据类型底层实现之一。当一个列表只有少量数据的时候,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么 Redis 就会使用压缩列表来做列表键的底层实现。ziplist 是由一系列特殊编码的连续内存块组成的顺序型的数据结构,ziplist 中可以包含多个 entry 节点,每个节点可以存放整数或者字符串。ziplist 在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表占用字节数、列表尾的偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。 + +```go +struct ziplist { + int32 zlbytes; // 整个压缩列表占用字节数 + int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点 + int16 zllength; // 元素个数 + T[] entries; // 元素内容列表,挨个挨个紧凑存储 + int8 zlend; // 标志压缩列表的结束,值恒为 0xFF +} +``` + +![ZipList压缩列表](images/Middleware/ZipList压缩列表.png) + +如果我们要查找定位第一个元素和最后一个元素,可以通过表头三个字段的长度直接定位,复杂度是 O(1)。而查找其他元素时,就没有这么高效了,只能逐个查找,此时的复杂度就是 O(N)。 + + + +### skipList跳跃表 + +sorted set 类型的排序功能便是通过「跳跃列表」数据结构来实现。跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。跳跃表支持平均 O(logN)、最坏 O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。跳表在链表的基础上,增加了多层级索引,通过索引位置的几个跳转,实现数据的快速定位,如下图所示: + +![skipList跳跃表](images/Middleware/skipList跳跃表.png) + +当需要查找 40 这个元素需要经历 三次查找。 + + + +### 整数数组(intset) + +当一个集合只包含整数值元素,且这个集合的元素数量不多时,Redis 就会使用整数集合作为集合键的底层实现。结构如下: + +```go +typedef struct intset{ + //编码方式 + uint32_t encoding; + //集合包含的元素数量 + uint32_t length; + //保存元素的数组 + int8_t contents[]; +}intset; +``` + +contents 数组是整数集合的底层实现:整数集合的每个元素都是 contents 数组的一个数组项(item),各个项在数组中按值的大小从小到大有序地排列,并且数组中不包含任何重复项。length 属性记录了整数集合包含的元素数量,也即是 contents 数组的长度。 + + + +## 持久化 + +### RDB + +**① save命令 —— 同步数据到磁盘上** + +`save` 命令执行一个同步操作,以RDB文件的方式保存所有数据的快照。 + +![RDB-save命令](images/Middleware/RDB-save.jpg) + +由于 `save` 命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,`save`命令执行速度会非常慢,阻塞所有客户端的请求。因此很少在生产环境直接使用SAVE 命令,可以使用BGSAVE 命令代替。如果在BGSAVE命令的保存数据的子进程发生错误的时,用 SAVE命令保存最新的数据是最后的手段。 + + + +**② bgsave命令 —— 异步保存数据到磁盘上** + +`bgsave` 命令执行一个异步操作,以RDB文件的方式保存所有数据的快照。 + +![RDB-gbsave](images/Middleware/RDB-gbsave.jpg) + +Redis使用Linux系统的`fock()`生成一个子进程来将DB数据保存到磁盘,主进程继续提供服务以供客户端调用。如果操作成功,可以通过客户端命令LASTSAVE来检查操作结果。 + + + +**③ 自动生成RDB** + +可以通过配置文件对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动进行数据集保存操作。比如说, 以下设置会让 Redis 在满足“ 60 秒内有至少有 1000 个键被改动”这一条件时, 自动进行数据集保存操作: + +```shell +# RDB自动持久化规则 +# 当 900 秒内有至少有 1 个键被改动时,自动进行数据集保存操作 +save 900 1 +# 当 300 秒内有至少有 10 个键被改动时,自动进行数据集保存操作 +save 300 10 +# 当 60 秒内有至少有 10000 个键被改动时,自动进行数据集保存操作 +save 60 10000 + +# RDB持久化文件名 +dbfilename dump-.rdb + +# 数据持久化文件存储目录 +dir /var/lib/redis + +# bgsave发生错误时是否停止写入,通常为yes +stop-writes-on-bgsave-error yes + +# rdb文件是否使用压缩格式 +rdbcompression yes + +# 是否对rdb文件进行校验和检验,通常为yes +rdbchecksum yes +``` + + + +### AOF + +AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,因为这个模式是**只追加**的方式,所以没有任何磁盘寻址的开销,所以很快,有点像 Mysql 中的binlog,AOF更适合做热备。 + +优点: + +- AOF是一秒一次去通过一个后台的线程fsync操作,数据丢失不用怕 + +缺点: + +- 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在**恢复**大数据集时的速度比 AOF 的恢复速度要快 +- 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之每秒同步策略的效率是比较高的 + +**AOF整个流程分两步**: + +- 第一步是命令的实时写入,不同级别可能有1秒数据损失。命令先追加到`aof_buf`然后再同步到AO磁盘,**如果实时写入磁盘会带来非常高的磁盘IO,影响整体性能** +- 第二步是对aof文件的**重写**,目的是为了减少AOF文件的大小,可以自动触发或者手动触发(**BGREWRITEAOF**),是Fork出子进程操作,期间Redis服务仍可用 + +![AOF](images/Middleware/AOF.jpg) + +- 在重写期间,由于主进程依然在响应命令,为了保证最终备份的完整性;它`依然会写入旧`的AOF中,如果重写失败,能够保证数据不丢失 +- 为了把重写期间响应的写入信息也写入到新的文件中,因此也会`为子进程保留一个buf`,防止新写的file丢失数据 +- 重写是直接把`当前内存的数据生成对应命令`,并不需要读取老的AOF文件进行分析、命令合并 +- **无论是 RDB 还是 AOF 都是先写入一个临时文件,然后通过`rename`完成文件的替换工作**。 + +关于Fork的建议: + +- 降低fork的频率,比如可以手动来触发RDB生成快照、与AOF重写 +- 控制Redis最大使用内存,防止fork耗时过长 +- 配置牛逼点,合理配置Linux的内存分配策略,避免因为物理内存不足导致fork失败 +- Redis在执行`BGSAVE`和`BGREWRITEAOF`命令时,哈希表的负载因子>=5,而未执行这两个命令时>=1。目的是**尽量减少写操作**,避免不必要的内存写入操作 +- **哈希表的扩展因子**:哈希表已保存节点数量 / 哈希表大小。因子决定了是否扩展哈希表 + + + +## 过期策略 + +**过期策略用于处理过期缓存数据**。Redis采用过期策略:`惰性删除` + `定期删除`。memcached采用过期策略:`惰性删除`。 + +### 定时过期 + +每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是**会占用大量的CPU资源去处理过期的数据**,从而影响缓存的响应时间和吞吐量。 + + + +### 惰性过期 + +只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却**对内存非常不友好**。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。 + + + +### 定期过期 + +每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源**达到最优**的平衡效果。 + +expires字典会保存所有设置了过期时间的key的过期时间数据,其中 key 是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。 + + + +## 内存淘汰策略 + +Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。 + +- **volatile-lru**:从已设置过期时间的数据集(server.db[i].expires)中挑选**最近最少使用**的数据淘汰 +- **volatile-ttl**:从已设置过期时间的数据集(server.db[i].expires)中挑选**将要过期**的数据淘汰 +- **volatile-random**:从已设置过期时间的数据集(server.db[i].expires)中**任意选择**数据淘汰 +- **allkeys-lru**:从数据集(server.db[i].dict)中挑选**最近最少使用**的数据淘汰 +- **allkeys-random**:从数据集(server.db[i].dict)中**任意选择数**据淘汰 +- **no-enviction(驱逐)**:禁止驱逐数据,**不删除**的意思 + + + +## 应用场景 + +- 缓存-键过期时间 + - 把session会话存在redis,过期删除 + - 缓存用户信息,缓存Mysql部分数据,用户先访问redis,redis没有再访问mysql,然后回写给redis + - 商城优惠卷过期时间 +- 排行榜-列表&有序集合 + - 热度/点击数排行榜 + - 直播间礼物积分排行 +- 计数器-天然支持计数器 + - 帖子浏览数 + - 视频播放数 + - 评论数 + - 点赞/踩 +- 社交网络-集合 + - 粉丝 + - 共同好友 + - 兴趣爱好 + - 标签 +- 消息队列-发布订阅 + - 配合ELK缓存收集来的日志 + + + +## 主从复制 + +**概念定义** + +- runID 服务器运行的ID +- offset 主服务器的复制偏移量和从服务器复制的偏移量 +- replication backlog 主服务器的复制积压缓冲区 + +在redis2.8之后,使用psync命令代替sync命令来执行复制的同步操作,psync命令具有完整重同步和部分重同步两种模式: + +- 其中完整同步用于处理初次复制情况:完整重同步的执行步骤和sync命令执行步骤一致,都是通过让主服务器创建并发送rdb文件,以及向从服务器发送保存在缓冲区的写命令来进行同步 +- 部分重同步是用于处理断线后重复制情况:当从服务器在断线后重新连接主服务器时,主服务可以讲主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只要接收并执行这些写命令,就可以讲数据库更新至主服务器当前所处的状态 + + + +### 完整重同步 + +![Redis主从复制-完整重同步](images/Middleware/Redis主从复制-完整重同步.jpg) + +1. slave发送psync给master,由于是第一次发送,不带上runid和offset +2. master接收到请求,发送master的runid和offset给从节点 +3. master生成保存rdb文件 +4. master发送rdb文件给slave +5. 在发送rdb这个操作的同时,写操作会复制到缓冲区replication backlog buffer中,并从buffer区发送到slave +6. slave将rdb文件的数据装载,并更新自身数据 + +如果网络的抖动或者是短时间的断链也需要进行完整同步就会导致大量的开销,这些开销包括了,bgsave的时间,rdb文件传输的时间,slave重新加载rdb时间,如果slave有aof,还会导致aof重写。这些都是大量的开销所以在redis2.8之后也实现了部分重同步的机制。 + + + +### 部分重同步 + +![Redis主从复制-部分重同步](images/Middleware/Redis主从复制-部分重同步.jpg) + +- 网络发生错误,master和slave失去连接 +- master依然向buffer缓冲区写入数据 +- slave重新连接上master +- slave向master发送自己目前的runid和offset +- master会判断slave发送给自己的offset是否存在buffer队列中,如果存在,则发送continue给slave,如果不存在,意味着可能错误了太多的数据,缓冲区已经被清空,这个时候就需要重新进行全量的复制 +- master发送从offset偏移后的缓冲区数据给slave +- slave获取数据更新自身数据 + + + +## 部署架构 + +### 单节点(Single) + +**优点** + +- 架构简单,部署方便 +- 高性价比:缓存使用时无需备用节点(单实例可用性可以用 supervisor 或 crontab 保证),当然为了满足业务的高可用性,也可以牺牲一个备用节点,但同时刻只有一个实例对外提供服务 +- 高性能 + + + +**缺点** + +- 不保证数据的可靠性 +- 在缓存使用,进程重启后,数据丢失,即使有备用的节点解决高可用性,但是仍然不能解决缓存预热问题,因此不适用于数据可靠性要求高的业务 +- 高性能受限于单核CPU的处理能力(Redis是单线程机制),CPU为主要瓶颈,所以适合操作命令简单,排序/计算较少场景 + + + +### 主从复制(Replication) + +**基本原理** + +主从复制模式中包含一个主数据库实例(Master)与一个或多个从数据库实例(Slave),如下图: + +![Redis主从复制模式(Replication)](images/Middleware/Redis主从复制模式(Replication).png) + +![Redis主从复制模式(Replication)优缺点](images/Middleware/Redis主从复制模式(Replication)优缺点.png) + + + +### 哨兵(Sentinel) + +Sentinel主要作用如下: + +- **监控**:Sentinel 会不断的检查主服务器和从服务器是否正常运行 +- **通知**:当被监控的某个Redis服务器出现问题,Sentinel通过API脚本向管理员或者其他的应用程序发送通知 +- **自动故障转移**:当主节点不能正常工作时,Sentinel会开始一次自动的故障转移操作,它会将与失效主节点是主从关系的其中一个从节点升级为新的主节点,并且将其他的从节点指向新的主节点 + +![Redis哨兵模式(Sentinel)](images/Middleware/Redis哨兵模式(Sentinel).png) + +![Redis哨兵模式(Sentinel)优缺点](images/Middleware/Redis哨兵模式(Sentinel)优缺点.png) + + + +### 集群(Cluster) + +![Redis集群模式(Cluster)](images/Middleware/Redis集群模式(Cluster).png) + +![Redis集群模式(Cluster)优缺点](images/Middleware/Redis集群模式(Cluster)优缺点.png) + + + +## 环境搭建 + +**Redis安装及配置** + +Redis的安装十分简单,打开redis的官网 [http://redis.io](http://redis.io/) 。 + +- 下载一个最新版本的安装包,如 redis-version.tar.gz +- 解压 `tar zxvf redis-version.tar.gz` +- 执行 make (执行此命令可能会报错,例如确实gcc,一个个解决即可) + +如果是 mac 电脑,安装redis将十分简单执行`brew install redis`即可。安装好redis之后,我们先不慌使用,先进行一些配置。打开`redis.conf`文件,我们主要关注以下配置: + +```shell +port 6379 # 指定端口为 6379,也可自行修改 +daemonize yes # 指定后台运行 +``` + + + +### 单节点(Single) + +安装好redis之后,我们来运行一下。启动redis的命令为 : + +```shell +$ /bin/redis-server path/to/redis.config +``` + +假设我们没有配置后台运行(即:daemonize no),那么我们会看到如下启动日志: + +```shell +93825:C 20 Jan 2019 11:43:22.640 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo +93825:C 20 Jan 2019 11:43:22.640 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=93825, just started +93825:C 20 Jan 2019 11:43:22.640 # Configuration loaded +93825:S 20 Jan 2019 11:43:22.641 * Increased maximum number of open files to 10032 (it was originally set to 256). + _._ + _.-``__ ''-._ + _.-`` `. `_. ''-._ Redis 5.0.3 (00000000/0) 64 bit + .-`` .-```. ```\/ _.,_ ''-._ + ( ' , .-` | `, ) Running in standalone mode + |`-._`-...-` __...-.``-._|'` _.-'| Port: 6380 + | `-._ `._ / _.-' | PID: 93825 + `-._ `-._ `-./ _.-' _.-' + |`-._`-._ `-.__.-' _.-'_.-'| + | `-._`-._ _.-'_.-' | http://redis.io + `-._ `-._`-.__.-'_.-' _.-' + |`-._`-._ `-.__.-' _.-'_.-'| + | `-._`-._ _.-'_.-' | + `-._ `-._`-.__.-'_.-' _.-' + `-._ `-.__.-' _.-' + `-._ _.-' + `-.__.-' +``` + + + +### 主从复制(Replication) + +Redis主从配置非常简单,过程如下(演示情况下主从配置在一台电脑上): + +**第一步**:复制两个redis配置文件(启动两个redis,只需要一份redis程序,两个不同的redis配置文件即可) + +```shell +mkdir redis-master-slave +cp path/to/redis/conf/redis.conf path/to/redis-master-slave master.conf +cp path/to/redis/conf/redis.conf path/to/redis-master-slave slave.conf +``` + +**第二步**:修改配置 + +```shell +## master.conf +port 6379 + +## master.conf +port 6380 +slaveof 127.0.0.1 6379 +``` + +**第三步**:分别启动两个redis + +```shell +redis-server path/to/redis-master-slave/master.conf +redis-server path/to/redis-master-slave/slave.conf +``` + +启动之后,打开两个命令行窗口,分别执行 `telnet localhost 6379` 和 `telnet localhost 6380`,然后分别在两个窗口中执行 `info` 命令,可以看到: + +```shell +# Replication +role:master + +# Replication +role:slave +master_host:127.0.0.1 +master_port:6379 +``` + +主从配置没问题。然后在master 窗口执行 set 之后,到slave窗口执行get,可以get到,说明主从同步成功。这时,我们如果在slave窗口执行 set ,会报错: + +```shell +-READONLY You can't write against a read only replica. +``` + +因为从节点是只读的。 + + + +### 哨兵(Sentinel) + +Sentinel是用来监控主从节点的健康情况。客户端连接Redis主从的时候,先连接Sentinel,Sentinel会告诉客户端主Redis的地址是多少,然后客户端连接上Redis并进行后续的操作。当主节点挂掉的时候,客户端就得不到连接了因而报错了,客户端重新向Sentinel询问主master的地址,然后客户端得到了[新选举出来的主Redis],然后又可以愉快的操作了。 + + + +**哨兵sentinel配置** + +为了说明sentinel的用处,我们做个试验。配置3个redis(1主2从),1个哨兵。步骤如下: + +```shell +mkdir redis-sentinel +cd redis-sentinel +cp redis/path/conf/redis.conf path/to/redis-sentinel/redis01.conf +cp redis/path/conf/redis.conf path/to/redis-sentinel/redis02.conf +cp redis/path/conf/redis.conf path/to/redis-sentinel/redis03.conf +touch sentinel.conf +``` + +上我们创建了 3个redis配置文件,1个哨兵配置文件。我们将 redis01设置为master,将redis02,redis03设置为slave。 + +```shell +vim redis01.conf +port 63791 + +vim redis02.conf +port 63792 +slaveof 127.0.0.1 63791 + +vim redis03.conf +port 63793 +slaveof 127.0.0.1 63791 + +vim sentinel.conf +daemonize yes +port 26379 +sentinel monitor mymaster 127.0.0.1 63793 1 # 下面解释含义 +``` + +上面的主从配置都熟悉,只有哨兵配置 sentinel.conf,需要解释一下: + +```shell +mymaster # 为主节点名字,可以随便取,后面程序里边连接的时候要用到 +127.0.0.1 63793 # 为主节点的 ip,port +1 # 后面的数字 1 表示选举主节点的时候,投票数。1表示有一个sentinel同意即可升级为master +``` + + + +**启动哨兵** + +上面我们配置好了redis主从,1主2从,以及1个哨兵。下面我们分别启动redis,并启动哨兵: + +```shell +redis-server path/to/redis-sentinel/redis01.conf +redis-server path/to/redis-sentinel/redis02.conf +redis-server path/to/redis-sentinel/redis03.conf + +redis-server path/to/redis-sentinel/sentinel.conf --sentinel +``` + +启动之后,可以分别连接到 3个redis上,执行info查看主从信息。 + + + +**模拟主节点宕机情况** + +运行上面的程序(**注意,在实验这个效果的时候,可以将sleep时间加长或者for循环增多,以防程序提前停止,不便看整体效果**),然后将主redis关掉,模拟redis挂掉的情况。现在主redis为redis01,端口为63791 + +```shell +redis-cli -p 63791 shutdown +``` + + + +### 集群(Cluster) + +上述所做的这些工作只是保证了数据备份以及高可用,目前为止我们的程序一直都是向1台redis写数据,其他的redis只是备份而已。实际场景中,单个redis节点可能不满足要求,因为: + +- 单个redis并发有限 +- 单个redis接收所有数据,最终回导致内存太大,内存太大回导致rdb文件过大,从很大的rdb文件中同步恢复数据会很慢 + +所以需要redis cluster 即redis集群。Redis 集群是一个提供在**多个Redis间节点间共享数据**的程序集。Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误。Redis 集群通过分区来提供**一定程度的可用性**,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令.。Redis 集群的优势: + +- 自动分割数据到不同的节点上 +- 整个集群的部分节点失败或者不可达的情况下能够继续处理命令 + +为了配置一个redis cluster,我们需要准备至少6台redis,为啥至少6台呢?我们可以在redis的官方文档中找到如下一句话: + +> Note that the minimal cluster that works as expected requires to contain at least three master nodes. + +因为最小的redis集群,需要至少3个主节点,既然有3个主节点,而一个主节点搭配至少一个从节点,因此至少得6台redis。然而对我来说,就是复制6个redis配置文件。本实验的redis集群搭建依然在一台电脑上模拟。 + + + +**配置 redis cluster 集群** + +上面提到,配置redis集群需要至少6个redis节点。因此我们需要准备及配置的节点如下: + +```shell +# 主:redis01 从 redis02 slaveof redis01 +# 主:redis03 从 redis04 slaveof redis03 +# 主:redis05 从 redis06 slaveof redis05 +mkdir redis-cluster +cd redis-cluster +mkdir redis01 到 redis06 6个文件夹 +cp redis.conf 到 redis01 ... redis06 +# 修改端口, 分别配置3组主从关系 +``` + + + +**启动redis集群** + +上面的配置完成之后,分别启动6个redis实例。配置正确的情况下,都可以启动成功。然后运行如下命令创建集群: + +```shell +redis-5.0.3/src/redis-cli --cluster create 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375 127.0.0.1:6376 --cluster-replicas 1 +``` + +**注意**,这里使用的是ip:port,而不是 domain:port ,因为我在使用 localhost:6371 之类的写法执行的时候碰到错误: + +```shell +ERR Invalid node address specified: localhost:6371 +``` + +执行成功之后,连接一台redis,执行 cluster info 会看到类似如下信息: + +```shell +cluster_state:ok +cluster_slots_assigned:16384 +cluster_slots_ok:16384 +cluster_slots_pfail:0 +cluster_slots_fail:0 +cluster_known_nodes:6 +cluster_size:3 +cluster_current_epoch:6 +cluster_my_epoch:1 +cluster_stats_messages_ping_sent:1515 +cluster_stats_messages_pong_sent:1506 +cluster_stats_messages_sent:3021 +cluster_stats_messages_ping_received:1501 +cluster_stats_messages_pong_received:1515 +cluster_stats_messages_meet_received:5 +cluster_stats_messages_received:3021 +``` + +我们可以看到`cluster_state:ok`,`cluster_slots_ok:16384`,`cluster_size:3`。 + + + +# RocketMQ + +RocketMQ 是阿里巴巴开源的分布式消息中间件。支持事务消息、顺序消息、批量消息、定时消息、消息回溯等。它里面有几个区别于标准消息中件间的概念,如Group、Topic、Queue等。系统组成则由Producer、Consumer、Broker、NameServer等。 + + + +**RocketMQ 特点** + +- 是一个队列模型的消息中间件,具有高性能、高可靠、高实时、分布式等特点 +- Producer、Consumer、队列都可以分布式 +- Producer 向一些队列轮流发送消息,队列集合称为 Topic,Consumer 如果做广播消费,则一个 Consumer 实例消费这个 Topic 对应的所有队列,如果做集群消费,则多个 Consumer 实例平均消费这个 Topic 对应的队列集合 +- 能够保证严格的消息顺序 +- 支持拉(pull)和推(push)两种消息模式 +- 高效的订阅者水平扩展能力 +- 实时的消息订阅机制 +- 亿级消息堆积能力 +- 支持多种消息协议,如 JMS、OpenMessaging 等 +- 较少的依赖 + + + +## 功能优势 + +- **削峰填谷**:主要解决瞬时写压力大于应用服务能力导致消息丢失、系统奔溃等问题 +- **系统解耦**:解决不同重要程度、不同能力级别系统之间依赖导致一死全死 +- **提升性能**:当存在一对多调用时,可以发一条消息给消息系统,让消息系统通知相关系统 +- **蓄流压测**:线上有些链路不好压测,可以通过堆积一定量消息再放开来压测 + + + +## 队列模式 + +### 集群模式 + +生产者往某个队列里面发送消息,一个队列可以存储多个生产者的消息,一个队列也可以有多个消费者,但是消费者之间是竞争关系,即每条消息只能被一个消费者消费。 + +![集群模式](images/Middleware/集群模式.jpg) + + + +### 广播模式 + +**为了解决一条消息能被多个消费者消费的问题**,发布/订阅模型就来了。该模型是将消息发往一个`Topic`即主题中,所有订阅了这个 `Topic` 的订阅者都能消费这条消息。 + +![广播模式](images/Middleware/广播模式.jpg) + + + +## 分布式事务消息 + +MQ与DB一致性原理(两方事务) + +![MQ与DB一致性原理](images/Middleware/MQ与DB一致性原理.png) + + + +## 最佳实践 + +### Producer + +- **Topic**:消息主题,通过Topic对不同的业务消息进行分类 +- **Tag**:消息标签,用来进一步区分某个Topic下的消息分类,消息从生产者发出即带上的属性 +- **key**:每个消息在业务层面的唯一标识码,要设置到 keys 字段,方便将来定位消息丢失问题。服务器会为每个消息创建索引(哈希索引),应用可以通过 topic,key来查询这条消息内容,以及消息被谁消费。由于是哈希索引,请务必保证key 尽可能唯一,这样可以避免潜在的哈希冲突 +- **日志**:消息发送成功或者失败,要打印消息日志,务必要打印 send result 和key 字段 +- **send**:send消息方法,只要不抛异常,就代表发送成功。但是发送成功会有多个状态,在sendResult里定义 + - **SEND_OK**:消息发送成功 + - **FLUSH_DISK_TIMEOUT**:消息发送成功,但是服务器刷盘超时,消息已经进入服务器队列,只有此时服务器宕机,消息才会丢失 + - **FLUSH_SLAVE_TIMEOUT**:消息发送成功,但是服务器同步到Slave时超时,消息已经进入服务器队列,只有此时服务器宕机,消息才会丢失 + - **SLAVE_NOT_AVAILABLE**:消息发送成功,但是此时slave不可用,消息已经进入服务器队列,只有此时服务器宕机,消息才会丢失 + +- **订阅关系一致** + + 多个Group ID订阅了多个Topic,并且每个Group ID里的多个消费者实例的订阅关系保持了一致。 + + ![RocketMQ消息正确订阅关系](images/Middleware/RocketMQ消息正确订阅关系.png) + + + +### Consumer + +- **消费幂等** + + 为了防止消息重复消费导致业务处理异常,消息队列RocketMQ版的消费者在接收到消息后,有必要根据业务上的唯一Key对消息做幂等处理。消息重复的场景如下: + + - **发送时消息重复** + - **投递时消息重复** + - **负载均衡时消息重复**(包括但不限于网络抖动、Broker重启以及消费者应用重启) + +- **日志**:消费时记录日志,以便后续定位问题 +- **批量消费**:尽量使用批量方式消费方式,可以很大程度上提高消费吞吐量 + + + +## 常见问题 + +**消息可靠性怎么保证?** + +消息丢失可能发生在生产者发送消息、MQ本身丢失消息、消费者丢失消息3个方面。 + +- **生产者丢失** + + **产生原因**:可能因为程序发送失败抛异常而没有重试处理,或发送过程成功但过程中网络闪断MQ没收到 + + **解决方案**: + + - 发送异常:本地消息表 + - 发送成功无回调:异步发送+回调通知+本地消息表 + +- **MQ丢失** + + **产生原因**:如果生产者保证消息发送到MQ,而MQ收到消息后还在内存中,这时候宕机了又没来得及同步给从节点,就有可能导致消息丢失 + + **解决方案**:RocketMQ分为同步刷盘和异步刷盘两种方式,默认的是异步刷盘。可以修改配置为同步刷盘来提高消息可靠性,但会对性能有损耗,需权衡 + +- **消费者丢失** + + **产生原因**:消费者丢失消息的场景:消费者刚收到消息,此时服务器宕机,MQ认为消费者已经消费,不会重复发送消息,消息丢失 + + **解决方案**:消费方不返回ack确认,重发的机制根据MQ类型的不同发送时间间隔、次数都不尽相同,如果重试超过次数之后会进入死信队列,需要手工来处理 + + + +**如果一直消费失败导致消息积压怎么处理?** + +因为考虑到时消费者消费一直出错的问题,那么我们可以从以下几个角度来考虑: + +- 消费者出错,肯定是程序或者其他问题导致的,如果容易修复,先把问题修复,让consumer恢复正常消费 +- 如果时间来不及处理很麻烦,做转发处理,写一个临时的consumer消费方案,先把消息消费,然后再转发到一个新的topic和MQ资源,这个新的topic的机器资源单独申请,要能承载住当前积压的消息 +- 处理完积压数据后,修复consumer,去消费新的MQ和现有的MQ数据,新MQ消费完成后恢复原状 + +![RocketMQ消费失败消息积压](images/Middleware/RocketMQ消费失败消息积压.jpg) + + + +**RocketMQ实现原理?** + +RocketMQ由NameServer注册中心集群、Producer生产者集群、Consumer消费者集群和若干Broker(RocketMQ进程)组成,它的架构原理是这样的: + +- Broker在启动的时候去向所有的NameServer注册,并保持长连接,每30s发送一次心跳 +- Producer在发送消息的时候从NameServer获取Broker服务器地址,根据负载均衡算法选择一台服务器来发送消息 +- Conusmer消费消息的时候同样从NameServer获取Broker地址,然后主动拉取消息来消费 + +![RocketMQ实现原理](images/Middleware/RocketMQ实现原理.jpg) + + + +**为什么 RocketMQ 不使用 Zookeeper 作为注册中心呢?** + +- 根据CAP理论,同时最多只能满足两个点,而zookeeper满足的是CP,也就是说zookeeper并不能保证服务的可用性,zookeeper在进行选举的时候,整个选举的时间太长,期间整个集群都处于不可用的状态,而这对于一个注册中心来说肯定是不能接受的,作为服务发现来说就应该是为可用性而设计 +- 基于性能的考虑,NameServer本身的实现非常轻量,而且可以通过增加机器的方式水平扩展,增加集群的抗压能力,而zookeeper的写是不可扩展的,而zookeeper要解决这个问题只能通过划分领域,划分多个zookeeper集群来解决,首先操作起来太复杂,其次这样还是又违反了CAP中的A的设计,导致服务之间是不连通的 +- 持久化的机制来带的问题,ZooKeeper 的 ZAB 协议对每一个写请求,会在每个 ZooKeeper 节点上保持写一个事务日志,同时再加上定期的将内存数据镜像(Snapshot)到磁盘来保证数据的一致性和持久性,而对于一个简单的服务发现的场景来说,这其实没有太大的必要,这个实现方案太重了。而且本身存储的数据应该是高度定制化的 +- 消息发送应该弱依赖注册中心,而RocketMQ的设计理念也正是基于此,生产者在第一次发送消息的时候从NameServer获取到Broker地址后缓存到本地,如果NameServer整个集群不可用,短时间内对于生产者和消费者并不会产生太大影响 + + + +**Broker是怎么保存数据的呢?** + +RocketMQ主要的存储文件包括commitlog文件、consumequeue文件、indexfile文件。 + +Broker在收到消息之后,会把消息保存到commitlog的文件当中,而同时在分布式的存储当中,每个broker都会保存一部分topic的数据,同时,每个topic对应的messagequeue下都会生成consumequeue文件用于保存commitlog的物理位置偏移量offset,indexfile中会保存key和offset的对应关系。 + +![Broker数据结构](images/Middleware/Broker数据结构.jpg) + + +CommitLog文件保存于${Rocket_Home}/store/commitlog目录中,可以根据文件名很明显看出来偏移量,每个文件默认1G,写满后自动生成一个新的文件。 + +由于同一个topic的消息并不是连续的存储在commitlog中,消费者如果直接从commitlog获取消息效率非常低,所以通过consumequeue保存commitlog中消息的偏移量的物理地址,这样消费者在消费的时候先从consumequeue中根据偏移量定位到具体的commitlog物理文件,然后根据一定的规则(offset和文件大小取模)在commitlog中快速定位。 + + + +**Master 和 Slave 之间是怎么同步数据的呢?** + +而消息在master和slave之间的同步是根据raft协议来进行的: + +- 在broker收到消息后,会被标记为uncommitted状态 +- 然后会把消息发送给所有的slave +- slave在收到消息之后返回ack响应给master +- master在收到超过半数的ack之后,把消息标记为committed +- 发送committed消息给所有slave,slave也修改状态为committed + + + +**RocketMQ 为什么速度快吗?** + +是因为使用了顺序存储、Page Cache和异步刷盘。 + +- 我们在写入commitlog的时候是顺序写入的,这样比随机写入的性能就会提高很多 +- 写入commitlog的时候并不是直接写入磁盘,而是先写入操作系统的PageCache +- 最后由操作系统异步将缓存中的数据刷到磁盘 + + + +**什么是事务、半事务消息?怎么实现的?** + +事务消息就是MQ提供的类似XA的分布式事务能力,通过事务消息可以达到分布式事务的最终一致性。半事务消息就是MQ收到了生产者的消息,但是没有收到二次确认,不能投递的消息。实现原理如下: + +- 生产者先发送一条半事务消息到MQ +- MQ收到消息后返回ack确认 +- 生产者开始执行本地事务 +- 如果事务执行成功发送commit到MQ,失败发送rollback +- 如果MQ长时间未收到生产者的二次确认commit或者rollback,MQ对生产者发起消息回查 +- 生产者查询事务执行最终状态 +- 根据查询事务状态再次提交二次确认 + +如果MQ收到二次确认commit,就可以把消息投递给消费者,反之如果是rollback,消息会保存下来并且在3天后被删除。 + +![RocketMQ事务消息](images/Middleware/RocketMQ事务消息.jpg) + + + +## 消息丢失 + +**消息发送流程** + +一条消息从生产到被消费,将会经历三个阶段: + +![Rocket消息丢失](images/Middleware/Rocket消息丢失.jpg) + +- 生产阶段:Producer 新建消息,然后通过网络将消息投递给 MQ Broker +- 存储阶段:消息将会存储在 Broker 端磁盘中 +- 消息阶段:Consumer 将会从 Broker 拉取消息 + +以上任一阶段都可能会丢失消息,我们只要找到这三个阶段丢失消息原因,采用合理的办法避免丢失,就可以彻底解决消息丢失的问题。 + + + +### 生产阶段 + +生产者(Producer) 通过网络发送消息给 Broker,当 Broker 收到之后,将会返回确认响应信息给 Producer。所以生产者只要接收到返回的确认响应,就代表消息在生产阶段未丢失。 + + + +### Broker 存储阶段 + +默认情况下,消息只要到了 Broker 端,将会优先保存到内存中,然后立刻返回确认响应给生产者。随后 Broker 定期批量的将一组消息从内存异步刷入磁盘。这种方式减少 I/O 次数,可以取得更好的性能,但是如果发生机器掉电,异常宕机等情况,消息还未及时刷入磁盘,就会出现丢失消息的情况。若想保证 Broker 端不丢消息,保证消息的可靠性,我们需要将消息保存机制修改为同步刷盘方式,即消息**存储磁盘成功**,才会返回响应。修改 Broker 端配置如下: + +```properties +# 默认情况为 ASYNC_FLUSH +flushDiskType = SYNC_FLUSH +``` + +若 Broker 未在同步刷盘时间内(**默认为 5s**)完成刷盘,将会返回 `SendStatus.FLUSH_DISK_TIMEOUT` 状态给生产者。 + + + +### 消费阶段 + +消费者从broker拉取消息,然后执行相应的业务逻辑。一旦执行成功,将会返回`ConsumeConcurrentlyStatus. CONSUME_SUCCESS` 状态给Broker。如果 Broker 未收到消费确认响应或收到其他状态,消费者下次还会再次拉取到该条消息,进行重试。这样的方式有效避免了消费者消费过程发生异常,或者消息在网络传输中丢失的情况。 + + + +# Zookeeper + +**下载地址:**http://www.apache.org/dist/zookeeper/ + +## ZK特性 + +Zookeeper主要靠其 **分布式数据一致性** 为集群提供 **分布式协调服务**,即指在集群的节点中进行可靠的消息传递,来协调集群的工作。主要具有如下特点: + +- **最终一致性:**Client无论连接到哪个Server,展示给它都是同一个视图,这是zookeeper最重要的性能 +- **可靠性:**如果一个消息或事物被一台Server接受,那么它将被所有的服务器接受 +- **实时性:**Zookeeper不能保证强一致性,只保证顺序一致性和最终一致性,因此称为**伪实时性**。由于网络延时等原因,Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口 +- **原子性:**更新只能成功或者失败,没有中间状态 +- **顺序性:**包括 **全序(Total order)** 和 **因果顺序(Causal order)** + - **全序:**如果消息a在消息b之前发送,则所有Server应该看到相同的结果 + - **因果顺序:**如果消息a在消息b之前发生(a导致了b),并被一起发送,则a始终在b之前被执行 + + + +## ZK角色 + +- **Server(服务端)** + - **Leader(领导者):**负责对所有对ZK状态变更的请求,将状态更新请求进行排序与编号,以保证集群内部消息处理的有序性 + - **Learner(学习者)** + - **Follower(追随者):**用于接收客户请求并向客户端返回结果,在选举过程中参与投票 + - **Observer(观察者):**其作用是为了扩展集群来提供读取速度,可以接收客户端连接,将写请求转发给Leader节点,但不参与投票,只同步Leader状态 +- **Client(客户端):**请求发起方 + +每个Server在工作过程中有三种状态: + +- **LOOKING:**当前Server不知道Leader是谁,正在搜寻 +- **LEADING:**当前Server即为选举出来的Leader +- **FOLLOWING:**Leader已经选举出来,当前Server与之同步 + + + +Zookeeper集群中,有Leader、Follower和Observer三种角色 + +- **Leader** + + Leader服务器是整个ZooKeeper集群工作机制中的核心,其主要工作: + + - 事务请求的唯一调度和处理者,保证集群事务处理的顺序性 + - 集群内部各服务的调度者 + +- **Follower** + + Follower服务器是ZooKeeper集群状态的跟随者,其主要工作: + + - 处理客户端非事务请求,转发事务请求给Leader服务器 + - 参与事务请求Proposal的投票 + - 参与Leader选举投票 + +- **Observer** + + Observer是3.3.0 版本开始引入的一个服务器角色,它充当一个观察者角色——观察ZooKeeper集群的最新状态变化并将这些状态变更同步过来。其工作: + + - 处理客户端的非事务请求,转发事务请求给 Leader 服务器 + - 不参与任何形式的投票 + + + +## Zookeeper下Server工作状态 + +服务器具有四种状态,分别是 LOOKING、FOLLOWING、LEADING、OBSERVING。 + +- 1.LOOKING:寻找Leader状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态 +- 2.FOLLOWING:跟随者状态。表明当前服务器角色是Follower +- 3.LEADING:领导者状态。表明当前服务器角色是Leader +- 4.OBSERVING:观察者状态。表明当前服务器角色是Observer + + + +## Leader选举 + +### 服务器启动的Leader选举 + +zookeeper集群初始化阶段,服务器(myid=1-5)**「依次」**启动,开始zookeeper选举Leader~ + +![服务器启动的Leader选举](images/Middleware/服务器启动的Leader选举.png) + +- 服务器1(myid=1)启动,当前只有一台服务器,无法完成Leader选举 + +- 服务器2(myid=2)启动,此时两台服务器能够相互通讯,开始进入Leader选举阶段 + + - 每个服务器发出一个投票 + + 服务器1 和 服务器2都将自己作为Leader服务器进行投票,投票的基本元素包括:服务器的myid和ZXID,我们以(myid,ZXID)形式表示。初始阶段,服务器1和服务器2都会投给自己,即服务器1的投票为(1,0),服务器2的投票为(2,0),然后各自将这个投票发给集群中的其他所有机器。 + + - 接受来自各个服务器的投票 + + 每个服务器都会接受来自其他服务器的投票。同时,服务器会校验投票的有效性,是否本轮投票、是否来自LOOKING状态的服务器。 + + - 处理投票 + + 收到其他服务器的投票,会将别人的投票跟自己的投票PK,PK规则如下: + + - 优先检查ZXID。ZXID比较大的服务器优先作为leader。 + - 如果ZXID相同的话,就比较myid,myid比较大的服务器作为leader。服务器1的投票是(1,0),它收到投票是(2,0),两者zxid都是0,因为收到的myid=2,大于自己的myid=1,所以它更新自己的投票为(2,0),然后重新将投票发出去。对于服务器2呢,即不再需要更新自己的投票,把上一次的投票信息发出即可。 + + - 统计投票 + + 每次投票后,服务器会统计所有投票,判断是否有过半的机器接受到相同的投票信息。服务器2收到两票,少于3(n/2+1,n为总服务器5),所以继续保持LOOKING状态 + +- 服务器3(myid=3)启动,继续进入Leader选举阶段 + + 跟前面流程一致,服务器1和2先投自己一票,因为服务器3的myid最大,所以大家把票改投给它。此时,服务器为3票(大于等于n/2+1),所以服务器3当选为Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING; + +- 服务器4启动,发起一次选举。 + + 此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。选票信息结果:服务器3为3票,服务器4为1票。服务器4并更改状态为FOLLOWING; + +- 服务器5启动,发起一次选举。 + + 同理,服务器也是把票投给服务器3,服务器5并更改状态为FOLLOWING; + +- 投票结束,服务器3当选为Leader + + + +### 服务器运行期间的Leader选举 + +zookeeper集群的五台服务器(myid=1-5)正在运行中,突然某个瞬间,Leader服务器3挂了,这时候便开始Leader选举~ + +![服务器运行期间的Leader选举](images/Middleware/服务器运行期间的Leader选举.png) + +- 变更状态 + + Leader 服务器挂了之后,余下的非Observer服务器都会把自己的服务器状态更改为LOOKING,然后开始进入Leader选举流程。 + +- 每个服务器发起投票 + + 每个服务器都把票投给自己,因为是运行期间,所以每台服务器的ZXID可能不相同。假设服务1,2,4,5的zxid分别为333,666,999,888,则分别产生投票(1,333),(2,666),(4,999)和(5,888),然后各自将这个投票发给集群中的其他所有机器。 + +- 接受来自各个服务器的投票 + +- 处理投票 + + 投票规则是跟Zookeeper集群启动期间一致的,优先检查ZXID,大的优先作为Leader,所以显然服务器zxid=999具有优先权。 + +- 统计投票 + +- 改变服务器状态 + + + +## 节点(znode) + +**① 节点组成** + +每个znode由4部分组成: + +- **path:**即节点名称,用于存放简单可视化的数据 +- **stat:**即状态信息,描述该znode的版本,权限等信息 +- **data:**与该znode关联的数据 +- **children:**该znode下的子节点 + + + +**② 节点类型** + +- **PERSISTENT(持久化目录节点)** + + 客户端与zookeeper断开连接后,该节点依旧存在 + +- **PERSISTENT_SEQUENTIAL(持久化顺序编号目录节点)** + + 客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号 + +- **EPHEMERAL(临时目录节点)** + + 客户端与zookeeper断开连接后,该节点被删除 + +- **EPHEMERAL_SEQUENTIAL(临时顺序编号目录节点)** + + 客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号 + + + +## 关键词 + +- **zxid(事务ID号)** + + ZooKeeper状态的每一次改变,都对应着一个递增的`Transaction id`,该id称为zxid。由于zxid的递增性质,如果zxid1小于zxid2,那么zxid1肯定先于zxid2发生。创建任意节点、更新任意节点的数据、删除任意节点,都会导致Zookeeper状态发生改变,从而导致zxid的值增加。 + +- **session(会话连接)** + + 在Client和Server通信之前,首先需要建立连接,该连接称为session。连接建立后,如果发生连接超时、授权失败或显式关闭连接,连接便处于CLOSED状态,此时session结束。 + + + +## Watcher监听机制 + +Zookeeper 允许客户端向服务端的某个Znode注册一个Watcher监听,当服务端的一些指定事件触发了这个Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher通知状态和事件类型做出业务上的改变。 + +![Watcher监听机制的工作原理](images/Middleware/Watcher监听机制的工作原理.png) + +- ZooKeeper的Watcher机制主要包括客户端线程、客户端 WatcherManager、Zookeeper服务器三部分 +- 客户端向ZooKeeper服务器注册Watcher的同时,会将Watcher对象存储在客户端的WatchManager中 +- 当zookeeper服务器触发watcher事件后,会向客户端发送通知, 客户端线程从 WatcherManager 中取出对应的 Watcher 对象来执行回调逻辑 + + + +**Watcher特性总结** + +- **「一次性:」** 一个Watch事件是一个一次性的触发器。一次性触发,客户端只会收到一次这样的信息 +- **「异步的:」** Zookeeper服务器发送watcher的通知事件到客户端是异步的,不能期望能够监控到节点每次的变化,Zookeeper只能保证最终的一致性,而无法保证强一致性 +- **「轻量级:」** Watcher 通知非常简单,它只是通知发生了事件,而不会传递事件对象内容 +- **「客户端串行:」** 执行客户端 Watcher 回调的过程是一个串行同步的过程 +- 注册 watcher用getData、exists、getChildren方法 +- 触发 watcher用create、delete、setData方法 + + + +## ZXID + +![Zookeeper-ZXID](images/Middleware/Zookeeper-ZXID.png) + +ZXID有两部分组成: + +- 任期:完成本次选举后,直到下次选举前,由同一Leader负责协调写入 +- 事务计数器:单调递增,每生效一次写入,计数器加一 + +ZXID的低32位是计数器,所以同一任期内,ZXID是连续的,每个结点又都保存着自身最新生效的ZXID,通过对比新提案的ZXID与自身最新ZXID是否相差“1”,来保证事务严格按照顺序生效的。 + + + +## 工作流程 + +**① 客户端发起的操作的主要流程** + +Leader可以执行增删改查操作,而Follower只能进行查询操作。所有的更新操作都会被转交给Leader来处理,Leader批准的任务,再发送给Follower去执行来保证和Leader的一致性。由于网络是不稳定的,为了保证执行顺序的一致,所有的任务都会被赋予一个唯一的顺序的编号,一定是按照这个编号来执行任务,保证任务顺序的一致性。 + + + +**② 客户端的请求什么时候才能算处理成功?为什么说集群过半机器宕机后无法再工作?** + +Leader在收到客户端提交过来的任务后,会向集群中所有的Follower发送提案等待Follower的投票,Follower们收到这个提议后,会进行投票,同意或者不同意,Leader会回收Follower的投票,一旦受到过半的投票表示同意,则Leader认为这个提案通过,再发送命令要求所有的Follower都进行这个提案中的任务。由于需要过半的机器同意才能执行任务,所以一旦集群中过半的机器挂掉,整个集群就无法工作了。 + +- **Leader工作流程** + + Leader主要有三个功能: + + - 恢复数据 + - 维持与Learner的心跳,接收Learner请求并判断Learner的请求消息类型 + - Learner的消息类型主要有如下四种,根据不同的消息类型,进行不同的处理: + - **PING消息:**指Learner的心跳信息 + - **REQUEST消息:**Follower发送的提议信息,包括写请求及同步请求 + - **ACK消息:**Follower的对提议的回复,超过半数的Follower通过,则commit该提议 + - **REVALIDATE消息:**用来延长SESSION有效时间 + + +- **Follower工作流程** + + Follower主要有四个功能: + + - 向Leader发送消息请求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息) + - 接收Leader消息并进行处理 + - 接收Client的请求,如果为写请求,发送给Leader进行投票 + - 返回Client结果 + + Follower的消息循环处理如下几种来自Leader的消息: + + - **PING消息:** 心跳消息 + +- **PROPOSAL消息:**Leader发起的提案,要求Follower投票 + + - **COMMIT消息:**服务器端最新一次提案的信息 + - **UPTODATE消息:**表明同步完成 + - **REVALIDATE消息:**根据Leader的REVALIDATE结果,关闭待revalidate的session还是允许其接受消息 + - **SYNC消息:**返回SYNC结果到客户端,这个消息最初由客户端发起,用来强制得到最新的更新 + + +- **Observer工作流程** + + 对于Observer的流程不再叙述,Observer流程和Follower的唯一不同的地方就是Observer不会参加Leader发起的投票。 + + + +## ZAB协议 + +ZooKeeper并没有完全采用Paxos算法,而是使用一种称为ZooKeeper Atomic Broadcast(ZAB,Zookeeper原子消息广播协议)的协议作为其数据一致性的核心算法,简称ZAB协议。ZAB协议分为两种模式: + +- **崩溃恢复模式(Recovery):**当服务初次启动或Leader节点挂了时,系统就会进入恢复模式,直到选出了有合法数量Follower的新Leader,然后新Leader负责将整个系统同步到最新状态 +- **消息广播模式(Boardcast):**ZAB协议中,所有的写请求都由Leader来处理。正常工作状态下,Leader接收请求并通过广播协议来处理(如:广播提议投票、广播命令) + + + +**① 崩溃恢复模式(Recovery)** + +为了使Leader挂了后系统能正常工作,需要解决以下两个问题: + +- 已经被处理的消息不能丢 +- 被丢弃的消息不能再次出现 + + + +**② 消息广播模式(Boardcast)** + +广播的过程实际上是一个简化的二阶段提交过程: + +- Leader 接收到消息请求后,将消息赋予一个全局唯一的 64 位自增 id,叫做:zxid,通过 zxid 的大小比较即可实现因果有序这一特性 +- Leader 通过先进先出队列(通过 TCP 协议来实现,以此实现了全局有序这一特性)将带有 zxid 的消息作为一个提案(proposal)分发给所有 follower +- 当 follower 接收到 proposal,先将 proposal 写到硬盘,写硬盘成功后再向 leader 回一个 ACK +- 当 leader 接收到合法数量的 ACKs 后,leader 就向所有 follower 发送 COMMIT 命令,同事会在本地执行该消息 +- 当 follower 收到消息的 COMMIT 命令时,就会执行该消息 + + + +**总结** + +个人认为 Zab 协议设计的优秀之处有两点,一是简化二阶段提交,提升了在正常工作情况下的性能;二是巧妙地利用率自增序列,简化了异常恢复的逻辑,也很好地保证了顺序处理这一特性。值得注意的是,ZAB提交事务并不像2PC一样需要全部Follower都ACK,只需要得到quorum(超过半数的节点)的ACK就可以了。 + + + +**③ ZAB 的四个阶段** + +- **Leader election(选举阶段):**节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准Leader(只有完成ZAB的四个阶段,准Leader才会成为真正的Leader)。本阶段的目的是就是为了选出一个准Leader,然后进入下一个阶段 +- **Discovery(发现阶段):**在次阶段,Followers跟准Leader进行通信,同步Followers最近接收的事务提议,这个一阶段的主要目的是发现当前大多数节点接收的最新提议 +- **Synchronization(同步阶段):**同步阶段主要是利用Leader前一阶段获得的最新提议历史,同步集群中所有的副本。只有当quorum都同步完成,准Leader才会成为真正的Leader。Follower只会接收zxid比自己的lastZxid大的提议 +- **Broadcast(广播阶段):**到了这个阶段,Zookeeper 集群才能正式对外提供事务服务,并且Leader可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步 + + + +**④ JAVA版ZAB协议** + +- **Fast Leader Election**:Fast Leader Election + + 前面提到 FLE 会选举拥有最新提议历史(lastZixd最大)的节点作为 leader,这样就省去了发现最新提议的步骤。这是基于拥有最新提议的节点也有最新提交记录的前提。成为 leader 的条件: + + - 选epoch最大的 + - epoch相等,选 zxid 最大的 + - epoch和zxid都相等,选择server id最大的(即配置zoo.cfg中的myid) + +- **Recovery Phase**:这一阶段Follower发送它们的 lastZixd 给Leader,Leader 根据 lastZixd 决定如何同步数据。这里的实现跟前面 Phase 2 有所不同:Follower 收到 TRUNC 指令会中止 L.lastCommittedZxid 之后的提议,收到 DIFF 指令会接收新的提议。 + +- **Broadcast Phase**:暂无 + + + +## ZK选举过程 + +最开始集群启动时,会选择xzid最小的机器作为leader。 + +当Leader崩溃或者Leader失去大多数的Follower,这时候ZK进入恢复模式,恢复模式需要重新选举出一个新的Leader,让所有的Server都恢复到一个正确的状态。ZK的选举算法使用**ZAB协议**: + +- 选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server +- 选举线程首先向所有Server发起一次询问(包括自己) +- 选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中 +- 收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server +- 线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2 + 1的Server票数, 设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来 + + + +**分析结论** + +要使Leader获得多数Server的支持,则Server总数最好是奇数2n+1,且存活的Server的数目不得少于n+1。因为需要过半存活集群才能工作,所以2n个机器提供的集群可靠性其实和2n-1个机器提供的集群可靠性是一样的。 + + + +## Zookeeper安装 + +### 单机模式 + +**第一步:安装部署** + +```shell +# 下载解压 +wget http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.4.9/zookeeper-3.4.9.tar.gz +tar -zxvf zookeeper-3.4.9.tar.gz + +# 设置全局变量 +vim ~/.bash_profile +# 最后一行加入 +export ZOOKEEPER_HOME=/home/zookeeper/zookeeper-3.4.9 +export PATH=$ZOOKEEPER_HOME/bin:$PATH +# 使之生效 +source ~/.bash_profile + +# 复制配置文件 +cp /home/zookeeper/zookeeper-3.4.9/conf/zoo_sample.cfg /home/zookeeper/zookeeper-3.4.9/conf/zoo.cfg +``` + +**第二步:配置信息** + +```shell +# 心跳间隔 +tickTime=2000 +# 保存数据目录 +dataDir=/home/zookeeper/zookeeper-3.4.9/dataDir +# 保存日志目录 +dataLogDir=/home/zookeeper/zookeeper-3.4.9/dataLogDir +# 客户端连接Zookeeper的端口 +clientPort=2181 +# 允许follower连接并同步到leader的初始化连接时间(心跳倍数),超过则连接失败 +initLimit=5 +# 表示leader和follower之间发送消息时, 请求和应答的最大时间长度 +syncLimit=2 +``` + + + +### 集群模式 + +**第一步:安装部署** + +安装方式参考单机模式。 + +**第二步:配置信息** + +在dataDir根目录下新建myid文件,并向文件myid中写入值(如:1、2、3……) + +```shell +# 1表示当前集群的id号 +echo "1" > myid +``` + +在单机配置情况下,新增下述参数: + +```shell +# 格式:server.X=A:B:C +# X表示myid(范围1~255) +# A是该server所在的IP地址 +# B配置该server和集群中的leader交换消息所使用的端口,即数据同步端口 +# C配置选举leader时所使用的端口 +server.1=10.24.1.62:2888:3888 +server.2=10.24.1.63:2888:3888 +server.3=10.24.1.64:2888:3888 +``` + + + +### 运维命令 + +```shell +# 启动ZK服务器 +./zkServer.sh start +# 使用ZK Client连接指定服务器 +./zkCli.sh -server 127.0.0.1:2181 +# 查看ZK服务状态 +./zkServer.sh status +# 停止ZK服务 +./zkServer.sh stop +# 重启ZK服务 +./zkServer.sh restart +``` + + + +### zoo.cfg配置参数 + +```shell +# 客户端连接server的端口,默认值2181 +clientPort=2181 +# 存储快照文件snapshot的目录 +dataDir=/User/lry/zookeeper/data +# ZK中的最小时间单元,单位为毫秒 +tickTime=5000 +# 事务日志输出目录 +dataLogDir=/User/lry/zookeeper/datalog +# Server端最大允许的请求堆积数,默认值为1000 +globalOutstandingLimit=1000 +# 每个事务日志文件的大小,默认值64M +preAllocSize=64 +# 每进行snapCount次事务日志输出后,触发一次快照,默认值100000,实际代码中是随机范围触发,避免并发情况 +snapCount=100000 +# 单个客户端与单台服务器之间的连接数的限制,是ip级别的,默认是60,如果设置为0,那么表明不作任何限制 +maxClientCnxns=60 +# 对于多网卡的机器,可以为每个IP指定不同的监听端口。默认是所有IP都监听clientPort指定的端口 +clientPortAddress=10.24.22.56 +# Session超时时间限制,如果客户端设置的超时时间不在这个范围,那么会被强制设置为最大或最小时间。默认的Session超时时间是在2*tickTime~20*tickTime这个范围 +minSessionTimeoutmaxSessionTimeout +# Follower在启动过程中,会从Leader同步所有最新数据,然后确定自己能够对外服务的起始状态。Leader允许F在 initLimit 时间内完成这个工作。 +initLimit +# 在运行过程中,Leader负责与ZK集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。如果L发出心跳包在syncLimit之后,还没有从F那里收到响应,那么就认为这个F已经不在线了。 +syncLimit +# 默认情况下,Leader是会接受客户端连接,并提供正常的读写服务。但是,如果你想让Leader专注于集群中机器的协调,那么可以将这个参数设置为no,这样一来,会大大提高写操作的性能 +leaderServes=yes +# server.[myid]=[hostname]:[数据同步和其它通讯端口]:[选举投票通讯] +server.x=[hostname]:nnnnn[:nnnnn] +# 对机器分组和权重设置 +group.x=nnnnn[:nnnnn]weight.x=nnnnn +# Leader选举过程中,打开一次连接的超时时间,默认是5s +cnxTimeout +# 每个节点最大数据量,是默认是1M +jute.maxbuffer +``` + + + +# Netty + +## Netty流程 + +从功能上,流程可以分为服务启动、建立连接、读取数据、业务处理、发送数据、关闭连接以及关闭服务。整体流程如下所示(图中没有包含关闭的部分): + +![Netty整体流程](images/Middleware/Netty整体流程.png) + +![Netty线程模型](images/Middleware/Netty线程模型.png) + + + +## 处理事件 + +Netty中Reactor线程和worker线程所处理的事件: + +1、Server端NioEventLoop处理的事件: + +![Server端NioEventLoop处理的事件](images/Middleware/Server端NioEventLoop处理的事件.png) + +2、Client端NioEventLoop处理的事件 + +![Client端NioEventLoop处理的事件](images/Middleware/Client端NioEventLoop处理的事件.png) + + + +### 服务启动 + +服务启动时,以 example 代码中的 EchoServer 为例,启动的过程以及相应的源码类如下: + +1. `EchoServer#new NioEventLoopGroup(1)->NioEventLoop#provider.openSelector()` : 创建 selector +2. `EchoServer#b.bind(PORT).sync->AbstractBootStrap#doBind()->initAndRegister()-> channelFactory.newChannel() / init(channel)` : 创建 serverSocketChannel 以及初始化 +3. `EchoServer#b.bind(PORT).sync->AbstractBootStrap#doBind()->initAndRegister()-> config().group().register(channel)` :从 boss group 中选择一个 NioEventLoop 开始注册 serverSocketChannel +4. `EchoServer#b.bind(PORT).sync->AbstractBootStrap#doBind()->initAndRegister()->config().group().register(channel)->AbstractChannel#register0(promise)->AbstractNioChannel#javaChannel().register(eventLoop().unwrappedSelector(), 0, this)` : 将 server socket channel 注册到选择的 NioEventLoop 的 selector +5. `EchoServer#b.bind(PORT).sync()->AbstractBootStrap#doBind()->doBind0()->AbstractChannel#doBind(localAddress)->NioServerSocketChannel#javaChannel().bind(localAddress, config.getBacklog())` : 绑定地址端口开始启动 +6. `EchoServer#b.bind(PORT).sync()->AbstractBootStrap#doBind()->doBind0()->AbstractChannel#pipeline.fireChannelActive()->AbstractNioChannel#selectionKey.interestOps(interestOps|readInterestOp)`: 注册 OP_READ 事件 + +上述启动流程中,1、2、3 由我们自己的线程执行,即mainThread,4、5、6 是由Boss Thread执行。相应时序图如下: +![Netty流程-服务启动](images/Middleware/Netty流程-服务启动.jpg) + + + +### 建立连接 + +服务启动后便是建立连接的过程了,相应过程及源码类如下: + +1. `NioEventLoop#run()->processSelectedKey()` NioEventLoop 中的 selector 轮询创建连接事件(OP_ACCEPT) +2. `NioEventLoop#run()->processSelectedKey()->AbstractNioMessageChannel#read->NioServerSocketChannel#doReadMessages()->SocketUtil#accept(serverSocketChannel)` 创建 socket channel +3. `NioEventLoop#run()->processSelectedKey()->AbstractNioMessageChannel#fireChannelRead->ServerBootstrap#ServerBootstrapAcceptor#channelRead-> childGroup.register(child)` 从worker group 中选择一个 NioEventLoop 开始注册 socket channel +4. `NioEventLoop#run()->processSelectedKey()->AbstractNioMessageChannel#fireChannelRead->ServerBootstrap#ServerBootstrapAcceptor#channelRead-> childGroup.register(child)->AbstractChannel#register0(promise)-> AbstractNioChannel#javaChannel().register(eventLoop().unwrappedSelector(), 0, this)` 将 socket channel 注册到选择的 NioEventLoop 的 selector +5. `NioEventLoop#run()->processSelectedKey()->AbstractNioMessageChannel#fireChannelRead->ServerBootstrap#ServerBootstrapAcceptor#channelRead-> childGroup.register(child)->AbstractChannel#pipeline.fireChannelActive()-> AbstractNioChannel#selectionKey.interestOps(interestOps | readInterestOp)` 注册 OP_ACCEPT 事件 + +同样,上述流程中 1、2、3 的执行仍由 Boss Thread 执行,直到 4、5 由具体的 Work Thread 执行。 +![Netty流程-建立连接](images/Middleware/Netty流程-建立连接.jpg) + + + +### 读写与业务处理 + +连接建立完毕后是具体的读写,以及业务处理逻辑。以 EchoServerHandler 为例,读取数据后会将数据传播出去供业务逻辑处理,此时的 EchoServerHandler 代表我们的业务逻辑,而它的实现也非常简单,就是直接将数据写回去。我们将这块看成一个整条,流程如下: + +1. `NioEventLoop#run()->processSelectedKey() NioEventLoop 中的 selector` 轮询创建读取事件(OP_READ) +2. `NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()`nioSocketChannel 开始读取数据 +3. `NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->pipeline.fireChannelRead(byteBuf)`把读取到的数据传播出去供业务处理 +4. `AbstractNioByteChannel#pipeline.fireChannelRead->EchoServerHandler#channelRead`在这个例子中即 EchoServerHandler 的执行 +5. `EchoServerHandler#write->ChannelOutboundBuffer#addMessage` 调用 write 方法 +6. `EchoServerHandler#flush->ChannelOutboundBuffer#addFlush` 调用 flush 准备数据 +7. `EchoServerHandler#flush->NioSocketChannel#doWrite` 调用 flush 发送数据 + +在这个过程中读写数据都是由 Work Thread 执行的,但是业务处理可以由我们自定义的线程池来处理,并且一般我们也是这么做的,默认没有指定线程的情况下仍然由 Work Thread 代为处理。 +![Netty流程-读写与业务处理](images/Middleware/Netty流程-读写与业务处理.jpg) + + + +### 关闭连接 + +服务处理完毕后,单个连接的关闭是什么样的呢? + +1. `NioEventLoop#run()->processSelectedKey()` NioEventLoop 中的 selector 轮询创建读取事件(OP_READ),这里关闭连接仍然是读取事件 +2. `NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->closeOnRead(pipeline)`当字节<0 时开始执行关闭 nioSocketChannel +3. `NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->closeOnRead(pipeline)->AbstractChannel#close->AbstractNioChannel#doClose()` 关闭 socketChannel +4. `NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->closeOnRead(pipeline)->AbstractChannel#close->outboundBuffer.failFlushed/close` 清理消息:不接受新信息,fail 掉所有 queue 中消息 +5. `NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->closeOnRead(pipeline)->AbstractChannel#close->fireChannelInactiveAndDeregister->AbstractNioChannel#doDeregister eventLoop().cancel(selectionKey())` 关闭多路复用器的 key + +时序图如下: +![Netty流程-关闭连接.jpg](images/Middleware/Netty流程-关闭连接.jpg) + + + +### 关闭服务 + +最后是关闭整个 Netty 服务: + +1. `NioEventLoop#run->closeAll()->selectionKey.cancel/channel.close` 关闭 channel,取消 selectionKey +2. `NioEventLoop#run->confirmShutdown->cancelScheduledTasks` 取消定时任务 +3. `NioEventLoop#cleanup->selector.close()` 关闭 selector + +时序图如下,为了好画将 NioEventLoop 拆成了 2 块: +![Netty流程-关闭服务.jpg](images/Middleware/Netty流程-关闭服务.jpg) + + + +## 长连接优化 + +### 更多连接 + +### 更高QPS + + + +## 线程模型 + +Netty通过Reactor模型基于多路复用器接收并处理用户请求,内部实现了两个线程池,boss线程池和work线程池,其中boss线程池的线程负责处理请求的accept事件,当接收到accept事件的请求时,把对应的socket封装到一个NioSocketChannel中,并交给work线程池,其中work线程池负责请求的read和write事件,由对应的Handler处理。 + +### 单线程Reactor线程模型 + +下图演示了单线程reactor线程模型,之所以称之为单线程,还是因为只有一个accpet Thread接受任务,之后转发到reactor线程中进行处理。两个黄色框表示的是Reactor Thread Group,里面有多个Reactor Thread。一个Reactor Thread Group中的Reactor Thread功能都是相同的,例如第一个黄色框中的Reactor Thread都是处理拆分后的任务的第一阶段,第二个黄色框中的Reactor Thread都是处理拆分后的任务的第二步骤。任务具体要怎么拆分,要结合具体场景,下图只是演示作用。**一般来说,都是以比较耗时的操作(例如IO)为切分点**。 + +![单线程reactor线程模型](images/Middleware/单线程reactor线程模型.png) + +特别的,如果我们在任务处理的过程中,不划分为多个阶段进行处理的话,那么单线程reactor线程模型就退化成了并行工作和模型。**事实上,可以认为并行工作者模型,就是单线程reactor线程模型的最简化版本。** + + + +### 多线程Reactor线程模型 + +所谓多线程reactor线程模型,无非就是有多个accpet线程,如下图中的虚线框中的部分。 + +![多线程reactor线程模型](images/Middleware/多线程reactor线程模型.png) + + + +### 混合型Reactor线程模型 + +混合型reactor线程模型,实际上最能体现reactor线程模型的本质: + +- 将任务处理切分成多个阶段进行,每个阶段处理完自己的部分之后,转发到下一个阶段进行处理。不同的阶段之间的执行是异步的,可以认为每个阶段都有一个独立的线程池。 +- 不同的类型的任务,有着不同的处理流程,划分时需要划分成不同的阶段。如下图蓝色是一种任务、绿色是另一种任务,两种任务有着不同的执行流程 + +![混合型reactor线程模型](images/Middleware/混合型reactor线程模型.png) + + + +### Netty-Reactor线程模型 + +![Netty-Reactor](images/Middleware/Netty-Reactor.png) + +图中大致包含了5个步骤,而我们编写的服务端代码中可能并不能完全体现这样的步骤,因为Netty将其中一些步骤的细节隐藏了,笔者将会通过图形分析与源码分析相结合的方式帮助读者理解这五个步骤。这个五个步骤可以按照以下方式简要概括: + +- 设置服务端ServerBootStrap启动参数 +- 通过ServerBootStrap的bind方法启动服务端,bind方法会在parentGroup中注册NioServerScoketChannel,监听客户端的连接请求 +- Client发起连接CONNECT请求,parentGroup中的NioEventLoop不断轮循是否有新的客户端请求,如果有,ACCEPT事件触发 +- ACCEPT事件触发后,parentGroup中NioEventLoop会通过NioServerSocketChannel获取到对应的代表客户端的NioSocketChannel,并将其注册到childGroup中 +- childGroup中的NioEventLoop不断检测自己管理的NioSocketChannel是否有读写事件准备好,如果有的话,调用对应的ChannelHandler进行处理 + + + +## HashedWheelTimer + +时间轮其实就是一种环形的数据结构,可以想象成时钟,分成很多格子,一个格子代表一段时间。并用一个链表保存在该格子上的计划任务,同时一个指针随着时间一格一格转动,并执行相应格子中的所有到期任务。任务通过时间取模决定放入那个格子。 + +![HashedWheelTimer](images/Middleware/HashedWheelTimer.png) + +在网络通信中管理上万的连接,每个连接都有超时任务,如果为每个任务启动一个Timer超时器,那么会占用大量资源。为了解决这个问题,可用Netty工具类HashedWheelTimer。 + +Netty 的时间轮 `HashedWheelTimer` 给出了一个**粗略的定时器实现**,之所以称之为粗略的实现是**因为该时间轮并没有严格的准时执行定时任务**,而是在每隔一个时间间隔之后的时间节点执行,并执行当前时间节点之前到期的定时任务。 + +当然具体的定时任务的时间执行精度可以通过调节 HashedWheelTimer 构造方法的时间间隔的大小来进行调节,在大多数网络应用的情况下,由于 IO 延迟的存在,并**不会严格要求具体的时间执行精度**,所以默认的 100ms 时间间隔可以满足大多数的情况,不需要再花精力去调节该时间精度。 + + + +**HashedWheelTimer的特点** + +- 从源码分析可以看出,其实 HashedWheelTimer 的时间精度并不高,误差能够在 100ms 左右,同时如果任务队列中的等待任务数量过多,可能会产生更大的误差 +- 但是 HashedWheelTimer 能够处理非常大量的定时任务,且每次定位到要处理任务的候选集合链表只需要 O(1) 的时间,而 Timer 等则需要调整堆,是 O(logN) 的时间复杂度 +- HashedWheelTimer 本质上是`模拟了时间的轮盘`,将大量的任务拆分成了一个个的小任务列表,能够有效`节省 CPU 和线程资源` + + + +### 源码解读 + +```java +public HashedWheelTimer(ThreadFactory threadFactory, long tickDuration, TimeUnit unit, + int ticksPerWheel, boolean leakDetection, long maxPendingTimeouts) { + ...... +} +``` + +- `threadFactory`:自定义线程工厂,用于创建线程对象 +- `tickDuration`:间隔多久走到下一槽(相当于时钟走一格) +- `unit`:定义tickDuration的时间单位 +- `ticksPerWheel`:一圈有多个槽 +- `leakDetection`:是否开启内存泄漏检测 +- `maxPendingTimeouts`:最多待执行的任务个数。0或负数表示无限制 + + + +### 优缺点 + +- **优点** + - 可以添加、删除、取消定时任务 + - 能高效的处理大批定时任务 +- **缺点** + - 对内存要求较高,占用较高的内存 + - 时间精度要求不高 + + + +### 定时任务方案 + +目前主流的一些定时任务方案: + +- Timer +- ScheduledExecutorService +- ThreadPoolTaskScheduler(基于ScheduledExecutorService) +- Netty的schedule(用到了PriorityQueue) +- Netty的HashedWheelTimer(时间轮) +- Kafka的TimingWheel(层级时间轮) + + + +### 使用案例 + +```java +// 构造一个 Timer 实例 +Timer timer = new HashedWheelTimer(); + +// 提交一个任务,让它在 5s 后执行 +Timeout timeout1 = timer.newTimeout(new TimerTask() { + @Override + public void run(Timeout timeout) { + System.out.println("5s 后执行该任务"); + } +}, 5, TimeUnit.SECONDS); + +// 再提交一个任务,让它在 10s 后执行 +Timeout timeout2 = timer.newTimeout(new TimerTask() { + @Override + public void run(Timeout timeout) { + System.out.println("10s 后执行该任务"); + } +}, 10, TimeUnit.SECONDS); + +// 取消掉那个 5s 后执行的任务 +if (!timeout1.isExpired()) { + timeout1.cancel(); +} + +// 原来那个 5s 后执行的任务,已经取消了。这里我们反悔了,我们要让这个任务在 3s 后执行 +// 我们说过 timeout 持有上、下层的实例,所以下面的 timer 也可以写成 timeout1.timer() +timer.newTimeout(timeout1.task(), 3, TimeUnit.SECONDS); +``` + + + + + +## ByteBuf + +### 工作流程 + +ByteBuf维护两个不同的索引:`读索引(readerIndex)` 和 `写索引(writerIndex)` 。如下图所示: + +![ByteBuf工作流程](images/Middleware/ByteBuf工作流程.png) + +- `ByteBuf` 维护了 `readerIndex` 和 `writerIndex` 索引 +- 当 `readerIndex > writerIndex` 时,则抛出 `IndexOutOfBoundsException` +- `ByteBuf`容量 = `writerIndex` +- `ByteBuf` 可读容量 = `writerIndex` - `readerIndex` +- `readXXX()` 和 `writeXXX()` 方法将会推进其对应的索引。自动推进 +- `getXXX()` 和 `setXXX()` 方法将对 `writerIndex` 和 `readerIndex` 无影响 + + + +### 使用模式 + +ByteBuf本质是一个由不同的索引分别控制读访问和写访问的字节数组。ByteBuf共有三种模式:`堆缓冲区模式(Heap Buffer)`、`直接缓冲区模式(Direct Buffer)` 和 `复合缓冲区模式(Composite Buffer)`。 + +#### 堆缓冲区模式(Heap Buffer) + +堆缓冲区模式又称为`支撑数组(backing array)`。将数据存放在JVM的堆空间,通过将数据存储在数组中实现。 + +- **优点**:由于数据存储在Jvm堆中可以快速创建和快速释放,并且提供了数组直接快速访问的方法 +- **缺点**:每次数据与I/O进行传输时,都需要将数据拷贝到直接缓冲区 + +```java +public static void heapBuffer() { + // 创建Java堆缓冲区 + ByteBuf heapBuf = Unpooled.buffer(); + if (heapBuf.hasArray()) { // 是数组支撑 + byte[] array = heapBuf.array(); + int offset = heapBuf.arrayOffset() + heapBuf.readerIndex(); + int length = heapBuf.readableBytes(); + handleArray(array, offset, length); + } +} +``` + + + +#### 直接缓冲区模式(Direct Buffer) + +Direct Buffer属于堆外分配的直接内存,不会占用堆的容量。适用于套接字传输过程,避免了数据从内部缓冲区拷贝到直接缓冲区的过程,性能较好。对于涉及大量I/O的数据读写,建议使用Direct Buffer。而对于用于后端的业务消息编解码模块建议使用Heap Buffer。 + +- **优点**: 使用Socket传递数据时性能很好,避免了数据从Jvm堆内存拷贝到直接缓冲区的过程。提高了性能 +- **缺点**: 相对于堆缓冲区而言,Direct Buffer分配内存空间和释放更为昂贵 + +```java +public static void directBuffer() { + ByteBuf directBuf = Unpooled.directBuffer(); + if (!directBuf.hasArray()) { + int length = directBuf.readableBytes(); + byte[] array = new byte[length]; + directBuf.getBytes(directBuf.readerIndex(), array); + handleArray(array, 0, length); + } +} +``` + + + +#### 复合缓冲区模式(Composite Buffer) + +Composite Buffer是Netty特有的缓冲区。本质上类似于提供一个或多个ByteBuf的组合视图,可以根据需要添加和删除不同类型的ByteBuf。 + +- Composite Buffer是一个组合视图。它提供一种访问方式让使用者自由组合多个ByteBuf,避免了拷贝和分配新的缓冲区 +- Composite Buffer不支持访问其支撑数组。因此如果要访问,需要先将内容拷贝到堆内存中,再进行访问 + +下图是将两个ByteBuf:头部 + Body 组合在一起,没有进行任何复制过程。仅仅创建了一个视图: + +![CompositeBuffer](images/Middleware/CompositeBuffer.png) + +```java +public static void byteBufComposite() { + // 复合缓冲区,只是提供一个视图 + CompositeByteBuf messageBuf = Unpooled.compositeBuffer(); + ByteBuf headerBuf = Unpooled.buffer(); // can be backing or direct + ByteBuf bodyBuf = Unpooled.directBuffer(); // can be backing or direct + messageBuf.addComponents(headerBuf, bodyBuf); + messageBuf.removeComponent(0); // remove the header + for (ByteBuf buf : messageBuf) { + System.out.println(buf.toString()); + } +} +``` + + + +### 字节级操作 + +#### 随机访问索引 + +ByteBuf的索引与普通的Java字节数组一样。第一个字节的索引是0,最后一个字节索引总是capacity()-1。访问方式如下: + +- readXXX()和writeXXX()方法将会推进其对应的索引readerIndex和writerIndex。自动推进 +- getXXX()和setXXX()方法用于访问数据,对writerIndex和readerIndex无影响 + +```java +public static void byteBufRelativeAccess() { + ByteBuf buffer = Unpooled.buffer(); // get reference form somewhere + for (int i = 0; i < buffer.capacity(); i++) { + byte b = buffer.getByte(i); // 不改变readerIndex值 + System.out.println((char) b); + } +} +``` + + + +#### 顺序访问索引 + +Netty的ByteBuf同时具有读索引和写索引,但JDK的ByteBuffer只有一个索引,所以JDK需要调用flip()方法在读模式和写模式之间切换。ByteBuf被读索引和写索引划分成3个区域:**可丢弃字节区域**、**可读字节区域** 和 **可写字节区域** 。 + +![ByteBuf顺序访问索引](images/Middleware/ByteBuf顺序访问索引.png) + + + +#### 可丢弃字节区域 + +可丢弃字节区域是指:[0,readerIndex)之间的区域。可调用discardReadBytes()方法丢弃已经读过的字节。 + +- discardReadBytes()效果 ----- 将可读字节区域(CONTENT)[readerIndex, writerIndex)往前移动readerIndex位,同时修改读索引和写索引 +- discardReadBytes()方法会移动可读字节区域内容(CONTENT)。如果频繁调用,会有多次数据复制开销,对性能有一定的影响 + + + +#### 可读字节区域 + +可读字节区域是指:[readerIndex, writerIndex)之间的区域。任何名称以read和skip开头的操作方法,都会改变readerIndex索引。 + + + +#### 可写字节区域 + +可写字节区域是指:[writerIndex, capacity)之间的区域。任何名称以write开头的操作方法都将改变writerIndex的值。 + + + +#### 索引管理 + +- markReaderIndex()+resetReaderIndex() ----- markReaderIndex()是先备份当前的readerIndex,resetReaderIndex()则是将刚刚备份的readerIndex恢复回来。常用于dump ByteBuf的内容,又不想影响原来ByteBuf的readerIndex的值 +- readerIndex(int) ----- 设置readerIndex为固定的值 +- writerIndex(int) ----- 设置writerIndex为固定的值 +- clear() ----- 效果是: readerIndex=0, writerIndex(0)。不会清除内存 +- 调用clear()比调用discardReadBytes()轻量的多。仅仅重置readerIndex和writerIndex的值,不会拷贝任何内存,开销较小 + + + +#### 查找操作(indexOf) + +查找ByteBuf指定的值。类似于,String.indexOf("str")操作 + +- 最简单的方法 —— indexOf() +- 利用ByteProcessor作为参数来查找某个指定的值 + +```java +public static void byteProcessor() { + ByteBuf buffer = Unpooled.buffer(); //get reference form somewhere + // 使用indexOf()方法来查找 + buffer.indexOf(buffer.readerIndex(), buffer.writerIndex(), (byte)8); + // 使用ByteProcessor查找给定的值 + int index = buffer.forEachByte(ByteProcessor.FIND_CR); +} +``` + + + +#### 派生缓冲——视图 + +派生缓冲区为ByteBuf提供了一个访问的视图。视图仅仅提供一种访问操作,不做任何拷贝操作。下列方法,都会呈现给使用者一个视图,以供访问: + +- duplicate() +- slice() +- slice(int, int) +- Unpooled.unmodifiableBuffer(...) +- Unpooled.wrappedBuffer(...) +- order(ByteOrder) +- readSlice(int) + +**理解** + +- 上面的6中方法,都会返回一个新的ByteBuf实例,具有自己的读索引和写索引。但是,其内部存储是与原对象是共享的。这就是视图的概念 +- 请注意:如果你修改了这个新的ByteBuf实例的具体内容,那么对应的源实例也会被修改,因为其内部存储是共享的 +- 如果需要拷贝现有缓冲区的真实副本,请使用copy()或copy(int, int)方法 +- 使用派生缓冲区,避免了复制内存的开销,有效提高程序的性能 + +```java +public static void byteBufSlice() { + Charset utf8 = Charset.forName("UTF-8"); + ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8); + ByteBuf sliced = buf.slice(0, 15); + System.out.println(sliced.toString(utf8)); + buf.setByte(0, (byte)'J'); + assert buf.getByte(0) == sliced.getByte(0); // return true +} + +public static void byteBufCopy() { + Charset utf8 = Charset.forName("UTF-8"); + ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8); + ByteBuf copy = buf.copy(0, 15); + System.out.println(copy.toString(utf8)); + buf.setByte(0, (byte)'J'); + assert buf.getByte(0) != copy.getByte(0); // return true +} +``` + + + +#### 读/写操作 + +如上文所提到的,有两种类别的读/写操作: + +- get()和set()操作 ----- 从给定的索引开始,并且保持索引不变 +- read()和write()操作 ----- 从给定的索引开始,并且根据已经访问过的字节数对索引进行访问 +- 下图给出get()操作API,对于set()操作、read()操作和write操作可参考书籍或API + +![ByteBuf-get](images/Middleware/ByteBuf-get.png) + + + +#### 更多操作 + +![ByteBuf-更多操作](images/Middleware/ByteBuf-更多操作.png) + +下面的两个方法操作字面意思较难理解,给出解释: + +- **hasArray()**:如果ByteBuf由一个字节数组支撑,则返回true。通俗的讲:ByteBuf是堆缓冲区模式,则代表其内部存储是由字节数组支撑的 +- **array()**:如果ByteBuf是由一个字节数组支撑泽返回数组,否则抛出UnsupportedOperationException异常。也就是,ByteBuf是堆缓冲区模式 + + + +### ByteBuf分配 + +创建和管理ByteBuf实例的多种方式:**按序分配(ByteBufAllocator)**、**Unpooled缓冲区** 和 **ByteBufUtil类**。 + +#### 按序分配:ByteBufAllocator接口 + +Netty通过接口ByteBufAllocator实现了(ByteBuf的)池化。Netty提供池化和非池化的ButeBufAllocator: + +- `ctx.channel().alloc().buffer()`:本质就是ByteBufAllocator.DEFAULT +- `ByteBufAllocator.DEFAULT.buffer()`:返回一个基于堆或者直接内存存储的Bytebuf。默认是堆内存 +- `ByteBufAllocator.DEFAULT`:有两种类型: UnpooledByteBufAllocator.DEFAULT(非池化)和PooledByteBufAllocator.DEFAULT(池化)。对于Java程序,默认使用PooledByteBufAllocator(池化)。对于安卓,默认使用UnpooledByteBufAllocator(非池化) +- 可以通过BootStrap中的Config为每个Channel提供独立的ByteBufAllocator实例 + +![img](images/Middleware/ByteBufAllocator.png) + +解释: + +- 上图中的buffer()方法,返回一个基于堆或者直接内存存储的Bytebuf ----- 缺省是堆内存。源码: AbstractByteBufAllocator() { this(false); } +- ByteBufAllocator.DEFAULT ----- 可能是池化,也可能是非池化。默认是池化(PooledByteBufAllocator.DEFAULT) + + + +#### Unpooled缓冲区——非池化 + +Unpooled提供静态的辅助方法来创建未池化的ByteBuf。 + +![Unpooled缓冲区](images/Middleware/Unpooled缓冲区.png) + +注意: + +- 上图的buffer()方法,返回一个未池化的基于堆内存存储的ByteBuf +- wrappedBuffer() ----- 创建一个视图,返回一个包装了给定数据的ByteBuf。非常实用 + +创建ByteBuf代码: + +```java + public void createByteBuf(ChannelHandlerContext ctx) { + // 1. 通过Channel创建ByteBuf + ByteBuf buf1 = ctx.channel().alloc().buffer(); + // 2. 通过ByteBufAllocator.DEFAULT创建 + ByteBuf buf2 = ByteBufAllocator.DEFAULT.buffer(); + // 3. 通过Unpooled创建 + ByteBuf buf3 = Unpooled.buffer(); +} +``` + + + +#### ByteBufUtil类 + +ByteBufUtil类提供了用于操作ByteBuf的静态的辅助方法: hexdump()和equals + +- hexdump():以十六进制的表示形式打印ByteBuf的内容。非常有价值 +- equals():判断两个ByteBuf实例的相等性 + + + +### 引用计数 + +Netty4.0版本中为ButeBuf和ButeBufHolder引入了引用计数技术。请区别引用计数和可达性分析算法(jvm垃圾回收) + +- 谁负责释放:一般来说,是由最后访问(引用计数)对象的那一方来负责将它释放 +- buffer.release():引用计数减1 +- buffer.retain():引用计数加1 +- buffer.refCnt():返回当前对象引用计数值 +- buffer.touch():记录当前对象的访问位置,主要用于调试 +- 引用计数并非仅对于直接缓冲区(direct Buffer)。ByteBuf的三种模式: 堆缓冲区(heap Buffer)、直接缓冲区(dirrect Buffer)和复合缓冲区(Composite Buffer)都使用了引用计数,某些时候需要程序员手动维护引用数值 + +```java +public static void releaseReferenceCountedObject(){ + ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); + // 引用计数加1 + buffer.retain(); + // 输出引用计数 + buffer.refCnt(); + // 引用计数减1 + buffer.release(); +} +``` + + + +## Zero-Copy + +**Netty** 的`Zero-copy` 体现在如下几个个方面: + +- **通过CompositeByteBuf实现零拷贝**:Netty提供了`CompositeByteBuf` 类,可以将多个ByteBuf合并为一个逻辑上的ByteBuf,避免了各个ByteBuf之间的拷贝 +- **通过wrap操作实现零拷贝**:通过`wrap`操作,可以将byte[]、ByteBuf、ByteBuffer等包装成一个Netty ByteBuf对象,进而避免了拷贝操作 +- 通过slice操作实现零拷贝:ByteBuf支持`slice`操作,可以将ByteBuf分解为多个共享同一个存储区域的ByteBuf,避免了内存的拷贝 +- **通过FileRegion实现零拷贝**:通过 `FileRegion` 包装的`FileChannel.tranferTo` 实现文件传输,可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环write方式导致的内存拷贝问题 + +### 零拷贝操作 + +#### 通过CompositeByteBuf实现零拷贝 + +![CompositeByteBuf实现零拷贝](images/Middleware/CompositeByteBuf实现零拷贝.png) + +```java +ByteBuf header = null; +ByteBuf body = null; + +// 传统合并header和body:两次额外的数据拷贝 +ByteBuf allBuf = Unpooled.buffer(header.readableBytes() + body.readableBytes()); +allBuf.writeBytes(header); +allBuf.writeBytes(body); + +// 合并header和body:内部这两个 ByteBuf 都是单独存在的, CompositeByteBuf 只是逻辑上是一个整体 +CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer(); +compositeByteBuf.addComponents(true, header, body); +// 底层封装了 CompositeByteBuf 操作 +ByteBuf allByteBuf = Unpooled.wrappedBuffer(header, body); +``` + + + +#### 通过wrap操作实现零拷贝 + +```java +byte[] bytes = null; + +// 传统方式:直接将byte[]数组拷贝到ByteBuf中 +ByteBuf byteBuf = Unpooled.buffer(); +byteBuf.writeBytes(bytes); + +// wrap方式:将bytes包装成为一个UnpooledHeapByteBuf对象, 包装过程中, 是不会有拷贝操作的 +// 即最后我们生成的生成的ByteBuf对象是和bytes数组共用了同一个存储空间, 对bytes的修改也会反映到ByteBuf对象中 +ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes); + +``` + + + +#### 通过slice操作实现零拷贝 + +slice 操作和 wrap 操作刚好相反,`Unpooled.wrappedBuffer` 可以将多个 ByteBuf 合并为一个, 而 slice 操作可以将一个 **ByteBuf **`切片` 为多个共享一个存储区域的 ByteBuf 对象. ByteBuf 提供了两个 slice 操作方法: + +```java +public ByteBuf slice(); +public ByteBuf slice(int index, int length); +``` + +不带参数的`slice`方法等同于`buf.slice(buf.readerIndex(), buf.readableBytes())` 调用, 即返回 buf 中可读部分的切片. 而 `slice(int index, int length)`方法相对就比较灵活了, 我们可以设置不同的参数来获取到 buf 的不同区域的切片。 + +用 `slice` 方法产生 header 和 body 的过程是没有拷贝操作的, header 和 body 对象在内部其实是共享了 byteBuf 存储空间的不同部分而已,即: + +![slice操作实现零拷贝](images/Middleware/slice操作实现零拷贝.png) + + + +#### 通过FileRegion实现零拷贝 + +Netty 中使用 FileRegion 实现文件传输的零拷贝, 不过在底层 FileRegion 是依赖于 **Java NIO** `FileChannel.transfer` 的零拷贝功能。当有了 FileRegion 后, 我们就可以直接通过它将文件的内容直接写入 **Channel** 中, 而不需要像传统的做法: 拷贝文件内容到临时 **buffer**, 然后再将 **buffer** 写入 **Channel.** 通过这样的零拷贝操作, 无疑对传输大文件很有帮助。 + + + +### 传统IO的流程 + +![传统IO的流程Copy](images/Middleware/传统IO的流程Copy.png) + +![传统IO的流程](images/Middleware/传统IO的流程.png) + +- **「第一步」**:将文件通过 **「DMA」** 技术从磁盘中拷贝到内核缓冲区 +- **「第二步」**:将文件从内核缓冲区拷贝到用户进程缓冲区域中 +- **「第三步」**:将文件从用户进程缓冲区中拷贝到 socket 缓冲区中 +- **「第四步」**:将socket缓冲区中的文件通过 **「DMA」** 技术拷贝到网卡 + + + +### 零拷贝整体流程图 + +![零拷贝CPU](images/Middleware/零拷贝CPU.png) + +![零拷贝整体流程图](images/Middleware/零拷贝整体流程图.png) + + + +## TCP粘包拆包 + +### 粘包拆包图解 + +![CP粘包拆包图解](images/Middleware/CP粘包拆包图解.png) + +假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到字节数是不确定的,故可能存在以下几种情况: + +- 服务端分两次读取到两个独立的数据包,分别是D1和D2,没有粘包和拆包 +- 服务端一次接收到了两个数据包,D1和D2粘在一起,发生粘包 +- 服务端分两次读取到数据包,第一次读取到了完整D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,发生拆包 +- 服务端分两次读取到数据包,第一次读取到部分D1包,第二次读取到剩余的D1包和全部的D2包 +- 当TCP缓存再小一点的话,会把D1和D2分别拆成多个包发送 + + + +### 产生原因 + +产生粘包和拆包问题的主要原因是,操作系统在发送TCP数据的时候,底层会有一个缓冲区,例如1024个字节大小: + +- 如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题 +- 如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包,也就是将一个大的包拆分为多个小包进行发送 + + + +### 解决方案 + +#### 固定长度 + +对于使用固定长度的粘包和拆包场景,可以使用: + +- `FixedLengthFrameDecoder`:每次读取固定长度的消息,如果当前读取到的消息不足指定长度,那么就会等待下一个消息到达后进行补足。其使用也比较简单,只需要在构造函数中指定每个消息的长度即可。 + +```java + @Override +protected void initChannel(SocketChannel ch) throws Exception { + // 这里将FixedLengthFrameDecoder添加到pipeline中,指定长度为20 + ch.pipeline().addLast(new FixedLengthFrameDecoder(20)); + // 将前一步解码得到的数据转码为字符串 + ch.pipeline().addLast(new StringDecoder()); + // 这里FixedLengthFrameEncoder是我们自定义的,用于将长度不足20的消息进行补全空格 + ch.pipeline().addLast(new FixedLengthFrameEncoder(20)); + // 最终的数据处理 + ch.pipeline().addLast(new EchoServerHandler()); +} +``` + + + +#### 指定分隔符 + +对于通过分隔符进行粘包和拆包问题的处理,Netty提供了两个编解码的类: + +- `LineBasedFrameDecoder`:通过换行符,即`\n`或者`\r\n`对数据进行处理 +- `DelimiterBasedFrameDecoder`:通过用户指定的分隔符对数据进行粘包和拆包处理 + +```java +@Override +protected void initChannel(SocketChannel ch) throws Exception { + String delimiter = "_$"; + // 将delimiter设置到DelimiterBasedFrameDecoder中,经过该解码一器进行处理之后,源数据将会 + // 被按照_$进行分隔,这里1024指的是分隔的最大长度,即当读取到1024个字节的数据之后,若还是未 + // 读取到分隔符,则舍弃当前数据段,因为其很有可能是由于码流紊乱造成的 + ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, + Unpooled.wrappedBuffer(delimiter.getBytes()))); + // 将分隔之后的字节数据转换为字符串数据 + ch.pipeline().addLast(new StringDecoder()); + // 这是我们自定义的一个编码器,主要作用是在返回的响应数据最后添加分隔符 + ch.pipeline().addLast(new DelimiterBasedFrameEncoder(delimiter)); + // 最终处理数据并且返回响应的handler + ch.pipeline().addLast(new EchoServerHandler()); +} +``` + + + +#### 数据包长度字段 + +处理粘拆包的主要思想是在生成的数据包中添加一个长度字段,用于记录当前数据包的长度。 + +- `LengthFieldBasedFrameDecoder`:按照参数指定的包长度偏移量数据对接收到的数据进行解码,从而得到目标消息体数据。解码过程如下图所示: + + ![LengthFieldBasedFrameDecoder](images/Middleware/LengthFieldBasedFrameDecoder.png) + +- `LengthFieldPrepender`:在响应的数据前面添加指定的字节数据,这个字节数据中保存了当前消息体的整体字节数据长度。编码过程如下图所示: + + ![LengthFieldPrepender](images/Middleware/LengthFieldPrepender.png) + +```java +@Override +protected void initChannel(SocketChannel ch) throws Exception { + // 这里将LengthFieldBasedFrameDecoder添加到pipeline的首位,因为其需要对接收到的数据 + // 进行长度字段解码,这里也会对数据进行粘包和拆包处理 + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2)); + // LengthFieldPrepender是一个编码器,主要是在响应字节数据前面添加字节长度字段 + ch.pipeline().addLast(new LengthFieldPrepender(2)); + // 对经过粘包和拆包处理之后的数据进行json反序列化,从而得到User对象 + ch.pipeline().addLast(new JsonDecoder()); + // 对响应数据进行编码,主要是将User对象序列化为json + ch.pipeline().addLast(new JsonEncoder()); + // 处理客户端的请求的数据,并且进行响应 + ch.pipeline().addLast(new EchoServerHandler()); + } +``` + + + +#### 自定义粘包拆包器 + +可以通过实现`MessageToByteEncoder`和`ByteToMessageDecoder`来实现自定义粘包和拆包处理的目的。 + +- `MessageToByteEncoder`:作用是将响应数据编码为一个ByteBuf对象 +- `ByteToMessageDecoder`:将接收到的ByteBuf数据转换为某个对象数据 + + + +## 高性能 + +- **IO线程模型**:同步非阻塞,用最少的资源做更多的事 +- **内存零拷贝**:尽量减少不必要的内存拷贝,实现了更高效率的传输 +- **内存池设计**:申请的内存可以重用,主要指直接内存。内部实现是用一颗二叉查找树管理内存分配情况 +- **串形化处理读写**:避免使用锁带来的性能开销 +- **高性能序列化协议**:支持 protobuf 等高性能序列化协议 + + + +## 操作系统调优 + +### 文件描述符 + +- 设置系统最大文件句柄数 + +```bash +# 查看 +cat /proc/sys/fs/file-max +# 修改 +在/etc/sysctl.conf插入fs.file-max=1000000 +# 配置生效 +sysctl -p +``` + +- 设置单进程打开的最大句柄数 + +默认单进程打开的最大句柄数是 `1024`,通过 `ulimit -a` 可以查看相关参数,示例如下: + +```powershell +[root@test ~]# ulimit -a +core file size (blocks, -c) 0 +data seg size (kbytes, -d) unlimited +scheduling priority (-e) 0 +file size (blocks, -f) unlimited +pending signals (-i) 256324 +max locked memory (kbytes, -l) 64 +max memory size (kbytes, -m) unlimited +open files (-n) 1024 +...... +``` + +当并发接入的TCP连接数超过上限时,就会报“too many open files”,所有新的客户端接入将失败。通过 `vi /etc/security/limits.conf` 命令添加如下配置参数: + +```powershell +* soft nofile 1000000 +* hard nofile 1000000 +``` + +修改之后保存,注销当前用户,重新登录,通过 `ulimit -a` 查看修改的状态是否生效。 + +**注意**:尽管我们可以将单个进程打开的最大句柄数修改的非常大,但是当句柄数达到一定数量级之后,处理效率将出现明显下降,因此,需要根据服务器的硬件配置和处理能力进行合理设置。 + + + +### TCP/IP相关参数 + +需要重点调优的TCP IP参数如下: + +- net.ipv4.tcp_rmem:为每个TCP连接分配的读缓冲区内存大小。第一个值时socket接收缓冲区分配的最小字节数。 + + + +### 多网卡队列和软中断 + +- **TCP缓冲区** + + 根据推送消息的大小,合理设置以下两个参数,对于海量长连接,通常 32K 是个不错的选择: + + - **SO_SNDBUF**:发送缓冲区大小 + - **SO_RCVBUF**:接收缓冲区大小 + +- **软中断** + + 使用命令 `cat /proc/interrupts` 查看网卡硬件中断的运行情况,如果全部被集中在CPU0上处理,则无法并行执行多个软中断。Linux kernel内核≥2.6.35的版本,可以开启RPS,网络通信能力提升20%以上,RPS原理是:根据数据包的源地址、目的地址和源端口等,计算出一个Hash值,然后根据Hash值来选择软中断运行的CPU,即实现每个链接和CPU绑定,通过Hash值来均衡软中断运行在多个CPU上。 + + + +## Netty性能调优 + +### 设置合理的线程数 + +**boss线程池优化** + +对于Netty服务端,通常只需要启动一个监听端口用于端侧设备接入,但是如果集群实例较少,甚至是单机部署,那么在短时间内大量设备接入时,需要对服务端的监听方式和线程模型做优化,即服务端监听多个端口,利用主从Reactor线程模型。由于同时监听了多个端口,每个ServerSocketChannel都对应一个独立的Acceptor线程,这样就能并行处理,加速端侧设备的接人速度,减少端侧设备的连接超时失败率,提高单节点服务端的处理性能。 + + + +**work线程池优化(I/O工作线程池)** + +对于I/O工作线程池的优化,可以先采用系统默认值(cpu内核数*2)进行性能测试,在性能测试过程中采集I/O线程的CPU占用大小,看是否存在瓶颈,具体策略如下: + +- 通过执行 `ps -ef|grep java` 找到服务端进程pid +- 执行`top -Hp pid`查询该进程下所有线程的运行情况,通过“shift+p”对CPU占用大小做排序,获取线程的pid及对应的CPU占用大小 +- 使用`printf'%x\n' pid`将pid转换成16进制格式 +- 通过`jstack -f pid`命令获取线程堆栈,或者通过jvisualvm工具打印线程堆栈,找到I/O work工作线程,查看他们的CPU占用大小及线程堆栈,关键词:`SelectorImpl.lockAndDoSelect` + +**分析** + +- 如果连续采集几次进行对比,发现线程堆栈都停留在SelectorImpl.lockAndDoSelect处,则说明I/O线程比较空闲,无需对工作线程数做调整 +- 如果发现I/O线程的热点停留在读或写操作,或停留在ChannelHandler的执行处,则可以通过适当调大NioEventLoop线程的个数来提升网络的读写性能。调整方式有两种: + - 接口API指定:在创建NioEventLoopGroup实例时指定线程数 + - 系统参数指定:通过-Dio.netty.eventLoopThreads来指定NioEventLoopGroup线程池(不建议) + + + +### 心跳检测优化 + +心跳检测的目的就是确认当前链路是否可用,对方是否活着并且能够正常接收和发送消息。从技术层面看,要解决链路的可靠性问题,必须周期性地对链路进行有效性检测。目前最流行和通用的做法就是心跳检测。 + +**海量设备接入的服务端心跳优化策略** + +- **要能够及时检测失效的连接,并将其剔除**。防止无效的连接句柄积压,导致OOM等问题 +- **设置合理的心跳周期**。防止心跳定时任务积压,造成频繁的老年代GC(新生代和老年代都有导致STW的GC,不过耗时差异较大),导致应用暂停 +- **使用Nety提供的链路空闲检测机制**。不要自己创建定时任务线程池,加重系统的负担,以及增加潜在的并发安全问题 + +**心跳检测机制分为三个层面** + +- **TCP层的心跳检测**:即TCP的 Keep-Alive机制,它的作用域是整个TCP协议栈 +- **协议层的心跳检测**:主要存在于长连接协议中,例如MQTT +- **应用层的心跳检测**:它主要由各业务产品通过约定方式定时给对方发送心跳消息实现 + +**心跳检测机制分类** + +- **Ping-Pong型心跳**:由通信一方定时发送Ping消息,对方接收到Ping消息后立即返回Pong答应消息给对方,属于“请求-响应型”心跳 +- **Ping-Ping型心跳**:不区分心跳请求和答应,由通信双发按照约定时间向对方发送心跳Ping消息,属于”双向心跳“ + +**心跳检测机制策略** + +- **心跳超时**:连续N次检测都没有收到对方的Pong应答消息或Ping请求消息,则认为链路已经发生逻辑失效 +- **心跳失败**:在读取和发送心跳消息的时候,如果直接发生了IO异常,说明链路已经失效 + +**链路空闲检测机制** + +- **读空闲**:链路持续时间T没有读取到任何消息 +- **写空闲**:链路持续时间T没有发送任何消息 +- **读写空闲**:链路持续时间T没有接收或者发送任何消息 + + + +**案例分析** + +由于移动无线网络的特点,推送服务的心跳周期并不能设置的太长,否则长连接会被释放,造成频繁的客户端重连,但是也不能设置太短,否则在当前缺乏统一心跳框架的机制下很容易导致信令风暴(例如微信心跳信令风暴问题)。具体的心跳周期并没有统一的标准,180S 也许是个不错的选择,微信为 300S。 + +在 Netty 中,可以通过在 ChannelPipeline 中增加 IdleStateHandler 的方式实现心跳检测,在构造函数中指定链路空闲时间,然后实现空闲回调接口,实现心跳的发送和检测。拦截链路空闲事件并处理心跳: + +```java +public class MyHandler extends ChannelHandlerAdapter { + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof IdleStateEvent) { + // 心跳处理 + } + } + } +``` + +**心跳优化结论** + +- 对于百万级的服务器,一般不建议很长的心跳周期和超时时长 +- 心跳检测周期通常不要超过60s,心跳检测超时通常为心跳检测周期的2倍 +- 建议通过IdleStateHandler实现心跳,不要自己创建定时任务线程池,加重系统负担和增加潜在并发安全问题 +- 发生心跳超时或心跳失败时,都需要关闭链路,由客户端发起重连操作,保证链路能够恢复正常 +- 链路空闲事件被触发后并没有关闭链路,而是触发IdleStateEvent事件,用户订阅IdleStateEvent事件,用于自定义逻辑处理。如关闭链路、客户端发起重新连接、告警和日志打印等 +- 链路空闲检测类库主要包括:IdleStateHandler、ReadTimeoutHandler、WriteTimeoutHandler + + + +### 接收和发送缓冲区调优 + +对于长链接,每个链路都需要维护自己的消息接收和发送缓冲区,JDK 原生的 NIO 类库使用的是java.nio.ByteBuffer, 它实际是一个长度固定的byte[],无法动态扩容。 + +**场景**:假设单条消息最大上限为10K,平均大小为5K,为满足10K消息处理,ByteBuffer的容量被设置为10K,这样每条链路实际上多消耗了5K内存,如果长链接链路数为100万,每个链路都独立持有ByteBuffer接收缓冲区,则额外损耗的总内存Total(M) =1000000×5K=4882M + + + +Netty提供的ByteBuf支持容量动态调整,同时提供了两种接收缓冲区的内存分配器: + +- **FixedRecvByteBufAllocator**:固定长度的接收缓冲区分配器,它分配的ByteBuf长度是固定大小的,并不会根据实际数据报大小动态收缩。但如果容量不足,支持动态扩展 +- **AdaptiveRecvByteBufAllocator**:容量动态调整的接收缓冲区分配器,会根据之前Channel接收到的数据报大小进行计算,如果连续填充满接收缓冲区的可写空间,则动态扩展容量。如果连续2次接收到的数据报都小于指定值,则收缩当前的容量,以节约内存 + +相对于FixedRecvByteBufAllocator,使用AdaptiveRecvByteBufAllocator更为合理,可在创建客户端或者服务端的时候指定RecvByteBufAllocator。 + +```java +Bootstrap b = new Bootstrap(); + b.group(group) + .channel(NioSocketChannel.class) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT) +``` + +**注意**:无论是接收缓冲区还是发送缓冲区,缓冲区的大小建议设置为消息的平均大小,不要设置成最大消息的上限,这会导致额外的内存浪费。 + + + +### 合理使用内存池 + +每个NioEventLoop线程处理N个链路。**链路处理流程**:开始处理A链路→**创建接收缓冲区(创建ByteBuf)**→消息解码→封装成POJO对象→提交至后台线程成Task→**释放接收缓冲区**→开始处理B链路。 + +如果使用内存池,则当A链路接收到新数据报后,从NioEventLoop的内存池中申请空闲的ByteBuf,解码完成后,调用release将ByteBuf释放到内存池中,供后续B链路继续使用。使用内存池优化后,单个NioEventLoop的ByteBuf申请和GC次数从原来的N=1000000/64= 15625次减少为最少0次(假设每次申请都有可用的内存)。 + +Netty默认不使用内存池,需要在创建客户端或者服务端的时候进行指定: + +```java +Bootstrap b = new Bootstrap(); + b.group(group) + .channel(NioSocketChannel.class) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) +``` + +使用内存池之后,内存的申请和释放必须成对出现,即 retain() 和 release() 要成对出现,否则会导致内存泄露。值得注意的是,如果使用内存池,完成 ByteBuf 的解码工作之后必须显式的调用 ReferenceCountUtil.release(msg) 对接收缓冲区 ByteBuf 进行内存释放,否则它会被认为仍然在使用中,这样会导致内存泄露。 + + + +### 防止I/O线程被意外阻塞 + +通常情况不能在Netty的I/O线程上做执行时间不可控的操作,如访问数据库、调用第三方服务等,但有一些隐形的阻塞操作却容易被忽略,如打印日志。 + +生产环境中,一般需要实时打印接口日志,其它日志处于ERROR级别,当服务发生I/O异常后,会记录异常日志。如果磁盘的WIO比较高,可能会发生写日志文件操作被同步阻塞,阻塞时间无法预测,就会导致Netty的NioEventLoop线程被阻塞,Socket链路无法被及时管理,其它链路也无法进行读写操作等。 + +常用的log4j虽然支持异步写日志(AsyncAppender),但当日志队列满之后,它会同步阻塞业务线程,直到日志队列有空闲位置可用。 + +类似问题具有极强的隐蔽性,往往WIO高的时间持续非常短,或是偶现的,在测试环境中很难模拟此类故障,问题定位难度大。 + + + +### I/O线程与业务线程分离 + +- 如果服务端不做复杂业务逻辑操作,仅是简单内存操作和消息转发,则可通过调大NioEventLoop工作线程池的方式,直接在I/O线程中执行业务ChannelHandler,这样便减少了一次线上上下文切换,性能反而更高 +- 如果有复杂的业务逻辑操作,则建议I/O线程和业务线程分离。 + - 对于I/O线程,由于互相之间不存在锁竞争,可以创建一个大的NioEventLoopGroup线程组,所有Channel都共享一个线程池 + - 对于后端的业务线程池,则建议创建多个小的业务线程池,线程池可以与I/O线程绑定,这样既减少了锁竞争,又提升了后端的处理性能 + + + +### 服务端并发连接数流控 + +无论服务端的性能优化到多少,都需要考虑流控功能。当资源成为瓶颈,或遇到端侧设备的大量接入,需要通过流控对系统做保护。一般Netty主要考虑并发连接数的控制。 + + + +## 案例分析 + +### 疑似内存泄漏 + +**环境**:8C16G的Linux + +**描述**:boss为1,worker为6,其余分配给业务使用,保持10W用户长链接,2W用户并发做消息请求 + +**分析**:dump内存堆栈发现Netty的ScheduledFutureTask增加了9076%,达到110W个实例。通过业务代码分析发现用户使用了IdleStateHandler用于在链路空闲时进行业务逻辑处理,但空闲时间比较大,为15分钟。Netty 的 IdleStateHandler 会根据用户的使用场景,启动三类定时任务,分别是:ReaderIdleTimeoutTask、WriterIdleTimeoutTask 和 AllIdleTimeoutTask,它们都会被加入到 NioEventLoop 的 Task 队列中被调度和执行。由于超时时间过长,10W 个长链接链路会创建 10W 个 ScheduledFutureTask 对象,每个对象还保存有业务的成员变量,非常消耗内存。用户的持久代设置的比较大,一些定时任务被老化到持久代中,没有被 JVM 垃圾回收掉,内存一直在增长,用户误认为存在内存泄露,即小问题被放大而引出的问题。 + +**解决**:重新设计和反复压测之后将超时时间设置为45秒,内存可以实现正常回收。 + + + +### 当心CLOSE_WAIT + +由于网络不稳定经常会导致客户端断连,如果服务端没有能够及时关闭 socket,就会导致处于 close_wait 状态的链路过多。close_wait 状态的链路并不释放句柄和内存等资源,如果积压过多可能会导致系统句柄耗尽,发生“Too many open files”异常,新的客户端无法接入,涉及创建或者打开句柄的操作都将失败。 + +close_wait 是被动关闭连接是形成的,根据 TCP 状态机,服务器端收到客户端发送的 FIN,TCP 协议栈会自动发送 ACK,链接进入 close_wait 状态。但如果服务器端不执行 socket 的 close() 操作,状态就不能由 close_wait 迁移到 last_ack,则系统中会存在很多 close_wait 状态的连接。通常来说,一个 close_wait 会维持至少 2 个小时的时间(系统默认超时时间的是 7200 秒,也就是 2 小时)。如果服务端程序因某个原因导致系统造成一堆 close_wait 消耗资源,那么通常是等不到释放那一刻,系统就已崩溃。 + +导致 close_wait 过多的可能原因如下: + +- **程序处理Bug**:导致接收到对方的 fin 之后没有及时关闭 socket,这可能是 Netty 的 Bug,也可能是业务层 Bug,需要具体问题具体分析 +- **关闭socket不及时**:例如 I/O 线程被意外阻塞,或者 I/O 线程执行的用户自定义 Task 比例过高,导致 I/O 操作处理不及时,链路不能被及时释放 + +**解决方案** + +- **不要在 Netty 的 I/O 线程(worker线程)上处理业务(心跳发送和检测除外)** +- **在I/O线程上执行自定义Task要当心** +- **IdleStateHandler、ReadTimeoutHandler和WriteTimeoutHandler使用要当** + + + +# RabbitMQ + +## 模式介绍 + +在 RabbitMQ 官网上提供了 6 中工作模式:简单模式、工作队列模式、发布/订阅模式、路由模式、主题模式 和 RPC 模式。本篇只对前 5 种工作方式进行介绍。 + + + +### 简单模式与工作队列模式 + +之所以将这两种模式合并在一起介绍,是因为它们工作原理非常简单,由 3 个对象组成:生产者、队列、消费者。 + +[![img](images/Middleware/rabbitmq-work-01.png)](http://images.extlight.com/rabbitmq-work-01.png) + +生产者负责生产消息,将消息发送到队列中,消费者监听队列,队列有消息就进行消费。 + +[![img](images/Middleware/rabbitmq-work-02.png)](http://images.extlight.com/rabbitmq-work-02.png) + +当有多个消费者时,消费者平均消费队列中的消息。代码演示: + +生产者: + +```java +//1.获取连接 +Connection connection = ConnectionUtil.getConnection(); +//2.创建通道 +Channel channel = connection.createChannel(); +//3.申明队列 +channel.queueDeclare(QUEUE_NAME, false, false, false, null); +//4.发送消息 +channel.basicPublish("", QUEUE_NAME, null, "hello simple".getBytes()); + +System.out.println("发送成功"); +//5.释放连接 +channel.close(); +connection.close(); +``` + +消费者: + +```java +// 1.获取连接 +Connection connection = ConnectionUtil.getConnection(); +// 2.创建通道 +Channel channel = connection.createChannel(); +// 3.申明队列 +channel.queueDeclare(QUEUE_NAME, false, false, false, null); +// 4.监听消息 +channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, + byte[] body) throws IOException { + String message = new String(body, "UTF-8"); + System.out.println("接收:" + message); + } +}); +``` + + + +### 发布/订阅、路由与主题模式 + +这 3 种模式都使用到交换机。生产者不直接与队列交互,而是将消息发送到交换机中,再由交换机将消息放入到已绑定该交换机的队列中给消费者消费。常用的交换机类型有 3 种:fanout、direct、topic。工作原理图如下: + +[![img](images/Middleware/rabbitmq-work-03-1.png)](http://images.extlight.com/rabbitmq-work-03-1.png) + +**fanout**:不处理路由键。只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout 类型交换机转发消息是最快的。 + +**其中,发布/订阅模式使用的是 fanout 类型的交换机。** + +[![img](images/Middleware/rabbitmq-work-04-1.png)](http://images.extlight.com/rabbitmq-work-04-1.png) + +**direct**:处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。如果一个队列绑定到该交换机上要求路由键 “dog”,则只有被标记为 “dog” 的消息才被转发,不会转发 dog.puppy,也不会转发 dog.guard,只会转发dog。 + +**其中,路由模式使用的是 direct 类型的交换机。** + +[![img](images/Middleware/rabbitmq-work-05-1.png)](http://images.extlight.com/rabbitmq-work-05-1.png) + +**topic**:将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号 “#” 匹配一个或多个词,符号“\*”匹配不多不少一个词。因此“audit.#” 能够匹配到“audit.irs.corporate”,但是“audit.*” 只会匹配到 “audit.irs”。 + +**其中,主题模式使用的是 topic 类型的交换机。**代码演示: + +生产者: + +```java +// 1.获取连接 +Connection connection = ConnectionUtil.getConnection(); +// 2.创建通道 +Channel channel = connection.createChannel(); +// 3.申明交换机 +channel.exchangeDeclare(EXCHANGE_NAME, "fanout"); +// 4.发送消息 +for (int i = 0; i < 100; i++) { + channel.basicPublish(EXCHANGE_NAME, "", null, ("hello ps" + i + "").getBytes()); +} + +System.out.println("发送成功"); +// 5.释放连接 +channel.close(); +connection.close(); +``` + +多个消费者: + +```java +// 1.获取连接 +Connection connection = ConnectionUtil.getConnection(); +// 2.创建通道 +Channel channel = connection.createChannel(); +// 3.申明交换机 +channel.exchangeDeclare(EXCHANGE_NAME, "fanout"); +// 4.队列绑定交换机 +channel.queueDeclare(QUEUE_NAME, false, false, false, null); +channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ""); +// 5.消费消息 +channel.basicQos(1); +channel.basicConsume(QUEUE_NAME, false, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + String message = new String(body, "UTF-8"); + System.out.println("recv1:" + message); + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + channel.basicAck(envelope.getDeliveryTag(), false); + } +}); +``` + + + +# Dubbo + +Apache Dubbo 是一款高性能、轻量级的开源 Java 服务框架。Apache Dubbo 提供了六大核心能力:**面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维**。 + +- **面向接口代理的高性能RPC调用** + + 提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节。 + +- **智能负载均衡** + + 内置多种负载均衡策略,智能感知下游节点健康状况,显著减少调用延迟,提高系统吞吐量。 + +- **服务自动注册与发现** + + 支持多种注册中心服务,服务实例上下线实时感知。 + +- **高度可扩展能力** + + 遵循微内核+插件的设计原则,所有核心能力如Protocol、Transport、Serialization被设计为扩展点,平等对待内置实现和第三方实现。 + +- **运行期流量调度** + + 内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布,同机房优先等功能。 + +- **可视化的服务治理与运维** + + 提供丰富服务治理、运维工具:随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数。 + +![DubboArchitecture](images/Middleware/DubboArchitecture.png) + +![ApacheDubbo](images/Middleware/ApacheDubbo.jpg) + + + +# Nacos + +## 基本架构 + +![Nacos架构图](images/Middleware/Nacos架构图.jpeg) + +### 服务 (Service) + +服务是指一个或一组软件功能(例如特定信息的检索或一组操作的执行),其目的是不同的客户端可以为不同的目的重用(例如通过跨进程的网络调用)。Nacos 支持主流的服务生态,如 Kubernetes Service、gRPC|Dubbo RPC Service 或者 Spring Cloud RESTful Service。 + + + +### 服务注册中心 (Service Registry) + +服务注册中心,它是服务,其实例及元数据的数据库。服务实例在启动时注册到服务注册表,并在关闭时注销。服务和路由器的客户端查询服务注册表以查找服务的可用实例。服务注册中心可能会调用服务实例的健康检查 API 来验证它是否能够处理请求。 + + + +### 服务元数据 (Service Metadata) + +服务元数据是指包括服务端点(endpoints)、服务标签、服务版本号、服务实例权重、路由规则、安全策略等描述服务的数据。 + + + +### 服务提供方 (Service Provider) + +是指提供可复用和可调用服务的应用方。 + + + +### 服务消费方 (Service Consumer) + +是指会发起对某个服务调用的应用方。 + + + +### 配置 (Configuration) + +在系统开发过程中通常会将一些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在。目的是让静态的系统工件或者交付物(如 WAR,JAR 包等)更好地和实际的物理运行环境进行适配。配置管理一般包含在系统部署的过程中,由系统管理员或者运维人员完成这个步骤。配置变更是调整系统运行时的行为的有效手段之一。 + + + +### 配置管理 (Configuration Management) + +在数据中心中,系统中所有配置的编辑、存储、分发、变更管理、历史版本管理、变更审计等所有与配置相关的活动统称为配置管理。 + + + +### 名字服务 (Naming Service) + +提供分布式系统中所有对象(Object)、实体(Entity)的“名字”到关联的元数据之间的映射管理服务,例如 ServiceName -> Endpoints Info, Distributed Lock Name -> Lock Owner/Status Info, DNS Domain Name -> IP List, 服务发现和 DNS 就是名字服务的2大场景。 + + + +### 配置服务 (Configuration Service) + +在服务或者应用运行过程中,提供动态配置或者元数据以及配置管理的服务提供者。 + + + +## 逻辑架构 + +![Nacos逻辑架构及其组件介绍](images/Middleware/Nacos逻辑架构及其组件介绍.png) + +- 服务管理:实现服务CRUD,域名CRUD,服务健康状态检查,服务权重管理等功能 +- 配置管理:实现配置管CRUD,版本管理,灰度管理,监听管理,推送轨迹,聚合数据等功能 +- 元数据管理:提供元数据CURD 和打标能力 +- 插件机制:实现三个模块可分可合能力,实现扩展点SPI机制 +- 事件机制:实现异步化事件通知,sdk数据变化异步通知等逻辑 +- 日志模块:管理日志分类,日志级别,日志可移植性(尤其避免冲突),日志格式,异常码+帮助文档 +- 回调机制:sdk通知数据,通过统一的模式回调用户处理。接口和数据结构需要具备可扩展性 +- 寻址模式:解决ip,域名,nameserver、广播等多种寻址模式,需要可扩展 +- 推送通道:解决server与存储、server间、server与sdk间推送性能问题 +- 容量管理:管理每个租户,分组下的容量,防止存储被写爆,影响服务可用性 +- 流量管理:按照租户,分组等多个维度对请求频率,长链接个数,报文大小,请求流控进行控制 +- 缓存机制:容灾目录,本地缓存,server缓存机制。容灾目录使用需要工具 +- 启动模式:按照单机模式,配置模式,服务模式,dns模式,或者all模式,启动不同的程序+UI +- 一致性协议:解决不同数据,不同一致性要求情况下,不同一致性机制 +- 存储模块:解决数据持久化、非持久化存储,解决数据分片问题 +- Nameserver:解决namespace到clusterid的路由问题,解决用户环境与nacos物理环境映射问题 +- CMDB:解决元数据存储,与三方cmdb系统对接问题,解决应用,人,资源关系 +- Metrics:暴露标准metrics数据,方便与三方监控系统打通 +- Trace:暴露标准trace,方便与SLA系统打通,日志白平化,推送轨迹等能力,并且可以和计量计费系统打通 +- 接入管理:相当于阿里云开通服务,分配身份、容量、权限过程 +- 用户管理:解决用户管理,登录,sso等问题 +- 权限管理:解决身份识别,访问控制,角色管理等问题 +- 审计系统:扩展接口方便与不同公司审计系统打通 +- 通知系统:核心数据变更,或者操作,方便通过SMS系统打通,通知到对应人数据变更 +- OpenAPI:暴露标准Rest风格HTTP接口,简单易用,方便多语言集成 +- Console:易用控制台,做服务管理、配置管理等操作 +- SDK:多语言sdk +- Agent:dns-f类似模式,或者与mesh等方案集成 +- CLI:命令行对产品进行轻量化管理,像git一样好用 + + + +## 功能特性 + +Nacos 的关键特性包括: + +- **服务发现和服务健康监测** + +- **动态配置服务** + +- **动态 DNS 服务** + +- **服务及其元数据管理** + + + +### 数据模型 + +Nacos 数据模型 Key 由三元组唯一确定, Namespace默认是空串,公共命名空间(public),分组默认是 DEFAULT_GROUP。 + +![nacos_data_model](images/Middleware/nacos_data_model.jpeg) + + + +### Nacos-SDK类视图 + +![nacos_sdk_class_relation](images/Middleware/nacos_sdk_class_relation.jpeg) + + + +### 配置领域模型 + +围绕配置,主要有两个关联的实体,一个是配置变更历史,一个是服务标签(用于打标分类,方便索引),由 ID 关联。 + +![nacos_config_er](images/Middleware/nacos_config_er.jpeg) + + + +## 安装部署 + +### 下载源码或者安装包 + +**从 Github 上下载源码方式** + +```bash +git clone https://github.com/alibaba/nacos.git +cd nacos/ +mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U +ls -al distribution/target/ + +// change the $version to your actual path +cd distribution/target/nacos-server-$version/nacos/bin +``` + +**下载编译后压缩包方式** + +您可以从 [最新稳定版本](https://github.com/alibaba/nacos/releases) 下载 `nacos-server-$version.zip` 包。 + +```bash + unzip nacos-server-$version.zip 或者 tar -xvf nacos-server-$version.tar.gz + cd nacos/bin +``` + + + +### 启动服务器 + +**Linux/Unix/Mac** + +启动命令(standalone代表着单机模式运行,非集群模式): + +```shell +sh startup.sh -m standalone +``` + +如果您使用的是ubuntu系统,或者运行脚本报错提示符号找不到,可尝试如下运行: + +```shell +bash startup.sh -m standalone +``` + +**Windows** + +启动命令(standalone代表着单机模式运行,非集群模式): + +```shell +startup.cmd -m standalone +``` + + + +### 服务测试 + +**服务注册** + +```shell +curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=nacos.naming.serviceName&ip=20.18.7.10&port=8080' +``` + +**服务发现** + +```shell +curl -X GET 'http://127.0.0.1:8848/nacos/v1/ns/instance/list?serviceName=nacos.naming.serviceName' +``` + +**发布配置** + +```shell +curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test&content=HelloWorld" +``` + +**获取配置** + +```shell +curl -X GET "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test" +``` + + + +### 关闭服务器 + +**Linux/Unix/Mac** + +```shell +sh shutdown.sh +``` + +**Windows** + +```shell +shutdown.cmd +``` + +或者双击 `shutdown.cmd`运行文件。 + + + +## 开源案例 + +### Spring Boot + +#### 启动配置管理 + +启动了 Nacos server 后,您就可以参考以下示例代码,为您的 Spring Boot 应用启动 Nacos 配置管理服务了。 + +**第一步**:添加依赖 + +```xml + + com.alibaba.boot + nacos-config-spring-boot-starter + ${latest.version} + +``` + +**注意**:版本 [0.2.x.RELEASE](https://mvnrepository.com/artifact/com.alibaba.boot/nacos-config-spring-boot-starter) 对应的是 Spring Boot 2.x 版本,版本 [0.1.x.RELEASE](https://mvnrepository.com/artifact/com.alibaba.boot/nacos-config-spring-boot-starter) 对应的是 Spring Boot 1.x 版本。 + +**第二步**:在 `application.properties` 中配置 Nacos server 的地址: + +```properties +nacos.config.server-addr=127.0.0.1:8848 +``` + +**第三步**:使用 `@NacosPropertySource` 加载 `dataId` 为 `example` 的配置源,并开启自动更新: + +```java +@SpringBootApplication +@NacosPropertySource(dataId = "example", autoRefreshed = true) +public class NacosConfigApplication { + public static void main(String[] args) { + SpringApplication.run(NacosConfigApplication.class, args); + } +} +``` + +**第四步**:通过 Nacos 的 `@NacosValue` 注解设置属性值。 + +```java +@Controller +@RequestMapping("config") +public class ConfigController { + + @NacosValue(value = "${useLocalCache:false}", autoRefreshed = true) + private boolean useLocalCache; + + @RequestMapping(value = "/get", method = GET) + @ResponseBody + public boolean get() { + return useLocalCache; + } +} +``` + +**第五步**:启动 `NacosConfigApplication`,调用 `curl http://localhost:8080/config/get`,返回内容是 `false`。 + +**第六步**:通过调用 [Nacos Open API](https://nacos.io/zh-cn/docs/open-api.html) 向 Nacos server 发布配置:dataId 为`example`,内容为`useLocalCache=true` + +```shell +curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=example&group=DEFAULT_GROUP&content=useLocalCache=true" +``` + +**第七步**:再次访问 `http://localhost:8080/config/get`,此时返回内容为`true`,说明程序中的`useLocalCache`值已经被动态更新了。 + + + +#### 启动服务发现 + +本节演示如何在您的 Spring Boot 项目中启动 Nacos 的服务发现功能。 + +**第一步**:添加依赖 + +```xml + + com.alibaba.boot + nacos-discovery-spring-boot-starter + ${latest.version} + +``` + +**注意**:版本 [0.2.x.RELEASE](https://mvnrepository.com/artifact/com.alibaba.boot/nacos-discovery-spring-boot-starter) 对应的是 Spring Boot 2.x 版本,版本 [0.1.x.RELEASE](https://mvnrepository.com/artifact/com.alibaba.boot/nacos-discovery-spring-boot-starter) 对应的是 Spring Boot 1.x 版本。 + +**第二步**:在 `application.properties` 中配置 Nacos server 的地址: + +```properties +nacos.discovery.server-addr=127.0.0.1:8848 +``` + +**第三步**:使用 `@NacosInjected` 注入 Nacos 的 `NamingService` 实例: + +```java +@Controller +@RequestMapping("discovery") +public class DiscoveryController { + + @NacosInjected + private NamingService namingService; + + @RequestMapping(value = "/get", method = GET) + @ResponseBody + public List get(@RequestParam String serviceName) throws NacosException { + return namingService.getAllInstances(serviceName); + } +} + +@SpringBootApplication +public class NacosDiscoveryApplication { + + public static void main(String[] args) { + SpringApplication.run(NacosDiscoveryApplication.class, args); + } +} +``` + +**第四步**:启动 `NacosDiscoveryApplication`,调用 `curl http://localhost:8080/discovery/get?serviceName=example`,此时返回为空 JSON 数组`[]`。 + +**第五步**:通过调用 [Nacos Open API](https://nacos.io/zh-cn/docs/open-api.html) 向 Nacos server 注册一个名称为 `example` 服务 + +```shell +curl -X PUT 'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=example&ip=127.0.0.1&port=8080' +``` + +**第六步**:再次访问 `curl http://localhost:8080/discovery/get?serviceName=example`,此时返回内容为: + +```json +[ + { + "instanceId": "127.0.0.1-8080-DEFAULT-example", + "ip": "127.0.0.1", + "port": 8080, + "weight": 1.0, + "healthy": true, + "cluster": { + "serviceName": null, + "name": "", + "healthChecker": { + "type": "TCP" + }, + "defaultPort": 80, + "defaultCheckPort": 80, + "useIPPort4Check": true, + "metadata": {} + }, + "service": null, + "metadata": {} + } +] +``` + + + +### Spring Cloud + +#### 启动配置管理 + +启动了 Nacos server 后,您就可以参考以下示例代码,为您的 Spring Cloud 应用启动 Nacos 配置管理服务了。 + +**第一步**:添加依赖 + +```xml + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + ${latest.version} + +``` + +**注意**:版本 [2.1.x.RELEASE](https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-config) 对应的是 Spring Boot 2.1.x 版本。版本 [2.0.x.RELEASE](https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-config) 对应的是 Spring Boot 2.0.x 版本,版本 [1.5.x.RELEASE](https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-config) 对应的是 Spring Boot 1.5.x 版本。 + +**第二步**:在 `bootstrap.properties` 中配置 Nacos server 的地址和应用名 + +```properties +spring.cloud.nacos.config.server-addr=127.0.0.1:8848 +spring.application.name=example +``` + +说明:之所以需要配置 `spring.application.name` ,是因为它是构成 Nacos 配置管理 `dataId`字段的一部分。 + +**第三步**:在 Nacos Spring Cloud 中,`dataId` 的完整格式如下: + +```properties +${prefix}-${spring.profiles.active}.${file-extension} +``` + +- `prefix` 默认为 `spring.application.name` 的值,也可以通过配置项 `spring.cloud.nacos.config.prefix`来配置。 +- `spring.profiles.active` 即为当前环境对应的 profile,详情可以参考 [Spring Boot文档](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html#boot-features-profiles)。 **注意:当 `spring.profiles.active` 为空时,对应的连接符 `-` 也将不存在,dataId 的拼接格式变成 `${prefix}.${file-extension}`** +- `file-exetension` 为配置内容的数据格式,可以通过配置项 `spring.cloud.nacos.config.file-extension` 来配置。目前只支持 `properties` 和 `yaml` 类型。 + +**第四步**:通过 Spring Cloud 原生注解 `@RefreshScope` 实现配置自动更新: + +```java +@RestController +@RequestMapping("/config") +@RefreshScope +public class ConfigController { + + @Value("${useLocalCache:false}") + private boolean useLocalCache; + + @RequestMapping("/get") + public boolean get() { + return useLocalCache; + } +} +``` + +**第五步**:首先通过调用 [Nacos Open API](https://nacos.io/zh-cn/docs/open-api.html) 向 Nacos Server 发布配置:dataId 为`example.properties`,内容为`useLocalCache=true` + +```shell +curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=example.properties&group=DEFAULT_GROUP&content=useLocalCache=true" +``` + +**第六步**:运行 `NacosConfigApplication`,调用 `curl http://localhost:8080/config/get`,返回内容是 `true`。 + +**第七步**:再次调用 [Nacos Open API](https://nacos.io/zh-cn/docs/open-api.html) 向 Nacos server 发布配置:dataId 为`example.properties`,内容为`useLocalCache=false` + +```shell +curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=example.properties&group=DEFAULT_GROUP&content=useLocalCache=false" +``` + +**第八步**:再次访问 `http://localhost:8080/config/get`,此时返回内容为`false`,说明程序中的`useLocalCache`值已经被动态更新了。 + + + +#### 启动服务发现 + +本节通过实现一个简单的 `echo service` 演示如何在您的 Spring Cloud 项目中启用 Nacos 的服务发现功能,如下图示: + +![echo_service](images/Middleware/echo_service.png) + +**第一步**:添加依赖: + +```xml + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + ${latest.version} + +``` + +**注意**:版本 [2.1.x.RELEASE](https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-discovery) 对应的是 Spring Boot 2.1.x 版本。版本 [2.0.x.RELEASE](https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-discovery) 对应的是 Spring Boot 2.0.x 版本,版本 [1.5.x.RELEASE](https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-discovery) 对应的是 Spring Boot 1.5.x 版本。 + +**第二步**:配置服务提供者,从而服务提供者可以通过 Nacos 的服务注册发现功能将其服务注册到 Nacos server 上。 + +i. 在 `application.properties` 中配置 Nacos server 的地址: + +```properties +server.port=8070 +spring.application.name=service-provider +spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 +``` + +ii. 通过 Spring Cloud 原生注解 `@EnableDiscoveryClient` 开启服务注册发现功能: + +```java +@SpringBootApplication +@EnableDiscoveryClient +public class NacosProviderApplication { + + public static void main(String[] args) { + SpringApplication.run(NacosProviderApplication.class, args); + } + + @RestController + class EchoController { + @RequestMapping(value = "/echo/{string}", method = RequestMethod.GET) + public String echo(@PathVariable String string) { + return "Hello Nacos Discovery " + string; + } + } +} +``` + +**第三步**:配置服务消费者,从而服务消费者可通过 Nacos 的服务注册发现功能从 Nacos server 上获取到它要调用的服务。 + +i. 在 `application.properties` 中配置 Nacos server 的地址: + +```properties +server.port=8080 +spring.application.name=service-consumer +spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 +``` + +ii. 通过 Spring Cloud 原生注解 `@EnableDiscoveryClient` 开启服务注册发现功能。给 [RestTemplate](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-resttemplate.html) 实例添加 `@LoadBalanced` 注解,开启 `@LoadBalanced` 与 [Ribbon](https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html) 的集成: + +```java +@SpringBootApplication +@EnableDiscoveryClient +public class NacosConsumerApplication { + + @LoadBalanced + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + public static void main(String[] args) { + SpringApplication.run(NacosConsumerApplication.class, args); + } + + @RestController + public class TestController { + + private final RestTemplate restTemplate; + + @Autowired + public TestController(RestTemplate restTemplate) {this.restTemplate = restTemplate;} + + @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET) + public String echo(@PathVariable String str) { + return restTemplate.getForObject("http://service-provider/echo/" + str, String.class); + } + } +} +``` + +**第四步**:启动 `ProviderApplication` 和 `ConsumerApplication` ,调用 `http://localhost:8080/echo/2018`,返回内容为 `Hello Nacos Discovery 2018`。 + + + +# Sentinel + +Sentinel(分布式系统的流量防卫兵)。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。Sentinel 具有以下特征: + +- **丰富的应用场景**:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等 +- **完备的实时监控**:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况 +- **广泛的开源生态**:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel +- **完善的 SPI 扩展点**:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等 + + + +## 主要特性 + +Sentinel 的主要特性: + +![Sentinel-features-overview](images/Middleware/Sentinel-features-overview.png) + + + +## 开源生态 + +Sentinel 的开源生态: + +![Sentinel-opensource-eco](images/Middleware/Sentinel-opensource-eco.png) + +Sentinel 分为两个部分: + +- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持 +- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器 + + + +## Quick Start + +### 手动接入Sentinel以及控制台 + +下面例子将展示应用如何三步接入 Sentinel。同时,Sentinel 也提供所见即所得的控制台,可实时监控资源以及管理规则。 + +**STEP 1. 在应用中引入Sentinel Jar包** + +如果应用使用 pom 工程,则在 `pom.xml` 文件中加入以下代码即可: + +```xml + + com.alibaba.csp + sentinel-core + 1.8.1 + +``` + +注意: Sentinel仅支持JDK 1.8或者以上版本。如果未使用依赖管理工具,请到 [Maven Center Repository](https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-core) 直接下载JAR 包。 + + + +**STEP 2. 定义资源** + +接下来,我们把需要控制流量的代码用 Sentinel API `SphU.entry("HelloWorld")` 和 `entry.exit()` 包围起来即可。在下面的例子中,我们将 `System.out.println("hello world");` 这端代码作为资源,用 API 包围起来(埋点)。参考代码如下: + +```java +public static void main(String[] args) { + initFlowRules(); + while (true) { + Entry entry = null; + try { + entry = SphU.entry("HelloWorld"); + /*您的业务逻辑 - 开始*/ + System.out.println("hello world"); + /*您的业务逻辑 - 结束*/ + } catch (BlockException e1) { + /*流控逻辑处理 - 开始*/ + System.out.println("block!"); + /*流控逻辑处理 - 结束*/ + } finally { + if (entry != null) { + entry.exit(); + } + } + } +} +``` + +完成以上两步后,代码端的改造就完成了。当然,我们也提供了 [注解支持模块](https://github.com/alibaba/Sentinel/wiki/注解支持),可以以低侵入性的方式定义资源。 + + + +**STEP 3. 定义规则** + +接下来,通过规则来指定允许该资源通过的请求次数,如下面的代码定义了资源 `HelloWorld` 每秒最多只能通过 20 个请求。 + +```java +private static void initFlowRules(){ + List rules = new ArrayList<>(); + FlowRule rule = new FlowRule(); + rule.setResource("HelloWorld"); + rule.setGrade(RuleConstant.FLOW_GRADE_QPS); + // Set limit QPS to 20. + rule.setCount(20); + rules.add(rule); + FlowRuleManager.loadRules(rules); +} +``` + +完成上面 3 步,Sentinel 就能够正常工作了。更多的信息可以参考 [使用文档](https://github.com/alibaba/Sentinel/wiki/如何使用)。 + + + +**STEP 4. 检查效果** + +Demo 运行之后,我们可以在日志 `~/logs/csp/${appName}-metrics.log.xxx` 里看到下面的输出: + +```shell +|--timestamp-|------date time----|-resource-|p |block|s |e|rt +1529998904000|2018-06-26 15:41:44|HelloWorld|20|0 |20|0|0 +1529998905000|2018-06-26 15:41:45|HelloWorld|20|5579 |20|0|728 +1529998906000|2018-06-26 15:41:46|HelloWorld|20|15698|20|0|0 +1529998907000|2018-06-26 15:41:47|HelloWorld|20|19262|20|0|0 +1529998908000|2018-06-26 15:41:48|HelloWorld|20|19502|20|0|0 +1529998909000|2018-06-26 15:41:49|HelloWorld|20|18386|20|0|0 +``` + +其中 `p` 代表通过的请求, `block` 代表被阻止的请求, `s` 代表成功执行完成的请求个数, `e` 代表用户自定义的异常, `rt` 代表平均响应时长。可以看到,这个程序每秒稳定输出 "hello world" 20 次,和规则中预先设定的阈值是一样的。 + + + +**STEP 5. 启动 Sentinel 控制台** + +您可以参考 [Sentinel 控制台文档](https://github.com/alibaba/Sentinel/wiki/控制台) 启动控制台,可以实时监控各个资源的运行情况,并且可以实时地修改限流规则。 + +![dashboard-monitoring](images/Middleware/dashboard-monitoring.png) + + + +# Influxdb + +InfluxDB 是用Go语言编写的一个开源分布式时序、事件和指标数据库,无需外部依赖。InfluxDB在DB-Engines的时序数据库类别里排名第一。 + +## 重要特性 + +- **极简架构:**单机版的InfluxDB只需要安装一个binary,即可运行使用,完全没有任何的外部依赖 +- **极强的写入能力:** 底层采用自研的TSM存储引擎,TSM也是基于LSM的思想,提供极强的写能力以及高压缩率 +- **高效查询:**对Tags会进行索引,提供高效的检索 +- **InfluxQL**:提供QL-Like的查询语言,极大的方便了使用,数据库在易用性上演进的终极目标都是提供Query Language +- **Continuous Queries**: 通过CQ能够支持auto-rollup和pre-aggregation,对常见的查询操作可以通过CQ来预计算加速查询 + + + +## 存储引擎 + +InfluxDB 采用自研的TSM (Time-Structured Merge Tree) 作为存储引擎, 其核心思想是通过牺牲掉一些功能来对性能达到极致优化,其官方文档上有项目存储引擎经历了从LevelDB到BlotDB,再到选择自研TSM的过程,整个选择转变的思考。 + +**时序数据库的需求:** + +- 数十亿个单独的数据点 +- 高写入吞吐量 +- 高读取吞吐量 +- 大型删除(数据过期) +- 主要是插入/追加工作负载,很少更新 + + + +### LSM + +**LSM 的局限性** + +在官方文档上有写, 为了解决高写入吞吐量的问题, Influxdb 一开始选择了LevelDB 作为其存储引擎。 然而,随着更多地了解人们对时间序列数据的需求,influxdb遇到了一些无法克服的挑战。 + + + +**LSM (日志结构合并树)为 LevelDB的引擎原理** + +- levelDB 不支持热备份。 对数据库进行安全备份必须关闭后才能复制。LevelDB的RocksDB和HyperLevelDB变体可以解决此问题 +- 时序数据库需要提供一种自动管理数据保存的方式。 即删除过期数据, 而在levelDB 中,删除的代价过高。(通过添加墓碑的方式, 段结构合并的时候才会真正物理性的删除) + + + +### TSM + +按不同的时间范围划分为不同的分区(Shard),因为时序数据写入都是按时间线性产生的,所以分区的产生也是按时间线性增长的,写入通常是在最新的分区,而不会散列到多个分区。分区的优点是数据回收的物理删除非常简单,直接把整个分区删除即可。 + +- 在最开始的时候, influxdb 采用的方案每个shard都是一个独立的数据库实例,底层都是一套独立的LevelDB存储引擎。 这时带来的问题是,LevelDB底层采用level compaction策略,每个存储引擎都会打开比较多的文件,随着shard的增多,最终进程打开的文件句柄会很快触及到上限 +- 由于遇到大量的客户反馈文件句柄过多的问题,InfluxDB在新版本的存储引擎选型中选择了BoltDB替换LevelDB。BoltDB底层数据结构是mmap B+树。 但由于B+ 树会产生大量的随机写。 所以写入性能较差 +- 之后Influxdb 最终决定仿照LSM 的思想自研TSM ,主要改进点是基于时序数据库的特性作出一些优化,包含Cache、WAL以及Data File等各个组件,也会有flush、compaction等这类数据操作 + + + +## 系统架构 + +![InfluxDB系统架构](images/Middleware/InfluxDB系统架构.jpg) + +- **DataBase:**用户可以通过 create database xxx 来创建一个database +- **Retention Policy(RP):** 数据保留策略, 可用来规定数据的的过期时间 +- **Shard Group:** 实现了数据分区,但是Shard Group只是一个逻辑概念,在它里面包含了大量Shard,Shard才是InfluxDB中真正存储数据以及提供读写服务的概念 + + + +# Spring + +Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。 + + + +**Spring的优点?** + +- spring属于低侵入式设计,代码的污染极低 +- spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性 +- Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用 +- spring对于主流的应用框架提供了集成支持 + + **使用Spring框架的好处是什么?** + +- **轻量:**Spring 是轻量的,基本的版本大约2MB +- **控制反转:**Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。 +- **面向切面的编程(AOP):**Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开 +- **容器:**Spring 包含并管理应用中对象的生命周期和配置 +- **MVC框架**:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品 +- **事务管理:**Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA) +- **异常处理:**Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常 + + + +## 相关概念 + +![Spring关系](images/Middleware/Spring关系.jpg) + +### Spring + +Spring是一个开源容器框架,可以接管web层,业务层,dao层,持久层的组件,并且可以配置各种bean,和维护bean与bean之间的关系。其核心就是控制反转(IOC),和面向切面(AOP),简单的说就是一个分层的轻量级开源框架。 + + + +### SpringMVC + +Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。SpringMVC是一种web层mvc框架,用于替代servlet(处理|响应请求,获取表单参数,表单校验等。SpringMVC是一个MVC的开源框架,SpringMVC = struts2 + spring,springMVC就相当于是Struts2加上Spring的整合。 + +![SpringMVC工作原理](images/Middleware/SpringMVC工作原理.png) + +SpringMVC是属于SpringWeb里面的一个功能模块(SpringWebMVC)。专门用来开发SpringWeb项目的一种MVC模式的技术框架实现。 + + + +### SpringBoot + +Springboot是一个微服务框架,延续了spring框架的核心思想IOC和AOP,简化了应用的开发和部署。Spring Boot是为了简化Spring应用的创建、运行、调试、部署等而出现的,使用它可以做到专注于Spring应用的开发,而无需过多关注XML的配置。提供了一堆依赖打包,并已经按照使用习惯解决了依赖问题—>习惯大于约定。 + +Spring Boot基本上是Spring框架的扩展,它消除了设置Spring应用程序所需的XML配置,为更快,更高效的开发生态系统铺平了道路。Spring Boot中的一些特点: + +- 创建独立的spring应用 +- 嵌入Tomcat, JettyUndertow 而且不需要部署他们 +- 提供的“starters” poms来简化Maven配置 +- 尽可能自动配置spring应用 +- 提供生产指标,健壮检查和外部化配置 +- 绝对没有代码生成和XML配置要求 + + + +## Spring原理 + +### 核心组件 + +![Spring核心组件](images/Middleware/Spring核心组件.png) + + + +### Spring常用模块 + +![Spring常用模块](images/Middleware/Spring常用模块.png) + +主要包括以下七个模块: + +- **Spring Context**:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等) +- **Spring Core**:核心类库,所有功能都依赖于该类库,提供IOC和DI服务 +- **Spring AOP**:AOP服务 +- **Spring Web**:提供了基本的面向Web的综合特性,提供对常见框架如Struts2的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器 +- **Spring MVC**:提供面向Web应用的Model-View-Controller,即MVC实现 +- **Spring DAO**:对JDBC的抽象封装,简化了数据访问异常的处理,并能统一管理JDBC事务 +- **Spring ORM**:对现有的ORM框架的支持 + + + +### Spring主要包 + +![Spring主要包](images/Middleware/Spring主要包.png) + + + +### Spring常用注解 + +![Spring常用注解](images/Middleware/Spring常用注解.png) + + + +## IoC + +IOC就是控制反转,指创建对象的控制权转移给Spring框架进行管理,并由Spring根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部依赖。 + +最直观的表达就是,以前创建对象的主动权和时机都是由自己把控的,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。 + +Spring的IOC有三种注入方式 :**构造器注入、setter方法注入、根据注解注入**。 + + + +## AOP + +AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。 + + + +### 静态代理 + +AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。 + +### 动态代理 + +Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理: + +- **JDK动态代理**:只提供接口的代理,不支持类的代理,要求被代理类实现接口。JDK动态代理的核心是InvocationHandler接口和Proxy类,在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起。 + + **InvocationHandler的invoke(Object proxy,Method method,Object[] args)**: + + - **proxy**:是最终生成的代理对象 + - **method**:是被代理目标实例的某个具体方法; + - **args**:是被代理目标实例某个方法的具体入参, 在方法反射调用时使用 + +- **CGLIB动态代理**:如果被代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。 + + + +**静态代理与动态代理区别?** + +生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。 + +IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。 + + + +### 应用场景 + +AOP 主要应用场景有: + +- Authentication 权限 +- Caching 缓存 +- Context passing 内容传递 +- Error handling 错误处理 +- Lazy loading 懒加载 +- Debugging 调试 +- logging, tracing, profiling and monitoring 记录跟踪 优化 校准 +- Performance optimization 性能优化 +- Persistence 持久化 +- Resource pooling 资源池 +- Synchronization 同步 +- Transactions 事务 + + + +## 过滤器(Filter) + +主要作用是过滤字符编码、做一些业务逻辑判断,主要用于对用户请求进行预处理,同时也可进行逻辑判断。Filter在请求进入servlet容器执行service()方法之前就会经过filter过滤,不像Intreceptor一样依赖于springmvc框架,只需要依赖于servlet。Filter启动是随WEB应用的启动而启动,只需要初始化一次,以后都可以进行拦截。Filter有如下几个种类: + +- 用户授权Filter:检查用户请求,根据请求过滤用户非法请求 +- 日志Filter:记录某些特殊的用户请求 +- 解码Filter:对非标准编码的请求解码 + + + +## 拦截器(Interceptor) + +主要作用是拦截用户请求,进行处理。比如判断用户登录情况、权限验证,只要针对Controller请求进行处理,是通过**HandlerInterceptor**。 + +Interceptor分两种情况,一种是对会话的拦截,实现spring的HandlerInterceptor接口并注册到mvc的拦截队列中,其中**preHandle()**方法在调用Handler之前进行拦截,**postHandle()**方法在视图渲染之前调用,**afterCompletion()**方法在返回相应之前执行;另一种是对方法的拦截,需要使用@Aspect注解,在每次调用指定方法的前、后进行拦截。 + + + +**Filter和Interceptor的区别** + +- Filter是基于函数回调(doFilter()方法)的,而Interceptor则是基于Java反射的(AOP思想) +- Filter依赖于Servlet容器,而Interceptor不依赖于Servlet容器 +- Filter对几乎所有的请求起作用,而Interceptor只能对action请求起作用 +- Interceptor可以访问Action的上下文,值栈里的对象,而Filter不能 +- 在action的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次 +- Filter在过滤是只能对request和response进行操作,而interceptor可以对request、response、handler、modelAndView、exception进行操作 + + + +### HandlerInterceptor + +HandlerInterceptor是springMVC项目中的拦截器,它拦截的目标是请求的地址,比MethodInterceptor先执行。 + +实现一个HandlerInterceptor拦截器可以直接实现HandlerInterceptor接口,也可以继承HandlerInterceptorAdapter类。 + +这两种方法殊途同归,其实HandlerInterceptorAdapter也就是声明了HandlerInterceptor接口中所有方法的默认实现,而我们在继承他之后只需要重写必要的方法。 + + + +### MethodInterceptor + +MethodInterceptor是AOP项目中的拦截器,它拦截的目标是方法,即使不是controller中的方法。实现MethodInterceptor 拦截器大致也分为两种,一种是实现MethodInterceptor接口,另一种利用AspectJ的注解或配置。 + + + +## Spring流程 + +### Spring容器启动流程 + +- 初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中 + + ① 实例化BeanFactory【DefaultListableBeanFactory】工厂,用于生成Bean对象 + ② 实例化BeanDefinitionReader注解配置读取器,用于对特定注解(如@Service、@Repository)的类进行读取转化成 BeanDefinition 对象,(BeanDefinition 是 Spring 中极其重要的一个概念,它存储了 bean 对象的所有特征信息,如是否单例,是否懒加载,factoryBeanName 等) + ③ 实例化ClassPathBeanDefinitionScanner路径扫描器,用于对指定的包目录进行扫描查找 bean 对象 + +- 将配置类的BeanDefinition注册到容器中 + +- 调用refresh()方法刷新容器 + + ① prepareRefresh()刷新前的预处理 + ② obtainFreshBeanFactory():获取在容器初始化时创建的BeanFactory + ③ prepareBeanFactory(beanFactory):BeanFactory的预处理工作,向容器中添加一些组件 + ④ postProcessBeanFactory(beanFactory):子类重写该方法,可以实现在BeanFactory创建并预处理完成以后做进一步的设置 + ⑤ invokeBeanFactoryPostProcessors(beanFactory):在BeanFactory标准初始化之后执行BeanFactoryPostProcessor的方法,即BeanFactory的后置处理器 + ⑥ registerBeanPostProcessors(beanFactory):向容器中注册Bean的后置处理器BeanPostProcessor,它的主要作用是干预Spring初始化bean的流程,从而完成代理、自动注入、循环依赖等功能 + ⑦ initMessageSource():初始化MessageSource组件,主要用于做国际化功能,消息绑定与消息解析 + ⑧ initApplicationEventMulticaster():初始化事件派发器,在注册监听器时会用到 + ⑨ onRefresh():留给子容器、子类重写这个方法,在容器刷新的时候可以自定义逻辑 + ⑩ registerListeners():注册监听器。将容器中所有ApplicationListener注册到事件派发器中,并派发之前步骤产生的事件 + ⑪ finishBeanFactoryInitialization(beanFactory):初始化所有剩下的单实例bean,核心方法是preInstantiateSingletons(),会调用getBean()方法创建对象 + ⑫ finishRefresh():发布BeanFactory容器刷新完成事件 + + + +### SpringMVC流程 + +- 用户发送请求至前端控制器DispatcherServlet +- DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handler +- 处理器映射器根据请求url找到具体的处理器Handler,生成处理器对象及处理器拦截器(如果有则生成),一并返回给DispatcherServlet +- DispatcherServlet 调用 HandlerAdapter处理器适配器,请求执行Handler +- HandlerAdapter 经过适配调用 具体处理器进行处理业务逻辑 +- Handler执行完成返回ModelAndView +- HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet +- DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析 +- ViewResolver解析后返回具体View +- DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中) +- DispatcherServlet响应用户 + +![SpringMVC流程](images/Middleware/SpringMVC流程.jpg) + +- 前端控制器 DispatcherServlet:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度 +- 处理器映射器 HandlerMapping:根据请求的URL来查找Handler +- 处理器适配器 HandlerAdapter:负责执行Handler +- 处理器 Handler:处理器,需要程序员开发 +- 视图解析器 ViewResolver:进行视图的解析,根据视图逻辑名将ModelAndView解析成真正的视图(view) +- 视图View:View是一个接口, 它的实现类支持不同的视图类型,如jsp,freemarker,pdf等等 + + + +### 非拦截器Http请求流程 + +用户的普通Http请求执行顺序: + +![用户的普通Http请求执行顺序](images/Middleware/用户的普通Http请求执行顺序.jpg) + + + +### 拦截器Http请求流程 + +过滤器、拦截器添加后的执行顺序: + +![过滤器、拦截器添加后的执行顺序](images/Middleware/过滤器、拦截器添加后的执行顺序.jpg) + + + +# SpringCloud + +![SpringCloud](images/Middleware/SpringCloud.png) + +## Eurake + +Netflix Eureka 是由 Netflix 开源的一款基于 REST 的服务发现组件,包括 Eureka Server 及 Eureka Client。 + +![Eurake介绍](images/Middleware/Eurake介绍.png) + +![Eurake](images/Middleware/Eurake.png) + +- **Eurake客户端**:负责将这个服务的信息注册到Eureka服务端中 +- **Eureka服务端**:相当于一个注册中心,里面有注册表,注册表中保存了各个服务所在的机器和端口号,可以通过Eureka服务端找到各个服务 + + + +## Zuul + +Zuul 是由 Netflix 孵化的一个致力于“网关 “解决方案的开源组件。 + +![Zuul介绍](images/Middleware/Zuul介绍.png) + +**网关的作用** + +- 统一入口:未全部为服务提供一个唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性 +- 鉴权校验:识别每个请求的权限,拒绝不符合要求的请求 +- 动态路由:动态的将请求路由到不同的后端集群中 +- 减少客户端与服务端的耦合:服务可以独立发展,通过网关层来做映射 + +![zuul过滤器的生命周期](images/Middleware/zuul过滤器的生命周期.png) + +![zuul限流参数](images/Middleware/zuul限流参数.png) + +![Zuul](images/Middleware/Zuul.png) + + + +## Feign + +Feign是是一个声明式的Web Service客户端。 + +![Feign介绍](images/Middleware/Feign介绍.png) + +Feign就是使用了动态代理: + +- 首先,如果你对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理 +- 接着你要是调用那个接口,本质就是会调用 Feign创建的动态代理,这是核心中的核心 +- Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址 +- 最后针对这个地址,发起请求、解析响应 + +![Feign](images/Middleware/Feign.png) + +![Feign远程调用流程](images/Middleware/Feign远程调用流程.png) + + + +## Ribbon + +Ribbon Netflix 公司开源的一个负载均衡的组件。 + +![Ribbon介绍](images/Middleware/Ribbon介绍.png) + +Ribbon在服务消费者端配置和使用,它的作用就是负载均衡,然后默认使用的负载均衡算法是轮询算法,Ribbon会从Eureka服务端中获取到对应的服务注册表,然后就知道相应服务的位置,然后Ribbon根据设计的负载均衡算法去选择一台机器,Feigin就会针对这些机器构造并发送请求,如下图所示: + +![Ribbon](images/Middleware/Ribbon.png) + +![Ribbon规则](images/Middleware/Ribbon规则.png) + + + +## Hystrix + +Hystrix是Netstflix 公司开源的一个项目,它提供了熔断器功能,能够阻止分布式系统中出现联动故障。 + +![Hystrix介绍](images/Middleware/Hystrix介绍.png) + +Hystrix是隔离、熔断以及降级的一个框架,说白了就是Hystrix会搞很多小线程池然后让这些小线程池去请求服务,返回结果,Hystrix相当于是个中间过滤区,如果我们的积分服务挂了,那我们请求积分服务直接就返回了,不需要等待超时时间结束抛出异常,这就是所谓的熔断,但是也不能啥都不干就返回啊,不然我们之后手动加积分咋整啊,那我们每次调用积分服务就在数据库里记录一条消息,这就是所谓的降级,Hystrix隔离、熔断和降级的全流程如下: + +![Hystrix](images/Middleware/Hystrix.png) + +![Hystrix熔断](images/Middleware/Hystrix熔断.png) + + + +## Gateway + +Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0、 Spring Boot 2.0 和 Project Reactor 等技术开发的网关, Spring Cloud Gateway 旨在为微服务架构提供简单、 有效且统一的 API 路由管理方式。 + +![Gateway介绍](images/Middleware/Gateway介绍.png) + + + +## Config + +Spring Cloud 中提供了分布式配置中 Spring Cloud Config ,为外部配置提供了客户端和服务器端的支持。 + +![Config介绍](images/Middleware/Config介绍.png) + + + +## Bus + +使用 Spring Cloud Bus, 可以非常容易地搭建起消息总线。 + +![Bus介绍](images/Middleware/Bus介绍.png) + + + +## OAuth2 + +Sprin Cloud 构建的微服务系统中可以使用 Spring Cloud OAuth2 来保护微服务系统。 + +![OAuth2介绍](images/Middleware/OAuth2介绍.png) + + + +## Sleuth + +Spring Cloud Sleuth是Spring Cloud 个组件,它的主要功能是在分布式系统中提供服务链路追踪的解决方案。 + +![Sleuth介绍](images/Middleware/Sleuth介绍.png) + + + +# MyBatis + +Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,加载驱动、创建连接、创建statement等繁杂的过程,开发者开发时只需要关注如何编写SQL语句,可以严格控制sql执行性能,灵活度高。 + +作为一个半ORM框架,MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。 + +通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。 + +由于MyBatis专注于SQL本身,灵活度高,所以比较适合对性能的要求很高,或者需求变化较多的项目,如互联网项目。 + + + +**Mybaits优缺点** + +- 优点 + - 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用 + - 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接 + - 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持) + - 能够与Spring很好的集成 + - 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护 + +- 缺点 + +- SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求 +- SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库 + + + +**#{}和${}的区别是什么?** + +- `${}` 是字符串替换 +- `#{}` 是预处理 + +使用#{}可以有效的防止SQL注入,提高系统安全性。 + + +## MyBatis架构 + +![Mybatis架构](images/Middleware/Mybatis架构.jpg) + + + +## MyBatis流程 + +![Mybatis流程](images/Middleware/Mybatis流程.png) + + + +## Dao接口工作原理 + +Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,根据类的全限定名+方法名,唯一定位到一个MapperStatement并调用执行器执行所代表的sql,然后将sql执行结果返回。 + +Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。 + + + +## MyBatis缓存 + +MyBatis 中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。一级缓存是指 SqlSession 级别的缓存,当在同一个 SqlSession 中进行相同的 SQL 语句查询时,第二次以后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存 1024 条 SQL。二级缓存是指可以跨 SqlSession 的缓存。是 mapper 级别的缓存,对于 mapper 级别的缓存不同的sqlsession 是可以共享的。 + +![MyBatis缓存机制](images/Middleware/MyBatis缓存机制.png) + +### 一级缓存 + +Mybatis的一级缓存原理(sqlsession级别)。第一次发出一个查询 sql,sql 查询结果写入 sqlsession 的一级缓存中,缓存使用的数据结构是一个 map。 + +- key:MapperID+offset+limit+Sql+所有的入参 +- value:用户信息 + +同一个 sqlsession 再次发出相同的 sql,就从缓存中取出数据。如果两次中间出现 commit 操作(修改、添加、删除),本 sqlsession 中的一级缓存区域全部清空,下次再去缓存中查询不到所以要从数据库查询,从数据库查询到再写入缓存。 + + + +### 二级缓存 + +二级缓存的范围是 mapper 级别(mapper 同一个命名空间),mapper 以命名空间为单位创建缓存数据结构,结构是 map。mybatis 的二级缓存是通过 CacheExecutor 实现的。CacheExecutor其实是 Executor 的代理对象。所有的查询操作,在 CacheExecutor 中都会先匹配缓存中是否存在,不存在则查询数据库。 + +- key:MapperID+offset+limit+Sql+所有的入参 + +**具体使用需要配置:** + +- Mybatis 全局配置中启用二级缓存配置 +- 在对应的 Mapper.xml 中配置 cache 节点 +- 在对应的 select 查询节点中添加 useCache=true + + + +## MyBatis主要组件 + +![MyBatis执行sql流程](images/Middleware/MyBatis执行sql流程.png) + +### SqlSessionFactoryBuilder + +从名称长可以看出来使用的建造者设计模式(Builder),用于构建SqlSessionFactory对象: + +- 解析mybatis的xml配置文件,然后创建Configuration对象(对应标签) +- 根据创建的Configuration对象,创建SqlSessionFactory(默认使用DefaultSqlSessionFactory) + + + +### SqlSessionFactory + +从名称上可以看出使用的工厂模式(Factory),用于创建并初始化SqlSession对象(数据库会话) + +- 调用openSession方法,创建SqlSession对象,可以将SqlSession理解为数据库连接(会话) +- openSession方法有多个重载,可以创建SqlSession关闭自动提交、指定ExecutorType、指定数据库事务隔离级别 + + + +### SqlSession + +如果了解web开发,就应该知道cookie和session吧,SqlSession的session和web开发中的session概念类似。session,译为“会话、会议”,数据的有效时间范围是在会话期间(会议期间),会话(会议)结束后,数据就清除了。也可以将SqlSession理解为一个数据库连接(但也不完全正确,因为创建SqlSession之后,如果不执行sql,那么这个连接是无意义的,所以数据库连接在执行sql的时候才建立的)。 + +SqlSession只是定义了执行sql的一些方法,而具体的实现由子类来完成,比如SqlSession有一个接口实现类DefaultSqlSession。MyBatis中通过Executor来执行sql的,在创建SqlSession的时候(openSession),同时会创建一个Executor对象。 + + + +### Executor + +Executor(人称“执行器”)是一个接口,定义了对JDBC的封装。MyBatis中提供了多种执行器,如下: + +![MyBatis-Executor](images/Middleware/MyBatis-Executor.png)CacheExecutor其实是一个Executor代理类,包含一个delegate,需要创建时手动传入(要入simple、reuse、batch三者之一)。ClosedExecutor,所有接口都会抛出异常,表示一个已经关闭的Executor。创建SqlSession时,默认使用的是SimpleExecutor。 + +Executor是对JDBC的封装。当我们使用JDBC来执行sql时,一般会先预处理sql,也就是conn.prepareStatement(sql),获取返回的PreparedStatement对象(实现了Statement接口),再调用statement的executeXxx()来执行sql。也就是说,Executor在执行sql的时候也是需要创建Statement对象的。 + + + +### StatementHandler + +在JDBC中,是调用Statement.executeXxx()来执行sql。在MyBatis,也是调用Statement.executeXxx()来执行sql,此时就不得不提StatementHandler,可以将其理解为一个工人,他的工作包括 + +- 对sql进行预处理 +- 调用statement.executeXxx()执行sql +- 将数据库返回的结果集进行对象转换(ORM) + + + +### ParameterHandler + + ParameterHandler的功能就是sql预处理后,进行设置参数。ParamterHandler有一个DefaultParameterHandler。 + + + +### ResultSetHandler + +当执行statement.execute()后,就可以通过statement.getResultSet()来获取结果集,获取到结果集之后,MyBatis会使用ResultSetHandler来将结果集的数据转换为Java对象(ORM映射)。ResultSetHandler有一个实现类,DefaultResultHandler,其重写handlerResultSets方法。 + + + +### TypeHandler + +TypeHandler主要用在两个地方: + +- 参数绑定,发生在ParameterHandler.setParamenters()中 + + MyBatis中,可以使用来定义结果的映射关系,包括每一个字段的类型。TypeHandler可以对某个字段按照xml中配置的类型进行设置值,比如设置sql的uid参数时,类型为INTEGER(jdbcType) + +- 获取结果集中的字段值,发生在ResultSetHandler处理结果集的过程中 + + + +# Nginx + +## Nginx安装 + +```shell +# CentOS +yum install nginx; +# Ubuntu +sudo apt-get install nginx; +# Mac +brew install nginx; +``` + +安装完成后,通过 `rpm \-ql nginx` 命令查看 `Nginx` 的安装信息: + +```shell +# Nginx配置文件 +/etc/nginx/nginx.conf # nginx 主配置文件 +/etc/nginx/nginx.conf.default + +# 可执行程序文件 +/usr/bin/nginx-upgrade +/usr/sbin/nginx + +# nginx库文件 +/usr/lib/systemd/system/nginx.service # 用于配置系统守护进程 +/usr/lib64/nginx/modules # Nginx模块目录 + +# 帮助文档 +/usr/share/doc/nginx-1.16.1 +/usr/share/doc/nginx-1.16.1/CHANGES +/usr/share/doc/nginx-1.16.1/README +/usr/share/doc/nginx-1.16.1/README.dynamic +/usr/share/doc/nginx-1.16.1/UPGRADE-NOTES-1.6-to-1.10 + +# 静态资源目录 +/usr/share/nginx/html/404.html +/usr/share/nginx/html/50x.html +/usr/share/nginx/html/index.html + +# 存放Nginx日志文件 +/var/log/nginx +``` + +主要关注的文件夹有两个: + +- `/etc/nginx/conf.d/` 是子配置项存放处, `/etc/nginx/nginx.conf` 主配置文件会默认把该文件夹中所有子配置项都引入 + +- `/usr/share/nginx/html/` 静态文件都放在这个文件夹,也可以根据你自己的习惯放在其他地方 + + + +## 运维命令 + +一般可以在 `/etc/nginx/nginx.conf` 中配置。 + +### systemctl命令 + +```shell +# 开机配置 +# 开机自动启动 +systemctl enable nginx +# 关闭开机自动启动 +systemctl disable nginx + +# 启动Nginx +systemctl start nginx # 启动Nginx成功后,可以直接访问主机IP,此时会展示Nginx默认页面 +# 停止Nginx +systemctl stop nginx +# 重启Nginx +systemctl restart nginx +# 重新加载Nginx +systemctl reload nginx +# 查看 Nginx 运行状态 +systemctl status nginx + +# 查看Nginx进程 +ps -ef | grep nginx +# 杀死Nginx进程 +kill -9 pid # 根据上面查看到的Nginx进程号,杀死Nginx进程,-9 表示强制结束进程 +``` + + + +### Nginx应用命令 + +```shell +# 启动 +nginx -s start +# 向主进程发送信号,重新加载配置文件,热重启 +nginx -s reload +# 重启 Nginx +nginx -s reopen +# 快速关闭 +nginx -s stop +# 等待工作进程处理完成后关闭 +nginx -s quit +# 查看当前 Nginx 最终的配置 +nginx -T +# 检查配置是否有问题 +nginx -t +``` + + + +## 配置规则 + +Nginx层级结构如下: + +![Nginx层级结构](images/Middleware/Nginx层级结构.png) + +### URI匹配 + +```shell +location = / { + # 完全匹配 = + # 大小写敏感 ~ + # 忽略大小写 ~* +} +location ^~ /images/ { + # 前半部分匹配 ^~ + # 可以使用正则,如: + # location ~* \.(gif|jpg|png)$ { } +} +location / { + # 如果以上都未匹配,会进入这里 +} +``` + +### upstream + +```nginx +语法:upstream name { + ... +} +上下文:http +示例: +upstream back_end_server{ + server 192.168.100.33:8081 +} +``` + +`upstream` 用于定义上游服务器(指的就是后台提供的应用服务器)的相关信息。 + +```nginx +upstream test_server{ + # weight=3 权重值,默认为1 + # max_conns=1000 上游服务器的最大并发连接数 + # fail_timeout=10s 服务器不可用的判定时间 + # max_fails=2 服务器不可用的检查次数 + # backup 备份服务器,仅当其他服务器都不可用时才会启用 + # down 标记服务器长期不可用,离线维护; + server 127.0.0.1:8081 weight=3 max_conns=1000 fail_timeout=10s max_fails=2; + # 限制每个worker子进程与上游服务器空闲长连接的最大数量 + keepalive 16; + # 单个长连接可以处理的最多 HTTP 请求个数 + keepalive_requests 100; + # 空闲长连接的最长保持时间 + keepalive_timeout 60s; +} +``` + + + +### proxy_pass + +```nginx +语法:proxy_pass URL; +上下文:location、if、limit_except +示例: +proxy_pass http://127.0.0.1:8081 +proxy_pass http://127.0.0.1:8081/proxy +``` + +`URL` 参数原则: + +- `URL` 必须以 `http` 或 `https` 开头 + +- `URL` 中可以携带变量 + +- `URL` 中是否带 `URI` ,会直接影响发往上游请求的 `URL` + +`URL` 后缀带 `/` 和不带 `/` 的区别: + +- 不带 `/` 意味着 `Nginx` 不会修改用户 `URL` ,而是直接透传给上游的应用服务器 +- 带 `/` 意味着 `Nginx` 会修改用户 `URL` ,修改方法是将 `location` 后的 `URL` 从用户 `URL` 中删除 + +```nginx +# 用户请求 URL :/bbs/abc/test.html +location /bbs/{ + # 请求到达上游应用服务器的 URL :/bbs/abc/test.html + proxy_pass http://127.0.0.1:8080; + # 请求到达上游应用服务器的 URL :/abc/test.html + proxy_pass http://127.0.0.1:8080/; +} +``` + + + +## 应用场景 + +### 反向代理 + +```nginx +# 1 /etc/nginx/conf.d/proxy.conf +# 1.1 模拟被代理服务 +server{ + listen 8080; + server_name localhost; + + location /proxy/ { + root /usr/share/nginx/html/proxy; + index index.html; + } +} +# 1.2 /usr/share/nginx/html/proxy/index.html +

121.42.11.34 proxy html

+ + +# 2 /etc/nginx/conf.d/proxy.conf +# 2.1 back server +upstream back_end { + server 121.42.11.34:8080 weight=2 max_conns=1000 fail_timeout=10s max_fails=3; + keepalive 32; + keepalive_requests 80; + keepalive_timeout 20s; +} +# 2.2 代理配置 +server { + listen 80; + # vim /etc/hosts进入配置文件,添加如下内容:121.5.180.193 proxy.lion.club + server_name proxy.lion.club; + location /proxy { + proxy_pass http://back_end/proxy; + } +} +``` + + + +### 负载均衡 + +```nginx +# 1 /etc/nginx/conf.d/balance.conf +# 1.1 模拟被代理服务1 +server{ + listen 8020; + location / { + return 200 'return 8020 \n'; + } +} +# 1.2 模拟被代理服务2 +server{ + listen 8030; + location / { + return 200 'return 8030 \n'; + } +} +# 1.3 模拟被代理服务3 +server{ + listen 8040; + location / { + return 200 'return 8040 \n'; + } +} + +# 2 /etc/nginx/conf.d/balance.conf +# 2.1 demo server list +upstream demo_server { + server 121.42.11.34:8020; + server 121.42.11.34:8030; + server 121.42.11.34:8040; +} + +# 2.2 代理配置 +server { + listen 80; + server_name balance.lion.club; + location /balance/ { + proxy_pass http://demo_server; + } +} +``` + + + +#### hash算法 + +```nginx +upstream demo_server { + hash $request_uri; + server 121.42.11.34:8020; + server 121.42.11.34:8030; + server 121.42.11.34:8040; +} + +server { + listen 80; + server_name balance.lion.club; + location /balance/ { + proxy_pass http://demo_server; + } +} +``` + +`hash $request_uri` 表示使用 `request_uri` 变量作为 `hash` 的 `key` 值,只要访问的 `URI` 保持不变,就会一直分发给同一台服务器。 + + + +#### ip_hash + +```nginx +upstream demo_server { + ip_hash; + server 121.42.11.34:8020; + server 121.42.11.34:8030; + server 121.42.11.34:8040; +} + +server { + listen 80; + server_name balance.lion.club; + + location /balance/ { + proxy_pass http://demo_server; + } +} +``` + +根据客户端的请求 `ip` 进行判断,只要 `ip` 地址不变就永远分配到同一台主机。它可以有效解决后台服务器 `session` 保持的问题。 + + + +#### 最少连接数算法 + +```nginx +upstream demo_server { + zone test 10M; # zone可以设置共享内存空间的名字和大小 + least_conn; + server 121.42.11.34:8020; + server 121.42.11.34:8030; + server 121.42.11.34:8040; +} + +server { + listen 80; + server_name balance.lion.club; + + location /balance/ { + proxy_pass http://demo_server; + } +} +``` + +各个 `worker` 子进程通过读取共享内存的数据,来获取后端服务器的信息。来挑选一台当前已建立连接数最少的服务器进行分配请求。 + + + +### 配置缓存 + +**① proxy_cache** + +存储一些之前被访问过、而且可能将要被再次访问的资源,使用户可以直接从代理服务器获得,从而减少上游服务器的压力,加快整个访问速度。 + +```nginx +语法:proxy_cache zone | off ; # zone 是共享内存的名称 +默认值:proxy_cache off; +上下文:http、server、location +``` + +**② proxy_cache_path** + +设置缓存文件的存放路径。 + +```nginx +语法:proxy_cache_path path [level=levels] ...可选参数省略,下面会详细列举 +默认值:proxy_cache_path off +上下文:http +``` + +参数含义: + +- `path` 缓存文件的存放路径 +- `level path` 的目录层级 +- `keys_zone` 设置共享内存 +- `inactive` 在指定时间内没有被访问,缓存会被清理,默认10分钟 + +**③ proxy_cache_key** + +设置缓存文件的 `key` 。 + +```nginx +语法:proxy_cache_key +默认值:proxy_cache_key $scheme$proxy_host$request_uri; +上下文:http、server、location +``` + +**④ proxy_cache_valid** + +配置什么状态码可以被缓存,以及缓存时长。 + +```nginx +语法:proxy_cache_valid [code...] time; +上下文:http、server、location +配置示例:proxy_cache_valid 200 304 2m;; # 说明对于状态为200和304的缓存文件的缓存时间是2分钟 +``` + +**⑤ proxy_no_cache** + +定义相应保存到缓存的条件,如果字符串参数的至少一个值不为空且不等于“ 0”,则将不保存该响应到缓存。 + +```nginx +语法:proxy_no_cache string; +上下文:http、server、location +示例:proxy_no_cache $http_pragma $http_authorization; +``` + +**⑥ proxy_cache_bypass** + +定义条件,在该条件下将不会从缓存中获取响应。 + +```nginx +语法:proxy_cache_bypass string; +上下文:http、server、location +示例:proxy_cache_bypass $http_pragma $http_authorization; +``` + +**⑦ upstream_cache_status 变量** + +它存储了缓存是否命中的信息,会设置在响应头信息中,在调试中非常有用。 + +```nginx +MISS: 未命中缓存 +HIT:命中缓存 +EXPIRED: 缓存过期 +STALE: 命中了陈旧缓存 +REVALIDDATED: Nginx验证陈旧缓存依然有效 +UPDATING: 内容陈旧,但正在更新 +BYPASS: X响应从原始服务器获取 +``` + +```nginx +proxy_cache_path /etc/nginx/cache_temp levels=2:2 keys_zone=cache_zone:30m max_size=2g inactive=60m use_temp_path=off; + +upstream cache_server{ + server 121.42.11.34:1010; + server 121.42.11.34:1020; +} + +server { + listen 80; + server_name cache.lion.club; + + # 场景一:实时性要求不高,则配置缓存 + location /demo { + proxy_cache cache_zone; # 设置缓存内存,上面配置中已经定义好的 + proxy_cache_valid 200 5m; # 缓存状态为200的请求,缓存时长为5分钟 + proxy_cache_key $request_uri; # 缓存文件的key为请求的URI + add_header Nginx-Cache-Status $upstream_cache_status # 把缓存状态设置为头部信息,响应给客户端 + proxy_pass http://cache_server; # 代理转发 + } + + # 场景二:实时性要求非常高,则配置不缓存 + # URI 中后缀为 .txt 或 .text 的设置变量值为 "no cache" + if ($request_uri ~ \.(txt|text)$) { + set $cache_name "no cache" + } + location /test { + proxy_no_cache $cache_name; # 判断该变量是否有值,如果有值则不进行缓存,如果没有值则进行缓存 + proxy_cache cache_zone; # 设置缓存内存 + proxy_cache_valid 200 5m; # 缓存状态为200的请求,缓存时长为5分钟 + proxy_cache_key $request_uri; # 缓存文件的key为请求的URI + add_header Nginx-Cache-Status $upstream_cache_status # 把缓存状态设置为头部信息,响应给客户端 + proxy_pass http://cache_server; # 代理转发 + } +} +``` + + + +### HTTPS + +下载证书的压缩文件,里面有个 `Nginx` 文件夹,把 `xxx.crt` 和 `xxx.key` 文件拷贝到服务器目录,再进行如下配置: + +```nginx +server { + listen 443 ssl http2 default_server; # SSL 访问端口号为 443 + server_name lion.club; # 填写绑定证书的域名(我这里是随便写的) + ssl_certificate /etc/nginx/https/lion.club_bundle.crt; # 证书地址 + ssl_certificate_key /etc/nginx/https/lion.club.key; # 私钥地址 + ssl_session_timeout 10m; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # 支持ssl协议版本,默认为后三个,主流版本是[TLSv1.2] + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } +} +``` + + + +### 开启gzip压缩 + +在 `/etc/nginx/conf.d/` 文件夹中新建配置文件 `gzip.conf` : + +```nginx +# 默认off,是否开启gzip +gzip on; +# 要采用 gzip 压缩的 MIME 文件类型,其中 text/html 被系统强制启用 +gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; +# ---- 以上两个参数开启就可以支持Gzip压缩了 ---- + + +# 默认 off,该模块启用后,Nginx 首先检查是否存在请求静态文件的 gz 结尾的文件,如果有则直接返回该 .gz 文件内容 +gzip_static on; +# 默认 off,nginx做为反向代理时启用,用于设置启用或禁用从代理服务器上收到相应内容 gzip 压缩 +gzip_proxied any; +# 用于在响应消息头中添加Vary:Accept-Encoding,使代理服务器根据请求头中的Accept-Encoding识别是否启用gzip压缩 +gzip_vary on; +# gzip 压缩比,压缩级别是 1-9,1 压缩级别最低,9 最高,级别越高压缩率越大,压缩时间越长,建议 4-6 +gzip_comp_level 6; +# 获取多少内存用于缓存压缩结果,16 8k 表示以 8k*16 为单位获得 +gzip_buffers 16 8k; +# 允许压缩的页面最小字节数,页面字节数从header头中的 Content-Length 中进行获取。默认值是 0,不管页面多大都压缩。建议设置成大于 1k 的字节数,小于 1k 可能会越压越大 +gzip_min_length 1k; +# 默认 1.1,启用 gzip 所需的 HTTP 最低版本 +gzip_http_version 1.1; +``` + + + +## 常用配置 + +### 侦听端口 + +```nginx +server { + # Standard HTTP Protocol + listen 80; + # Standard HTTPS Protocol + listen 443 ssl; + # For http2 + listen 443 ssl http2; + # Listen on 80 using IPv6 + listen [::]:80; + # Listen only on using IPv6 + listen [::]:80 ipv6only=on; +} +``` + + + +### 访问日志 + +```nginx +server { + # Relative or full path to log file + access_log /path/to/file.log; + # Turn 'on' or 'off' + access_log on; +} +``` + + + +### 域名 + +```nginx +server { + # Listen to yourdomain.com + server_name yourdomain.com; + # Listen to multiple domains server_name yourdomain.com www.yourdomain.com; + # Listen to all domains + server_name *.yourdomain.com; + # Listen to all top-level domains + server_name yourdomain.*; + # Listen to unspecified Hostnames (Listens to IP address itself) + server_name ""; +} +``` + + + +### 静态资源 + +```nginx +server { + listen 80; + server_name yourdomain.com; + location / { + root /path/to/website; + } +} +``` + + + +### 重定向 + +```nginx +server { + listen 80; + server_name www.yourdomain.com; + return 301 http://yourdomain.com$request_uri; +} + +server { + listen 80; + server_name www.yourdomain.com; + location /redirect-url { + return 301 http://otherdomain.com; + } +} +``` + + + +### 反向代理 + +```nginx +server { + listen 80; + server_name yourdomain.com; + location / { + proxy_pass http://0.0.0.0:3000; + # where 0.0.0.0:3000 is your application server (Ex: node.js) bound on 0.0.0.0 listening on port 3000 + } +} +``` + + + +### 负载均衡 + +```nginx +upstream node_js { + server 0.0.0.0:3000; + server 0.0.0.0:4000; + server 123.131.121.122; +} + +server { + listen 80; + server_name yourdomain.com; + location / { + proxy_pass http://node_js; + } +} +``` + + + +### SSL协议 + +```nginx +server { + listen 443 ssl; + server_name yourdomain.com; + ssl on; + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/privatekey.pem; + ssl_stapling on; + ssl_stapling_verify on; + ssl_trusted_certificate /path/to/fullchain.pem; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_session_timeout 1h; + ssl_session_cache shared:SSL:50m; + add_header Strict-Transport-Security max-age=15768000; +} + +# Permanent Redirect for HTTP to HTTPS +server { + listen 80; + server_name yourdomain.com; + return 301 https://$host$request_uri; +} +``` + + + +## 日志分析 + +```shell +# 统计IP访问量 +awk '{print $1}' access.log | sort -n | uniq | wc -l +# 查看某一时间段的IP访问量(4-5点) +grep "07/Apr/2017:0[4-5]" access.log | awk '{print $1}' | sort | uniq -c| sort -nr | wc -l +# 查看访问最频繁的前100个IP +awk '{print $1}' access.log | sort -n |uniq -c | sort -rn | head -n 100 +# 查看访问100次以上的IP +awk '{print $1}' access.log | sort -n |uniq -c |awk '{if($1 >100) print $0}'|sort -rn +# 查询某个IP的详细访问情况,按访问频率排序 +grep '104.217.108.66' access.log |awk '{print $7}'|sort |uniq -c |sort -rn |head -n 100 + +# 页面访问统计 +# 查看访问最频的页面(TOP100) +awk '{print $7}' access.log | sort |uniq -c | sort -rn | head -n 100 +# 查看访问最频的页面([排除php页面】(TOP100) +grep -v ".php" access.log | awk '{print $7}' | sort |uniq -c | sort -rn | head -n 100 +# 查看页面访问次数超过100次的页面 +cat access.log | cut -d ' ' -f 7 | sort |uniq -c | awk '{if ($1 > 100) print $0}' | less +# 查看最近1000条记录,访问量最高的页面 +tail -1000 access.log |awk '{print $7}'|sort|uniq -c|sort -nr|less + +# 请求量统计 +# 统计每秒的请求数,top100的时间点(精确到秒) +awk '{print $4}' access.log |cut -c 14-21|sort|uniq -c|sort -nr|head -n 100 +# 统计每分钟的请求数,top100的时间点(精确到分钟) +awk '{print $4}' access.log |cut -c 14-18|sort|uniq -c|sort -nr|head -n 100 +# 统计每小时的请求数,top100的时间点(精确到小时) +awk '{print $4}' access.log |cut -c 14-15|sort|uniq -c|sort -nr|head -n 100 + +# 性能分析 +# 列出传输时间超过 3 秒的页面,显示前20条 +cat access.log|awk '($NF > 3){print $7}'|sort -n|uniq -c|sort -nr|head -20 +# 列出php页面请求时间超过3秒的页面,并统计其出现的次数,显示前100条 +cat access.log|awk '($NF > 1 && $7~/\.php/){print $7}'|sort -n|uniq -c|sort -nr|head -100 + +# 蜘蛛抓取统计 +# 统计蜘蛛抓取次数 +grep 'Baiduspider' access.log |wc -l +# 统计蜘蛛抓取404的次数 +grep 'Baiduspider' access.log |grep '404' | wc -l + +# TCP连接统计 +# 查看当前TCP连接数 +netstat -tan | grep "ESTABLISHED" | grep ":80" | wc -l +# 用tcpdump嗅探80端口的访问看看谁最高 +tcpdump -i eth0 -tnn dst port 80 -c 1000 | awk -F"." '{print $1"."$2"."$3"."$4}' | sort | uniq -c | sort -nr + +awk '{print $1}' $logpath |sort -n|uniq|wc -l +# 系统正在统计某一个时间段IP访问量为 +sed -n '/22\/Jun\/2017:1[5]/,/22\/Jun\/2017:1[6]/p' $logpath|awk '{print $1}'|sort -n|uniq|wc -l +# 访问100次以上的IP +awk '{print $1}' $logpath|sort -n|uniq -c|awk '{if($1>100) print $0}'|sort -rn +# 访问最频繁的请求(TOP50) +awk '{print $7}' $logpath |sort |uniq -c|sort -rn |head -n 50 +# 统计每秒的请求数(TOP50) +awk '{print $4}' $logpath|cut -c 14-21|sort |uniq -c|sort -nr|head -n 50 +# 统计每分钟的请求数(TOP50) +awk '{print $4}' $logpath|cut -c 14-18|sort|uniq -c|sort -nr|head -n 50 +# 统计每小时请求数(TOP50) +awk '{print $4}' $logpath|cut -c 14-15|sort|uniq -c|sort -nr|head -n 50 +# 传输时间超过1秒的请求(TOP20) +cat $logpath|awk '($NF > 1){print $7}'|sort -n|uniq -c|sort -nr|head -20 +``` + + + +# LVS + +LVS是Linux Virtual Server的简写,意即Linux虚拟服务器,是一个虚拟的服务器集群系统。 + +## 工作模式 + +LVS有三种常见的负载均衡的模式:**NAT模式(网络地址转换模式)、IP TUN(隧道模式)、DR(知己路由模式)**。阿里云还提供了两种模式:**full NAT模式、ENAT模式**。 + + + +**术语** + +- **DS**:Director Server,指的是前端负载均衡器 +- **RS**:Real Server,后端真实的工作服务器 +- **VIP**:Virtual Ip Address,向外部直接面向用户请求,作为用户请求的目标的IP地址 +- **DIP**:Director Server IP,主要用于和内部主机通讯的IP地址 +- **RIP**:Real Server IP,后端服务器的IP地址 +- **CIP**:Client IP,客户端主机IP地址 + +(假设 cip 是200.200.200.2, vip是200.200.200.1) + + + +### NAT模式(网络地址转换) + +**NAT模式(NetWork Address Translation-网络地址转换)**。客户发出请求,发送请求给链接调度器的VIP,调度器将请求报文中的目标Ip地址改为RIP。这样服务器RealServer将请求的内容发给调度器,调度器再将报文中的源IP地址改为VIP。 + +![LVS-NAT](images/Middleware/LVS-NAT.png) + +**原理** + +- client发出请求(sip 200.200.200.2,dip 200.200.200.1) +- 请求包到达lvs,lvs修改请求包为(sip 200.200.200.2, dip rip) +- 请求包到达rs, rs回复(sip rip,dip 200.200.200.2) +- 这个回复包不能直接给client,因为rip不是VIP会被reset掉 +- 但是因为lvs是网关,所以这个回复包先走到网关,网关有机会修改sip +- 网关修改sip为VIP,修改后的回复包(sip 200.200.200.1,dip 200.200.200.2)发给client + + + +![LVS-NAT-IP](images/Middleware/LVS-NAT-IP.png) + +**优点** + +- 配置简单 +- 支持端口映射(看名字就知道) +- RIP一般是私有地址,主要用户LVS和RS之间通信 + +**缺点** + +- LVS和所有RS必须在同一个vlan +- 进出流量都要走LVS转发 +- LVS容易成为瓶颈 +- 一般而言需要将VIP配置成RS的网关 + + + +**分析** + +- **为什么NAT要求lvs和RS在同一个vlan?** + + 因为回复包必须经过lvs再次修改sip为vip,client才认,如果回复包的sip不是client包请求的dip(也就是vip),那么这个连接会被reset掉。如果LVS不是网关,因为回复包的dip是cip,那么可能从其它路由就走了,LVS没有机会修改回复包的sip + + + +**NAT结构** + +- LVS修改进出包的(sip, dip)的时只改了其中一个(所以才有接下来的full NAT) +- NAT最大的缺点是要求LVS和RS必须在同一个vlan,这样限制了LVS集群和RS集群的部署灵活性 + +![LVS-NAT-STR](images/Middleware/LVS-NAT-STR.png) + + + +### IP TUN模型(IP隧道) + +**IP TUN模型(IP Tunneling-IP隧道)**。和DR模式差不多,但是比DR多了一个隧道技术以支持realserver不在同一个物理环境中。就是realserver一个在北京,一个工作在上海。在原有的IP报文外再次封装多一层IP首部,内部IP首部(源地址为CIP,目标IIP为VIP),外层IP首部(源地址为DIP,目标IP为RIP + +**原理** + +- 请求包到达LVS后,LVS将请求包封装成一个新的IP报文 +- 新的IP包的目的IP是某一RS的IP,然后转发给RS +- RS收到报文后IPIP内核模块解封装,取出用户的请求报文 +- 发现目的IP是VIP,而自己的tunl0网卡上配置了这个IP,从而愉快地处理请求并将结果直接发送给客户 + + + +**优点** + +- 集群节点可以跨vlan +- 跟DR一样,响应报文直接发给client + +**缺点** + +- RS上必须安装运行IPIP模块 +- 多增加了一个IP头 +- LVS和RS上的tunl0虚拟网卡上配置同一个VIP(类似DR) + + + +**分析** + +- **为什么IP TUN不要求同一个vlan?** + + 因为IP TUN中不是修改MAC来路由,所以不要求同一个vlan,只要求lvs和rs之间ip能通就行。DR模式要求的是lvs和RS之间广播能通 + +- **IP TUN性能** + + 回包不走LVS,但是多做了一次封包解包,不如DR好 + + + +**IP TUN结构** + +- 图中红线是再次封装过的包,ipip是操作系统的一个内核模块 +- DR可能在小公司用的比较多,IP TUN用的少一些 + +![LVS-TP-TUN-STR](images/Middleware/LVS-TP-TUN-STR.png) + + + +### DR模式(直接路由) + +**DR模式(Director Routing-直接路由)**。整个DR模式都是停留在第二层的数据链路层。直接修改MAC。实现报文的转发。 + +![LVS-DR](images/Middleware/LVS-DR.png) + +**原理** + +- 请求流量(sip 200.200.200.2, dip 200.200.200.1) 先到达LVS +- 然后LVS,根据负载策略挑选众多 RS中的一个,然后将这个网络包的MAC地址修改成这个选中的RS的MAC +- 然后丢给交换机,交换机将这个包丢给选中的RS +- 选中的RS看到MAC地址是自己的、dip也是自己的,愉快地手下并处理、回复 +- 回复包(sip 200.200.200.1, dip 200.200.200.2) +- 经过交换机直接回复给client了(不再走LVS) + + + +![LVS-DR-IP](images/Middleware/LVS-DR-IP.png) + +**优点** + +- DR模式是性能最好的一种模式,入站请求走LVS,回复报文绕过LVS直接发给Client + +**缺点** + +- 要求LVS和rs在同一个vlan +- RS需要配置vip同时特殊处理arp +- 不支持端口映射 + + + +**分析** + +- **为什么要求LVS和RS在同一个vlan(或说同一个二层网络里)?** + + 因为DR模式依赖多个RS和LVS共用同一个VIP,然后依据MAC地址来在LVS和多个RS之间路由,所以LVS和RS必须在一个vlan或者说同一个二层网络里 + +- **DR 模式为什么性能最好?** + + 因为回复包不走LVS了,大部分情况下都是请求包小,回复包大,LVS很容易成为流量瓶颈,同时LVS只需要修改进来的包的MAC地址。 + +- **DR 模式为什么回包不需要走LVS了?** + + 因为RS和LVS共享同一个vip,回复的时候RS能正确地填好sip为vip,不再需要LVS来多修改一次(后面讲的NAT、Full NAT都需要)。 + + + +**DR结构** + +- 绿色是请求包进来,红色是修改过MAC的请求包 + +![LVS-DR-STR](images/Middleware/LVS-DR-STR.png) + + + +### full NAT模式 + +**full NAT模式(full NetWork Address Translation-全部网络地址转换)**。 + +**原理(类似NAT)** + +- client发出请求(sip 200.200.200.2 dip 200.200.200.1) +- 请求包到达lvs,lvs修改请求包为**(sip 200.200.200.1, dip rip)** 注意这里sip/dip都被修改了 +- 请求包到达rs, rs回复(sip rip,dip 200.200.200.1) +- 这个回复包的目的IP是VIP(不像NAT中是 cip),所以LVS和RS不在一个vlan通过IP路由也能到达lvs +- lvs修改sip为vip, dip为cip,修改后的回复包(sip 200.200.200.1,dip 200.200.200.2)发给client + + + +**优点** + +- 解决了NAT对LVS和RS要求在同一个vlan的问题,适用更复杂的部署形式 + +**缺点** + +- RS看不到cip(NAT模式下可以看到) +- 进出流量还是都走的lvs,容易成为瓶颈(跟NAT一样都有这个问题) + + + +**分析** + +- **为什么full NAT解决了NAT中要求的LVS和RS必须在同一个vlan的问题?** + +因为LVS修改进来的包的时候把(sip, dip)都修改了(这也是full的主要含义吧),RS的回复包目的地址是vip(NAT中是cip),所以只要vip和rs之间三层可通就行,这样LVS和RS可以在不同的vlan了,也就是LVS不再要求是网关,从而LVS和RS可以在更复杂的网络环境下部署。 + +- **为什么full NAT后RS看不见cip了?** + +因为cip被修改掉了,RS只能看到LVS的vip,在阿里内部会将cip放入TCP包的Option中传递给RS,RS上一般部署自己写的toa模块来从Options中读取的cip,这样RS能看到cip了, 当然这不是一个开源的通用方案。 + + + +**full NAT结构** + +- full NAT解决了NAT的同vlan的要求,**基本上可以用于公有云** +- 但还没解决进出流量都走LVS的问题(LVS要修改进出的包) + +![LVS-full-NAT-STR](images/Middleware/LVS-full-NAT-STR.png) + + + +### ENAT模式 + +**ENAT模式(enhence NAT)**。ENAT模式在内部也会被称为 三角模式或者DNAT/SNAT模式。 + +**原理** + +- client发出请求(cip,vip) +- 请求包到达lvs,lvs修改请求包为(vip,rip),并将cip放入TCP Option中 +- 请求包根据ip路由到达rs, ctk模块读取TCP Option中的cip +- 回复包(RIP, vip)被ctk模块截获,并将回复包改写为(vip, cip) +- 因为回复包的目的地址是cip所以不需要经过lvs,可以直接发给client + + + +**优点** + +- 不要求LVS和RS在同一个vlan +- 出去的流量不需要走LVS,性能好 + +**缺点** + +- 集团实现的自定义方案,需要在所有RS上安装ctk组件(类似full NAT中的toa) + + + +**分析** + +- **为什么ENAT的回复包不需要走回LVS了?** + +因为full NAT模式下要走回去是需要LVS再次改写回复包的IP,而ENAT模式下,该事情在RS上被ctk模块提前做掉。 + +- **为什么ENAT的LVS和RS可以在不同的vlan?** + +跟full NAT一样。 + + + +**ENAT结构** + +![LVS-ENAT-STR](images/Middleware/LVS-ENAT-STR.png) + +## 调度算法 + +### 静态调度算法 + +只根据算法进行调度 而不考虑后端服务器的实际连接情况和负载情况。 + +- **RR:轮叫调度(Round Robin)** + 调度器通过”轮叫”调度算法将外部请求按顺序轮流分配到集群中的真实服务器上,它均等地对待每一台服务器,而不管服务器上实际的连接数和系统负载。 + +- **WRR:加权轮叫(Weight RR)** + 调度器通过“加权轮叫”调度算法根据真实服务器的不同处理能力来调度访问请求。这样可以保证处理能力强的服务器处理更多的访问流量。调度器可以自动问询真实服务器的负载情况,并动态地调整其权值。 + +- **DH:目标地址散列调度(Destination Hash )** + 根据请求的目标IP地址,作为散列键(HashKey)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。 + +- **SH:源地址 hash(Source Hash)** + 源地址散列”调度算法根据请求的源IP地址,作为散列键(HashKey)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。 + + + +### 动态调度算法 + +- **LC:最少链接(Least Connections)** + 调度器通过”最少连接”调度算法动态地将网络请求调度到已建立的链接数最少的服务器上。如果集群系统的真实服务器具有相近的系统性能,采用”最小连接”调度算法可以较好地均衡负载。 + +- **WLC:加权最少连接(默认)(Weighted Least Connections)** + 在集群系统中的服务器性能差异较大的情况下,调度器采用“加权最少链接”调度算法优化负载均衡性能,具有较高权值的服务器将承受较大比例的活动连接负载。调度器可以自动问询真实服务器的负载情况,并动态地调整其权值。 + +- **SED:最短延迟调度(Shortest Expected Delay )** + 在WLC基础上改进,Overhead = (ACTIVE+1)*256/加权,不再考虑非活动状态,把当前处于活动状态的数目+1来实现,数目最小的,接受下次请求,+1的目的是为了考虑加权的时候,非活动连接过多缺陷:当权限过大的时候,会倒置空闲服务器一直处于无连接状态。 + +- **NQ永不排队/最少队列调度(Never Queue Scheduling NQ)** + 无需队列。如果有台 realserver的连接数=0就直接分配过去,不需要再进行sed运算,保证不会有一个主机很空间。在SED基础上无论+几,第二次一定给下一个,保证不会有一个主机不会很空闲着,不考虑非活动连接,才用NQ,SED要考虑活动状态连接,对于DNS的UDP不需要考虑非活动连接,而httpd的处于保持状态的服务就需要考虑非活动连接给服务器的压力。 + +- **LBLC:基于局部性的最少链接(locality-Based Least Connections)** + 基于局部性的最少链接”调度算法是针对目标IP地址的负载均衡,目前主要用于Cache集群系统。该算法根据请求的目标IP地址找出该目标IP地址最近使用的服务器,若该服务器是可用的且没有超载,将请求发送到该服务器;若服务器不存在,或者该服务器超载且有服务器处于一半的工作负载,则用“最少链接”的原则选出一个可用的服务器,将请求发送到该服务器。 + +- **LBLCR:带复制的基于局部性最少连接(Locality-Based Least Connections with Replication)** + 带复制的基于局部性最少链接”调度算法也是针对目标IP地址的负载均衡,目前主要用于Cache集群系统。它与LBLC算法的不同之处是它要维护从一个目标IP地址到一组服务器的映射,而LBLC算法维护从一个目标IP地址到一台服务器的映射。该算法根据请求的目标IP地址找出该目标IP地址对应的服务器组,按”最小连接”原则从服务器组中选出一台服务器,若服务器没有超载,将请求发送到该服务器;若服务器超载,则按“最小连接”原则从这个集群中选出一台服务器,将该服务器加入到服务器组中,将请求发送到该服务器。同时,当该服务器组有一段时间没有被修改,将最忙的服务器从服务器组中删除,以降低复制的程度。 + + + +## 常见问题分析 + +### 脑裂 + +由于两台高可用服务器对之间,在指定时间内,无法相互检测到对方的心跳,而各自启动故障切换转移功能,取得资源服务及所有权,而此时的两台高可用服务器对都还或者,并且正在运行,这样就会导致同一个IP或服务在两段同时启动而发生冲突的严重问题,最严重的是两台主机占用同一个VIP,当用户写入数据的时候可能同时写在两台服务器上。 +**1)产生裂脑原因** + +- 心跳链路故障,导致无法通信 +- 开启防火墙阻挡心跳消息传输 +- 心跳网卡地址配置等不正确 +- 其他:心跳方式不同,心跳广播冲突,软件bug等1234 + +备注: + +- 心跳线坏了(故障或老化) +- 网卡相关驱动坏了,IP配置即冲突问题(直连) +- 心跳线间连接的设备故障(网卡及交换机) +- 仲裁机器出问题 + +**2)防止裂脑的方法** + +- 采用串行或以太网电缆连接,同时用两条心跳线路 +- 做好裂脑的监控报警,在问题发生时人为第一时间介入仲裁 +- 启用磁盘锁,即正在服务的一方只在发现心跳线全部断开时,才开启磁盘锁 +- fence设备(智能电源管理设备) +- 增加仲裁盘 +- 加冗余线路 + + + +### 负载不均 + +**原因分析** + +- lvs自身的会话保持参数设置。优化:使用cookie代替session +- lvs调度算法设置,例如rr、wrr +- 后端RS节点的会话保持参数,例如apache的keepalive参数 +- 访问量较少的情况下,不均衡的现象更加明显 +- 用户发送的请求时间长短和请求资源多少以及大小 + + + +### 排错 + +先检查客户端到服务端——>然后检查负载均衡到RS端——>最后检查客户端到LVS端。 + +- 调度器上lvs调度规则及IP正确性 +- RS节点上VIP和ARP抑制的检查 + + + +**生成思路** + +- 对绑定的VIP做实时监控,出问题报警或自动处理后报警 +- 把绑定的VIP做成配置文件,例如: vim /etc/sysconfig/network-scripts/lo:0 + + + +**ARP抑制的配置思路** + +- 如果是单个VIP,那么可以用stop传参设置0 +- 如果RS端有多个VIP绑定,此时,即使是停止VIP绑定也不一定不要置0. +- RS节点上自身提供服务的检查 +- 辅助排除工具有tcpdump、ping等 +- 负载均衡和反向代理三角形排查理论 + + + +# Keepalived + +Keepalived是一个免费开源的,用C编写的类似于layer3, 4 & 7交换机制软件,具备我们平时说的第3层、第4层和第7层交换机的功能。主要提供loadbalancing(负载均衡)和 high-availability(高可用)功能,负载均衡实现需要依赖Linux的虚拟服务内核模块(ipvs),而高可用是通过VRRP协议实现多台机器之间的故障转移服务。 + + + +## 功能体系 + +![Keepalived体系结构](images/Middleware/Keepalived体系结构.jpg) + +Keepalived的所有功能是配置keepalived.conf文件来实现的。上图是Keepalived的功能体系结构,大致分两层: + +- **内核空间(kernel space)** + + 主要包括IPVS(IP虚拟服务器,用于实现网络服务的负载均衡)和NETLINK(提供高级路由及其他相关的网络功能)两个部份。 + +- **用户空间(user space)** + + - WatchDog:负载监控checkers和VRRP进程的状况 + - VRRP Stack:负载负载均衡器之间的失败切换FailOver,如果只用一个负载均稀器,则VRRP不是必须的 + - Checkers:负责真实服务器的健康检查healthchecking,是keepalived最主要的功能。换言之,可以没有VRRP Stack,但健康检查healthchecking是一定要有的 + - IPVS wrapper:用户发送设定的规则到内核ipvs代码 + - Netlink Reflector:用来设定vrrp的vip地址等 + + + +**重要功能** + +- 管理LVS负载均衡软件 +- 实现LVS集群节点的健康检查中 +- 作为系统网络服务的高可用性(failover) + + + +**高可用故障切换转移原理** + +Keepalived高可用服务对之间的故障切换转移,是通过 VRRP (Virtual Router Redundancy Protocol ,虚拟路由器冗余协议)来实现的。 + +在 Keepalived服务正常工作时,主 Master节点会不断地向备节点发送(多播的方式)心跳消息,用以告诉备Backup节点自己还活看,当主 Master节点发生故障时,就无法发送心跳消息,备节点也就因此无法继续检测到来自主 Master节点的心跳了,于是调用自身的接管程序,接管主Master节点的 IP资源及服务。而当主 Master节点恢复时,备Backup节点又会释放主节点故障时自身接管的IP资源及服务,恢复到原来的备用角色。 + +**VRRP** + +全称Virtual Router Redundancy Protocol ,中文名为虚拟路由冗余协议 ,VRRP的出现就是为了解决静态踣甶的单点故障问题,VRRP是通过一种竞选机制来将路由的任务交给某台VRRP路由器的。 + + + +## 脑裂问题 + +在高可用(HA)系统中,当联系2个节点的“心跳线”断开时,本来为一整体、动作协调的HA系统,就分裂成为2个独立的个体。由于相互失去了联系,都以为是对方出了故障。两个节点上的HA软件像“裂脑人”一样,争抢“共享资源”、争起“应用服务”,就会发生严重后果——或者共享资源被瓜分、2边“服务”都起不来了;或者2边“服务”都起来了,但同时读写“共享存储”,导致数据损坏(常见如数据库轮询着的联机日志出错)。 + +对付HA系统“裂脑”的对策,目前达成共识的的大概有以下几条: + +- 添加冗余的心跳线,例如:双线条线(心跳线也HA),尽量减少“裂脑”发生几率 +- 启用磁盘锁。正在服务一方锁住共享磁盘,“裂脑”发生时,让对方完全“抢不走”共享磁盘资源。但使用锁磁盘也会有一个不小的问题,如果占用共享盘的一方不主动“解锁”,另一方就永远得不到共享磁盘。现实中假如服务节点突然死机或崩溃,就不可能执行解锁命令。后备节点也就接管不了共享资源和应用服务。于是有人在HA中设计了“智能”锁。即:正在服务的一方只在发现心跳线全部断开(察觉不到对端)时才启用磁盘锁。平时就不上锁了 +- 设置仲裁机制。例如设置参考IP(如网关IP),当心跳线完全断开时,2个节点都各自ping一下参考IP,不通则表明断点就出在本端。不仅“心跳”、还兼对外“服务”的本端网络链路断了,即使启动(或继续)应用服务也没有用了,那就主动放弃竞争,让能够ping通参考IP的一端去起服务。更保险一些,ping不通参考IP的一方干脆就自我重启,以彻底释放有可能还占用着的那些共享资源 + + + +### 产生原因 + +一般来说,裂脑的发生,有以下几种原因: + +- 高可用服务器对之间心跳线链路发生故障,导致无法正常通信 + - 因心跳线坏了(包括断了,老化) + - 因网卡及相关驱动坏了,ip配置及冲突问题(网卡直连) + - 因心跳线间连接的设备故障(网卡及交换机) + - 因仲裁的机器出问题(采用仲裁的方案) +- 高可用服务器上开启了 iptables防火墙阻挡了心跳消息传输 +- 高可用服务器上心跳网卡地址等信息配置不正确,导致发送心跳失败 +- 其他服务配置不当等原因,如心跳方式不同,心跳广插冲突、软件Bug等 + +**提示:** Keepalived配置里同一 VRRP实例如果 virtual_router_id两端参数配置不一致也会导致裂脑问题发生。 + + + +### 解决方案 + +在实际生产环境中,我们可以从以下几个方面来防止裂脑问题的发生: + +- 同时使用串行电缆和以太网电缆连接,同时用两条心跳线路,这样一条线路坏了,另一个还是好的,依然能传送心跳消息 +- 当检测到裂脑时强行关闭一个心跳节点(这个功能需特殊设备支持,如Stonith、feyce)。相当于备节点接收不到心跳消患,通过单独的线路发送关机命令关闭主节点的电源 +- 做好对裂脑的监控报警(如邮件及手机短信等或值班).在问题发生时人为第一时间介入仲裁,降低损失。例如,百度的监控报警短倍就有上行和下行的区别。报警消息发送到管理员手机上,管理员可以通过手机回复对应数字或简单的字符串操作返回给服务器.让服务器根据指令自动处理相应故障,这样解决故障的时间更短. + + + +在实施高可用方案时,要根据业务实际需求确定是否能容忍这样的损失。对于一般的网站常规业务,这个损失是可容忍的。 + + + +## 安装部署 + +**第一步:keepalived软件安装** + + `yum install keepalived -y ` + +``` +/etc/keepalived +/etc/keepalived/keepalived.conf #keepalived服务主配置文件 +/etc/rc.d/init.d/keepalived #服务启动脚本 +/etc/sysconfig/keepalived +/usr/bin/genhash +/usr/libexec/keepalived +/usr/sbin/keepalived +``` + + + +**第二步:配置文件说明** + +全局配置 + +```shell + global_defs { # 全局配置 + notification_email { # 定义报警邮件地址 + acassen@firewall.loc + failover@firewall.loc + sysadmin@firewall.loc + } + notification_email_from Alexandre.Cassen@firewall.loc # 定义发送邮件的地址 + smtp_server 192.168.200.1 # 邮箱服务器 + smtp_connect_timeout 30 # 定义超时时间 + router_id LVS_DEVEL # 定义路由标识信息,相同局域网唯一 + } +``` + +虚拟ip配置brrp + +```shell +vrrp_instance VI_1 { # 定义实例 + state MASTER # 状态参数 master/backup 只是说明 + interface eth0 # 虚IP地址放置的网卡位置 + virtual_router_id 51 # 同一家族要一直,同一个集群id一致 + priority 100 # 优先级决定是主还是备 越大越优先 + advert_int 1 # 主备通讯时间间隔 + authentication { + auth_type PASS + auth_pass 1111 # 认证 + } + virtual_ipaddress { + 192.168.200.16 # 设备之间使用的虚拟ip地址 + 192.168.200.17 + 192.168.200.18 + } +} +``` + + + +**第三步:最终配置文件** + +主负载均衡服务器配置 + +```shell +[root@lb01 conf]# cat /etc/keepalived/keepalived.conf +! Configuration File for keepalived + +global_defs { + router_id lb01 +} + +vrrp_instance VI_1 { + state MASTER + interface eth0 + virtual_router_id 51 + priority 150 + advert_int 1 + authentication { + auth_type PASS + auth_pass 1111 + } + virtual_ipaddress { + 10.0.0.3 + } +} +``` + +备负载均衡服务器配置 + +```shell +[root@lb02 ~]# cat /etc/keepalived/keepalived.conf +! Configuration File for keepalived + +global_defs { + router_id lb02 +} + +vrrp_instance VI_1 { + state BACKUP + interface eth0 + virtual_router_id 51 + priority 100 + advert_int 1 + authentication { + auth_type PASS + auth_pass 1111 + } + virtual_ipaddress { + 10.0.0.3 + } +} +``` + + + +**第四步:启动keepalived** + +```shell +[root@lb02 ~]# /etc/init.d/keepalived start +Starting keepalived: [ OK ] +``` + + + +**第五步:在进行访问测试之前要保证后端的节点都能够单独的访问** + +测试连通性. 后端节点 + +```shell +[root@lb01 conf]# curl -H host:www.etiantian.org 10.0.0.8 +web01 www +[root@lb01 conf]# curl -H host:www.etiantian.org 10.0.0.7 +web02 www +[root@lb01 conf]# curl -H host:www.etiantian.org 10.0.0.9 +web03 www +[root@lb01 conf]# curl -H host:bbs.etiantian.org 10.0.0.9 +web03 bbs +[root@lb01 conf]# curl -H host:bbs.etiantian.org 10.0.0.8 +web01 bbs +[root@lb01 conf]# curl -H host:bbs.etiantian.org 10.0.0.7 +web02 bbs +``` + + + +**第六步:查看虚拟ip状态** + +```shell +[root@lb01 conf]# ip a +1: lo: mtu 65536 qdisc noqueue state UNKNOWN + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + inet 127.0.0.1/8 scope host lo + inet6 ::1/128 scope host + valid_lft forever preferred_lft forever +2: eth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 + link/ether 00:0c:29:90:7f:0d brd ff:ff:ff:ff:ff:ff + inet 10.0.0.5/24 brd 10.0.0.255 scope global eth0 + inet 10.0.0.3/24 scope global secondary eth0:1 + inet6 fe80::20c:29ff:fe90:7f0d/64 scope link + valid_lft forever preferred_lft forever +3: eth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000 + link/ether 00:0c:29:90:7f:17 brd ff:ff:ff:ff:ff:ff + inet 172.16.1.5/24 brd 172.16.1.255 scope global eth1 + inet6 fe80::20c:29ff:fe90:7f17/64 scope link + valid_lft forever preferred_lft forever +``` + + + +**第七步:【总结】配置文件修改** + +Keepalived主备配置文件区别: + +- router_id 信息不一致 +- state 状态描述信息不一致 +- priority 主备竞选优先级数值不一致 + + + +# HAProxy + +HAProxy提供高可用性、负载均衡以及基于TCP和HTTP应用的代 理,支持虚拟主机,它是免费、快速并且可靠的一种解决方案。HAProxy特别适用于那些负载特大的web站点,这些站点通常又需要会话保持或七层处理。HAProxy运行在当前的硬件上,完全可以支持数以万计的并发连接。并且它的运行模式使得它可以很简单安全的整合进您当前的架构中, 同时可以保护你的web服务器不被暴露到网络上。 + + + +## 四层负载均衡 + +所谓的四层就是ISO参考模型中的第四层。四层负载均衡也称为四层交换机,它主要是**通过分析IP层及TCP/UDP层的流量实现的基于IP加端口的负载均衡**。常见的基于四层的负载均衡器有**LVS、F5**等。 + + ![四层负载均衡](images/Middleware/四层负载均衡.png) + +以常见的TCP应用为例,负载均衡器在接收到第一个来自客户端的SYN请求时,会通过设定的负载均衡算法选择一个最佳的后端服务器,同时将报文中目标IP地址修改为后端服务器IP,然后直接转发给该后端服务器,这样一个负载均衡请求就完成了。从这个过程来看,一个TCP连接是客户端和服务器直接建立的,而负载均衡器只不过完成了一个类似路由器的转发动作。在某些负载均衡策略中,为保证后端服务器返回的报文可以正确传递给负载均衡器,在转发报文的同时可能还会对报文原来的源地址进行修改。 + + + +## 七层负载均衡 + +七层负载均衡器也称为七层交换机,位于OSI的最高层,即应用层,此时负载均衡器支持多种应用协议,常见的有**HTTP、FTP、SMTP**等。七层负载均衡器**可以根据报文内容,再配合负载均衡算法来选择后端服务器**,因此也称为“内容交换器”。比如,对于Web服务器的负载均衡,七层负载均衡器不但可以根据“IP+端口”的方式进行负载分流,还可以根据网站的URL、访问域名、浏览器类别、语言等决定负载均衡的策略。例如,有两台Web服务器分别对应中英文两个网站,两个域名分别是A、B,要实现访问A域名时进入中文网站,访问B域名时进入英文网站,这在四层负载均衡器中几乎是无法实现的,而七层负载均衡可以根据客户端访问域名的不同选择对应的网页进行负载均衡处理。常见的七层负载均衡器有HAproxy、Nginx等。 + +![七层负载均衡](images/Middleware/七层负载均衡.png) + +这里仍以常见的TCP应用为例,由于负载均衡器要获取到报文的内容,因此只能先代替后端服务器和客户端建立连接,接着,才能收到客户端发送过来的报文内容,然后再根据该报文中特定字段加上负载均衡器中设置的负载均衡算法来决定最终选择的内部服务器。纵观整个过程,七层负载均衡器在这种情况下类似于一个代理服务器。整个过程如下图所示。 + + + +**四层和七层负载均衡** + +对比四层负载均衡和七层负载均衡运行的整个过程,可以看出,在七层负载均衡模式下,负载均衡器与客户端及后端的服务器会分别建立一次TCP连接,而在四层负载均衡模式下,仅建立一次TCP连接。由此可知,七层负载均衡对负载均衡设备的要求更高,而七层负载均衡的处理能力也必然低于四层模式的负载均衡。 + + + +## 负载均衡策略 + +- roundrobin:表示简单的轮询 +- static-rr:表示根据权重 +- leastconn:表示最少连接者先处理 +- source:表示根据请求的源IP,类似Nginx的IP_hash机制 +- uri:表示根据请求的URI +- url_param:表示根据HTTP请求头来锁定每一次HTTP请求 +- rdp-cookie(name):表示根据据cookie(name)来锁定并哈希每一次TCP请求 + + + +**常用的负载均衡算法** + +- 轮询算法:roundrobin +- 根据请求源IP算法:source +- 最少连接者先处理算法:lestconn + + + +## HAProxy与LVS的区别 + +HAProxy负载均衡与LVS负载均衡的区别: + +- 两者都是软件负载均衡产品,但是LVS是基于Linux操作系统实现的一种软负载均衡,而HAProxy是基于第三应用实现的软负载均衡 +- LVS是基于四层的IP负载均衡技术,而HAProxy是基于四层和七层技术、可提供TCP和HTTP应用的负载均衡综合解决方案 +- LVS工作在ISO模型的第四层,因此其状态监测功能单一,而HAProxy在状态监测方面功能强大,可支持端口、URL、脚本等多种状态检测方式 +- HAProxy虽然功能强大,但是整体处理性能低于四层模式的LVS负载均衡,而LVS拥有接近硬件设备的网络吞吐和连接负载能力 + +综上所述,HAProxy和LVS各有优缺点,没有好坏之分,要选择哪个作为负载均衡器,要以实际的应用环境来决定。 + + + +## 安装配置 + +**第一步:安装依赖** + +```shell +[root@test ~] # yum -y install make gcc gcc-c++ openssl-devel +``` + +**第二步:安装haproxy** + +```shell +[root@test ~] # wget http://haproxy.1wt.eu/download/1.3/src/haproxy-1.3.20.tar.gz +[root@test ~] # tar zcvf haproxy-1.3.20.tar.gz +[root@test ~] # cd haproxy-1.3.20 +[root@test ~] # make TARGET=linux26 PREFIX=/usr/local/haproxy # 将haproxy安装到/usr/local/haproxy +[root@test ~] # make install PREFIX=/usr/local/haproxy +``` + +Redis主从复制-部分重同步.jpeg diff --git a/OS.md b/OS.md new file mode 100644 index 0000000..28feab4 --- /dev/null +++ b/OS.md @@ -0,0 +1,1768 @@ +
OS
+ +Author:李茹钰(`echo`) + +Introduction:收纳技术相关的 `JDK Tools`、`Linux Tools`、`Git` 等总结! + +[TOC] + +# TCP + +TCP是**面向连接的、可靠的、基于字节流**的传输层通信协议: + +![TCP协议](images/OS/TCP协议.jpg) + +- **面向连接**:一定是**一对一**才能连接,不能像UDP协议可以一个主机同时向多个主机发送消息,即一对多是无法做到的 + +- **可靠的**:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端 + +- **字节流**:消息是**没有边界**的,所以无论消息有多大都可以进行传输。并且消息是**有序的**,当**前一个**消息没有收到的时候,即使它先收到了后面的字节已经收到,那么也不能扔给应用层去处理,同时对**重复**的报文会自动丢弃 + + + +**TCP头部格式** + +![TCP头部格式](images/OS/TCP头部格式.jpg) + +- **序列号**:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。**用来解决网络包乱序问题** +- **确认应答号**:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。**用来解决不丢包的问题** +- **控制位:** + - **ACK**:该位为 `1` 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 `SYN` 包之外该位必须设置为 `1` + - **RST**:该位为 `1` 时,表示 TCP 连接中出现异常必须强制断开连接 + - **SYN**:该位为 `1` 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定 + - **FIN**:该位为 `1` 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 `FIN` 位置为 1 的 TCP 段 + + + +## 网络模型 + +谈一谈你对 TCP/IP 四层模型,OSI 七层模型的理解? + +### OSI参考模型 + +OSI(Open System Interconnect),即开放式系统互联。 一般都叫OSI参考模型,是ISO(国际标准化组织)组织在1985年研究的网络互连模型。ISO为了更好的使网络应用更为普及,推出了OSI参考模型。其含义就是推荐所有公司使用这个规范来控制网络。这样所有公司都有相同的规范,就能互联了。 + +![OSI参考模型](images/OS/OSI参考模型.png) + + + +### TCP/IP五层模型 + + TCP/IP五层协议和OSI的七层协议对应关系如下: + +![TCP/IP五层模型](images/OS/TCPIP五层模型.png) + + + +## TCP状态 + +![TCP状态](images/OS/TCP状态.png) + +- **CLOSED:** 表示初始状态 +- **LISTEN:** 表示服务器端的某个SOCKET处于监听状态,可以接受连接了 +- **SYN_RCVD:** 表示接收到了SYN报文 +- **SYN_SENT:** 表示客户端已发送SYN报文 +- **ESTABLISHED:**表示连接已经建立了 +- **TIME_WAIT:**表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了 +- **CLOSING:** 表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报 文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接 +- **CLOSE_WAIT:** 表示在等待关闭 + + + +**如何在 Linux 系统中查看 TCP 状态?** + +TCP 的连接状态查看,在 Linux 可以通过 `netstat -napt` 命令查看: + +![TCP连接状态查看](images/OS/TCP连接状态查看.jpg) + + + +### TIME_WAIT + +**① 为什么需要 TIME_WAIT 状态?** + +主动发起关闭连接的一方,才会有 `TIME-WAIT` 状态。需要 TIME-WAIT 状态,主要是两个原因: + +- 防止具有相同「四元组」的「旧」数据包被收到 +- 保证「被动关闭连接」的一方能被正确的关闭,即保证最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭 + + + +**② TIME_WAIT 过多有什么危害?** + +如果服务器有处于 TIME-WAIT 状态的 TCP,则说明是由服务器方主动发起的断开请求。过多的 TIME-WAIT 状态主要的危害有两种: + +- 第一是内存资源占用 +- 第二是对端口资源的占用,一个 TCP 连接至少消耗一个本地端口 + +第二个危害是会造成严重的后果的,要知道,端口资源也是有限的,一般可以开启的端口为 `32768~61000`,也可以通过如下参数设置指定 + +```shell +net.ipv4.ip_local_port_range +``` + +**如果发起连接一方的 TIME_WAIT 状态过多,占满了所有端口资源,则会导致无法创建新连接。** + +客户端受端口资源限制: + +- 客户端TIME_WAIT过多,就会导致端口资源被占用,因为端口就65536个,被占满就会导致无法创建新的连接 + +服务端受系统资源限制: + +- 由于一个四元组表示 TCP 连接,理论上服务端可以建立很多连接,服务端确实只监听一个端口 但是会把连接扔给处理线程,所以理论上监听的端口可以继续监听。但是线程池处理不了那么多一直不断的连接了。所以当服务端出现大量 TIME_WAIT 时,系统资源被占满时,会导致处理不过来新的连接 + + + +**③ 如何优化 TIME_WAIT?** + +这里给出优化 `TIME-WAIT` 的几个方式,都是有利有弊: + +- 打开 `net.ipv4.tcp_tw_reuse` 和 `net.ipv4.tcp_timestamps` 选项 +- `net.ipv4.tcp_max_tw_buckets` +- 程序中使用`SO_LINGER`,应用强制使用`RST`关闭 + + + +**④ 为什么 TIME_WAIT 等待的时间是 2MSL?** + +`MSL` 是 Maximum Segment Lifetime,**报文最大生存时间**,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 `TTL` 字段,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。 + +**MSL 与 TTL 的区别**: MSL 的单位是时间,而 TTL 是经过路由跳数。所以 **MSL 应该要大于等于 TTL 消耗为 0 的时间**,以确保报文已被自然消亡。 + +TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是: 网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以**一来一回需要等待 2 倍的时间**。 + +比如如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 Fin 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。 + +`2MSL` 的时间是从**客户端接收到 FIN 后发送 ACK 开始计时的**。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 **2MSL 时间将重新计时**。 + +在 Linux 系统里 `2MSL` 默认是 `60` 秒,那么一个 `MSL` 也就是 `30` 秒。**Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒**。 + +其定义在 Linux 内核代码里的名称为 TCP_TIMEWAIT_LEN: + +```shell +#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT state, about 60 seconds */ +``` + +如果要修改TIME_WAIT的时间长度,只能修改Linux内核代码里TCP_TIMEWAIT_LEN的值,并重新编译Linux内核。 + + + +## 连接过程 + +![TCP连接的过程和状态变化](images/OS/TCP连接的过程和状态变化.jpg) + +参考文档:https://www.cnblogs.com/jojop/p/14111160.html + +### TCP三次握手 + +![三次握手](images/OS/三次握手.jpg) + +- 假设一开始客户端和服务端都处于`CLOSED`的状态。然后先是服务端主动监听某个端口,处于`LISTEN`状态 + +- 【第一个报文】:客户端会随机初始化序号(`client_isn`),将此序号置于 `TCP` 首部的「序号」字段中,同时把 `SYN` 标志位置为 `1` ,表示 `SYN` 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于 `SYN-SENT` 状态 + + ![SYN报文](images/OS/SYN报文.jpg) + +- 【第二个报文】:服务端收到客户端的 `SYN` 报文后,首先服务端也随机初始化自己的序号(`server_isn`),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入 `client_isn + 1`, 接着把 `SYN` 和 `ACK` 标志位置为 `1`。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 `SYN-RCVD` 状态 + + ![SYN+ACK报文](images/OS/SYN+ACK报文.jpg) + +- 【第三个报文】:客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 `ACK` 标志位置为 `1` ,其次「确认应答号」字段填入 `server_isn + 1` ,最后把报文发送给服务端,**这次报文可以携带客户到服务器的数据**,之后客户端处于 `ESTABLISHED` 状态 + + ![ACK报文](images/OS/ACK报文.jpg) + +- 服务器收到客户端的应答报文后,也进入 `ESTABLISHED` 状态 + + + +**第三次握手是否可以携带数据?** + +**第三次握手是可以携带数据的,前两次握手是不可以携带数据的**。一旦完成三次握手,双方都处于 `ESTABLISHED` 状态,此时连接就已建立完成,客户端和服务端就可以相互发送数据了。假设第三次握手的报文的`seq`是`x+1`: + +- **如果有携带数据**:下次客户端发送的报文,`seq=服务器发回的ACK号` +- **如果没有携带数据**:第三次握手的报文不消耗`seq`,下次客户端发送的报文,`seq`序列号为`x+1` + +**① 服务端SYN-RECV流程** + +![TCP服务端-SYN_RECV流程](images/OS/TCP服务端-SYN_RECV流程.png) + +**② 客户端SYN-SEND流程** + +![TCP客户端-SYN_SEND流程](images/OS/TCP客户端-SYN_SEND流程.png) + +- **场景1:sk->sk_write_pending != 0** + + 这个值默认是0的,那什么情况会导致不为0呢?答案是协议栈发送数据的函数遇到socket状态不是ESTABLISHED的时候,会对这个变量做++操作,并等待一小会时间尝试发送数据。 + +- **场景2:icsk->icsk_accept_queue.rskq_defer_accept != 0** + + 客户端先bind到一个端口和IP,然后setsockopt(TCP_DEFER_ACCEPT),然后connect服务器,这个时候就会出现rskq_defer_accept=1的情况,这时候内核会设置定时器等待数据一起在回复ACK包。 + +- **场景3:icsk->icsk_ack.pingpong != 0** + + pingpong这个属性实际上也是一个套接字选项,用来表明当前链接是否为交互数据流,如其值为1,则表明为交互数据流,会使用延迟确认机制。 + + + +**为什么是三次握手?不是两次、四次?** + +TCP建立连接时,通过三次握手**能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号**。序列号能够保证数据包不重复、不丢弃和按序传输。不使用「两次握手」和「四次握手」的原因: + +- **两次握手**:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号 +- **四次握手**:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数 + +接下来以三个方面分析三次握手的原因: + +- 三次握手才可以阻止重复历史连接的初始化(主要原因) +- 三次握手才可以同步双方的初始序列号 +- 三次握手才可以避免资源浪费 + +**原因一:避免历史连接** + +客户端连续发送多次 SYN 建立连接的报文,在**网络拥堵**情况下: + +- 一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端 +- 那么此时服务端就会回一个 `SYN + ACK` 报文给客户端 +- 客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送 `RST` 报文给服务端,表示中止这一次连接 + +![三次握手避免历史连接](images/OS/三次握手避免历史连接.jpg) + +如果是两次握手连接,就不能判断当前连接是否是历史连接,三次握手则可以在客户端(发送方)准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接: + +- 如果是历史连接(序列号过期或超时),则第三次握手发送的报文是 `RST` 报文,以此中止历史连接 +- 如果不是历史连接,则第三次发送的报文是 `ACK` 报文,通信双方就会成功建立连接 + +所以,TCP 使用三次握手建立连接的最主要原因是**防止历史连接初始化了连接。** + +**原因二:同步双方初始序列号** + +TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素,它的作用: + +- 接收方可以去除重复的数据 +- 接收方可以根据数据包的序列号按序接收 +- 可以标识发送出去的数据包中, 哪些是已经被对方收到的 + +可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带「初始序列号」的 `SYN` 报文的时候,需要服务端回一个 `ACK` 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,**这样一来一回,才能确保双方的初始序列号能被可靠的同步。** + +![同步双方初始序列号](images/OS/同步双方初始序列号.jpg) + +四次握手其实也能够可靠的同步双方的初始化序号,但由于**第二步和第三步可以优化成一步**,所以就成了「三次握手」。而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。 + +**原因三:避免资源浪费** + +如果只有「两次握手」,当客户端的 `SYN` 请求连接在网络中阻塞,客户端没有接收到 `ACK` 报文,就会重新发送 `SYN` ,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的 `ACK` 确认信号,所以每收到一个 `SYN` 就只能先主动建立一个连接,这会造成什么情况呢?如果客户端的 `SYN` 阻塞了,重复发送多次 `SYN` 报文,那么服务器在收到请求后就会**建立多个冗余的无效链接,造成不必要的资源浪费。** + +![避免资源浪费](images/OS/避免资源浪费.jpg) + +即两次握手会造成消息滞留情况下,服务器重复接受无用的连接请求 `SYN` 报文,而造成重复分配资源。 + + + +### TCP四次挥手 + +![四次挥手](images/OS/四次挥手.jpg) + +四次挥手过程: + +- 客户端打算关闭连接,此时会发送一个 TCP 首部 `FIN` 标志位被置为 `1` 的报文,也即 `FIN` 报文,之后客户端进入 `FIN_WAIT_1` 状态 +- 服务端收到该报文后,就向客户端发送 `ACK` 应答报文,接着服务端进入 `CLOSED_WAIT` 状态 +- 客户端收到服务端的 `ACK` 应答报文后,之后进入 `FIN_WAIT_2` 状态 +- 等待服务端处理完数据后,也向客户端发送 `FIN` 报文,之后服务端进入 `LAST_ACK` 状态 +- 客户端收到服务端的 `FIN` 报文后,回一个 `ACK` 应答报文,之后进入 `TIME_WAIT` 状态 +- 服务器收到了 `ACK` 应答报文后,就进入了 `CLOSE` 状态,至此服务端已经完成连接的关闭 +- 客户端在经过 `2MSL` 一段时间后,自动进入 `CLOSE` 状态,至此客户端也完成连接的关闭 + + + +**为什么挥手需要四次?** + +再来回顾下四次挥手双方发 `FIN` 包的过程,就能理解为什么需要四次了。 + +- 关闭连接时,客户端向服务端发送 `FIN` 时,仅仅表示客户端不再发送数据了但是还能接收数据。 +- 服务器收到客户端的 `FIN` 报文时,先回一个 `ACK` 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 `FIN` 报文给客户端来表示同意现在关闭连接。 + +从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 `ACK` 和 `FIN` 一般都会分开发送,从而比三次握手导致多了一次。 + + + +## TCP优化 + +正确有效的使用TCP参数可以提高 TCP 性能。以下将从三个角度来阐述提升 TCP 的策略,分别是: + +### TCP三次握手优化 + +![优化TCP三次握手的策略](images/OS/优化TCP三次握手的策略.jpg) + + + +### TCP四次挥手优化 + +![优化TCP四次挥手的策略](images/OS/优化TCP四次挥手的策略.jpg) + + + +### TCP数据传输优化 + +![TCP数据传输的优化策略](images/OS/TCP数据传输的优化策略.jpg) + + + +## 常见问题 + +### TCP和UDP + +**TCP和UDP的区别?** + +- **连接** + - TCP 是面向连接的传输层协议,传输数据前先要建立连接 + - UDP 是不需要连接,即刻传输数据 + +- **服务对象** + - TCP 是一对一的两点服务,即一条连接只有两个端点 + - UDP 支持一对一、一对多、多对多的交互通信 + +- **可靠性** + - TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达 + - UDP 是尽最大努力交付,不保证可靠交付数据 + +- **拥塞控制、流量控制** + - TCP 有拥塞控制和流量控制机制,保证数据传输的安全性 + - UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率 + +- **首部开销** + - TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 `20` 个字节,如果使用了「选项」字段则会变长的 + - UDP 首部只有 8 个字节,并且是固定不变的,开销较小 + +- **传输方式** + - TCP 是流式传输,没有边界,但保证顺序和可靠 + - UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序 + +- **分片不同** + - TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片 + - UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层,但是如果中途丢了一个分片,则就需要重传所有的数据包,这样传输效率非常差,所以通常 UDP 的报文应该小于 MTU + + + +### ISN + +**① 为什么客户端和服务端的初始序列号 ISN 是不相同的?** + +如果一个已经失效的连接被重用了,但是该旧连接的历史报文还残留在网络中,如果序列号相同,那么就无法分辨出该报文是不是历史报文,如果历史报文被新的连接接收了,则会产生数据错乱。所以,每次建立连接前重新初始化一个序列号主要是为了通信双方能够根据序号将不属于本连接的报文段丢弃。另一方面是为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收。 + + + +**② 初始序列号 ISN 是如何随机产生的?** + +起始 `ISN` 是基于时钟的,每 4 毫秒 + 1,转一圈要 4.55 个小时。RFC1948 中提出了一个较好的初始化序列号 ISN 随机生成算法。 + +**ISN = M + F (localhost, localport, remotehost, remoteport)** + +- `M` 是一个计时器,这个计时器每隔 4 毫秒加 1 +- `F` 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出,用 MD5 算法是一个比较好的选择 + + + +## Socket + +基于TCP协议的客户端和服务器工作: + +![基于TCP协议的客户端和服务器工作](images/OS/基于TCP协议的客户端和服务器工作.jpg) + +- 服务端和客户端初始化 `socket`,得到文件描述符 +- 服务端调用 `bind`,将绑定在 IP 地址和端口 +- 服务端调用 `listen`,进行监听 +- 服务端调用 `accept`,等待客户端连接 +- 客户端调用 `connect`,向服务器端的地址和端口发起连接请求 +- 服务端 `accept` 返回用于传输的 `socket` 的文件描述符 +- 客户端调用 `write` 写入数据;服务端调用 `read` 读取数据 +- 客户端断开连接时,会调用 `close`,那么服务端 `read` 读取数据的时候,就会读取到了 `EOF`,待处理完数据后,服务端调用 `close`,表示连接关闭 + + + +> **listen 时候参数 backlog 的意义?** +> +> Linux内核中会维护两个队列: +> +> - 未完成连接队列(SYN 队列):接收到一个 SYN 建立连接请求,处于 SYN_RCVD 状态; +> - 已完成连接队列(Accpet 队列):已完成 TCP 三次握手过程,处于 ESTABLISHED 状态; +> +> ![SYN队列与Accpet队列](images/OS/SYN队列与Accpet队列.jpg) +> +> SYN 队列 与 Accpet 队列 +> +> ```shell +> int listen (int socketfd, int backlog) +> ``` +> +> - 参数一 socketfd 为 socketfd 文件描述符 +> - 参数二 backlog,这参数在历史内环版本有一定的变化 +> +> 在早期Linux内核backlog是SYN队列大小,也就是未完成的队列大小。在Linux内核2.2之后,backlog变成accept队列,也就是已完成连接建立的队列长度,**所以现在通常认为backlog是accept队列**。但是上限值是内核参数somaxconn的大小,也就说accpet队列长度=min(backlog, somaxconn)。 + +> **accept 发送在三次握手的哪一步?** +> +> 我们先看看客户端连接服务端时,发送了什么? +> +> ![客户端连接服务端](images/OS/客户端连接服务端.jpg) +> +> - 客户端的协议栈向服务器端发送了 SYN 包,并告诉服务器端当前发送序列号 client_isn,客户端进入 SYNC_SENT 状态 +> - 服务器端的协议栈收到这个包之后,和客户端进行 ACK 应答,应答的值为 client_isn+1,表示对 SYN 包 client_isn 的确认,同时服务器也发送一个 SYN 包,告诉客户端当前我的发送序列号为 server_isn,服务器端进入 SYNC_RCVD 状态 +> - 客户端协议栈收到 ACK 之后,使得应用程序从 `connect` 调用返回,表示客户端到服务器端的单向连接建立成功,客户端的状态为 ESTABLISHED,同时客户端协议栈也会对服务器端的 SYN 包进行应答,应答数据为 server_isn+1 +> - 应答包到达服务器端后,服务器端协议栈使得 `accept` 阻塞调用返回,这个时候服务器端到客户端的单向连接也建立成功,服务器端也进入 ESTABLISHED 状态 +> +> 从上面的描述过程,我们可以得知**客户端 connect 成功返回是在第二次握手,服务端 accept 成功返回是在三次握手成功之后。** + +> **客户端调用 close 了,连接是断开的流程是什么?** +> +> 我们看看客户端主动调用了 `close`,会发生什么? +> +> ![客户端调用close过程](images/OS/客户端调用close过程.jpg) +> +> - 客户端调用 `close`,表明客户端没有数据需要发送了,则此时会向服务端发送FIN报文,进入FIN_WAIT_1状态 +> - 服务端接收到了 FIN 报文,TCP协议栈会为 FIN 包插入一个文件结束符 `EOF` 到接收缓冲区中,应用程序可以通过 `read` 调用来感知这个 FIN 包。这个 `EOF` 会被**放在已排队等候的其他已接收的数据之后**,这就意味着服务端需要处理这种异常情况,因为EOF表示在该连接上再无额外数据到达。此时服务端进入 CLOSE_WAIT 状态 +> - 接着,当处理完数据后,自然就会读到 `EOF`,于是也调用 `close` 关闭它的套接字,这会使得会发出一个 FIN 包,之后处于 LAST_ACK 状态 +> - 客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态 +> - 服务端收到 ACK 确认包后,就进入了最后的 CLOSE 状态 +> - 客户端进过 `2MSL` 时间之后,也进入 CLOSE 状态 + + + +## TCP源码 + +### tcp_v4_connect() + +- 描述: 建立与服务器连接,发送SYN段 + +- 返回值: 0或错误码 + +- 代码关键路径: + + ```c + int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) + { + .....  + /* 设置目的地址和目标端口 */ + inet->dport = usin->sin_port; + inet->daddr = daddr; + .... + /* 初始化MSS上限 */ + tp->rx_opt.mss_clamp = 536; + + /* Socket identity is still unknown (sport may be zero). + * However we set state to SYN-SENT and not releasing socket + * lock select source port, enter ourselves into the hash tables and + * complete initialization after this. + */ + tcp_set_state(sk, TCP_SYN_SENT);/* 设置状态 */ + err = tcp_v4_hash_connect(sk);/* 将传输控制添加到ehash散列表中,并动态分配端口 */ + if (err) + goto failure; + .... + if (!tp->write_seq)/* 还未计算初始序号 */ + /* 根据双方地址、端口计算初始序号 */ + tp->write_seq = secure_tcp_sequence_number(inet->saddr, + inet->daddr, + inet->sport, + usin->sin_port); + + /* 根据初始序号和当前时间,随机算一个初始id */ + inet->id = tp->write_seq ^ jiffies; + + /* 发送SYN段 */ + err = tcp_connect(sk); + rt = NULL; + if (err) + goto failure; + + return 0; + } + ``` + + + +### sys_accept() + +- 描述: 调用tcp_accept(), 并把它返回的newsk进行连接描述符分配后返回给用户空间。 + +- 返回值: 连接描述符 + +- 代码关键路径: + + ```c + asmlinkage long sys_accept(int fd, struct sockaddr __user *upeer_sockaddr, int __user *upeer_addrlen) + { + struct socket *sock, *newsock; + ..... + sock = sockfd_lookup(fd, &err);/* 获得侦听端口的socket */ + ..... + if (!(newsock = sock_alloc()))/* 分配一个新的套接口,用来处理与客户端的连接 */ + ..... + /* 调用传输层的accept,对TCP来说,是inet_accept */ + err = sock->ops->accept(sock, newsock, sock->file->f_flags); + .... + if (upeer_sockaddr) {/* 调用者需要获取对方套接口地址和端口 */ + /* 调用传输层回调获得对方的地址和端口 */ + if(newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 2)<0) { + } + /* 成功后复制到用户态 */ + err = move_addr_to_user(address, len, upeer_sockaddr, upeer_addrlen); + } + ..... + if ((err = sock_map_fd(newsock)) < 0)/* 为新连接分配文件描述符 */ + + return err; + } + ``` + + + +### tcp_accept() + +**[注]**: 在内核2.6.32以后对应函数为inet_csk_accept(). + +- 描述: 通过在规定时间内,判断tcp_sock->accept_queue队列非空,代表有新的连接进入. + +- 返回值: (struct sock *)newsk; + +- 代码关键路径: + + ```c + struct sock *tcp_accept(struct sock *sk, int flags, int *err) + { + .... + /* Find already established connection */ + if (!tp->accept_queue) {/* accept队列为空,说明还没有收到新连接 */ + long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);/* 如果套口是非阻塞的,或者在一定时间内没有新连接,则返回 */ + + if (!timeo)/* 超时时间到,没有新连接,退出 */ + goto out; + + /* 运行到这里,说明有新连接到来,则等待新的传输控制块 */ + error = wait_for_connect(sk, timeo); + if (error) + goto out; + } + + req = tp->accept_queue; + if ((tp->accept_queue = req->dl_next) == NULL) + tp->accept_queue_tail = NULL; + + newsk = req->sk; + sk_acceptq_removed(sk); + tcp_openreq_fastfree(req); + .... + + return newsk; + } + ``` + +### 三次握手 + +#### 客户端发送SYN段 + +- 由tcp_v4_connect()->tcp_connect()->tcp_transmit_skb()发送,并置为TCP_SYN_SENT. + +- 代码关键路径: + + ```c + /* 构造并发送SYN段 */ + int tcp_connect(struct sock *sk) + { + struct tcp_sock *tp = tcp_sk(sk); + struct sk_buff *buff; + + tcp_connect_init(sk);/* 初始化传输控制块中与连接相关的成员 */ + + /* 为SYN段分配报文并进行初始化 */ + buff = alloc_skb(MAX_TCP_HEADER + 15, sk->sk_allocation); + if (unlikely(buff == NULL)) + return -ENOBUFS; + + /* Reserve space for headers. */ + skb_reserve(buff, MAX_TCP_HEADER); + + TCP_SKB_CB(buff)->flags = TCPCB_FLAG_SYN; + TCP_ECN_send_syn(sk, tp, buff); + TCP_SKB_CB(buff)->sacked = 0; + skb_shinfo(buff)->tso_segs = 1; + skb_shinfo(buff)->tso_size = 0; + buff->csum = 0; + TCP_SKB_CB(buff)->seq = tp->write_seq++; + TCP_SKB_CB(buff)->end_seq = tp->write_seq; + tp->snd_nxt = tp->write_seq; + tp->pushed_seq = tp->write_seq; + tcp_ca_init(tp); + + /* Send it off. */ + TCP_SKB_CB(buff)->when = tcp_time_stamp; + tp->retrans_stamp = TCP_SKB_CB(buff)->when; + + /* 将报文添加到发送队列上 */ + __skb_queue_tail(&sk->sk_write_queue, buff); + sk_charge_skb(sk, buff); + tp->packets_out += tcp_skb_pcount(buff); + /* 发送SYN段 */ + tcp_transmit_skb(sk, skb_clone(buff, GFP_KERNEL)); + TCP_INC_STATS(TCP_MIB_ACTIVEOPENS); + + /* Timer for repeating the SYN until an answer. */ + /* 启动重传定时器 */ + tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto); + return 0; + } + ``` + + + +#### 服务端发送SYN和ACK处理 + +服务端接收到SYN段后,发送SYN/ACK处理: + +- 由tcp_v4_do_rcv()->tcp_rcv_state_process()->tcp_v4_conn_request()->tcp_v4_send_synack(). + +- tcp_v4_send_synack() + + - tcp_make_synack(sk, dst, req); ** 根据路由、传输控制块、连接请求块中的构建SYN+ACK段 ** + + - ip_build_and_send_pkt(); * 生成IP数据报并发送出去 * + + +![服务端接收到SYN段后_发送SYN_ACK处理流程](images/OS/服务端接收到SYN段后_发送SYN_ACK处理流程.jpg) + + - 代码关键路径: + + ```c + /* 向客户端发送SYN+ACK报文 */ + static int tcp_v4_send_synack(struct sock *sk, struct open_request *req, + struct dst_entry *dst) + { + int err = -1; + struct sk_buff * skb; + + /* First, grab a route. */ + /* 查找到客户端的路由 */ + if (!dst && (dst = tcp_v4_route_req(sk, req)) == NULL) + goto out; + + /* 根据路由、传输控制块、连接请求块中的构建SYN+ACK段 */ + skb = tcp_make_synack(sk, dst, req); + + if (skb) {/* 生成SYN+ACK段成功 */ + struct tcphdr *th = skb->h.th; + + /* 生成校验码 */ + th->check = tcp_v4_check(th, skb->len, + req->af.v4_req.loc_addr, + req->af.v4_req.rmt_addr, + csum_partial((char *)th, skb->len, + skb->csum)); + + /* 生成IP数据报并发送出去 */ + err = ip_build_and_send_pkt(skb, sk, req->af.v4_req.loc_addr, + req->af.v4_req.rmt_addr, + req->af.v4_req.opt); + if (err == NET_XMIT_CN) + err = 0; + } + + out: + dst_release(dst); + return err; + } + ``` + + + +#### 客户端回复确认ACK段 + +- 由tcp_v4_do_rcv()->tcp_rcv_state_process().当前客户端处于TCP_SYN_SENT状态。 + +- tcp_rcv_synsent_state_process(); \* tcp_rcv_synsent_state_process处理SYN_SENT状态下接收到的TCP段 * + + - tcp_ack(); ** 处理接收到的ack报文 ** + + - tcp_send_ack(); * 在主动连接时,向服务器端发送ACK完成连接,并更新窗口 * + + - alloc_skb(); ** 构造ack段 ** + - tcp_transmit_skb(); ** 将ack段发出 ** + + - tcp_urg(sk, skb, th); ** 处理完第二次握手后,还需要处理带外数据 ** + + - tcp_data_snd_check(sk); \* 检测是否有数据需要发送 * + + - 检查sk->sk_send_head队列上是否有待发送的数据。 + - tcp_write_xmit(); ** 将TCP发送队列上的段发送出去 ** + +- 代码关键路径: + +```c +/* 在SYN_SENT状态下处理接收到的段,但是不处理带外数据 */ +static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, + struct tcphdr *th, unsigned len) +{ + struct tcp_sock *tp = tcp_sk(sk); + int saved_clamp = tp->rx_opt.mss_clamp; + + /* 解析TCP选项并保存到传输控制块中 */ + tcp_parse_options(skb, &tp->rx_opt, 0); + + if (th->ack) {/* 处理ACK标志 */ + /* rfc793: + * "If the state is SYN-SENT then + * first check the ACK bit + * If the ACK bit is set + * If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send + * a reset (unless the RST bit is set, if so drop + * the segment and return)" + * + * We do not send data with SYN, so that RFC-correct + * test reduces to: + */ + if (TCP_SKB_CB(skb)->ack_seq != tp->snd_nxt) + goto reset_and_undo; + + if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr && + !between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp, + tcp_time_stamp)) { + NET_INC_STATS_BH(LINUX_MIB_PAWSACTIVEREJECTED); + goto reset_and_undo; + } + + /* Now ACK is acceptable. + * + * "If the RST bit is set + * If the ACK was acceptable then signal the user "error: + * connection reset", drop the segment, enter CLOSED state, + * delete TCB, and return." + */ + + if (th->rst) {/* 收到ACK+RST段,需要tcp_reset设置错误码,并关闭套接口 */ + tcp_reset(sk); + goto discard; + } + + /* rfc793: + * "fifth, if neither of the SYN or RST bits is set then + * drop the segment and return." + * + * See note below! + * --ANK(990513) + */ + if (!th->syn)/* 在SYN_SENT状态下接收到的段必须存在SYN标志,否则说明接收到的段无效,丢弃该段 */ + goto discard_and_undo; + + /* rfc793: + * "If the SYN bit is on ... + * are acceptable then ... + * (our SYN has been ACKed), change the connection + * state to ESTABLISHED..." + */ + + /* 从首部标志中获取显示拥塞通知的特性 */ + TCP_ECN_rcv_synack(tp, th); + if (tp->ecn_flags&TCP_ECN_OK)/* 如果支持ECN,则设置标志 */ + sk->sk_no_largesend = 1; + + /* 设置与窗口相关的成员变量 */ + tp->snd_wl1 = TCP_SKB_CB(skb)->seq; + tcp_ack(sk, skb, FLAG_SLOWPATH); + + /* Ok.. it's good. Set up sequence numbers and + * move to established. + */ + tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; + tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1; + + /* RFC1323: The window in SYN & SYN/ACK segments is + * never scaled. + */ + tp->snd_wnd = ntohs(th->window); + tcp_init_wl(tp, TCP_SKB_CB(skb)->ack_seq, TCP_SKB_CB(skb)->seq); + + if (!tp->rx_opt.wscale_ok) { + tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 0; + tp->window_clamp = min(tp->window_clamp, 65535U); + } + + if (tp->rx_opt.saw_tstamp) {/* 根据是否支持时间戳选项来设置传输控制块的相关字段 */ + tp->rx_opt.tstamp_ok = 1; + tp->tcp_header_len = + sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED; + tp->advmss -= TCPOLEN_TSTAMP_ALIGNED; + tcp_store_ts_recent(tp); + } else { + tp->tcp_header_len = sizeof(struct tcphdr); + } + + /* 初始化PMTU、MSS等成员变量 */ + if (tp->rx_opt.sack_ok && sysctl_tcp_fack) + tp->rx_opt.sack_ok |= 2; + + tcp_sync_mss(sk, tp->pmtu_cookie); + tcp_initialize_rcv_mss(sk); + + /* Remember, tcp_poll() does not lock socket! + * Change state from SYN-SENT only after copied_seq + * is initialized. */ + tp->copied_seq = tp->rcv_nxt; + mb(); + tcp_set_state(sk, TCP_ESTABLISHED); + + /* Make sure socket is routed, for correct metrics. */ + tp->af_specific->rebuild_header(sk); + + tcp_init_metrics(sk); + + /* Prevent spurious tcp_cwnd_restart() on first data + * packet. + */ + tp->lsndtime = tcp_time_stamp; + + tcp_init_buffer_space(sk); + + /* 如果启用了连接保活,则启用连接保活定时器 */ + if (sock_flag(sk, SOCK_KEEPOPEN)) + tcp_reset_keepalive_timer(sk, keepalive_time_when(tp)); + + if (!tp->rx_opt.snd_wscale)/* 首部预测 */ + __tcp_fast_path_on(tp, tp->snd_wnd); + else + tp->pred_flags = 0; + + if (!sock_flag(sk, SOCK_DEAD)) {/* 如果套口不处于SOCK_DEAD状态,则唤醒等待该套接口的进程 */ + sk->sk_state_change(sk); + sk_wake_async(sk, 0, POLL_OUT); + } + + /* 连接建立完成,根据情况进入延时确认模式 */ + if (sk->sk_write_pending || tp->defer_accept || tp->ack.pingpong) { + /* Save one ACK. Data will be ready after + * several ticks, if write_pending is set. + * + * It may be deleted, but with this feature tcpdumps + * look so _wonderfully_ clever, that I was not able + * to stand against the temptation 8) --ANK + */ + tcp_schedule_ack(tp); + tp->ack.lrcvtime = tcp_time_stamp; + tp->ack.ato = TCP_ATO_MIN; + tcp_incr_quickack(tp); + tcp_enter_quickack_mode(tp); + tcp_reset_xmit_timer(sk, TCP_TIME_DACK, TCP_DELACK_MAX); + +discard: + __kfree_skb(skb); + return 0; + } else {/* 不需要延时确认,立即发送ACK段 */ + tcp_send_ack(sk); + } + return -1; + } + + /* No ACK in the segment */ + + if (th->rst) {/* 收到RST段,则丢弃传输控制块 */ + /* rfc793: + * "If the RST bit is set + * + * Otherwise (no ACK) drop the segment and return." + */ + + goto discard_and_undo; + } + + /* PAWS check. */ + /* PAWS检测失效,也丢弃传输控制块 */ + if (tp->rx_opt.ts_recent_stamp && tp->rx_opt.saw_tstamp && tcp_paws_check(&tp->rx_opt, 0)) + goto discard_and_undo; + + /* 在SYN_SENT状态下收到了SYN段并且没有ACK,说明是两端同时打开 */ + if (th->syn) { + /* We see SYN without ACK. It is attempt of + * simultaneous connect with crossed SYNs. + * Particularly, it can be connect to self. + */ + tcp_set_state(sk, TCP_SYN_RECV);/* 设置状态为TCP_SYN_RECV */ + + if (tp->rx_opt.saw_tstamp) {/* 设置时间戳相关的字段 */ + tp->rx_opt.tstamp_ok = 1; + tcp_store_ts_recent(tp); + tp->tcp_header_len = + sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED; + } else { + tp->tcp_header_len = sizeof(struct tcphdr); + } + + /* 初始化窗口相关的成员变量 */ + tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; + tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1; + + /* RFC1323: The window in SYN & SYN/ACK segments is + * never scaled. + */ + tp->snd_wnd = ntohs(th->window); + tp->snd_wl1 = TCP_SKB_CB(skb)->seq; + tp->max_window = tp->snd_wnd; + + TCP_ECN_rcv_syn(tp, th);/* 从首部标志中获取显式拥塞通知的特性。 */ + if (tp->ecn_flags&TCP_ECN_OK) + sk->sk_no_largesend = 1; + + /* 初始化MSS相关的成员变量 */ + tcp_sync_mss(sk, tp->pmtu_cookie); + tcp_initialize_rcv_mss(sk); + + /* 向对端发送SYN+ACK段,并丢弃接收到的SYN段 */ + tcp_send_synack(sk); +#if 0 + /* Note, we could accept data and URG from this segment. + * There are no obstacles to make this. + * + * However, if we ignore data in ACKless segments sometimes, + * we have no reasons to accept it sometimes. + * Also, seems the code doing it in step6 of tcp_rcv_state_process + * is not flawless. So, discard packet for sanity. + * Uncomment this return to process the data. + */ + return -1; +#else + goto discard; +#endif + } + /* "fifth, if neither of the SYN or RST bits is set then + * drop the segment and return." + */ + +discard_and_undo: + tcp_clear_options(&tp->rx_opt); + tp->rx_opt.mss_clamp = saved_clamp; + goto discard; + +reset_and_undo: + tcp_clear_options(&tp->rx_opt); + tp->rx_opt.mss_clamp = saved_clamp; + return 1; +} +``` + + + +#### 服务端收到ACK段 + +- 由tcp_v4_do_rcv()->tcp_rcv_state_process().当前服务端处于TCP_SYN_RECV状态变为TCP_ESTABLISHED状态。 + +- 代码关键路径: + + ```c + /* 除了ESTABLISHED和TIME_WAIT状态外,其他状态下的TCP段处理都由本函数实现 */ + int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb, + struct tcphdr *th, unsigned len) + { + struct tcp_sock *tp = tcp_sk(sk); + int queued = 0; + + tp->rx_opt.saw_tstamp = 0; + + switch (sk->sk_state) { + ..... + /* SYN_RECV状态的处理 */ + if (tcp_fast_parse_options(skb, th, tp) && tp->rx_opt.saw_tstamp &&/* 解析TCP选项,如果首部中存在时间戳选项 */ + tcp_paws_discard(tp, skb)) {/* PAWS检测失败,则丢弃报文 */ + if (!th->rst) {/* 如果不是RST段 */ + /* 发送DACK给对端,说明接收到的TCP段已经处理过 */ + NET_INC_STATS_BH(LINUX_MIB_PAWSESTABREJECTED); + tcp_send_dupack(sk, skb); + goto discard; + } + /* Reset is accepted even if it did not pass PAWS. */ + } + + /* step 1: check sequence number */ + if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {/* TCP段序号无效 */ + if (!th->rst)/* 如果TCP段无RST标志,则发送DACK给对方 */ + tcp_send_dupack(sk, skb); + goto discard; + } + + /* step 2: check RST bit */ + if(th->rst) {/* 如果有RST标志,则重置连接 */ + tcp_reset(sk); + goto discard; + } + + /* 如果有必要,则更新时间戳 */ + tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq); + + /* step 3: check security and precedence [ignored] */ + + /* step 4: + * + * Check for a SYN in window. + */ + if (th->syn && !before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {/* 如果有SYN标志并且序号在接收窗口内 */ + NET_INC_STATS_BH(LINUX_MIB_TCPABORTONSYN); + tcp_reset(sk);/* 复位连接 */ + return 1; + } + + /* step 5: check the ACK field */ + if (th->ack) {/* 如果有ACK标志 */ + /* 检查ACK是否为正常的第三次握手 */ + int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH); + + switch(sk->sk_state) { + case TCP_SYN_RECV: + if (acceptable) { + tp->copied_seq = tp->rcv_nxt; + mb(); + /* 正常的第三次握手,设置连接状态为TCP_ESTABLISHED */ + tcp_set_state(sk, TCP_ESTABLISHED); + sk->sk_state_change(sk); + + /* Note, that this wakeup is only for marginal + * crossed SYN case. Passively open sockets + * are not waked up, because sk->sk_sleep == + * NULL and sk->sk_socket == NULL. + */ + if (sk->sk_socket) {/* 状态已经正常,唤醒那些等待的线程 */ + sk_wake_async(sk,0,POLL_OUT); + } + + /* 初始化传输控制块,如果存在时间戳选项,同时平滑RTT为0,则需计算重传超时时间 */ + tp->snd_una = TCP_SKB_CB(skb)->ack_seq; + tp->snd_wnd = ntohs(th->window) << + tp->rx_opt.snd_wscale; + tcp_init_wl(tp, TCP_SKB_CB(skb)->ack_seq, + TCP_SKB_CB(skb)->seq); + + /* tcp_ack considers this ACK as duplicate + * and does not calculate rtt. + * Fix it at least with timestamps. + */ + if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr && + !tp->srtt) + tcp_ack_saw_tstamp(tp, 0); + + if (tp->rx_opt.tstamp_ok) + tp->advmss -= TCPOLEN_TSTAMP_ALIGNED; + + /* Make sure socket is routed, for + * correct metrics. + */ + /* 建立路由,初始化拥塞控制模块 */ + tp->af_specific->rebuild_header(sk); + + tcp_init_metrics(sk); + + /* Prevent spurious tcp_cwnd_restart() on + * first data packet. + */ + tp->lsndtime = tcp_time_stamp;/* 更新最近一次发送数据包的时间 */ + + tcp_initialize_rcv_mss(sk); + tcp_init_buffer_space(sk); + tcp_fast_path_on(tp);/* 计算有关TCP首部预测的标志 */ + } else { + return 1; + } + break; + ..... + } + } else + goto discard; + ..... + + /* step 6: check the URG bit */ + tcp_urg(sk, skb, th);/* 检测带外数据位 */ + + /* tcp_data could move socket to TIME-WAIT */ + if (sk->sk_state != TCP_CLOSE) {/* 如果tcp_data需要发送数据和ACK则在这里处理 */ + tcp_data_snd_check(sk); + tcp_ack_snd_check(sk); + } + + if (!queued) { /* 如果段没有加入队列,或者前面的流程需要释放报文,则释放它 */ + discard: + __kfree_skb(skb); + } + return 0; + } + ``` + + + + + +# HTTP + +https://juejin.cn/post/6844903789078675469#heading-23 + +## HTTP缓存 + +**HTTP 缓存的好处?** + +- 减少亢余的数据传输,节约资源 +- 缓解服务器压力,提高网站性能 +- 加快客户加载网页的速度 + + + +**不想使用缓存的几种方式** + +- Ctrl + F5强制刷新,都会直接向服务器提取数据 +- 按F5刷新或浏览器的刷新按钮,默认加上Cache-Control:max-age=0,即会走协商缓存 + + + +**浏览器的缓存分类** + +- 强缓存 200 (from memory cache)和200 (from disk cache) +- 协商缓存 304 (Not Modified) + + + +**刷新操作的缓存策略** + +- 正常操作:强制缓存有效,协商缓存有效 +- 手动刷新:强制缓存失效,协商缓存有效 +- 强制刷新:强制缓存失效,协商缓存失效 + + + +### 缓存流程 + +强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存,主要过程如下: + +![HTTP缓存](images/OS/HTTP缓存.jpg) + + + +### 强制缓存 + +如果启用了强缓存,请求资源时不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的network中看到请求返回的200状态码,并在状态码的后面跟着from disk cache 或者from memory cache关键字。两者的差别在于获取缓存的位置不一样。 + +- **Pragma**:在 http1.1 中被遗弃 +- **Cache-Control**:http1.1 时出现的header信息。设置过期时间(绝对时间、时间点),超过了这个时间点就代表资源过期。但是用户的本地时间是可以自行调整的,所以会出现问题。 + - **max-age=x(单位秒)**:缓存内容将在x秒后失效,如 `Cache-Control:max-age=36000` + - **no-cache**:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定 + - **no-store**:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存 + - **private**:所有内容只有客户端可以缓存,**Cache-Control的默认取值** + - **public**:所有内容都将被缓存(客户端和代理服务器都可缓存) +- **Expires**:是http1.0的规范,它的值是一个绝对时间的GMT格式的时间字符串。设置过期时长(相对时间、时间段),指定一个时间长度,跟本地时间无关,在这个时间段内缓存是有效的。 + - 同在Response Headers中 + - 同为控制缓存过期 + - 已被Cache-Control代替 + +**注意**:生效优先级为(从高到低):**Pragma > Cache-Control > Expires** 。 + + + +#### Pragma + +在 http1.1 中被遗弃。 + + + +#### Cache-Control + +![HTTP缓存-Cache-Control](images/OS/HTTP缓存-Cache-Control.png) + +**第一步**:浏览器首次发起请求,缓存为空,服务器响应: + +![HTTP缓存-Cache-Control-第一步](images/OS/HTTP缓存-Cache-Control-第一步.png) + +浏览器缓存此响应,缓存寿命为接收到此响应开始计时 100s 。 + +**第二步**:10s 过后,浏览器再次发起请求,检测缓存未过期,浏览器计算 Age: 10 ,然后直接使用缓存,这里是直接去内存中的缓存,from disk 是取磁盘上的缓存: + +![HTTP缓存-Cache-Control-第二步](images/OS/HTTP缓存-Cache-Control-第二步.png) + +**第三步**:100s 过后,浏览器再次发起请求,检测缓存过期,向服务器发起验证缓存请求。如果服务器对比文件已发生改变,则如 1;否则不返回文件数据报文,直接返回 304: + +![HTTP缓存-Cache-Control-第三步](images/OS/HTTP缓存-Cache-Control-第三步.png) + + + +#### Expires + +Expires是HTTP/1.0控制网页缓存的字段,其值为服务器返回该请求的结果缓存的到期时间,即再次发送请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。 + +Expires是HTTP/1.0的字段,但是现在浏览器的默认使用的是HTTP/1.1,那么在HTTP/1.1中网页缓存还是否由Expires控制?到了HTTP/1.1,Expires已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用**客户端的时间**与**服务端返回的时间**做对比,如果客户端与服务端的时间由于某些原因(时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存直接失效,那么强制缓存存在的意义就毫无意义。 + + + +### 协商缓存 + +协商缓存(对比缓存)就是由服务器来确定缓存资源是否可用,所以客户端与服务器端要通过某种标识来进行通信,从而让服务器判断请求资源是否可以缓存访问,这主要涉及到下面两组header字段。这两组搭档都是成对出现的,即第一次请求的响应头带上某个字段(Last-Modified或者Etag),则后续请求则会带上对应的请求字段(If-Modified-Since或者If-None-Match),若响应头没有Last-Modified或者Etag字段,则请求头也不会有对应的字段。 + +**注意**:Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。 + + + +#### ETag/If-None-Match + +![ETag与If-None-Match](images/OS/ETag与If-None-Match.jpg) + +**Etag** + +Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),如下: + +![Etag](images/OS/Etag.png) + +**If-None-Match** + +If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200,如下: + +![If-None-Match](images/OS/If-None-Match.png) + +**注意**:Etag/If-None-Match优先级高于Last-Modified/If-Modified-Since,同时存在则只有Etag/If-None-Match生效。 + + + +#### Last-Modified/If-Modified-Since + +![Last-Modified与If-Modified-Since](images/OS/Last-Modified与If-Modified-Since.jpg) + +**Last-Modified** + +Last-Modified是服务器响应请求时,返回该资源文件在服务器最后被修改的时间,如下: + +![Last-Modified](images/OS/Last-Modified.png) + + + +**If-Modified-Since** + +If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件,如下: + +![If-Modified-Since](images/OS/If-Modified-Since.png) + + + +**常见问题** + +**问题一:为什么要有Etag?** + +你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题: + +> 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET; +> 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒); +> 某些服务器不能精确的得到文件的最后修改时间。 + +**问题二:如果什么缓存策略都没设置,那么浏览器会怎么处理?** + +如果什么缓存策略都没设置,没有Cache-Control也没有Expires,对于这种情况,浏览器会采用一个启发式的算法(LM-Factor),通常会取响应头中的 Date 减去 Last-Modified 值的 10% (不同的浏览器可能不一样)作为缓存时间。 + + + +## 请求方法 + +截止到HTTP1.1共有下面几种方法: + +| 方法 | 描述 | +| ------- | ------------------------------------------------------------ | +| GET | GET请求会显示请求指定的资源。一般来说GET方法应该只用于数据的读取,而不应当用于会产生副作用的非幂等的操作中。它期望的应该是而且应该是安全的和幂等的。这里的安全指的是,请求不会影响到资源的状态 | +| POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改 | +| PUT | PUT请求会身向指定资源位置上传其最新内容,PUT方法是幂等的方法。通过该方法客户端可以将指定资源的最新数据传送给服务器取代指定的资源的内容 | +| PATCH | PATCH方法出现的较晚,它在2010年的RFC 5789标准中被定义。PATCH请求与PUT请求类似,同样用于资源的更新。二者有以下两点不同:1.PATCH一般用于资源的部分更新,而PUT一般用于资源的整体更新。2.当资源不存在时,PATCH会创建一个新的资源,而PUT只会对已在资源进行更新 | +| DELETE | DELETE请求用于请求服务器删除所请求URI(统一资源标识符,Uniform Resource Identifier)所标识的资源。DELETE请求后指定资源会被删除,DELETE方法也是幂等的 | +| OPTIONS | 允许客户端查看服务器的性能 | +| CONNECT | HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器 | +| HEAD | 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头 | +| TRACE | 回显服务器收到的请求,主要用于测试或诊断 | + + + +## 头参数 + +### 常见请求头 + +| 名称 | 作用 | +| ----------------- | ------------------------------------------------------------ | +| Authorization | 用于设置身份认证信息 | +| User-Agent | 用户标识,如:OS和浏览器的类型和版本 | +| If-Modified-Since | 值为上一次服务器返回的 `Last-Modified` 值,用于确认某个资源是否被更改过,没有更改过(304)就从缓存中读取 | +| If-None-Match | 值为上一次服务器返回的 ETag 值,一般会和`If-Modified-Since`一起出现 | +| Cookie | 已有的Cookie | +| Referer | 表示请求引用自哪个地址,比如你从页面A跳转到页面B时,值为页面A的地址 | +| Host | 请求的主机和端口号 | + + + +### 常见响应头 + +| 名称 | 作用 | +| ----------------- | ------------------------------------------------------------ | +| Date | 服务器的日期 | +| Last-Modified | 该资源最后被修改时间 | +| Transfer-Encoding | 取值为一般为chunked,出现在Content-Length不能确定的情况下,表示服务器不知道响应版体的数据大小,一般同时还会出现`Content-Encoding`响应头 | +| Set-Cookie | 设置Cookie | +| Location | 重定向到另一个URL,如输入浏览器就输入baidu.com回车,会自动跳到 https://www.baidu.com ,就是通过这个响应头控制的 | +| Server | 后台服务器 | + + + +## 状态码 + +HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型: + +| 分类 | 分类描述 | +| ---- | ---------------------------------------------- | +| 1xx | 信息,服务器收到请求,需要请求者继续执行操作 | +| 2xx | 成功,操作被成功接收并处理 | +| 3xx | 重定向,需要进一步的操作以完成请求 | +| 4xx | 客户端错误,请求包含语法错误或无法完成请求 | +| 5xx | 服务器错误,服务器在处理请求的过程中发生了错误 | + +一般我们只需要知道几个常见的就行,比如 200,400,401,403,404,500,502。 + + + +## 请求流程 + +说下浏览器请求一个网址的过程? + +- 首先通过DNS服务器把域名解析成IP地址,通过IP和子网掩码判断是否属于同一个子网 +- 构造应用层请求http报文,传输层添加TCP/UDP头部,网络层添加IP头部,数据链路层添加以太网协议头部 +- 数据经过路由器、交换机转发,最终达到目标服务器,目标服务器同样解析数据,最终拿到http报文,按照对应的程序的逻辑响应回去 + +![HTTP请求流程](images/OS/HTTP请求流程.jpg) + + + +# OS + +## 处理器 + +常见处理器有X86、ARM、MIPS、PowerPC四种。 + +### X86 + +X86架构是芯片巨头Intel设计制造的一种微处理器体系结构的统称。如果这样说你不理解,那么当我说出8086,80286等这样的词汇时,相信你肯定马上就理解了,正是基于此,X86架构这个名称被广为人知。 + +如今,我们所用的PC绝大部分都是X86架构。可见X86架构普及程度,这也和Intel的霸主地位密切相关。 + +x86采用CISC(Complex Instruction Set Computer,复杂指令集计算机)架构。与采用RISC不同的是,在CISC处理器中,程序的各条指令是按顺序串行执行的,每条指令中的各个操作也是按顺序串行执行的。顺序执行的优点是控制简单,但计算机各部分的利用率不高,执行速度慢。 + +- 优势 + - 速度快:单挑指令功能强大,指令数相对较少 + - 带宽要求低:还是因为指令数相对少,即使高频率运行也不需要很大的带宽往CPU传输指令 + - 由于X86采用CISC,因此指令均是按顺序串行执行的,每条指令中的各个操作也是按顺序串行执行的,而顺序执行的优点就是控制简单 + - 个人认为其最大的优势就是:产业化规模更大。目前觉大数据的CPU厂商(如AMD,Intel)生产的就是这种处理器 +- 劣势 + - 通用寄存器组——X86指令集只有8个通用寄存器。所以,CISC的CPU执行是大多数时间是在访问存储器中的数据,而不是寄存器中的。这就是拖慢了整个系统的速度。而RISC系统往往具有非常多的通用寄存器,并采用了重叠寄存器窗口和寄存器堆等技术使寄存器资源得到充分的利用。 + - 解码——这是X86 CPU才有的东西。其作用是把长度不定的X86指令转换为长度固定的类似于RISC的指令,并交给RISC内核。解码分为硬件解码和微解码,对于简单的X86指令只要硬件解码即可,速度较快,而遇到复杂的X86指令则需要进行微解码,并把它分为若干条简单指令,速度较慢且很复杂 + - 寻址范围小——约束了用户需要 + - 计算机各部分的利用率不高,执行速度慢 + + + +### ARM + +ARM是高级精简指令集的简称(Advanced RISC Machine),它是一个32位的精简指令集架构,但也配备16位指令集,一般来讲比等价32位代码节省达35%,却能保留32位系统的所有优势。 + +- 优势: + - 体积小、低功耗、低成本、高性能——ARM被广泛应用在嵌入式系统中的最重要的原因 + - 支持Thumb(16位)/ARM(32位)双指令集,能很好的兼容8位/16位器件 + - 大量使用寄存器,指令执行速度更快 + - 大多数数据操作都在寄存器中完成 + - 寻址方式灵活简单,执行效率高 + - 流水线处理方式 + - 指令长度固定,大多数是简单指定且都能在一个时钟周期内完成,易于设计超标量与流水线 +- 劣势 + - 性能差距稍大。ARM要在性能上接近X86,频率必须比X86处理器高很多,但是频率一高能耗就疯涨,抵消了ARM的优点 + + + +### MIPS + +MIPS架构(英语:MIPS architecture,为Microprocessor without interlocked piped stages architecture的缩写,亦为Millions of Instructions Per Second的相关语),是一种采取精简指令集(RISC)的处理器架构,1981年出现,由MIPS科技公司开发并授权,广泛被使用在许多电子产品、网络设备、个人娱乐装置与商业装置上。最早的MIPS架构是32位,最新的版本已经变成64位。 它的基本特点是: + +- 包含大量的寄存器、指令数和字符 +- 可视的管道延时时隙 + +这些特性使MIPS架构能够提供最高的每平方毫米性能和当今SoC设计中最低的能耗。 + +- 优势 + - MIPS支持64bit指令和操作,ARM目前只到32bit + - MIPS有专门的除法器,可以执行除法指令 + - MIPS的内核寄存器比ARM多一倍,所以同样的性能下MIPS的功耗会比ARM更低,同样功耗下性能比ARM更高 + - MIPS指令比ARM稍微多一点,稍微灵活一点 + - MIPS开放 +- 劣势 + - MIPS的内存地址起始有问题,这导致了MIPS在内存和Cache的支持方面都有限制,现在的MIPS处理器单内核面对高容量内存时有问题 + - MIPS今后的发展方向时并行线程,类似INTEL的超线程,而ARM未来的发展方向时物理多核,目前看来物理多核占优 + - MIPS虽然结构更加简单,但是到现在还是顺序单发射,ARM已经进化到了乱序双发射 + + + +### PowerPC + + + +## 内存管理 + +### 虚拟内存 + +单片机是没有操作系统的,所以每次写完代码,都需要借助工具把程序烧录进去,这样程序才能跑起来。另外,单片机的 CPU 是直接操作内存的「物理地址」。 + +![单片机内存](images/OS/单片机内存.png) + +在这种情况下,要想在内存中同时运行两个程序是不可能的。如果第一个程序在 2000 的位置写入一个新的值,将会擦掉第二个程序存放在相同位置上的所有内容,所以同时运行两个程序是根本行不通的,这两个程序会立刻崩溃。 + + + +**操作系统的解决方案** + +![进程的中间层](images/OS/进程的中间层.png) + +**操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来,然后为每个进程分配独立的一套虚拟地址。**如果程序要访问虚拟地址的时候,由操作系统转换成不同的物理地址,不同的进程运行的时候,写入的是不同的物理地址,这样就不会冲突了。于是,这里就引出了两种地址的概念: + +- 我们程序所使用的内存地址叫做:**虚拟内存地址**(Virtual Memory Address) +- 实际存在硬件里面的空间地址叫:**物理内存地址**(Physical Memory Address) + +操作系统引入了虚拟内存,进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存,如下图所示: + +![虚拟地址寻址](images/OS/虚拟地址寻址.png) + +操作系统管理虚拟地址与物理地址之间关系的方式: + +- **内存分段**(比较早提出) +- **内存分页** + + + +### 内存分段 + +程序是由若干个逻辑分段组成的,如可由代码分段、数据分段、栈段、堆段组成。**不同的段是有不同的属性的,所以就用分段(Segmentation)的形式把这些段分离出来。** + + + +**① 分段机制中的虚拟地址和物理地址映射** + +分段机制下的虚拟地址由两部分组成,**段选择因子**和**段内偏移量**。 + +![内存分段-寻址的方式](images/OS/内存分段-寻址的方式.png) + +- **段选择因子**就保存在段寄存器里面。段选择因子里面最重要的是**段号**,用作段表的索引。**段表**里面保存的是这个**段的基地址、段的界限和特权等级**等 +- 虚拟地址中的**段内偏移量**应该位于 0 和段界限之间,如果段内偏移量是合法的,就将段基地址加上段内偏移量得到物理内存地址 + + + +**② 程序虚拟地址映射** + +在上面,知道了虚拟地址是通过**段表**与物理地址进行映射的,分段机制会把程序的虚拟地址分成 4 个段,每个段在段表中有一个项,在这一项找到段的基地址,再加上偏移量,于是就能找到物理内存中的地址,如下图: + +![内存分段-虚拟地址与物理地址](images/OS/内存分段-虚拟地址与物理地址.png) + +如果要访问段 3 中偏移量 500 的虚拟地址,我们可以计算出物理地址为,段 3 基地址 7000 + 偏移量 500 = 7500。分段的办法很好,解决了程序本身不需要关心具体的物理内存地址的问题,但它也有一些不足之处: + +- **产生内存碎片** +- **内存交换效率低** + + + +**③ 内存碎片** + +内存碎片主要有两类问题: + +- 外部内存碎片:产生了多个不连续的小物理内存,导致新的程序无法被装载。 +- 内部内存碎片:程序所有内存都被装载到物理内存,但程序有部分的内存可能并不是很常使用,这也会导致内存浪费 + +**解决方案** + +- 解决外部内存碎片的问题就是:**内存交换**。先把占用内存的程序写到硬盘中,然后再从硬盘读到内存中(装在位置已变) + + + +**④ 内存交换效率低** + +对于多进程的系统来说,用分段的方式,内存碎片是很容易产生的,产生了内存碎片,那不得不重新 `Swap` 内存区域,这个过程会产生性能瓶颈。因为硬盘的访问速度要比内存慢太多了,每一次内存交换,我们都需要把一大段连续的内存数据写到硬盘上。所以,**如果内存交换的时候,交换的是一个占内存空间很大的程序,这样整个机器都会显得卡顿。** + + + +为了解决**内存分段**的**内存碎片**和**内存交换效率低**的问题,就出现了**内存分页**。 + + + +### 内存分页 + +分段的好处就是能产生连续的内存空间,但是会出现内存碎片和内存交换的空间太大的问题。要解决这些问题,那么就要想出能少出现一些内存碎片的办法。另外,当需要进行内存交换的时候,让需要交换写入或者从磁盘装载的数据更少一点,这样就可以解决问题了。这个办法,也就是**内存分页**(Paging)。 + + + +**① 内存分页(Paging)** + +**分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小**。这样一个连续并且尺寸固定的内存空间,我们叫**页**(`Page`)。在 Linux 下,每一页的大小为 `4KB`。虚拟地址与物理地址之间通过**页表**来映射,如下图: + +![内存映射](images/OS/内存映射.png) + +页表实际上存储在 CPU 的**内存管理单元** (MMU) 中,于是 CPU 就可以直接通过 MMU,找出要实际要访问的物理内存地址。而当进程访问的虚拟地址在页表中查不到时,系统会产生一个**缺页异常**,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。 + + + +**② 分页解决分段问题** + +由于内存空间都是预先划分好的,也就不会像分段会产生间隙非常小的内存,这正是分段会产生内存碎片的原因。而**采用了分页,那么释放的内存都是以页为单位释放的,也就不会产生无法给进程使用的小内存。** + +如果内存空间不够,操作系统会把其他正在运行的进程中的「最近没被使用」的内存页面给释放掉,也就是暂时写在硬盘上,称为**换出**(Swap Out)。一旦需要的时候,再加载进来,称为**换入**(Swap In)。所以,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,**内存交换的效率就相对比较高。** + +![换入换出](images/OS/换入换出.png) + +更进一步地,分页的方式使得我们在加载程序的时候,不再需要一次性都把程序加载到物理内存中。我们完全可以在进行虚拟内存和物理内存的页之间的映射之后,并不真的把页加载到物理内存里,而是**只有在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存里面去。** + + + +**③ 分页机制中虚拟地址和物理地址的映射** + +在分页机制下,虚拟地址分为两部分,**页号**和**页内偏移**。页号作为页表的索引,**页表**包含物理页每页所在**物理内存的基地址**,这个基地址与页内偏移的组合就形成了物理内存地址,见下图: + +![内存分页寻址](images/OS/内存分页寻址.png) + +总结一下,对于一个内存地址转换,其实就是这样三个步骤: + +- 把虚拟内存地址,切分成页号和偏移量 +- 根据页号,从页表里面,查询对应的物理页号 +- 直接拿物理页号,加上前面的偏移量,就得到了物理内存地址 + + + +**④ 简单分页缺陷** + +有空间上的缺陷。因为操作系统是可以同时运行非常多的进程的,那这不就意味着页表会非常的庞大。 + +在 32 位的环境下,每个进程的虚拟地址空间共有 4GB,假设一个页的大小是 4KB(2^12),那么就需要大约 100 万 (2^20) 个页,每个「页表项」需要 4 个字节大小来存储,那么整个 4GB 空间的映射就需要有 `4MB` 的内存来存储页表。 + +这 4MB 大小的页表,看起来也不是很大。但是要知道每个进程都是有自己的虚拟地址空间的,也就说都有自己的页表。 + +那么,`100` 个进程的话,就需要 `400MB` 的内存来存储页表,这是非常大的内存了,更别说 64 位的环境了。 + + + +**⑤ 多级页表** + +要解决上面的问题,就需要采用的是一种叫作**多级页表**(Multi-Level Page Table)的解决方案。 + +把这个 100 多万个「页表项」的单级页表再分页,将页表(一级页表)分为 `1024` 个页表(二级页表),每个表(二级页表)中包含 `1024` 个「页表项」,形成**二级分页**。如下图所示: + +![二级分页](images/OS/二级分页.png) + +如果使用了二级分页,一级页表就可以覆盖整个 4GB 虚拟地址空间,但**如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表**。做个简单的计算,假设只有 20% 的一级页表项被用到了,那么页表占用的内存空间就只有 4KB(一级页表) + 20% * 4MB(二级页表)= `0.804MB`,这对比单级页表的 `4MB` 是不是一个巨大的节约? + +那么为什么不分级的页表就做不到这样节约内存呢?我们从页表的性质来看,保存在内存中的页表承担的职责是将虚拟地址翻译成物理地址。假如虚拟地址在页表中找不到对应的页表项,计算机系统就不能工作了。所以**页表一定要覆盖全部虚拟地址空间,不分级的页表就需要有 100 多万个页表项来映射,而二级分页则只需要 1024 个页表项**(此时一级页表覆盖到了全部虚拟地址空间,二级页表在需要时创建)。我们把二级分页再推广到多级页表,就会发现页表占用的内存空间更少了,这一切都要归功于对局部性原理的充分应用。 + +对于 64 位的系统,两级分页肯定不够了,就变成了四级目录,分别是: + +- 全局页目录项 PGD(Page Global Directory) +- 上层页目录项 PUD(Page Upper Directory) +- 中间页目录项 PMD(Page Middle Directory) +- 页表项 PTE(Page Table Entry) + +![64位页表四级目录](images/OS/64位页表四级目录.png) + + + +**⑥ TLB** + +多级页表虽然解决了空间上的问题,但是虚拟地址到物理地址的转换就多了几道转换的工序,这显然就降低了这俩地址转换的速度,也就是带来了时间上的开销。程序是有局部性的,即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域。 + +我们就可以利用这一特性,把最常访问的几个页表项存储到访问速度更快的硬件,于是计算机科学家们,就在 CPU 芯片中,加入了一个专门存放程序最常访问的页表项的 Cache,这个 Cache 就是 TLB(Translation Lookaside Buffer) ,通常称为页表缓存、转址旁路缓存、快表等。 + +![页表项地址转换](images/OS/页表项地址转换.png) + +在 CPU 芯片里面,封装了内存管理单元(Memory Management Uni)芯片,它用来完成地址转换和 TLB 的访问与交互。有了 TLB 后,那么 CPU 在寻址时,会先查 TLB,如果没找到,才会继续查常规的页表。TLB 的命中率其实是很高的,因为程序最常访问的页就那么几个。 + + + +### 段页式内存管理 + +内存分段和内存分页并不是对立的,是可以组合起来在同一个系统中使用的,那么组合起来后,通常称为**段页式内存管理**。 + +![段页式地址空间](images/OS/段页式地址空间.png) + +段页式内存管理实现的方式: + +- 先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制 +- 接着再把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页 + +这样地址结构就由**段号、段内页号和页内位移**三部分组成。用于段页式地址变换的数据结构是每一个程序一张段表,每个段又建立一张页表,段表中的地址是页表的起始地址,而页表中的地址则为某页的物理页号,如图所示: + +![段页式管理中的段表、页表与内存的关系](images/OS/段页式管理中的段表、页表与内存的关系.png) + +段页式地址变换中要得到物理地址须经过三次内存访问: + +- 第一次访问段表,得到页表起始地址 +- 第二次访问页表,得到物理页号 +- 第三次将物理页号与页内位移组合,得到物理地址 + +可用软、硬件相结合的方法实现段页式地址变换,这样虽然增加了硬件成本和系统开销,但提高了内存的利用率。 + + + +### Linux 内存管理 + +**① Intel处理器的发展历史** + +早期 Intel 的处理器从 80286 开始使用的是段式内存管理。但是很快发现,光有段式内存管理而没有页式内存管理是不够的,这会使它的 X86 系列会失去市场的竞争力。因此,在不久以后的 80386 中就实现了对页式内存管理。也就是说,80386 除了完成并完善从 80286 开始的段式内存管理的同时,还实现了页式内存管理。 + +但是这个 80386 的页式内存管理设计时,没有绕开段式内存管理,而是建立在段式内存管理的基础上,这就意味着,**页式内存管理的作用是在由段式内存管理所映射而成的地址上再加上一层地址映射。** + +由于此时由段式内存管理映射而成的地址不再是“物理地址”了,Intel 就称之为“线性地址”(也称虚拟地址)。于是,段式内存管理先将逻辑地址映射成线性地址,然后再由页式内存管理将线性地址映射成物理地址。 + +![IntelX86逻辑地址解析过程](images/OS/IntelX86逻辑地址解析过程.png) + +- **逻辑地址**:程序所使用的地址,通常是没被段式内存管理映射的地址。即段式内存管理转换前的地址 +- **线性地址**:也叫**虚拟地址**,通过段式内存管理映射的地址。页式内存管理转换前的地址 + + + +**② Linux管理内存** + +**Linux 系统主要采用了分页管理,但是由于 Intel 处理器的发展史,Linux 系统无法避免分段管理**。于是 Linux 就把所有段的基地址设为 `0`,每个段都是从 0 地址开始的整个 4GB 虚拟空间(32 位环境下),也就意味着所有程序的地址空间都是线性地址空间(虚拟地址),相当于屏蔽了 CPU 逻辑地址的概念,所以段只被用于访问控制和内存保护。 + + + +**③ Linux的虚拟地址空间分布** + +在 Linux 操作系统中,虚拟地址空间的内部又被分为**内核空间和用户空间**两部分,不同位数的系统,地址空间的范围也不同。比如最常见的 32 位和 64 位系统,如下所示: + +![Linux虚拟地址空间分布](images/OS/Linux虚拟地址空间分布.png) + +- `32` 位系统的内核空间占用 `1G`,位于最高处,剩下的 `3G` 是用户空间 +- `64` 位系统的内核空间和用户空间都是 `128T`,分别占据整个内存空间的最高和最低处,剩下的中间部分是未定义的 + + + +**④ 内核空间与用户空间** + +- 进程在用户态时,只能访问用户空间内存 +- 只有进入内核态后,才可以访问内核空间的内存; + +虽然每个进程都各自有独立的虚拟内存,但是**每个虚拟内存中的内核地址,其实关联的都是相同的物理内存**。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。 + +![每个进程的内核空间都是一致的](images/OS/每个进程的内核空间都是一致的.png) + + + +**⑤ 32位操作系统中的用户空间分布** + +用户空间内存,从**低到高**分别是 7 种不同的内存段: + +- 程序文件段,包括二进制可执行代码 +- 已初始化数据段,包括静态常量 +- 未初始化数据段,包括未初始化的静态变量 +- 堆段,包括动态分配的内存,从低地址开始向上增长 +- 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关) +- 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 `8 MB`。当然系统也提供了参数,以便我们自定义大小 + +![32位操作系统中的用户空间分布](images/OS/32位操作系统中的用户空间分布.png) + +在这 7 个内存段中,堆和文件映射段的内存是动态分配的。比如说,使用 C 标准库的 `malloc()` 或者 `mmap()` ,就可以分别在堆和文件映射段动态分配内存。 + + + +## 进程和线程 + +### 进程 + +#### 单进程创建的线程数 + +**一个进程最多可以创建多少个线程?**这个问题跟两个东西有关系: + +- **进程的虚拟内存空间上限**。因为创建一个线程,操作系统需要为其分配一个栈空间,如果线程数量越多,所需的栈空间就要越大,那么虚拟内存就会占用的越多 +- **系统参数限制**。虽然 Linux 并没有内核参数来控制单个进程创建的最大线程个数,但是有系统级别的参数来控制整个系统的最大线程个数 + +**结论** + +- 32 位系统:用户态的虚拟空间只有 3G,如果创建线程时分配的栈空间是 10M,那么一个进程最多只能创建 300 个左右的线程 +- 64 位系统:用户态的虚拟空间大到有 128T,理论上不会受虚拟内存大小的限制,而会受系统的参数或性能限制 + + + +**① 在进程里创建一个线程需要消耗多少虚拟内存大小?** + +我们可以执行 ulimit -a 这条命令,查看进程创建线程时默认分配的栈空间大小,比如我这台服务器默认分配给线程的栈空间大小为 8M。 + +![创建线程时默认分配的栈空间大小](images/OS/创建线程时默认分配的栈空间大小.jpg) + + + +**② 32位Linux系统** + +一个进程的虚拟空间是 4G,内核分走了1G,**留给用户用的只有 3G**。那么假设创建一个线程需要占用 10M 虚拟内存,总共有 3G 虚拟内存可以使用。于是可以算出,最多可以创建差不多 300 个(3G/10M)左右的线程。 + +![32位的系统创建线程](images/OS/32位的系统创建线程.png) + +如果想使得进程创建上千个线程,那么我们可以调整创建线程时分配的栈空间大小,比如调整为 512k: + +```shell +$ ulimit -s 512 +``` + + + +**③ 64位Linux系统** + +测试服务器的配置:64位系统、2G 物理内存、单核 CPU。 + +64 位系统意味着用户空间的虚拟内存最大值是 128T,这个数值是很大的,如果按创建一个线程需占用 10M 栈空间的情况来算,那么理论上可以创建 128T/10M 个线程,也就是 1000多万个线程,有点魔幻。所以按 64 位系统的虚拟内存大小,理论上可以创建无数个线程。事实上,肯定创建不了那么多线程,除了虚拟内存的限制,还有系统的限制。 + +比如下面这三个内核参数的大小,都会影响创建线程的上限: + +- `/proc/sys/kernel/threads-max`:表示系统支持的最大线程数,默认值是 `14553` +- `/proc/sys/kernel/pid_max`:表示系统全局的 PID 号数值的限制,每一个进程或线程都有 ID,ID 的值超过这个数,进程或线程就会创建失败,默认值是 `32768` +- `/proc/sys/vm/max_map_count`:表示限制一个进程可以拥有的VMA(虚拟内存区域)的数量,具体什么意思我也没搞清楚,反正如果它的值很小,也会导致创建线程失败,默认值是 `65530` + +在这台服务器跑了前面的程序,其结果如下: + +![64位的系统创建线程](images/OS/64位的系统创建线程.png) + +可以看到,创建了 14374 个线程后,就无法在创建了,而且报错是因为资源的限制。前面我提到的 `threads-max` 内核参数,它是限制系统里最大线程数,默认值是 14553。我们可以运行那个测试线程数的程序后,看下当前系统的线程数是多少,可以通过 `top -H` 查看。 + +![top-H线程数](images/OS/top-H线程数.png) + +左上角的 Threads 的数量显示是 14553,与 `threads-max` 内核参数的值相同,所以我们可以认为是因为这个参数导致无法继续创建线程。那么,我们可以把 threads-max 参数设置成 `99999`: + +```shell +echo 99999 > /proc/sys/kernel/threads-max +``` + +设置完 threads-max 参数后,我们重新跑测试线程数的程序,运行后结果如下图: + +![64位的系统创建线程-不限制thread-max](images/OS/64位的系统创建线程-不限制thread-max.png) + +可以看到,当进程创建了 32326 个线程后,就无法继续创建里,且报错是无法继续申请内存。此时的上限个数很接近 `pid_max` 内核参数的默认值(32768),那么我们可以尝试将这个参数设置为 99999: + +```shell +echo 99999 > /proc/sys/kernel/pid_max +``` + +设置完 pid_max 参数后,继续跑测试线程数的程序,运行后结果创建线程的个数还是一样卡在了 32768 了。经过查阅资料发现,`max_map_count` 这个内核参数也是需要调大的,但是它的数值与最大线程数之间有什么关系,我也不太明白,只是知道它的值是会限制创建线程个数的上限。然后,我把 max_map_count 内核参数也设置成后 99999: + +```shell +echo 99999 > /proc/sys/kernel/pid_max +``` + +继续跑测试线程数的程序,结果如下图: + +![64位的系统创建线程-不限制](images/OS/64位的系统创建线程-不限制.png) + +当创建差不多 5 万个线程后,我的服务器就卡住不动了,CPU 都已经被占满了,毕竟这个是单核 CPU,所以现在是 CPU 的瓶颈了。 + +接下来,我们换个思路测试下,把创建线程时分配的栈空间调大,比如调大为 100M,在大就会创建线程失败。 + +```shell +ulimit -s 1024000 +``` + +设置完后,跑测试线程的程序,其结果如下: + +![64位的系统创建线程-大栈空间](images/OS/64位的系统创建线程-大栈空间.png) + +总共创建了 26390 个线程,然后就无法继续创建了,而且该进程的虚拟内存空间已经高达 25T,要知道这台服务器的物理内存才 2G。为什么物理内存只有 2G,进程的虚拟内存却可以使用 25T 呢?因为虚拟内存并不是全部都映射到物理内存的,程序是有**局部性的特性**,也就是某一个时间只会执行部分代码,所以只需要映射这部分程序就好。 + +你可以从上面那个 top 的截图看到,虽然进程虚拟空间很大,但是物理内存(RES)只有使用了 400M+。 + + + +### 线程 diff --git a/Others.md b/Others.md new file mode 100644 index 0000000..d688a20 --- /dev/null +++ b/Others.md @@ -0,0 +1,1578 @@ +
Others
+ +Author:李茹钰(`echo`) + +Introduction:收纳其它相关的知识总结! + +[TOC] + +# 常用软件 + +## 画图工具 + +### draw.io + +本地或在线画图地址:https://draw.io + +![draw.io](images/Others/draw.io.jpeg) + +### Excalidraw + +在线画图地址:https://excalidraw.com + +![Excalidraw](images/Others/Excalidraw.png) + +### ProcessOn + +在线画图地址:https://www.processon.com + +![ProcessOn](images/Others/ProcessOn.png) + +### Carbon + +代码截图网址地址:https://carbon.now.sh + +![Carbon](images/Others/Carbon.png) + + + +## 数据抓包 + +### Fiddler + +Fiddler是一个蛮好用的抓包工具,可以将网络传输发送与接受的数据包进行截获、重发、编辑、转存等操作。也可以用来检测网络安全。反正好处多多,举之不尽呀!当年学习的时候也蛮费劲,一些蛮实用隐藏的小功能用了之后就忘记了,每次去网站上找也很麻烦,所以搜集各大网络的资料,总结了一些常用的功能。 + +Fiddler 下载地址 :https://www.telerik.com/download/fiddler + +win8之后用“Fiddler for .NET4”而win8之前用“Fiidler for .NET2”比较好。 + +#### Fiddler抓包简介 + +##### 代理设置 + +Fiddler是通过改写HTTP代理,让数据从它那通过,来监控并且截取到数据。当然Fiddler很屌,在打开它的那一瞬间,它就已经设置好了浏览器的代理了。当你关闭的时候,它又帮你把代理还原了,是不是很贴心。 + +![Fiddler抓包简介](images/Others/Fiddler抓包简介.png) + + + +##### Capture Traffic + +Fiddler想要抓到数据包,要确保Capture Traffic是开启,在File –> Capture Traffic。开启后再左下角会有显示,当然也可以直接点击左下角的图标来关闭/开启抓包功能。 + +![Fiddler-CaptureTraffic](images/Others/Fiddler-CaptureTraffic.png) + +Fiddler开始工作了,抓到的数据包就会显示在列表里面,下面总结了这些都是什么意思: + +![Fiddler-CaptureTraffic-Debugger](images/Others/Fiddler-CaptureTraffic-Debugger.png) + + + +##### Statistics请求的性能数据分析 + +随意点击一个请求,就可以看到Statistics关于HTTP请求的性能以及数据分析了。 + +![Fiddler-Statistics](images/Others/Fiddler-Statistics.png) + + + +##### Inspectors查看数据内容 + +Inspectors是用于查看会话的内容,上半部分是请求的内容,下半部分是响应的内容: + +![Fiddler-Inspectors](images/Others/Fiddler-Inspectors.png) + + + +##### AutoResponder允许拦截指定规则的请求 + +AutoResponder允许你拦截指定规则的求情,并返回本地资源或Fiddler资源,从而代替服务器响应。看下图5步,我将“baidu”这个关键字与我电脑“f:\Users\YukiO\Pictures\boy.jpeg”这张图片绑定了,点击Save保存后勾选Enable rules,再访问baidu,就会被劫持。这个玩意有很多匹配规则,如: + +1. 字符串匹配(默认):只要包含指定字符串(不区分大小写),全部认为是匹配 + +| 字符串匹配(baidu) | 是否匹配 | +| :--------------------- | :------- | +| http://www.baidu.com | 匹配 | +| http://pan.baidu.com | 匹配 | +| http://tieba.baidu.com | 匹配 | + +2. 正则表达式匹配:以“regex:”开头,使用正则表达式来匹配,这个是区分大小写的 + +| 字符串匹配(regex:.+.(jpg \| gif \| bmp ) $) | 是否匹配 | +| :-------------------------------------------- | :------- | +| http://bbs.fishc.com/Path1/query=foo.bmp&bar | 不匹配 | +| http://bbs.fishc.com/Path1/query=example.gif | 匹配 | +| http://bbs.fishc.com/Path1/query=example.bmp | 匹配 | +| http://bbs.fishc.com/Path1/query=example.Gif | 不匹配 | + + + +![Fiddler-AutoResponder-1](images/Others/Fiddler-AutoResponder-1.png) + +![Fiddler-AutoResponder-2](images/Others/Fiddler-AutoResponder-2.png) + + + +##### Composer自定义请求发送服务器 + +Composer允许自定义请求发送到服务器,可以手动创建一个新的请求,也可以在会话表中,拖拽一个现有的请求。Parsed模式下你只需要提供简单的URLS地址即可(如下图,也可以在RequestBody定制一些属性,如模拟浏览器User-Agent)。 + +![Fiddler-Composer](images/Others/Fiddler-Composer.png) + + + +##### Filters请求过滤规则 + +Fiters是过滤请求用的,左边的窗口不断的更新,当你想看你系统的请求的时候,你刷新一下浏览器,一大片不知道哪来请求,看着碍眼,它还一直刷新你的屏幕。这个时候通过过滤规则来过滤掉那些不想看到的请求。 + +![Fiddler-Filters](images/Others/Fiddler-Filters.png) + +勾选左上角的Use Filters开启过滤器,这里有两个最常用的过滤条件:Zone和Host + +- Zone:指定只显示内网(Intranet)或互联网(Internet)的内容 + + ![Fiddler-Filters-Zone](images/Others/Fiddler-Filters-Zone.png) + +- Host:指定显示某个域名下的会话 + + ![Fiddler-Filters-Host](images/Others/Fiddler-Filters-Host.png) + + 如果框框为黄色(如图),表示修改未生效,点击红圈里的文字即可。 + + + +#### Fiddler设置解密HTTPS的网络数据 + +Fiddler可以通过伪造CA证书来欺骗浏览器和服务器。Fiddler是个很会装逼的好东西,大概原理就是在浏览器面前Fiddler伪装成一个HTTPS服务器,而在真正的HTTPS服务器面前Fiddler又装成浏览器,从而实现解密HTTPS数据包的目的。 + +解密HTTPS需要手动开启,依次点击: + +1. Tools –> Fiddler Options –> HTTPS + +![Fiddler-HTTPS-1](images/Others/Fiddler-HTTPS-1.png) + +2. 勾选Decrypt HTTPS Traffic + +![Fiddler-HTTPS-2](images/Others/Fiddler-HTTPS-2.png) + +3. 点击OK + +![Fiddler-HTTPS-3](images/Others/Fiddler-HTTPS-3.png) + + + +#### Fiddler抓取IPhone/Android数据包 + +想要Fiddler抓取移动端设备的数据包,其实很简单,先来说说移动设备怎么去访问网络,看了下面这张图,就明白了。 + +![Fiddler抓取移动端-1](images/Others/Fiddler抓取移动端-1.png) + +可以看得出,移动端的数据包,都是要走wifi出去,所以我们可以把自己的电脑开启热点,将手机连上电脑,Fiddler开启代理后,让这些数据通过Fiddler,Fiddler就可以抓到这些包,然后发给路由器(如图): + +![Fiddler抓取移动端-2](images/Others/Fiddler抓取移动端-2.png) + +1. 打开Wifi热点,让手机连上(我这里用的360wifi,其实随意一个都行) + +![Fiddler抓取移动端-3](images/Others/Fiddler抓取移动端-3.png) + +2. 打开Fidder,点击菜单栏中的 [Tools] –> [Fiddler Options] + +![Fiddler抓取移动端-4](images/Others/Fiddler抓取移动端-4.png) + +3. 点击 [Connections] ,设置代理端口是8888, 勾选 Allow remote computers to connect, 点击OK + +![Fiddler抓取移动端-5](images/Others/Fiddler抓取移动端-5.png) + +4. 这时在 Fiddler 可以看到自己本机无线网卡的IP了(要是没有的话,重启Fiddler,或者可以在cmd中ipconfig找到自己的网卡IP) + +![Fiddler抓取移动端-6](images/Others/Fiddler抓取移动端-6.png) + +![Fiddler抓取移动端-7](images/Others/Fiddler抓取移动端-7.png) + +5. 在手机端连接PC的wifi,并且设置代理IP与端口(代理IP就是上图的IP,端口是Fiddler的代理端口8888) + +![Fiddler抓取移动端-8](images/Others/Fiddler抓取移动端-8.png) + +6. 访问网页输入代理IP和端口,下载Fiddler的证书,点击下图FiddlerRoot certificate + +![Fiddler抓取移动端-9](images/Others/Fiddler抓取移动端-9.png) + +【注意】:如果打开浏览器碰到类似下面的报错,请打开Fiddler的证书解密模式(Fiddler 设置解密HTTPS的网络数据) + +``` +No root certificate was found. Have you enabled HTTPS traffic decryption in Fiddler yet? +``` + +![Fiddler抓取移动端-10](images/Others/Fiddler抓取移动端-10.png)![Fiddler抓取移动端-11](images/Others/Fiddler抓取移动端-11.png) + +![Fiddler抓取移动端-12](images/Others/Fiddler抓取移动端-12.png)![Fiddler抓取移动端-13](images/Others/Fiddler抓取移动端-13.png) + +7. 安装完了证书,可以用手机访问应用,就可以看到截取到的数据包了。(下图选中是布卡漫画的数据包,下面还有QQ邮箱的) + +![Fiddler抓取移动端-14](images/Others/Fiddler抓取移动端-14.png) + + + +#### Fiddler内置命令与断点 + +Fiddler还有一个藏的很深的命令框,就是眼前,我用了几年的Fiddler都没有发现它,偶尔在别人的文章发现还有这个小功能,还蛮好用的,整理下记录在这里。Fiddler断点功能就是将请求截获下来,但是不发送,这个时候你可以干很多事情,比如说,把包改了,再发送给服务器君。还有balabala一大堆的事情可以做,就不举例子了。 + +![Fiddler内置命令与断点](images/Others/Fiddler内置命令与断点.png) + +| **命令** | **对应请求项** | **介绍** | **示例** | +| :------- | :------------- | :----------------------------------------------------------- | :------------- | +| ? | All | 问号后边跟一个字符串,可以匹配出包含这个字符串的请求 | ?google | +| > | Body | 大于号后面跟一个数字,可以匹配出请求大小,大于这个数字请求 | >1000 | +| < | Body | 小于号跟大于号相反,匹配出请求大小,小于这个数字的请求 | <100 | +| = | Result | 等于号后面跟数字,可以匹配HTTP返回码 | =200 | +| @ | Host | @后面跟Host,可以匹配域名 | @www.baidu.com | +| select | Content-Type | select后面跟响应类型,可以匹配到相关的类型 | select image | +| cls | All | 清空当前所有请求 | cls | +| dump | All | 将所有请求打包成saz压缩包,保存到“我的文档\Fiddler2\Captures”目录下 | dump | +| start | All | 开始监听请求 | start | +| stop | All | 停止监听请求 | stop | + + + +| **断点命令** | | | | +| :----------: | -------- | ------------------------------------------------------- | ------------------------------------ | +| bpafter | All | bpafter后边跟一个字符串,表示中断所有包含该字符串的请求 | bpafter baidu(输入bpafter解除断点) | +| bpu | All | 跟bpafter差不多,只不过这个是收到请求了,中断响应 | bpu baidu(输入bpu解除断点) | +| bps | Result | 后面跟状态吗,表示中断所有是这个状态码的请求 | bps 200(输入bps解除断点) | +| bpv / bpm | HTTP方法 | 只中断HTTP方法的命令,HTTP方法如POST、GET | bpv get(输入bpv解除断点) | +| g / go | All | 放行所有中断下来的请求 | g | + + + +**断点命令**:断点可以直接点击Fiddler下图的图标位置,就可以设置全部请求的断点,断点的命令可以精确设置需要截获那些请求。如下示例: + +![Fiddler-断点命令](images/Others/Fiddler-断点命令.png) + + + +### Wireshark + +### 科来网络分析系统 + + + +## SSH + +### XShell/XFtp + +https://www.netsarang.com/zh/free-for-home-school + +![XShell](images/Others/XShell.png) + +### SecureCRT + +https://www.vandyke.com/cgi-bin/releases.php?product=securecrt + +![SecureCRT](images/Others/SecureCRT.jpg) + +### Terminal.icu + +https://www.terminal.icu/ + +![Terminal.icu](images/Others/Terminal.icu.png) + + + +## Chrome + +### 谷歌访问助手 + +http://www.ggfwzs.com + +### Postman + +https://www.postman.com/downloads + + + +## 代码对比 + +### WinMerge + +![WinMerge](images/Others/WinMerge.png) + +### Diffuse + +![Diffuse](images/Others/Diffuse.png) + +### Beyond Compare + +![BeyondCompare](images/Others/BeyondCompare.png) + +### Altova DiffDog + +![AltovaDiffDog](images/Others/AltovaDiffDog.png) + +### AptDiff + +![AptDiff](images/Others/AptDiff.png) + +### Code Compare + +![CodeCompare](images/Others/CodeCompare.png) + +### jq22 + +一款在线的文本比较工具,不想安装软件的直接用这个就好了!地址:http://www.jq22.com/textDifference + +![jq22](images/Others/jq22.png) + + + +## 其它软件 + +### 图壳 + +免费好用稳定的图床网站。https://imgkr.com/ + +![图壳](images/Others/图壳.gif) + +### 小码短连接 + +简单易用的渠道短链接统计工具。https://xiaomark.com/ + +![小码短连接](images/Others/小码短连接.png) + +非常好用的长链接转短链接工具,能够让链接看起来更简洁。并且,转换为短链接之后,还能在后台监测访问数据,如访问次数、访问人数。 + +### removebg + +抠图神器。https://www.remove.bg/zh + +![removebg](images/Others/removebg.png) + +### 今日热榜 + +你关心的热点。今日热榜提供各站热榜聚合:微信、今日头条、百度、知乎、V2EX、微博、贴吧、豆瓣、天涯、虎扑、Github、抖音...。追踪全网热点、简单高效阅读。https://tophub.today + +![今日热榜](images/Others/今日热榜.png) + + + +# 开源工具 + +[**Swagger**](https://swagger.io/): API Documentation & Design Tools for Teams + +[**jvm-sandbox**](https://github.com/alibaba/jvm-sandbox):Real - time non-invasive AOP framework container based on JVM + +**[JimuReport](https://github.com/zhangdaiscott/JimuReport)**:这是一款免费的数据可视化工具,报表与大屏设计!类似于excel操作风格,在线拖拽完成报表设计!功能涵盖: 报表设计、图形报表、打印设计、大屏设计等,永久免费! + +**[sa-token](https://github.com/dromara/sa-token)**:这可能是史上功能最全的Java权限认证框架!目前已集成——登录认证、权限认证、分布式Session会话、微服务网关鉴权、单点登录、OAuth2.0、踢人下线、Redis集成、前后台分离、记住我模式、模拟他人账号、临时身份切换、账号封禁、多账号认证体系、注解式鉴权、路由拦截式鉴权、花式token生成、自动续签、同端互斥登录、会话治理、密码加密、jwt集成、Spring集成、WebFlux集成... + +**[soul](https://github.com/dromara/soul)**:应用于所有微服务场景的,可扩展、高性能、响应式的 API 网关解决方案。 + +**[arthas](https://github.com/alibaba/arthas)**:Arthas旨在帮助开发人员解决Java应用程序的生产问题,无需修改代码或重新启动服务器。有了Arthas,你就可以在不重新启动JVM或需要额外的代码更改的情况下实时地对问题进行故障排除。 + +**[miaosha](https://github.com/qiurunze123/miaosha)**:该项目是对高并发大流量如何进行秒杀架构,而做的一个系统整理,如果你完全没接触过 MQ、SpringBoot、Redis、Dubbo、ZK 、Maven,lua等,那么我建议你可以先在网上搜一下每一块知识的快速入门。 + +[**Guava**](https://github.com/google/guava):Google core libraries for Java + +[**TransmittableThreadLocal**](https://github.com/alibaba/transmittable-thread-local):The missing Java™ std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits ThreadLocal values between threads even using thread pooling components. + +[**FastJSON**](https://github.com/alibaba/fastjson):A fast JSON parser/generator for Java. + +[**Druid**](https://github.com/alibaba/druid):阿里巴巴计算平台事业部出品,为监控而生的数据库连接池 + +[**JetCache**](https://github.com/alibaba/jetcache):JetCache is a Java cache framework. + +[**Metrics**](https://github.com/alibaba/metrics):The metrics library for Apache Dubbo and any frameworks or systems. + + + +# IDEA + +**JetBrains 破解许可服务器**:https://www.licensez.com + +## 常见设置 + +### 显示工具条 + +**效果图** +![显示工具条](images/Others/显示工具条.png) +**设置方法** + +- 标注1:View–>Toolbar +- 标注2:View–>Tool Buttons + + + +### 设置鼠标悬浮提示 + +**效果图** +![设置鼠标悬浮提示](images/Others/设置鼠标悬浮提示.png) + +**设置方法** +File–>settings–>Editor–>General–>勾选Show quick documentation… +![在这里插入图片描述](images/Others/20181030154227362.png) + + + +### 显示方法分隔符 + +**效果图** +![在这里插入图片描述](images/Others/20181030154728569.png) +**设置方法** + +File–>settings–>Editor–>Appearance–>勾选 + +![在这里插入图片描述](images/Others/2018103015481187.png) + + + +### 忽略大小写提示 + +**效果图** +备注:idea的默认设置是严格区分大小写提示的,例如输入string不会提示String,不方便编码 +![在这里插入图片描述](images/Others/20181030155133360.png) +**设置方法** +File–>settings–>Editor–>General -->Code Completion --> +![在这里插入图片描述](images/Others/20181030155413727.png) + + + +### 主题设置 + +**效果图** +备注:有黑白两种风格 +![在这里插入图片描述](images/Others/20181030155545483.png) +![在这里插入图片描述](images/Others/20181030155612301.png) +**设置方法** +File–>settings–>Appearance & Behavior–>Appearance–> +![在这里插入图片描述](images/Others/2018103015572874.png) + + + +### 护眼主题设置 + +**效果图** +![在这里插入图片描述](images/Others/20190110100508868.png) +**设置方法** + +如果想将编辑页面变换主题,可以去设置里面调节背景颜色 +![在这里插入图片描述](images/Others/20190110100622631.png) + +如果需要很好看的编码风格,这里有很多主题 +http://color-themes.com/?view=index&layout=Generic&order=popular&search=&page=1 +点击相应主题,往下滑点击按钮 +![在这里插入图片描述](images/Others/20190110100734530.png) +下载下来有很多Jar包 +![在这里插入图片描述](images/Others/2019011010115881.png) +![在这里插入图片描述](images/Others/2019011010123767.png) + +在上面的位置选择导入jar包,然后重启idea生效,重启之后去设置 + +![在这里插入图片描述](images/Others/20190110101907801.png) + + + +### 自动导入包 +**效果图** +备注:默认情况是需要手动导入包的,比如我们需要导入Map类,那么需要手动导入,如果不需要使用了,删除了Map的实例,导入的包也需要手动删除,设置了这个功能这个就不需要手动了,自动帮你实现自动导入包和去包,不方便截图,效果请亲测~ +**设置方法** +File–>settings–>Editor–>general–>Auto Import–> + +![在这里插入图片描述](images/Others/2018103015593523.png) + + + +### 单行显示多个Tabs + +**效果图** +默认是显示单排的Tabs: +![在这里插入图片描述](images/Others/20181030160351154.png) +单行显示多个Tabs: + +![在这里插入图片描述](images/Others/20181030160604417.png) +**设置方法** +File–>settings–>Editor–>General -->Editor Tabs–>去掉√ +![在这里插入图片描述](images/Others/20181030160533499.png) + + + +### 设置字体 + +**效果图** +备注:默认安装启动Idea字体很小,看着不习惯,需要调整字体大小与字体(有需要可以调整) +**设置方法** +File–>settings–>Editor–>Font–> +![在这里插入图片描述](images/Others/20181030161017921.png) + + + +### 配置类文档注释信息和方法注释模版 + +**效果图** +备注:团队开发时方便追究责任与管理查看 +![在这里插入图片描述](images/Others/20181030161142910.png) +![在这里插入图片描述](images/Others/20181030161254216.png) +**设置方法** +https://blog.csdn.net/zeal9s/article/details/83514565 + + + +### 水平或者垂直显示代码 + +**效果图** +备注:Eclipse如果需要对比代码,只需要拖动Tabs即可,但是idea要设置 +![在这里插入图片描述](images/Others/20181030162041400.png) +**设置方法** +鼠标右击Tabs +![在这里插入图片描述](images/Others/20181030161922248.png) + + + +### 更换快捷键 + +**效果图** +备注:从Eclipse移植到idea编码,好多快捷键不一致,导致编写效率降低,现在我们来更换一下快捷键 +**设置方法** + +- 方法一: + +File–>Setting–> +![在这里插入图片描述](images/Others/20181030165223996.png) + +例如设置成Eclipse的,设置好了之后可以ctrl+d删除单行代码(idea是ctrl+y) + +- 方法二:设置模板 +- File–>Setting–> + ![在这里插入图片描述](images/Others/20181030165549295.png) +- 方法三: + +![在这里插入图片描述](images/Others/20181030165842703.png) +以ctrl+o重写方法为例 + +![在这里插入图片描述](images/Others/20181030170008544.png) + + + +### 注释去掉斜体 + +**效果图** +![在这里插入图片描述](images/Others/20181031135509461.png) +**设置方法** +File–>settings–>Editor–> +![在这里插入图片描述](images/Others/20181031135416445.png) + +![在这里插入图片描述](images/Others/20181031135540101.png) + + + +### 代码检测警告提示等级设置 + +![在这里插入图片描述](images/Others/20190316140152621.png) +强烈建议,不要给关掉,不要嫌弃麻烦,他的提示都是对你好,帮助你提高你的代码质量,很有帮助的。 + + + +### 项目目录相关–折叠空包 + +![在这里插入图片描述](images/Others/20190316140238852.png) + + + +### 窗口复位 + +![在这里插入图片描述](images/Others/20190316140505814.png) +这个就是当你把窗口忽然间搞得乱七八糟的时候,还可以挽回,就是直接restore一下,就好啦。 + + + +### 查看本地代码历史 + +![在这里插入图片描述](images/Others/20190316140617590.png) + + + +## 常用插件 + +### .ignore + +生成各种ignore文件,一键创建git ignore文件的模板,免得自己去写,如下图。 + +地址:https://plugins.jetbrains.com/plugin/7495--ignore + +![ignore](images/Others/ignore.gif) + + + +### Lombok + +支持lombok的各种注解,从此不用写getter setter这些 可以把注解还原为原本的java代码 非常方便。 + +地址:https://plugins.jetbrains.com/plugin/6317-lombok-plugin + +![Lombok](images/Others/Lombok.gif) + + + +### GsonFormat + +一键根据json文本生成java类 非常方便。 + +地址:https://plugins.jetbrains.com/plugin/7654-gsonformat + +![GsonFormat](images/Others/GsonFormat.gif) + + + +### GenerateAllSetter + +一键调用一个对象的所有set方法并且赋予默认值 在对象字段多的时候非常方便。 + +地址:https://plugins.jetbrains.com/plugin/9360-generateallsetter + +![GenerateAllSetter](images/Others/GenerateAllSetter.gif) + + + +### GenerateSerialVersionUID + +`Alt + Insert` 生成`serialVersionUID` + +![GenerateSerialVersionUID](images/Others/GenerateSerialVersionUID.gif) + + + +### MyBatisCodeHelper-Pro + +MybatisCodeHelperPro 是一款IDEA下全方位支持Mybatis的插件 大部分功能是免费的。 + +① 接口与xml 互相跳转 高清图标 更改图标 使用快捷键跳转 + +![MyBatisCodeHelper-Pro](images/Others/MyBatisCodeHelper-Pro.gif) + +② 一键添加param注解 + +![addParamAnnotation](images/Others/addParamAnnotation.gif) + + + +### Maven Helper + +一键查看maven依赖,查看冲突的依赖,一键进行exclude依赖,对于大型项目 非常方便。 + +地址:https://plugins.jetbrains.com/plugin/7179-maven-helper + +![Maven-Helper](images/Others/Maven-Helper.gif) + + + +### Rainbow Brackets + +代码作色工具(Rainbow Brackets)插件可以实现配对括号相同颜色,并且实现选中区域代码高亮的功能。 + +- 高亮效果快捷键 + - mac: command+鼠标右键单击 + - windows: ctrl+鼠标右键单击 + +- 选中部分外暗淡效果快捷键:alt+鼠标右键单击 + +地址:https://plugins.jetbrains.com/plugin/10080-rainbow-brackets + +![Rainbow-Brackets](images/Others/Rainbow-Brackets.gif) + +事实上,代码作色之后,可以非常方便我们阅读。类似的工具还有:Grep Console 来自定义设置控制台输出颜色等。 + + + +### HighlightBracketPair + +自动化高亮显示光标所在代码块对应的括号,可以定制颜色和形状。 + +![HighlightBracketPair](images/Others/HighlightBracketPair.gif) +![HighlightBracketPair-set](images/Others/HighlightBracketPair-set.jpg) + + + +### CodeGlance + +在编辑区的右侧显示的代码地图。 + +![CodeGlance](images/Others/CodeGlance.png) + + + +### Alibaba Java Coding Guidelines + +阿里巴巴出品的java代码规范插件(p3c)。可以扫描整个项目找到不规范的地方 并且大部分可以自动修复。Alibaba Java Code Guidelines 插件实现了开发手册中的的 53 条规则,大部分基于 PMD 实现,其中有 4 条规则基于 IDEA 实现,并且基于 IDEA Inspection 实现了实时检测功能。部分规则实现了 Quick Fix 功能。目前,插件检测有两种模式:实时检测、手动触发。 + +地址:https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines + + + +### Key promoter X + +Key Promoter X 是一个**快捷键提示插件**,如果鼠标操作是能够用快捷键替代,Key Promoter X 会提示可以用什么快捷键替代。详细使用文档。 + +地址:https://plugins.jetbrains.com/plugin/9792-key-promoter-x + +![key-promoter-x](images/Others/key-promoter-x.gif) + + +### Translation + +最好用的翻译插件,功能很强大,界面很漂亮。 + +地址:https://plugins.jetbrains.com/plugin/8579-translation + +![Translation](images/Others/Translation.gif) + + + +### SequenceDiagram + +时序图生成工具。有时我们需要梳理业务逻辑或者阅读源码。从中,我们需要了解整个调用链路,反向生成 UML 的时序图是强需求。其中,SequenceDiagram 插件是一个非常棒的插件。 + +地址:https://plugins.jetbrains.com/plugin/8286-sequencediagram + +![SequenceDiagram](images/Others/SequenceDiagram.gif) + + + +### CamelCase + +命名风格转换插件,可以在 kebab-case,SNAKE_CASE,PascalCase,camelCase,snake_case 和 空格风格之间切换。快捷键苹果为 **⇧+⌥+ U** ,windows 下为 **Shift + Alt +U**。 + +![CamelCase](images/Others/CamelCase.gif) + + + +### MybatisX + +**Mybatis-plus** 团队为 **Mybatis** 开发的插件,提供了 **Mapper** 接口和 **XML**之间的跳转和自动生成模版的功能。另外这个名字是我起的,嘿嘿! + +![MybatisX](images/Others/MybatisX.gif) + + + + + +### Git Commit Template + +老是有人吐槽你提交的 **Git** 不规范?你可以试试这个插件。它提供了很好的 **Git** 格式化模版,你可以按照实际情况格式化你的提交信息。 + +![Git-Commit-Template](images/Others/Git-Commit-Template.jpg) + + + +### Extra Icons + +最后是一个美化插件,为一些文件类型提供官方没有的图标。来看看效果吧。 + +![Extra-Icons](images/Others/Extra-Icons.jpg) + + + +### RestfulToolkit + +开发时经常会根据 URI 的部分信息来查找对应的Controller 中方法,RestfulToolkit提供了一套 RESTful 服务开发辅助工具集。 + +地址:https://plugins.jetbrains.com/plugin/10292-restfultoolkit + +![RestfulToolkit](images/Others/iuYryuy.jpg) + +### Grep Console + +### EasyCode + +### CheckStyle + +### Java Stream Debugger + +https://www.e-learn.cn/topic/3624009 + +## 功能插件 + +### FindBugs-IDEA + +检测代码中可能的bug及不规范的位置,检测的模式相比p3c更多,写完代码后检测下 避免低级bug,强烈建议用一下,一不小心就发现很多老代码的bug。 + +地址:https://plugins.jetbrains.com/plugin/3847-findbugs-idea + +![img](images/Others/1697faf5f3381462) + + + +### VisualVM Launcher + +运行java程序的时候启动visualvm,方便查看jvm的情况 比如堆内存大小的分配,某个对象占用了多大的内存,jvm调优必备工具。 + +地址:https://plugins.jetbrains.com/plugin/7115-visualvm-launcher + +![img](images/Others/1697faf61a88910f) + + + + + +### MyBatis Log Plugin + +Mybatis现在是java中操作数据库的首选,在开发的时候,我们都会把Mybatis的脚本直接输出在console中,但是默认的情况下,输出的脚本不是一个可以直接执行的。如果我们想直接执行,还需要在手动转化一下。MyBatis Log Plugin 这款插件是直接将Mybatis执行的sql脚本显示出来,无需处理,可以直接复制出来执行的,如图: + +![img](images/Others/v2-2e63a1d6ddf2782ab8a0cda4d3e41502_hd.jpg) + + + +### Activate-Power-Mode + +根据Atom的插件activate-power-mode(或Power mode II)的效果移植到IDEA上。 + +![8](images/Others/intellij-idea-zhuangbi-top-5-5.gif) + + + +### Background Image Plus + +可设置idea背景图片的插件,不但可以设置固体的图片,还可以设置一段时间后随机变化背景图片,以及设置图片的透明度等等。 + +![img](images/Others/772743-20181027200424736-854569575.png) + + + +### Material Theme UI + +这是一款主题插件,可以让你的ide的图标变漂亮,配色搭配的很到位,还可以切换不同的颜色,甚至可以自定义颜色。默认的配色就很漂亮了,如果需要修改配色,可以在工具栏中Tools->Material Theme然后修改配色等。 + +![tools](images/Others/material-theme-ui-tools.png) + +![](images/Others/oceanic.png) + + + +## 集成插件 + +### 集成JIRA + +Jira是一个广泛使用的项目与事务跟踪工具,被广泛应用于缺陷跟踪、客户服务、需求收集、流程审批、任务跟踪、项目跟踪和敏捷管理等工作领域。idea可以很好的跟它集成,参考下图: + +File -> Settings ->Task -> Servers 点击右侧上面的+号,选择JIRA,然后输入JIRA的Server地址,用户名、密码即可 + +![IDEA-JIRA-1](images/Others/IDEA-JIRA-1.png) + +然后打开Open Task界面 + +![IDEA-JIRA-2](images/Others/IDEA-JIRA-2.png) + +如果JIRA中有分配给你的Task,idea能自动列出来 + +![IDEA-JIRA-3](images/Others/IDEA-JIRA-3.png) + +代码修改后,向svn提交时,会自动与该任务关联 + +![IDEA-JIRA-4](images/Others/IDEA-JIRA-4.png) + +将每次提交的代码修改与JIRA上的TASK关联后,有什么好处呢?我们每天可能要写很多代码,修复若干bug,日子久了以后,谁也不记得当初为了修复某个bug做了哪些修改,只要你按上面的操作正确提交,idea都会帮你记着这些细节: + +![IDEA-JIRA-5](images/Others/IDEA-JIRA-5.png) + +如上图,选择最近提交的TASK列表,选择Switch to,idea就会自动打开该TASK关联的源代码,并定位到修改过的代码行。当然如果该TASK已经Close了,也可以选择Remove将其清空。 + + + +### UML类图插件 + +idea已经集成了该功能,只是默认没打开,仍然打开Settings界面,定位到Plugins,输入UML,参考下图: + +![img](images/Others/IDEA-UML-1.png) + + 确认UML 这个勾已经勾上了,然后点击Apply,重启idea,然后仍然找一个java类文件,右击Diagram + +![img](images/Others/IDEA-UML-2.png) + +然后,就自个儿爽去吧 + +![img](images/Others/IDEA-UML-3.png) + + + +### 集成SSH + +java项目经常会在linux上部署,每次要切换到SecureCRT这类终端工具未免太麻烦,idea也想到了这一点: + +![img](images/Others/IDEA-SSH-1.png) + +然后填入IP、用户名、密码啥的 + +![img](images/Others/IDEA-SSH-2.png) + +点击OK,就能连接上linux了 + +![img](images/Others/IDEA-SSH-3.png) + +注:如果有中文乱码问题,可以在Settings里调整编码为utf-8 + +![img](images/Others/IDEA-SSH-4.png) + + + +### 集成FTP + +![img](images/Others/IDEA-FTP-1.png) + +点击上图中的...,添加一个Remote Host + +![img](images/Others/IDEA-FTP-2.png) + +填写ftp的IP、用户名、密码,根路径啥的,然后点击Test FTP Connection,正常的话,应该能连接,如果连接不通,点击Advanced Options,参考下图调整下连接选项 + +![img](images/Others/IDEA-FTP-3.png) + +配置了FTP连接后,在提交代码时,可以选择提交完成后将代码自动上传到ftp服务器 + +![img](images/Others/IDEA-FTP-4.png) + + + +### Database管理工具 + +先看效果吧: + +![img](images/Others/IDEA-Database-1.png) + +有了这个,再也不羡慕vs.net的db管理功能了。配置也很简单,就是点击+号,增加一个Data Source即可 + +![img](images/Others/IDEA-Database-2.png) + +唯一要注意的是,intellij idea不带数据库驱动,所以在上图中,要手动指定db driver的jar包路径。 + + + +## Debug + +### 弹框/显示 + +勾选Show debug window on breakpoint,则请求进入到断点后自动激活Debug窗口: + +![img](images/Others/856154-20170905111655647-1134637623.png) + +如果IDEA底部没有显示工具栏或状态栏,可以在View里打开,显示出工具栏会方便我们使用: + +  ![img](images/Others/856154-20170905112617351-1554043487.png) + + + +### 快捷键 + +在菜单栏Run里有调试对应的功能,同时可以查看对应的快捷键:  ![img](images/Others/856154-20170905124338444-556465721.png) + + 首先说第一组按钮,共8个按钮,从左到右依次如下: + +    ![img](images/Others/856154-20170905134837851-1615718043.png) + +- **Show Execution Point (Alt + F10)**:如果你的光标在其它行或其它页面,点击这个按钮可跳转到当前代码执行的行 +- **Step Over (F8)**:步过,一行一行地往下走,如果这一行上有方法不会进入方法 +- **Step Into (F7)**:步入,如果当前行有方法,可以进入方法内部,一般用于进入自定义方法内 +- **Force Step Into (Alt + Shift + F7)**:强制步入,能进入任何方法,查看底层源码的时候可以用这个进入官方类库的方法 +- **Step Out (Shift + F8)**:步出,从步入的方法内退出到方法调用处,此时方法已执行完毕,只是还没有完成赋值 +- **Drop Frame (默认无)**:回退断点,后面章节详细说明 +- **Run to Cursor (Alt + F9)**:运行到光标处,你可以将光标定位到你需要查看的那一行,然后使用这个功能,代码会运行至光标行,而不需要打断点 +- **Evaluate Expression (Alt + F8)**:计算表达式,后面章节详细说明 + +第二组按钮,共7个按钮,从上到下依次如下: + +     ![img](images/Others/856154-20170905134011101-1824595229.png) + +- **Rerun 'xxxx'**:重新运行程序,会关闭服务后重新启动程序 + +- **Update 'tech' application (Ctrl + F5)**:更新程序,一般在你的代码有改动后可执行这个功能 +- **Resume Program (F9)**:恢复程序,比如,你在第20行和25行有两个断点,当前运行至第20行,按F9,则运行到下一个断点(即第25行),再按F9,则运行完整个流程,因为后面已经没有断点了 +- **Pause Program**:暂停程序,启用Debug。目前没发现具体用法 +- **Stop 'xxx' (Ctrl + F2)**:连续按两下,关闭程序。有时候你会发现关闭服务再启动时,报端口被占用,这是因为没完全关闭服务的原因,你就需要查杀所有JVM进程了 +- **View Breakpoints (Ctrl + Shift + F8)**:查看所有断点,后面章节会涉及到 +- **Mute Breakpoints**:哑的断点,选择这个后,所有断点变为灰色,断点失效,按F9则可以直接运行完程序。再次点击,断点变为红色,有效 + + +### 变量查看 + +在IDEA中,参数所在行后面会显示当前变量的值:  ![img](images/Others/856154-20170905154209179-9123997.png) + +光标悬停到参数上,显示当前变量信息: + +  ![img](images/Others/856154-20170905154425772-770303651.png) + +![img](images/Others/856154-20170905154724866-160919363.png) + +在Variables里查看,这里显示当前方法里的所有变量: + +   ![img](images/Others/856154-20170905155339491-1166069157.png) + +在Watches里,点击New Watch,输入需要查看的变量。或者可以从Variables里拖到Watche里查看: + +  ![img](images/Others/856154-20170905160057038-750351531.png) + +如果你发现你没有Watches,可能在下图所在的地方: + +  ![img](images/Others/856154-20170905160433710-2004658473.png) + +![img](images/Others/856154-20170905160515538-1647769062.png) + + + +### 计算表达式 + +  ![img](images/Others/856154-20170905160826444-1625048711.png) + +![img](images/Others/856154-20170905161614694-93470669.png) + +设置变量,在计算表达式的框里,可以改变变量的值,这样有时候就能很方便我们去调试各种值的情况了不是: + +![img](images/Others/856154-20170905162404288-824548249.png) + + + +### 智能步入 + +一行代码里有好几个方法,怎么只选择某一个方法进入。之前提到过使用Step Into (Alt + F7) 或者 Force Step Into (Alt + Shift + F7)进入到方法内部,但这两个操作会根据方法调用顺序依次进入,这比较麻烦。 + +  那么智能步入就很方便了,智能步入,这个功能在Run里可以看到,Smart Step Into (Shift + F7): + +  ![img](images/Others/856154-20170905152523304-803289488.png) + +  按Shift + F7,会自动定位到当前断点行,并列出需要进入的方法,如图5.2,点击方法进入方法内部。 + + 如果只有一个方法,则直接进入,类似Force Step Into。 + +  ![img](images/Others/856154-20170905163730929-1374653206.png) [图5.2] + + + +### 断点条件设置 + +通过设置断点条件,在满足条件时,才停在断点处,否则直接运行: + +  ![img](images/Others/856154-20170905165253944-1162138475.png) [图6.1] + +点击View Breakpoints (Ctrl + Shift + F8),查看所有断点。Java Line Breakpoints 显示了所有的断点,在右边勾选Condition,设置断点的条件。 + +- 勾选Log message to console,则会将当前断点行输出到控制台 +- 勾选Evaluate and log,可以在执行这行代码是计算表达式的值,并将结果输出到控制台 + +![img](images/Others/856154-20170905170655163-1805982960.png) + +  ![img](images/Others/856154-20170905170947257-1667065155.png) + + + +### 异常断点 + +通过设置异常断点,在程序中出现需要拦截的异常时,会自动定位到异常行。点击+号添加Java Exception Breakpoints,添加异常断点。然后输入需要断点的异常类,如图6.7,之后可以在Java Exception Breakpoints里看到添加的异常断点。我这里添加了一个NullPointerException异常断点,出现空指针异常后,自动定位在空指针异常行: + +  ![img](images/Others/856154-20170905200131851-150143203.png) + +![img](images/Others/856154-20170905200305147-527881101.png)  ![img](images/Others/856154-20170905200726069-688175303.png) + + + +### 多线程调试 + +  一般情况下我们调试的时候是在一个线程中的,一步一步往下走。但有时候你会发现在Debug的时候,想发起另外一个请求都无法进行了?那是因为IDEA在Debug时默认阻塞级别是ALL,会阻塞其它线程,只有在当前调试线程走完时才会走其它线程。可以在View Breakpoints里选择Thread,如图下图,然后点击Make Default设置为默认选项: + +![img](images/Others/856154-20170905204329757-1196950664.png) + +  切换线程,在下图中Frames的下拉列表里,可以切换当前的线程,如下我这里有两个Debug的线程,切换另外一个则进入另一个Debug的线程:![img](images/Others/856154-20170905205012663-56609868.png) + + + +### 回退断点 + +在调试的时候,想要重新走一下流程而不用再次发起一个请求? + +所谓的断点回退,其实就是回退到上一个方法调用的开始处,在IDEA里测试无法一行一行地回退或回到到上一个断点处,而是回到上一个方法。回退的方式有两种: + +​ 方法一:是Drop Frame按钮,按调用的方法逐步回退,包括三方类库的其它方法(取消Show All Frames按钮会显示三方类库的方法,如图8.3)。 + +![img](images/Others/856154-20170905211428554-1617570377.png) + +  方法二:在调用栈方法上选择要回退的方法,右键选择Drop Frame(图8.4),回退到该方法的上一个方法调用处,此时再按F9(Resume Program),可以看到程序进入到该方法的断点处了。 + +  ![img](images/Others/856154-20170905212138101-113776159.png) + +但有一点需要注意,断点回退只能重新走一下流程,之前的某些参数/数据的状态已经改变了的是无法回退到之前的状态的,如对象、集合、更新了数据库数据等等。 + + + +### 中断Debug + +想要在Debug的时候,中断请求,不要再走剩余的流程了? + +  ![img](images/Others/856154-20170905213656241-1998475384.png) + + + +## 常用设置 + + +### UID + +快捷键:`ALT` + `INS` + +### 折叠空包 + +![clipboard.png](images/Others/bV13Sa) + +![clipboard.png](images/Others/bV13RH) + + + +### 显示边沟图标 + +Editor→General→Gutter Icons + +### 设置自动import包 + +可选,对于不能import \*的要求的,建议不要用这个: + +[![img](images/Others/417876-20171119103804546-819180423.png)](https://images2017.cnblogs.com/blog/417876/201711/417876-20171119103804546-819180423.png) + +如果非要用这个自动导入却不想导入\*的,可以通过配置这个来解决: + +**![img](images/Others/417876-20171128105146972-1080966086.png)** + +调整import包导入的顺序,保持和Eclipse一致:![img](images/Others/417876-20171129081632175-614878351.png) + + + +### 右下角显示内存 + +[![img](images/Others/417876-20171119103943062-564729605.png)](https://images2017.cnblogs.com/blog/417876/201711/417876-20171119103943062-564729605.png) + +点击右下角可以回收内存。 + + + +### 自定义Javadoc注释 + +@date可能不是标准的Javadoc,但是在业界标准来说,这个已经成为Javadoc必备的注释,因为大多数人都用这个来标注日期。建议:注释不要太个性,比如自定义类说明,日期时间字段等等;尽量保持统一的代码风格,建议参考阿里巴巴Java开发手册: + +[![img](images/Others/417876-20171119150102656-1550889934.png)](https://images2017.cnblogs.com/blog/417876/201711/417876-20171119150102656-1550889934.png) + + + +### 鼠标放上去自动显示文档 + +![设置功能](images/Others/201705221495449669133450.jpg) + +![演示效果](images/Others/201705221495449833127277.png) + + + +## 快捷键 + +Mac 键盘符号和修饰键说明 + +- `⌘` ——> `Command` +- `⇧` ——> `Shift` +- `⌥` ——> `Option` +- `⌃` ——> `Control` +- `↩︎` ——> `Return/Enter` +- `⌫` ——> `Delete` +- `⌦` ——> `向前删除键(Fn + Delete)` +- `↑` ——> `上箭头` +- `↓` ——> `下箭头` +- `←` ——> `左箭头` +- `→` ——> `右箭头` +- `⇞` ——> `Page Up(Fn + ↑)` +- `⇟` ——> `Page Down(Fn + ↓)` +- `⇥` ——> `右制表符(Tab键)` +- `⇤` ——> `左制表符(Shift + Tab)` +- `⎋` ——> `Escape(Esc)` +- `End` ——> `Fn + →` +- `Home` ——> `Fn + ←` + +### Editing(编辑) + +| 快捷键 | 作用 | +| ----------------------------------------------- | ------------------------------------------------------------ | +| `Control + Space` | 基本的代码补全(补全任何类、方法、变量) | +| `Control + Shift + Space` | 智能代码补全(过滤器方法列表和变量的预期类型) | +| `Command + Shift + Enter` | 自动结束代码,行末自动添加分号 | +| `Command + P` | 显示方法的参数信息 | +| `Control + J` | 快速查看文档 | +| `Shift + F1` | 查看外部文档(在某些代码上会触发打开浏览器显示相关文档) | +| `Command` + 鼠标放在代码上 | 显示代码简要信息 | +| `Command + F1` | 在错误或警告处显示具体描述信息 | +| `Command + N`, `Control + Enter`, `Control + N` | 生成代码(`getter`、`setter`、`hashCode`、`equals`、`toString`、构造函数等) | +| `Control + O` | 覆盖方法(重写父类方法) | +| `Control + I` | 实现方法(实现接口中的方法) | +| `Command + Option + T` | 包围代码(使用`if...else`、`try...catch`、`for`、`synchronized`等包围选中的代码) | +| `Command + /` | 注释 / 取消注释与行注释 | +| `Command + Option + /` | 注释 / 取消注释与块注释 | +| `Option` + 方向键上 | 连续选中代码块 | +| `Option` + 方向键下 | 减少当前选中的代码块 | +| `Control + Shift + Q` | 显示上下文信息 | +| `Option + Enter` | 显示意向动作和快速修复代码 | +| `Command + Option + L` | 格式化代码 | +| `Control + Option + O` | 优化 import | +| `Control + Option + I` | 自动缩进线 | +| `Tab / Shift + Tab` | 缩进代码 / 反缩进代码 | +| `Command + X` | 剪切当前行或选定的块到剪贴板 | +| `Command + C` | 复制当前行或选定的块到剪贴板 | +| `Command + V` | 从剪贴板粘贴 | +| `Command + Shift + V` | 从最近的缓冲区粘贴 | +| `Command + D` | 复制当前行或选定的块 | +| `Command + Delete` | 删除当前行或选定的块的行 | +| `Control + Shift + J` | 智能的将代码拼接成一行 | +| `Command + Enter` | 智能的拆分拼接的行 | +| `Shift + Enter` | 开始新的一行 | +| `Command + Shift + U` | 大小写切换 | +| `Command + Shift + ]` / `Command + Shift + [` | 选择直到代码块结束 / 开始 | +| `Option + Fn + Delete` | 删除到单词的末尾 | +| `Option + Delete` | 删除到单词的开头 | +| `Command` + 加号 / `Command` + 减号 | 展开 / 折叠代码块 | +| `Command + Shift` + 加号 | 展开所以代码块 | +| `Command + Shift` + 减号 | 折叠所有代码块 | +| `Command + W` | 关闭活动的编辑器选项卡 | + + + +### Search/Replace(查询/替换) + +| 快捷键 | 作用 | +| --------------------- | --------------------------------------------------------- | +| `Double Shift` | 查询任何东西 | +| `Command + F` | 文件内查找 | +| `Command + G` | 查找模式下,向下查找 | +| `Command + Shift + G` | 查找模式下,向上查找 | +| `Command + R` | 文件内替换 | +| `Command + Shift + F` | 全局查找(根据路径) | +| `Command + Shift + R` | 全局替换(根据路径) | +| `Command + Shift + S` | 查询结构(Ultimate Edition 版专用,需要在 Keymap 中设置) | +| `Command + Shift + M` | 替换结构(Ultimate Edition 版专用,需要在 Keymap 中设置) | + + + +### Usage Search(使用查询) + +| 快捷键 | 作用 | +| ------------------------------ | --------------------------------- | +| `Option + F7` / `Command + F7` | 在文件中查找用法 / 在类中查找用法 | +| `Command + Shift + F7` | 在文件中突出显示的用法 | +| `Command + Option + F7` | 显示用法 | + + + +### Compile and Run(编译和运行) + +| 快捷键 | 作用 | +| -------------------------------------------- | -------------------------- | +| `Command + F9` | 编译 Project | +| `Command + Shift + F9` | 编译选择的文件、包或模块 | +| `Control + Option + R` | 弹出 Run 的可选择菜单 | +| `Control + Option + D` | 弹出 Debug 的可选择菜单 | +| `Control + R` | 运行 | +| `Control + D` | 调试 | +| `Control + Shift + R`, `Control + Shift + D` | 从编辑器运行上下文环境配置 | + + + +### Debugging(调试) + +| 快捷键 | 作用 | +| ---------------------- | ------------------------------------------------------------ | +| `F8` | 进入下一步,如果当前行断点是一个方法,则不进入当前方法体内 | +| `F7` | 进入下一步,如果当前行断点是一个方法,则进入当前方法体内,如果该方法体还有方法,则不会进入该内嵌的方法中 | +| `Shift + F7` | 智能步入,断点所在行上有多个方法调用,会弹出进入哪个方法 | +| `Shift + F8` | 跳出 | +| `Option + F9` | 运行到光标处,如果光标前有其他断点会进入到该断点 | +| `Option + F8` | 计算表达式(可以更改变量值使其生效) | +| `Command + Option + R` | 恢复程序运行,如果该断点下面代码还有断点则停在下一个断点上 | +| `Command + F8` | 切换断点(若光标当前行有断点则取消断点,没有则加上断点) | +| `Command + Shift + F8` | 查看断点信息 | + + + +### Navigation(导航) + +| 快捷键 | 作用 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| `Command + O` | 查找类文件 | +| `Command + Shift + O` | 查找所有类型文件、打开文件、打开目录,打开目录需要在输入的内容前面或后面加一个反斜杠`/` | +| `Command + Option + O` | 前往指定的变量 / 方法 | +| `Control` + 方向键左 / `Control` + 方向键右 | 左右切换打开的编辑 tab 页 | +| `F12` | 返回到前一个工具窗口 | +| `Esc` | 从工具窗口进入代码文件窗口 | +| `Shift + Esc` | 隐藏当前或最后一个活动的窗口,且光标进入代码文件窗口 | +| `Command + Shift + F4` | 关闭活动 `run/messages/find/... tab` | +| `Command + L` | 在当前文件跳转到某一行的指定处 | +| `Command + E` | 显示最近打开的文件记录列表 | +| `Option` + 方向键左 / `Option` + 方向键右 | 光标跳转到当前单词 / 中文句的左 / 右侧开头位置 | +| `Command + Option` + 方向键左 / `Command + Option` + 方向键右 | 退回 / 前进到上一个操作的地方 | +| `Command + Shift + Delete` | 跳转到最后一个编辑的地方 | +| `Option + F1` | 显示当前文件选择目标弹出层,弹出层中有很多目标可以进行选择(如在代码编辑窗口可以选择显示该文件的 Finder) | +| `Command + B` / `Command` + 鼠标点击 | 进入光标所在的方法/变量的接口或是定义处 | +| `Command + Option + B` | 跳转到实现处,在某个调用的方法名上使用会跳到具体的实现处,可以跳过接口 | +| `Option + Space`, `Command + Y` | 快速打开光标所在方法、类的定义 | +| `Control + Shift + B` | 跳转到类型声明处 | +| `Command + U` | 前往当前光标所在方法的父类的方法 / 接口定义 | +| `Control` + 方向键下 / `Control` + 方向键上 | 当前光标跳转到当前文件的前一个 / 后一个方法名位置 | +| `Command + ]` / `Command + [` | 移动光标到当前所在代码的花括号开始 / 结束位置 | +| `Command + F12` | 弹出当前文件结构层,可以在弹出的层上直接输入进行筛选(可用于搜索类中的方法) | +| `Control + H` | 显示当前类的层次结构 | +| `Command + Shift + H` | 显示方法层次结构 | +| `Control + Option + H` | 显示调用层次结构 | +| `F2` / `Shift + F2` | 跳转到下一个 / 上一个突出错误或警告的位置 | +| `F4` / `Command` + 方向键下 | 编辑 / 查看代码源 | +| `Option + Home` | 显示到当前文件的导航条 | +| `F3` | 选中文件 / 文件夹 / 代码行,添加 / 取消书签 | +| `Option + F3` | 选中文件 / 文件夹/代码行,使用助记符添加 / 取消书签 | +| `Control + 0`…`Control + 9` | 定位到对应数值的书签位置 | +| `Command + F3` | 显示所有书签 | + + + +### Refactoring(重构) + +| 快捷键 | 作用 | +| ---------------------- | ---------------------------------- | +| `F5` | 复制文件到指定目录 | +| `F6` | 移动文件到指定目录 | +| `Command + Delete` | 在文件上为安全删除文件,弹出确认框 | +| `Shift + F6` | 重命名文件 | +| `Command + F6` | 更改签名 | +| `Command + Option + N` | 一致性 | +| `Command + Option + M` | 将选中的代码提取为方法 | +| `Command + Option + V` | 提取变量 | +| `Command + Option + F` | 提取字段 | +| `Command + Option + C` | 提取常量 | +| `Command + Option + P` | 提取参数 | + + + +### VCS / Local History(版本控制 / 本地历史记录) + +| 快捷键 | 作用 | +| -------------------- | -------------------------- | +| `Command + K` | 提交代码到版本控制器 | +| `Command + T` | 从版本控制器更新代码 | +| `Option + Shift + C` | 查看最近的变更记录 | +| `Control + C` | 快速弹出版本控制器操作面板 | + + + +### Live Templates(动态代码模板) + +| 快捷键 | 作用 | +| ---------------------- | ---------------------------------------------- | +| `Command + Option + J` | 弹出模板选择窗口,将选定的代码使用动态模板包住 | +| `Command + J` | 插入自定义动态代码模板 | + + + +### General(通用) + +| 快捷键 | 作用 | +| --------------------------- | ------------------------------------------------------------ | +| `Command + 1`…`Command + 9` | 打开相应编号的工具窗口 | +| `Command + S` | 保存所有 | +| `Command + Option + Y` | 同步、刷新 | +| `Control + Command + F` | 切换全屏模式 | +| `Command + Shift + F12` | 切换最大化编辑器 | +| `Option + Shift + F` | 添加到收藏夹 | +| `Option + Shift + I` | 检查当前文件与当前的配置文件 | +| Control + ` | 快速切换当前的 scheme(切换主题、代码样式等) | +| `Command + ,` | 打开 IDEA 系统设置 | +| `Command + ;` | 打开项目结构对话框 | +| `Shift + Command + A` | 查找动作(可设置相关选项) | +| `Control + Shift + Tab` | 编辑窗口标签和工具窗口之间切换(如果在切换的过程加按上 delete,则是关闭对应选中的窗口) | + + + +# Product + +## Axure RP + +提供快速原型设计工具。 + + + +## 蓝湖 + +提供产品设计。 + +地址:https://lanhuapp + + + +## 墨刀 + +地址:https://modao.cc/feature/flowchart + +提供原型、流程图、设计图、思维导图等功能。 + + + +# Manger + +## 时间管理 + +### 四象限法则 + +使用四象限法则来确定需要做的事情的优先级。 + +![时间管理-四象限法则](images/Others/时间管理-四象限法则.png) + +### 番茄工作法 + +![番茄工作法](images/Others/番茄工作法.jpeg) + +**番茄工作法优点** + +- 减轻对时间的焦虑 +- 提高工作效率、集中力、注意力,减少工作中断 +- 增强决策意识 +- 激励效果唤醒持久工作奖励 +- 巩固达成工作目标的决心 +- 完善工作任务预估流程,精确任务时间和质量 +- 优化工作、学习的流程,提高时间效率 +- 锻炼工作时间把控与高效时间管理能力 + + + +**番茄工作法原则** + +- 每一个番茄时间约为25分钟,每完成一个番茄时间休息5分钟 +- 在每一个番茄时间里,如遇突发事件被迫打断,该番茄时间作废 +- 非必需马山待办事项应安排到下一个番茄时间处理,并记录到敬业签 +- 每连续完成4个番茄时间后,建议休息时间增加到25分钟作为奖励 + + + +### PDCA循环管理 + +PDCA是最早由美国质量统计控制之父Shewhat(休哈特)提出的PDS(Plan Do See)演化而来,由美国质量管理专家戴明改进成为PDCA模式,所以又称为“戴明环”;它是全面质量管理所应遵循的科学程序。全面质量管理活动的全部过程,就是质量计划的制订和组织实现的过程,这个过程就是按照PDCA循环,不停顿地周而复始地运转的。 + +![pdca](images/Others/pdca.png) + +- **P——Plan(计划)** + 包括方针和目标的确定,以及活动规划的制定。 +- **D——Do(执行)** + 根据已知的信息,设计具体的方法、方案和计划布局;再根据设计和布局,进行具体运作,实现计划中的内容。 +- **C——Check(检查)** + 总结执行计划的结果,分清哪些对了,哪些错了,明确效果,找出问题。 +- **A——Action(处理)** + 对总结检查的结果进行处理,对成功的经验加以肯定,并予以标准化;对于失败的教训也要总结,引起重视。对于没有解决的问题,应提交给下一个PDCA循环中去解决。 + +每件事情,通常我们会先做计划(P),计划完了以后去实施(D),实施的过程中进行检查(C),检查执行结果是否达到了预期,分析影响的因素、出现问题的原因,并提出解决的措施,然后再把检查的结果进行改进、实施、改善(A)。 + + + +**适应范围** + +PDCA循环工作方法无论是在解决生产质量问题上还是在解决企业管理问题以及完善人生目标方面,都不愧为一个好的、有益的工作方法。   + + + +**现代观点** + +- **P(Planning)**:计划,包括三小部分:目标(goal)、实施计划(plan)、收支预算(budget) +- **D(design)**:设计方案和布局 +- **C(4C)**:4C管理,Check(检查)、Communicate(沟通)、Clean (清理)、Control(控制) +- **A(2A)**:Act(执行,对总结检查的结果进行处理)、Aim(按照目标要求行事,如改善、提高) + + + +## 项目管理 + + + +## 团队管理 + +### 离职退群方式 + +- 大群悄悄退,无伤大碍 +- 小群知会领导,正式退场 +- 关系好的同事群,发个红包再退群 +- 同事微信别删,都是日后资源 + + + +## 知识卡片 + +https://www.yinxiang.com/everhub/ + +### 多维度思考 + +![独立思考的三条法则](images/Others/独立思考的三条法则.jpg) + +![坚持健康思考的3条法则](images/Others/坚持健康思考的3条法则.jpg) + +![创造性思考的3条法则](images/Others/创造性思考的3条法则.jpg) + +![合作性思考的3条法则](images/Others/合作性思考的3条法则.jpg) + + + +## 演讲能力 + +### PPT演讲力 + +#### 定位PPT演讲主题 + +![定位PPT演讲主题](images/Others/定位PPT演讲主题.jpg) + + + +#### PPT演讲创新思维 + +![PPT演讲创新思维](images/Others/PPT演讲创新思维.jpg) + + + +#### PPT演讲的逻辑结构 + +![PPT演讲的逻辑结构](images/Others/PPT演讲的逻辑结构.jpg) + + + +#### 讲出好故事的HIT大法 + +![讲出好故事的HIT大发](images/Others/讲出好故事的HIT大发.jpg) + + + +#### 自我介绍公式MTV + +![自我介绍公式MTV](images/Others/自我介绍公式MTV.jpg) + + + +#### 打造个人品牌营销 + +![打造个人品牌营销](images/Others/打造个人品牌营销.jpg) + + + +#### 顶级演讲者的素养 + +![顶级演讲者的素养](images/Others/顶级演讲者的素养.jpg) + + + +#### 六步准备高光演讲 + +![六步准备高光演讲](images/Others/六步准备高光演讲.jpg) + + + +#### PPT演讲大树模型 + +![PPT演讲大树模型](images/Others/PPT演讲大树模型.jpeg) + + + +### 即兴演讲 + +![即兴交流的7种力量](images/Others/即兴交流的7种力量.jpg) + + + +### 精简发言 + +![精简发言的3个重点](images/Others/精简发言的3个重点.jpg) + diff --git a/README.md b/README.md index bdd42d3..df14e4d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # lemon-guide -收纳`操作系统`、`JAVA`、`算法`、`数据库`、`中间件`、`解决方案`、`架构`、`DevOps`和`大数据`等技术栈总结! +收纳了 `操作系统`、`JAVA`、`算法`、`数据库`、`中间件`、`解决方案`、`架构`、`DevOps` 和 `大数据` 等技术栈总结!其内容有来源笔者个人总结的内容,也有来源于互联网各种经典场景或案例的总结,目的在于把常用的技术内容进行归纳整理记录。 + # [1 OS](OS.md) @@ -8,6 +9,7 @@ 提供OS + # [2 JAVA](JAVA.md) 提供JAVA diff --git a/Solution.md b/Solution.md new file mode 100644 index 0000000..08d2f4b --- /dev/null +++ b/Solution.md @@ -0,0 +1,5743 @@ +
Solution
+ +Author:李茹钰(`echo`) + +Introduction:收纳技术相关的 `幂等`、`限流`、`降级`、`断路器`、`事务`、`缓存`、`分库分表` 等总结! + +[TOC] + + + +# 高可用设计 + +**什么是高可用?** + +在定义什么是高可用,可以先定义下什么是不可用,一个网站的内容最终呈现在用户面前需要经过若干个环节,而其中只要任何一个环节出现了故障,都可能导致网站页面不可访问,这个也就是网站不可用的情况。 + +参考维基百科,看看维基怎么定义高可用:`系统无中断地执行其功能的能力,代表系统的可用性程度,是进行系统设计时的准则之一`。 + +这个难点或是重点在于“无中断”,要做到 7 x 24 小时无中断无异常的服务提供。 + + + +**为什么需要高可用?** + +一套对外提供服务的系统是需要硬件,软件相结合,但是我们的软件会有bug,硬件会慢慢老化,网络总是不稳定,软件会越来越复杂和庞大,除了硬件软件在本质上无法做到“无中断”,外部环境也可能导致服务的中断,例如断电,地震,火灾,光纤被挖掘机挖断,这些影响的程度可能更大。 + + + +**高可用的评价纬度** + +在业界有一套比较出名的评定网站可用性的指标,常用N个9来量化可用性,可以直接映射到网站正常运行时间的百分比上 + +| 描述 | N个9 | 可用性级别 | 年度停机时间 | +| ------------------------ | ---- | ---------- | ------------ | +| 基本可用 | 2个9 | 99% | 87.6小时 | +| 较高可用 | 3个9 | 99% | 8.8小时 | +| 具备故障自动恢复能力可用 | 4个9 | 99.99% | 53分钟 | +| 极高可用 | 5个9 | 99.999% | 5分钟 | + +一般互联网公司也是按照这个指标去界定可用性,不过在执行的过程中也碰到了一些问题,例如,有一些服务的升级或数据迁移明明可以在深夜停机或停服务进行,然而考虑到以后的报告要显示出我们的系统达到了多少个9的高可用,而放弃停服务这种简单的解决方案,例如停机2个小时,就永远也达不到4个9。然而在一些高并发的场合,例如在秒杀或拼团,虽然服务停止了几分钟,但是这个对整个公司业务的影响可能是非常重大的,分分钟丢失的订单可能是一个庞大的数量。所以N个9来量化可用性其实也得考虑业务的情况。 + + + +## 服务冗余 + +### 冗余策略 + +每一个访问可能都会有多个服务组成而成,每个机器每个服务都可能出现问题,所以第一个考虑到的就是每个服务必须不止一份可以是多份,所谓多份一致的服务就是服务的冗余,这里说的服务泛指了机器的服务,容器的服务,还有微服务本身的服务。 + +在机器服务层面需要考虑,各个机器间的冗余是否有在物理空间进行隔离冗余 ,例如是否所有机器是否有分别部署在不同机房,如果在同一个机房是否做到了部署在不同的机柜,如果是docker容器是否部署在分别不同的物理机上面。 采取的策略其实也还是根据服务的业务而定,所以需要对服务进行分级评分,从而采取不同的策略,不同的策略安全程度不同,伴随这的成本也是不同,安全等级更高的服务可能还不止考虑不同机房,还需要把各个机房所处的区域考虑进行,例如,两个机房不要处在同一个地震带上等等。 + +![服务冗余-冗余策略](images/Solution/服务冗余-冗余策略.jpg) + + + +### 无状态化 + +服务的冗余会要求我们可以随时对服务进行扩容或者缩容,有可能我们会从2台机器变成3台机器,想要对服务进行随时随地的扩缩容,就要求我们的服务是一个无状态化,所谓无状态化就是每个服务的服务内容和数据都是一致的。 + +例如,从我们的微服务架构来看,我们总共分水平划分了好几个层,正因为我们每个层都做到了无状态,所以在这个水平架构的扩张是非常的简单。假设,我们需要对网关进行扩容,我们只需要增加服务就可以,而不需要去考虑网关是否存储了一个额外的数据。 + +![服务冗余-无状态化](images/Solution/服务冗余-无状态化.jpg) + +网关不保存任何的session数据,不提供会造成一致性的服务,将不一致的数据进行几种存储,借助更加擅长数据同步的中间件来完成。这个是目前主流的方案,服务本身尽可能提供逻辑的服务,将数据的一致性保证集中式处理,这样就可以把“状态”抽取出来,让网关保持一个“无状态” + +这里仅仅是举了网关的例子,在微服务只基本所有的服务,都应该按照这种思路去做,如果服务中有状态,就应该把状态抽取出来,让更加擅长处理数据的组件来处理,而不是在微服务中去兼容有数据的状态。 + + + +## 数据存储高可用 + +之前上面说的服务冗余,可以简单的理解为计算的高可用,计算高可用只需要做到无状态既可简单的扩容缩容,但是对于需要存储数据的系统来说,数据本身就是有状态。 + +跟存储与计算相比,有一个本质的差别:`将数据从一台机器搬到另一台机器,需要经过线路进行传输`。 + +网络是不稳定的,特别是跨机房的网络,ping的延时可能是几十几百毫秒,虽然毫秒对于人来说几乎没有什么感觉,但是对于高可用系统来说,就是本质上的不同,这意味着整个系统在某个时间点上,数据肯定是不一致的。按照“数据+逻辑=业务”的公式来看,数据不一致,逻辑一致,最后的业务表现也会不一致。举个例子 + +![数据存储高可用](images/Solution/数据存储高可用.jpg) + +无论是正常情况下的传输延时,还是异常情况下的传输中断,都会导致系统的数据在某个时间点出现不一致,而数据的不一致又会导致业务出现问题,但是如果数据不做冗余,系统的高可用无法保证 + +> 所以,存储高可用的难点不在于怎么备份数据,而在于如何减少或者规避数据不一致对业务造成的影响 + +分布式领域中有一个著名的CAP定理,从理论上论证了存储高可用的复杂度,也就是说,存储高可用不可能同时满足“一致性,可用性,分区容错性”,最多只能满足2个,其中分区容错在分布式中是必须的,就意味着,我们在做架构设计时必须结合业务对一致性和可用性进行取舍。 + +存储高可用方案的本质是将数据复制到多个存储设备中,通过数据冗余的方式来现实高可用,其复杂度主要呈现在数据复制的延迟或中断导致数据的不一致性,我们在设计存储架构时必须考虑到一下几个方面: + +- 数据怎么进行复制 +- 架构中每个节点的职责是什么 +- 数据复制出现延迟怎么处理 +- 当架构中节点出现错误怎么保证高可用 + + + +### 数据主从复制 + +主从复制是最常见的也是最简单的存储高可用方案,例如Mysql,redis等等 + +![数据存储高可用-数据主从复制](images/Solution/数据存储高可用-数据主从复制.jpg) + +其架构的优点就是简单,主机复制写和读,而从机只负责读操作,在读并发高时候可用扩张从库的数量减低压力,主机出现故障,读操作也可以保证读业务的顺利进行。 + +缺点就是客户端必须感知主从关系的存在,将不同的操作发送给不同的机器进行处理,而且主从复制中,从机器负责读操作,可能因为主从复制时延大,出现数据不一致性的问题。 + + + +### 数据主从切换 + +刚说了主从切换存在两个问题: 1.主机故障写操作无法进行 2.需要人工将其中一台从机器升级为主机 + +为了解决这个两个问题,我们可以设计一套主从自动切换的方案,其中设计到对主机的状态检测,切换的决策,数据丢失和冲突的问题。 + +1.主机状态检测 + +需要多个检查点来检测主机的机器是否正常,进程是否存在,是否出现超时,是否写操作不可执行,读操作是否不可执行,将其进行汇总,交给切换决策 + +2.切换决策 + +确定切换的时间决策,什么情况下从机就应该升级为主机,是进程不存在,是写操作不可这行,连续检测多少失败次就进行切换。应该选择哪一个从节点升级为主节点,一般来说或应该选同步步骤最大的从节点来进行升级。切换是自动切换还是半自动切换,通过报警方式,让人工做一次确认。 + +3.数据丢失和数据冲突 数据写到主机,还没有复制到从机主机就挂了,这个时候怎么处理,这个也得考虑业务的方式,是要确保CP或AP + +![数据存储高可用-数据主从切换](images/Solution/数据存储高可用-数据主从切换.jpg) + +还要考虑一个数据冲突的问题,这个问题在mysql中大部分是由自增主键引起,就算不考虑自增主键会引起数据冲突的问题,其实自增主键还要引起很多的问题,这里不细说,避免使用自增主键。 + + + +### 数据分片 + +上述的数据冗余可以通过数据的复制来进行解决,但是数据的扩张需要通过数据的分片来进行解决(如果在关系型数据库是分表)。 + +![数据存储高可用-数据分片](images/Solution/数据存储高可用-数据分片.jpg) + +何为数据分片(segment,fragment, shard, partition),就是按照一定的规则,将数据集划分成相互独立、正交的数据子集,然后将数据子集分布到不同的节点上。 + +HDFS , mongoDB 的sharding 模式也基本是基于这种分片的模式去实现,我们在设计分片主要考虑到的点是: + +- 做数据分片,如何将数据映射到节点 +- 数据分片的特征值,即按照数据中的哪一个属性(字段)来分片 +- 数据分片的元数据的管理,如何保证元数据服务器的高性能、高可用,如果是一组服务器,如何保证强一致性 + + + +## 柔性化/异步化 + +### 异步化 + +在每一次调用,时间越长存在超时的风险就越大,逻辑越复杂执行的步骤越多存在失败的风险也就越大,如果在业务允许的情况下,用户调用只给用户必须要的结果,而不是需要同步的结果可以放在另外的地方异步去操作,这就减少了超时的风险也把复杂业务进行拆分减低复杂度。当然异步化的好处是非常多,例如削封解耦等等,这里只是从可用的角度出发。异步化大致有这三种的实现方式: + +- 服务端接收到请求后,创建新的线程处理业务逻辑,服务端先回应答给客户端 + +![服务高可用-异步化方式一](images/Solution/服务高可用-异步化方式一.jpg) + +- 服务端接收到请求后,服务端先回应答给客户端,再继续处理业务逻辑 + +![服务高可用-异步化方式二](images/Solution/服务高可用-异步化方式二.jpg) + +- 服务端接收到请求后,服务端把信息保存在消息队列或者数据库,回应答给客户端,服务端业务处理进程再从消息队列或者数据库上读取信息处理业务逻辑 + +![服务高可用-异步化方式三](images/Solution/服务高可用-异步化方式三.jpg) + + + +### 柔性化 + +什么是柔性化,想象一个场景,我们的系统会给每个下单的用户增加他们下单金额对应的积分,当一个用户下单完毕后,我们给他增加积分的服务出现了问题,这个时候,我们是要取消掉这个订单还是先让订单通过,积分的问题通过重新或者报警来处理呢? + +所谓的柔性化,就是在我们业务中允许的情况下,做不到给予用户百分百可用的通过降级的手段给到用户尽可能多的服务,而不是非得每次都交出去要么100分或0分的答卷。 + +怎么去做柔性化,更多其实是对业务的理解和判断,柔性化更多是一种思维,需要对业务场景有深入的了解。 + +在电商订单的场景中,下单,扣库存,支付是一定要执行的步骤,如果失败则订单失败,但是加积分,发货,售后是可以柔性处理,就算出错也可以通过日志报警让人工去检查,没必要为加积分损失整个下单的可用性 + +![服务高可用-柔性化](images/Solution/服务高可用-柔性化.jpg) + + + +## 兜底/容错 + +兜底是可能我们经常谈论的是一种降级的方案,方案是用来实施,但是这里兜底可能更多是一种思想,更多的是一种预案,每个操作都可以犯错,我们也可以接受犯错,但是每个犯错我们都必须有一个兜底的预案,这个兜底的预案其实就是我们的容错或者说最大程度避免更大伤害的措施,实际上也是一个不断降级的过程。举个例子: + +![容错案例一](images/Solution/容错案例一.jpg) + +例如我们首页请求的用户个性化推荐商品的接口,发现推荐系统出错,我们不应该去扩大(直接把异常抛给用户)或保持调用接口的错误,而是应该兼容调用接口的错误,做到更加柔性化,这时候可以选择获取之前没有失败接口的缓存数据,如果没有则可以获取通用商品不用个性化推荐,如果也没有可以读取一些静态文字进行展示。 + +由于我们架构进行了分层,分成APP,网关,业务逻辑层,数据访问层等等,在组织结构也进行了划分,与之对应的是前端组,后端业务逻辑组,甚至有中台组等等。既然有代码和人员架构的划分层级,那么每一层都必须有这样的思想:包容下一层的错误,为上一层提供尽可能无措的服务。举个例子: + +![容错案例二](images/Solution/容错案例二.jpg) + +商品的美元售价假设要用商品人民币售价/汇率,这个时候错误发生在低层的数据层,上一层如果直接进行除,肯定就抛出 java.lang.ArithmeticException: / by zero,本着我们对任何一层调用服务都不可信的原则,应该对其进行容错处理,不能让异常扩散,更要保证我们这一层对上一次尽可能的作出最大努力确定的服务。 + + + +## 负载均衡 + +相信负载均衡这个话题基本已经深入每个做微服务开发或设计者的人心,负载均衡的实现有硬件和软件,硬件有F5,A10等机器,软件有LVS,nginx,HAProxy等等,负载均衡的算法有 random , RoundRobin , ConsistentHash等等。 + +### Nginx负载均衡故障转移 + +![Nginx负载均衡故障转移](images/Solution/Nginx负载均衡故障转移.jpg) + +**转移流程** + +nginx 根据给定好的负载均衡算法进行调度,当请求到tomcat1,nginx发现tomcat1出现连接错误(节点失效),nginx会根据一定的机制将tomcat1从调用的负载列表中清除,在下一次请求,nginx不会分配请求到有问题的tomcat1上面,会将请求转移到其他的tomcat之上。 + + + +**节点失效** + +nginx默认判断节点失效是以connect refuse和timeout为标准,在对某个节点进行fails累加,当fails大于max_fails时,该节点失效。 + + + +**节点恢复** + +当某个节点失败的次数大于max_fails时,但不超过fail_timeout,nginx将不在对该节点进行探测,直到超过失效时间或者所有的节点都失效,nginx会对节点进行重新探测。 + + + +### ZK负载均衡故障转移 + +![ZK负载均衡故障转移](images/Solution/ZK负载均衡故障转移.jpg) + +在使用ZK作为注册中心时,故障的发现是由Zk去进行发现,业务逻辑层通过watch的心跳机制将自己注册到zk上,网关对zk进行订阅就可以知道有多少可以调用的列表。当业务逻辑层在重启或者被关闭时就会跟zk断了心跳,zk会更新可调用列表。 + +使用zk作为负载均衡的协调器,最大的问题是zk对于服务是否可用是基于pingpong的方式,只要服务心跳存在,zk就认为服务是处在于可用状态,但是服务如果处在于假死的状态,zk是无从得知的。这个时候,业务逻辑服务是否真正可用只能够由网关知道。 + + + +**幂等设计** + +为何会牵出幂等设计的问题,主要是因为负载均衡的failover策略,就是对失败的服务会进行重试,一般来说,如果是读操作的服务,重复执行也不会出问题,但想象一下,如果是一个创建订单减库存的操作,第一次调用也tomcat1超时,再重新调用了tomcat2,这个时候我们都不能确认超时调用的tomcat1是否真的被调用,有可能根本就调用不成功,有可能已经调用成功但是因为某些原因返回超时而已,所以,很大程度这个接口会被调用2次。如果我们没有保证幂等性,就有可能一个订单导致了减少2次的库存。所谓的幂等性,就是得保证在同一个业务中,一个接口被调用了多次,其导致的结果都是一样的。 + + + +## 服务限流降级熔断 + +先来讲讲微服务中限流/熔断的目的是什么,微服务后,系统分布式部署,系统之间通过rpc框架通信,整个系统发生故障的概率随着系统规模的增长而增长,一个小的故障经过链路的传递放大,有可能会造成更大的故障。 + +限流跟高可用的关系是什么,假定我们的系统最多只能承受500个人的并发访问,但整个时候突然增加到1000个人进来,一下子就把整个系统给压垮了,本来还有500个人能享受到我们系统的服务,突然间变成了所有人都无法得到服务,与其让1000人都不法得到服务,不如就让500个人得到服务,拒绝掉另外500个人。限流是对访问的隔离,是保证了部门系统承受范围内用户的可用性。 + +熔断跟高可用的关系是什么,上面说了微服务是一个错综复杂的调用链关系,假设 模块A 调用 模块B , 模块B 又调用了 模块C , 模块C 调用了 模块D,这个时候,模块D 出了问题出现严重的时延,这个时候,整个调用链就会被 模块D 给拖垮,A 等B,B等C,C等D,而且A B C D的资源被锁死得不到释放,如果流量大的话还容易引起雪崩。熔断,主动丢弃 模块D 的调用,并在功能上作出一些降级才能保证到我们系统的健壮性。 熔断是对模块的隔离,是保证了最大功能的可用性。 + + + +## 服务治理 + +### 服务模块划分 + +服务模块与服务模块之间有着千丝万缕的关系,但服务模块在业务中各有权重,例如订单模块可能是一家电商公司的重中之重,如果出问题将会直接影响整个公司的营收,而一个后台的查询服务模块可能也重要,但它的重要等级绝对是没有像订单这么重要。所以,在做服务治理时,必须明确各个服务模块的重要等级,这样才能更好的做好监控,分配好资源。这个在各个公司有各个公司的一个标准,例如在电商公司,确定服务的级别可能会更加倾向对用用户请求数和营收相关的作为指标。 + +| 服务级别 | 服务模块 | +| -------- | ------------------------------------------------------ | +| 一级服务 | 支付系统 订单服务 商品服务 用户服务 发布系统 ... | +| 二级服务 | 消息服务 权限系统 CRM系统 积分系统 BI系统 评论系统 ... | +| 三级服务 | 后台日志系统 | + +可能真正的划分要比这个更为复杂,必须根据具体业务去定,这个可以从平时服务模块的访问量和流量去预估,往往更重要的模块也会提供更多的资源,所以不仅要对技术架构了如指掌,还要对公司各种业务形态了然于心才可以。 + +服务分级不仅仅在故障界定起到重要主要,而且决定了服务监控的力度,服务监控在高可用中起到了一个保障的作用,它不仅可以保留服务奔溃的现场以等待日后复盘,更重要的是它可以起到一个先知,先行判断的角色,很多时候可以预先判断危险,防范于未然。 + + + +### 服务监控 + +服务监控是微服务治理的一个重要环节,监控系统的完善程度直接影响到我们微服务质量的好坏,我们的微服务在线上运行的时候有没有一套完善的监控体系能去了解到它的健康情况,对整个系统的可靠性和稳定性是非常重要,可靠性和稳定性是高可用的一个前提保证。 + +服务的监控更多是对于风险的预判,在出现不可用之间就提前的发现问题,如果系统获取监控报警系统能自我修复则可以将错误消灭在无形,如果系统发现报警无法自我修复则可以通知人员提早进行接入。 + +一个比较完善的微服务监控体系需要涉及到哪些层次,如下图,大致可以划分为五个层次的监控 + +![高可用-服务监控](images/Solution/高可用-服务监控.jpg) + +**基础设施监控** + +例如网络,交换机,路由器等低层设备,这些设备的可靠性稳定性就直接影响到上层服务应用的稳定性,所以需要对网络的流量,丢包情况,错包情况,连接数等等这些基础设施的核心指标进行监控。 + + + +**系统层监控** + +涵盖了物理机,虚拟机,操作系统这些都是属于系统级别监控的方面,对几个核心指标监控,如cpu使用率,内存占用率,磁盘IO和网络带宽情况。 + + + +**应用层监控** + +例如对url访问的性能,访问的调用数,访问的延迟,还有对服务提供性能进行监控,服务的错误率,对sql也需要进行监控,查看是否有慢sql,对与cache来说,需要监控缓存的命中率和性能,每个服务的响应时间和qps等等。 + + + +**业务监控** + +比方说一个电商网站,需要关注它的用户登录情况,注册情况,下单情况,支付情况,这些直接影响到实际触发的业务交易情况,这个监控可以提供给运营和公司高管他们需需要关注的数据,直接可能对公司战略产生影响。 + + + +**端用户体验监控** + +用户通过浏览器,客户端打开练到到我们的服务,那么在用户端用户的体验是怎么样,用户端的性能是怎么样,有没有产生错误,这些信息也是需要进行监控并记录下来,如果没有监控,有可能用户的因为某些原因出错或者性能问题造成体验非常的差,而我们并没有感知,这里面包括了,监控用户端的使用性能,返回码,在哪些城市地区他们的使用情况是怎么样,还有运营商的情况,包括电信,联通用户的连接情况。我们需要进一步去知道是否有哪些渠道哪些用户接入的时候存在着问题,包括我们还需要知道客户端使用的操作系统浏览器的版本。 + + + +## 解决方案 + +### 冷备 + +冷备,通过停止数据库对外服务的能力,通过文件拷贝的方式将数据快速进行备份归档的操作方式。简而言之,冷备,就是复制粘贴,在linux上通过`cp`命令就可以很快完成。可以通过人为操作,或者定时脚本进行。有如下好处: + +- 简单 +- 快速备份(相对于其他备份方式) +- 快速恢复。只需要将备份文件拷贝回工作目录即完成恢复过程(亦或者修改数据库的配置,直接将备份的目录修改为数据库工作目录)。更甚,通过两次`mv`命令就可瞬间完成恢复。 +- 可以按照时间点恢复。比如,几天前发生的拼多多优惠券漏洞被人刷掉很多钱,可以根据前一个时间点进行还原,“挽回损失”。 + +以上的好处,对于以前的软件来说,是很好的方式。但是对于现如今的很多场景,已经不好用了,因为: + +- 服务需要停机。n个9肯定无法做到了。然后,以前我们的停机冷备是在凌晨没有人使用的时候进行,但是现在很多的互联网应用已经是面向全球了,所以,任何时候都是有人在使用的。 +- 数据丢失。如果不采取措施,那么在完成了数据恢复后,备份时间点到还原时间内的数据会丢失。传统的做法,是冷备还原以后,通过数据库日志手动恢复数据。比如通过redo日志,更甚者,我还曾经通过业务日志去手动回放请求恢复数据。恢复是极大的体力活,错误率高,恢复时间长。 +- 冷备是全量备份。全量备份会造成磁盘空间浪费,以及容量不足的问题,只能通过将备份拷贝到其他移动设备上解决。所以,整个备份过程的时间其实更长了。想象一下每天拷贝几个T的数据到移动硬盘上,需要多少移动硬盘和时间。并且,全量备份是无法定制化的,比如只备份某一些表,是无法做到的。 + +如何权衡冷备的利弊,是每个业务需要考虑的。 + + + +### 双机热备 + +热备,和冷备比起来,主要的差别是不用停机,一边备份一边提供服务。但还原的时候还是需要停机的。由于我们讨论的是和存储相关的,所以不将共享磁盘的方式看作双机热备。 + +#### Active/Standby模式 + +相当于1主1从,主节点对外提供服务,从节点作为backup。通过一些手段将数据从主节点同步到从节点,当故障发生时,将从节点设置为工作节点。数据同步的方式可以是偏软件层面,也可以是偏硬件层面的。偏软件层面的,比如mysql的master/slave方式,通过同步binlog的方式;sqlserver的订阅复制方式。偏硬件层面,通过扇区和磁盘的拦截等镜像技术,将数据拷贝到另外的磁盘。偏硬件的方式,也被叫做数据级灾备;偏软件的,被叫做应用级灾备。后文谈得更多的是应用级灾备。 + +#### 双机互备 + +本质上还是Active/Standby,只是互为主从而已。双机互备并不能工作于同一个业务,只是在服务器角度来看,更好的压榨了可用的资源。比如,两个业务分别有库A和B,通过两个机器P和Q进行部署。那么对于A业务,P主Q从,对于B业务,Q主P从。整体上看起来是两个机器互为主备。这种架构下,读写分离是很好的,单写多读,减少冲突又提高了效率。 + +其他的高可用方案还可以参考各类数据库的多种部署模式,比如mysql的主从、双主多从、MHA;redis的主从,哨兵,cluster等等。 + + + +### 同城双活 + +前面讲到的几种方案,基本都是在一个局域网内进行的。业务发展到后面,有了同城多活的方案。和前面比起来,不信任的粒度从机器转为了机房。这种方案可以解决某个IDC机房整体挂掉的情况(停电,断网等)。 + +同城双活其实和前文提到的双机热备没有本质的区别,只是“距离”更远了,基本上还是一样(同城专线网速还是很快的)。双机热备提供了灾备能力,双机互备避免了过多的资源浪费。 + +在程序代码的辅助下,有的业务还可以做到真正的双活,即同一个业务,双主,同时提供读写,只要处理好冲突的问题即可。需要注意的是,并不是所有的业务都能做到。 + +业界更多采用的是两地三中心的做法。远端的备份机房能更大的提供灾备能力,能更好的抵抗地震,恐袭等情况。双活的机器必须部署到同城,距离更远的城市作为灾备机房。灾备机房是不对外提供服务的,只作为备份使用,发生故障了才切流量到灾备机房;或者是只作为数据备份。原因主要在于:**距离太远,网络延迟太大**。 + +![两地三中心](images/Solution/两地三中心.jpg) + +如上图,用户流量通过负载均衡,将服务A的流量发送到IDC1,服务器集A;将服务B的流量发送到IDC2,服务器B;同时,服务器集a和b分别从A和B进行同城专线的数据同步,并且通过长距离的异地专线往IDC3进行同步。当任何一个IDC当机时,将所有流量切到同城的另一个IDC机房,完成了failover。当城市1发生大面积故障时,比如发生地震导致IDC1和2同时停止工作,则数据在IDC3得以保全。同时,如果负载均衡仍然有效,也可以将流量全部转发到IDC3中。不过,此时IDC3机房的距离非常远,网络延迟变得很严重,通常用户的体验的会受到严重影响的。 + +![两地三中心主从模式](images/solution/两地三中心主从模式.png) + +上图是一种基于Master-Slave模式的两地三中心示意图。城市1中的两个机房作为1主1从,异地机房作为从。也可以采用同城双主+keepalived+vip的方式,或者MHA的方式进行failover。但城市2不能(最好不要)被选择为Master。 + + + +### 异地双活 + +同城双活可以应对大部分的灾备情况,但是碰到大面积停电,或者自然灾害的时候,服务依然会中断。对上面的两地三中心进行改造,在异地也部署前端入口节点和应用,在城市1停止服务后将流量切到城市2,可以在降低用户体验的情况下,进行降级。但用户的体验下降程度非常大。 + +所以大多数的互联网公司采用了异地双活的方案。 + +![简单的异地双活示意图](images/Solution/简单的异地双活示意图.jpg) + +上图是一个简单的异地双活的示意图。流量经过LB后分发到两个城市的服务器集群中,服务器集群只连接本地的数据库集群,只有当本地的所有数据库集群均不能访问,才failover到异地的数据库集群中。 + +在这种方式下,由于异地网络问题,双向同步需要花费更多的时间。更长的同步时间将会导致更加严重的吞吐量下降,或者出现数据冲突的情况。吞吐量和冲突是两个对立的问题,你需要在其中进行权衡。例如,为了解决冲突,引入分布式锁/分布式事务;为了解决达到更高的吞吐量,利用中间状态、错误重试等手段,达到最终一致性;降低冲突,将数据进行恰当的sharding,尽可能在一个节点中完成整个事务。 + +对于一些无法接受最终一致性的业务,饿了么采用的是下图的方式: + +![饿了么异地双活方案](images/solution/饿了么异地双活方案.jpg) + +> 对于个别一致性要求很高的应用,我们提供了一种强一致的方案(Global Zone),Globa Zone是一种跨机房的读写分离机制,所有的写操作被定向到一个 Master 机房进行,以保证一致性,读操作可以在每个机房的 Slave库执行,也可以 bind 到 Master 机房进行,这一切都基于我们的数据库访问层(DAL)完成,业务基本无感知。 +> +> 《饿了么异地多活技术实现(一)总体介绍》 + +也就是说,在这个区域是不能进行双活的。采用主从而不是双写,自然解决了冲突的问题。 + +实际上,异地双活和异地多活已经很像了,双活的结构更为简单,所以在程序架构上不用做过多的考虑,只需要做传统的限流,failover等操作即可。但其实双活只是一个临时的步骤,最终的目的是切换到多活。因为双活除了有数据冲突上的问题意外,还无法进行横向扩展。 + + + +### 异地多活 + +![异地多活的示意图](images/Solution/异地多活的示意图.jpg) + +根据异地双活的思路,我们可以画出异地多活的一种示意图。每个节点的出度和入度都是4,在这种情况下,任何节点下线都不会对业务有影响。但是,考虑到距离的问题,一次写操作将带来更大的时间开销。时间开销除了影响用户体验以外,还带来了更多的数据冲突。在严重的数据冲突下,使用分布式锁的代价也更大。这将导致系统的复杂度上升,吞吐量下降。所以上图的方案是无法使用的。 + +回忆一下我们在解决网状网络拓扑的时候是怎么优化的?引入中间节点,将网状改为星状: + +![星状的异地多活](images/Solution/星状的异地多活.jpg) + +改造为上图后,每个城市下线都不会对数据造成影响。对于原有请求城市的流量,会被重新LoadBalance到新的节点(最好是LB到最近的城市)。为了解决数据安全的问题,我们只需要针对中心节点进行处理即可。但是这样,对于中心城市的要求,比其他城市会更高。比如恢复速度,备份完整性等,这里暂时不展开。我们先假定中心是完全安全的。 + + + +# 注册中心 + +注册中心主要是为分布式服务的发布与发现提供一层统一标准化的基础组件,便于使用者直接操作简单接口即可实现服务发布与发现功能。 + +## 服务注册 + +服务注册有两种形式:客户端注册和代理注册。 + +### 客户端注册 + +客户端注册是服务自己要负责注册与注销的工作。当服务启动后注册线程向注册中心注册,当服务下线时注销自己。 + +![注册中心-客户端注册](images/Solution/注册中心-客户端注册.png) + +这种方式缺点是注册注销逻辑与服务的业务逻辑耦合在一起,如果服务使用不同语言开发,那需要适配多套服务注册逻辑。 + + + +### 代理注册 + +代理注册由一个单独的代理服务负责注册与注销。当服务提供者启动后以某种方式通知代理服务,然后代理服务负责向注册中心发起注册工作。 + +![注册中心-代理注册](images/Solution/注册中心-代理注册.png) + +这种方式的缺点是多引用了一个代理服务,并且代理服务要保持高可用状态。 + + + +## 服务发现 + +服务发现也分为客户端发现和代理发现。 + +### 客户端发现 + +客户端发现是指客户端负责向注册中心查询可用服务地址,获取到所有的可用实例地址列表后客户端根据**负载均衡**算法选择一个实例发起请求调用。 + +![注册中心-客户端发现](images/Solution/注册中心-客户端发现.png) + +这种方式非常直接,客户端可以控制负载均衡算法。但是缺点也很明显,获取实例地址、负载均衡等逻辑与服务的业务逻辑耦合在一起,如果服务发现或者负载平衡有变化,那么所有的服务都要修改重新上线。 + + + +### 代理发现 + +代理发现是指新增一个路由服务负责服务发现获取可用的实例列表,服务消费者如果需要调用服务A的一个实例可以直接将请求发往路由服务,路由服务根据配置好的负载均衡算法从可用的实例列表中选择一个实例将请求转发过去即可,如果发现实例不可用,路由服务还可以自行重试,服务消费者完全不用感知。 + +![注册中心-代理发现](images/Solution/注册中心-代理发现.png) + + + +## 心跳机制 + +如果服务有多个实例,其中一个实例出现宕机,注册中心是可以实时感知到,并且将该实例信息从列表中移出,也称为摘机。如何实现摘机?业界比较常用的方式是通过心跳检测的方式实现,心跳检测有**主动**和**被动**两种方式。 + +### 被动检测 + +**被动检测**是指服务主动向注册中心发送心跳消息,时间间隔可自定义,比如配置5秒发送一次,注册中心如果在三个周期内比如说15秒内没有收到实例的心跳消息,就会将该实例从列表中移除。 + +![注册中心-心跳机制-被动检测](images/Solution/注册中心-心跳机制-被动检测.png) + +上图中服务A的实例2已经宕机不能主动给注册中心发送心跳消息,15秒之后注册就会将实例2移除掉。 + + + +### 主动检测 + +**主动检测**是注册中心主动发起,每隔几秒中会给所有列表中的服务实例发送心跳检测消息,如果多个周期内未发送成功或未收到回复就会主动移除该实例。 + +![注册中心-心跳机制-主动检测](images/Solution/注册中心-心跳机制-主动检测.png) + + + +## Dubbo注册中心 + +### 核心功能 + +针对使用者,主要关注注册中心的以下五个核心接口,通过以下五个核心接口,可以实现服务的动态发布、动态发现以及优雅下线等操作: + +- **注册服务**:将服务提供者的URL暴露至注册中心 +- **注销服务**:从注册中心将服务提供者的地址进行摘除 +- **订阅服务**:将从注册中心订阅需要消费的URL至本地,当注册中心的服务清单发展变化时,自动通知订阅者 +- **退订服务**:取消向注册中心订阅的服务监听者 +- **查找服务**:向注册中心查找指定的服务清单列表 + + + +### 生产特性 + +Zookeeper会有一些未知的问题出现,所以需要生产特性来应对各种可能出现的问题,从而提升产品的质量。该类功能针对使用者一般情况是没办法直接感受到,但其发挥的作用就是保障产品的高质量服务: + +- **自动重连**:与Zookeeper或Redis的连接断开后,支持自动重新连接 +- **自动切换**:与Zookeeper或Redis的连接失败后,支持自动切换 +- **自动清理**:Zookeeper或Redis中的数据过期后,支持自动清理(超时自动摘除) +- **自动恢复**:与Zookeeper或Redis自动重连成功后,支持自动恢复(即再次发起注册、订阅或监听动作) +- **自动重试**:失败自动重试(注册、注销、订阅、退订、监听和取消监听失败,无限重试至成功为止) +- **自动缓存**:Client订阅的服务清单会自动离线缓存至JVM本地的文件(变更通知时更新) + + + +## 业界方案 + +下面结合各个维度对比一下各组件: + +| 组件 | 优点 | 缺点 | 访问协议 | 一致性算法 | +| :-------- | :----------------------------------------------------------- | :----------------------------------------------------------- | :------- | :---------- | +| Zookeeper | 1.功能强大,不仅仅只是服务发现;2.提供watcher机制可以实时获取服务提供者的状态;3.广泛使用,dubbo等微服务框架已支持 | 1.没有健康检查;2.需要在服务中引入sdk,集成复杂度高;3.不支持多数据中心 | TCP | Paxos(CP) | +| Consul | 1.开箱即用,方便集成;2.带健康检查;3.支持多数据中心;4.提供web管理界面 | 不能实时获取服务变换通知 | HTTP/DNS | Raft(CP) | +| Nacos | 1.开箱即用,适用于dubbo,spring cloud等;2.AP模型,数据最终一致性;3.注册中心,配置中心二合一(二合一也不一定是优点),提供控制台管理;4.纯国产,各种有中文文档,久经双十一考验 | 刚刚开源不久,社区热度不够,依然存在bug | HTTP/DNS | CP+AP | + + + +### Zookeeper + +![Zookeeper](images/Solution/zookeeper注册中心.jpg) + + + +### Consul + +Consul是HashiCorp公司推出的开源工,使用Go语言开发,具有开箱即可部署方便的特点。Consul是分布式的、高可用的、 可横向扩展的用于实现分布式系统的服务发现与配置。 + + + +**Consul有哪些优势?** + +- 服务注册发现:Consul提供了通过DNS或者restful接口的方式来注册服务和发现服务。服务可根据实际情况自行选择 +- 健康检查:Consul的Client可以提供任意数量的健康检查,既可以与给定的服务相关联,也可以与本地节点相关联 +- 多数据中心:Consul支持多数据中心,这意味着用户不需要担心Consul自身的高可用性问题以及多数据中心带来的扩展接入等问题 + + + +**Consul的架构图** + +![Consul的架构图](images/Solution/Consul的架构图.png) + +Consul 实现多数据中心依赖于gossip protocol协议。这样做的目的: + +- 不需要使用服务器的地址来配置客户端;服务发现是自动完成的 +- 健康检查故障的工作不是放在服务器上,而是分布式的 + + + +**Consul的使用场景** + +Consul的应用场景包括**服务注册发现**、**服务隔离**、**服务配置**等。 + +- **服务注册发现场景**中consul作为注册中心,服务地址被注册到consul中以后,可以使用consul提供的dns、http接口查询,consul支持health check + +- **服务隔离场景**中consul支持以服务为单位设置访问策略,能同时支持经典的平台和新兴的平台,支持tls证书分发,service-to-service加密 + +- **服务配置场景**中consul提供key-value数据存储功能,并且能将变动迅速地通知出去,借助Consul可以实现配置共享,需要读取配置的服务可以从Consul中读取到准确的配置信息 + + + +### Nacos + +![Nacos](images/Solution/Nacos.png) + + + +# API网关 + +**API网关是什么?** + +API网关是随着微服务(Microservice)概念兴起的一种架构模式。原本一个庞大的单体应用(All in one)业务系统被拆分成许多微服务(Microservice)系统进行独立的维护和部署,服务拆分带来的变化是API的规模成倍增长,API的管理难度也在日益增加,使用API网关发布和管理API逐渐成为一种趋势。一般来说,API网关是运行于外部请求与内部服务之间的一个流量入口,实现对外部请求的协议转换、鉴权、流控、参数校验、监控等通用功能。 + + + +**为什么要做API网关?** + +在没有API网关之前,业务研发人员如果要将内部服务输出为对外的HTTP API接口。通常要搭建一个Web应用,用于完成基础的鉴权、限流、监控日志、参数校验、协议转换等工作,同时需要维护代码逻辑、基础组件的升级,研发效率相对比较低。此外,每个Web应用都需要维护机器、配置、数据库等,资源利用率也非常差。 + +![API网关-为什么做](images/Solution/API网关-为什么做.png) + + + +**Tomcat自身问题** + +- **缓存太多**。Tomcat用了很多对象池技术,内存有限的情况下,流量一高很容易触发gc + +- **内存copy**。Tomcat默认用堆内存,所以数据需要读到堆内,而后端服务是Netty,有堆外内存,需要通过数次copy + +- **Tomcat读body是阻塞的**。Tomcat的NIO模型和Reactor模型不一样,读body是Block的 +- **Tomcat对链接重用的次数是有限制的**。默认是100次,当达到100次后,Tomcat会通过在响应头里添加Connection:close,让客户端关闭该链接,否则如果再用该链接发送的话,会出现400 + +**Tomcat Buffer** + +Tomcat buffer 的关系图如下: + +![TomcatBuffer](images/Solution/TomcatBuffer.png) + +通过上面的图,我们可以看出,Tomcat 对外封装的很好,内部默认的情况下会有三次 copy。 + + + +## 基本功能 + +- **反向代理**:类似于Nginx效果,实现外部HTTP请求反向代理转为内部RPC请求进行转发 +- **动态发现**:加入后端微服务中心,实现动态发现后端服务实例 +- **负载均衡**:根据后端服务的实例列表进行负载均衡分配 +- **服务路由**:可以根据请求URL中的参数进行不同服务的调用路由 + + + +## 功能设计 + +### API发布 + +使用API网关的控制面,业务研发人员可以轻松的完成API的全生命周期管理,如下图所示: + +![API网关-API生命周期管理](images/Solution/API网关-API生命周期管理.png) + +业务研发人员从创建API开始,完成参数录入、DSL脚本生成;接着可以通过文档和MOCK功能进行API测试;API测试完成后,为了保证上线稳定性,管理平台提供了发布审批、灰度上线、版本回滚等一系列安全保证措施;API运行期间会监控API的调用失败情况、记录请求日志,一旦发现异常及时发出告警;最后,对于不再使用的API进行下线操作后,会回收API所占用的各类资源并等待重新启用。整个生命周期,全部通过配置化、流程化的方式,由业务研发人员全自助管理,上手时间基本在10分钟以内,极大地提升了研发效率。 + + + +### 配置中心 + +API网关的配置中心存放API的相关配置信息——使用自定义的DSL(Domain-Specific Language,领域专用语言)来描述,用于向API网关的数据面下发API的路由、规则、组件等配置变更。配置中心的设计上使用统一配置管理服务和本地缓存结合的方式,实现动态配置,不停机发布。API的配置如下图所示: + +![API网关-配置中心](images/Solution/API网关-配置中心.png) + +API配置的详细说明 + +- **Name、Group**:名字、所属分组 +- **Request**:请求的域名、路径、参数等信息 +- **Response**:响应的结果组装、异常处理、Header、Cookies信息 +- **Filters、FilterConfigs**:API使用到的功能组件和配置信息 +- **Invokers**:后端服务(RPC/HTTP/Function)的请求规则和编排信息 + + + +### API路由 + +API网关的数据面在感知到API配置后,会在内存中建立请求路径与API配置的路由信息。通常HTTP请求路径上,会包含一些路径变量,考虑到性能问题,没有采用正则匹配的方式,而是设计了两种数据结构来存储。如下图所示: + +![API网关-API路由](images/Solution/API网关-API路由.png) + +一种是不包含路径变量的直接映射的MAP结构。其中,Key就是完整的域名和路径信息,Value是具体的API配置。 + +另外一种是包含路径变量的前缀树数据结构。通过前缀匹配的方式,先进行叶子节点精确查找,并将查找节点入栈处理,如果匹配不上,则将栈顶节点出栈,再将同级的变量节点入栈,如果仍然找不到,则继续回溯,直到找到(或没找到)路径节点并退出。 + + + +### 功能组件 + +当请求流量命中API请求路径进入服务端,具体处理逻辑由DSL中配置的一系列功能组件完成。网关提供了丰富的功能组件集成,包括链路追踪、实时监控、访问日志、参数校验、鉴权、限流、熔断降级、灰度分流等,如下图所示: + +![API网关-功能组件](images/Solution/API网关-功能组件.png) + + + +### 协议转换&服务调用 + +API调用的最后一步,就是协议转换以及服务调用了。网关需要完成的工作包括:获取HTTP请求参数、Context本地参数,拼装后端服务参数,完成HTTP协议到后端服务的协议转换,调用后端服务获取响应结果并转换为HTTP响应结果。 + +![API网关-协议转换&服务调用](images/Solution/API网关-协议转换&服务调用.png) + +上图以调用后端RPC服务为例,通过JsonPath表达式获取HTTP请求不同部位的参数值,替换RPC请求参数相应部位的Value,生成服务参数DSL,最后借助RPC泛化调用完成本次服务调用。 + + + +### 高性能设计 + +![API网关-高性能设计](images/Solution/API网关-高性能设计.png) + + + +### 稳定性保障 + +提供了一些常规的稳定性保障手段,来保证自身和后端服务的可用性。如下图所示: + +![API网关-稳定性保障](images/Solution/API网关-稳定性保障.png) + +- **流量管控**:从用户自定义UUID限流、App限流、IP限流、集群限流等多个维度提供流量保护 +- **请求缓存**:对于一些幂等的、查询频繁的、数据及时性不敏感的请求,业务研发人员可开启请求缓存功能 +- **超时管理**:每个API都设置了处理超时时间,对于超时的请求,进行快速失败的处理,避免资源占用 +- **熔断降级**:支持熔断降级功能,实时监控请求的统计信息,达到配置的失败阈值后,自动熔断,返回默认值 + + + +### 故障自愈 + +API网关服务端接入了弹性伸缩模块,可根据CPU等指标进行快速扩容、缩容。除此之外,还支持快速摘除问题节点,以及更细粒度的问题组件摘除。 + +![API网关-故障自愈](images/Solution/API网关-故障自愈.png) + + + +### 可迁移 + +对于一些已经在对外提供API的Web服务,业务研发人员为了减少运维成本和后续的研发提效,考虑将其迁移到API网关。对于一些非核心API,可以考虑使用灰度发布功能直接迁移。但是对于一些核心API,上面的灰度发布功能是机器级别的,粒度较大,不够灵活,不能很好的支持灰度验证过程。 + + + +**解决方案** + +API网关为业务研发人员提供了一个灰度SDK,接入SDK的Web服务,可在识别灰度流量后转发到API网关进行验证。灰度哪些API、灰度百分比可以在API网关管理端动态调节,实时生效,业务研发人员还可以通过SPI的方式自定义灰度策略。灰度验证通过后,再把API迁移到API网关,保障迁移过程的稳定性。 + + + +**灰度过程** + +**灰度前**:在API网关管理平台创建API分组,域名配置为目前使用的域名。在Oceanus上,原域名规则不变。 + +![API网关-灰度前](images/Solution/API网关-灰度前.png) + +**灰度中**:在API网关管理平台开启灰度功能,灰度SDK将灰度流量转发到网关服务,进行验证。 + +![API网关-灰度中](images/Solution/API网关-灰度中.png) + +**灰度后**:通过灰度流量验证API网关上的API配置符合预期后再迁移。 + +![API网关-灰度后](images/Solution/API网关-灰度后.png) + + + +### 自动生成DSL + +业务研发人员在实际使用网关管理平台时,我们尽量通过图形化的页面配置来减轻DSL的编写负担。但服务参数转换的DSL配置,仍然需要业务研发人员手工编写。一般来说,生成服务参数DSL的流程是: + +- 引入服务的接口包依赖 +- 拿到服务参数类定义 +- 编写Testcase生成JSON模板 +- 填写参数映射规则 +- 最后手工录入管理平台,发布API + +整个过程非常繁琐,且容易出错。如果需要录入的API多达几十上百个,全部由业务研发人员手工录入的效率是非常低下的。 + + + +**解决方案** + +那么能不能将服务参数DSL的生成过程给自动化呢? 答案是可以的,业务RD只需在网关录入API文档信息,然后录入服务的Appkey、服务名、方法名信息,API网关管理端会从最新发布的服务框架控制台获取到服务参数的JSON Schema信息,JSON Schema定义了服务参数的类型和结构信息,管理端可根据这些信息,自动生成服务参数的JSON Mock数据。结合API文档的信息,自动替换参数名相同的Value值。 这套DSL自动生成方案,使用过程中对业务透明、标准化,业务方只需升级最新版本服务框架即可使用,极大提升研发效率,目前受到业务研发人员的广泛好评。 + +![API网关-自动生成DSL](images/Solution/API网关-自动生成DSL.png) + + + +### API操作提效 + +**快速创建API** + +API网关的核心能力是建立在API配置的基础上的,但提供强大功能的同时带来了较高的复杂性,不少业务研发人员吐槽API配置太繁琐,学习成本高。快速创建API的功能应运而生,业务研发人员只需要提供少量的信息就可以创建API。快速创建API的功能当前分为4种类型(后端RPC服务API、后端HTTP服务API、SSO CallBack API、Nest API),未来会根据业务应用场景的不同,提供更多的快速创建API类型。 + + + +**批量操作** + +业务研发人员在API网关上,需要管理非常多的业务分组,每个业务分组,最多可以有200个API配置,多个API可能有很多相同的配置,如组件配置,错误码配置和跨域配置的。每个API对于相同的配置都要配置一遍,操作重复度很高。因此API网关支持批量操作多个API:勾选多个API后,通过【批量操作】功能可一次性完成多个API配置更新,降低业务重复配置的操作成本。 + + + +**API导入导出** + +API网关提供在不同研发环境相互导入导出API的能力,业务研发人员在线下测试完成后,只需要使用API导入导出功能,即可将配置导出到线上生产环境,避免重复配置。 + + + +### 自定义组件 + +API网关提供了丰富的系统组件完成鉴权、限流、监控能力,能够满足大部分的业务需求。但仍有一些特殊的业务需求,如自定义验签、自定义结果处理等。API网关通过提供加载自定义组件能力,支持业务完成一些自定义逻辑的扩展。下图是自定义组件实现的一个实例。getName中填写自定义组件申请时的名称,invoke方法中实现自定义组件的业务逻辑,如继续执行、进行页面跳转、直接返回结果、抛出异常等。 + +![API网关-自定义组件](images/Solution/API网关-自定义组件.png) + + + +### 服务编排 + +一般情况下,网关上配置的一个API对应后端一个RPC或者HTTP服务。如果调用端有聚合和编排后端服务的需求,那么有多少后端服务,就必须发起多少次HTTP的请求调用。由此就会带来一些问题,调用端的HTTP请求次数过多,效率低,在调用端聚合服务的逻辑过重。 + +服务编排的需求应运而生,服务编排是对既有服务进行编排调用,同时对获取的数据进行处理。主要应用在数据聚合场景:一次HTTP请求返回的数据需要调用多个或多次服务(RPC或HTTP)才能获取到完整的结果。 + +通过独立部署的方式提供服务编排能力,API网关与服务编排服务之间通过RPC进行调用。这样可以解耦API网关与服务编排服务,避免因服务编排能力影响集群上的其他服务,同时多一次RPC调用并不会有明显耗时增加。使用上对业务研发人员也是透明的,非常方便,业务研发人员在管理端配置好服务编排的API,通过配置中心同时下发到API网关服务端和服务编排服务上,即可开始使用服务编排能力。整体的交互架构图如下: + +![API网关-服务编排](images/Solution/API网关-服务编排.png) + + + +## 流量治理 + +### API鉴权 + +请求安全是API网关非常重要的能力,集成了丰富的安全相关的系统组件,包括有基础的请求签名、SSO单点登录、基于SSO鉴权的UAC/UPM访问控制、用户鉴权Passport、商家鉴权EPassport、商家权益鉴权、反爬等等。业务研发人员只需要简单配置,即可使用。 + + + +### 黑白名单 + +### 流量控制 + +### 熔断器 + +### 服务降级 + +### 流量调度 + +### 流量Copy + +### 流量预热 + + + +### 集群隔离 + +API网关按业务线维度进行集群隔离,也支持重要业务独立部署。如下图所示: + +![API网关-集群隔离](images/Solution/API网关-集群隔离.png) + + + +### 请求隔离 + +服务节点维度,API网关支持请求的快慢线程池隔离。快慢线程池隔离主要用于一些使用了同步阻塞组件的API,例如SSO鉴权、自定义鉴权等,可能导致长时间阻塞共享业务线程池。快慢隔离的原理是统计API请求的处理时间,将请求处理耗时较长,超过容忍阈值的API请求隔离到慢线程池,避免影响其他正常API的调用。除此之外,也支持业务研发人员配置自定义线程池进行隔离。具体的线程隔离模型如下图所示: + +![API网关-请求隔离](images/Solution/API网关-请求隔离.png) + + + +### 灰度发布 + +API网关作为请求入口,往往肩负着请求流量灰度验证的重任。 + +**灰度场景** + +在灰度能力上,支持灰度API自身逻辑,也支持灰度下游服务,也可以同时灰度API自身逻辑和下游服务。如下图所示: + +![API网关-灰度场景](images/Solution/API网关-灰度场景.png) + +灰度API自身逻辑时,通过将流量分流到不同的API版本实现灰度能力;灰度下游服务时,通过给流量打标,分流到指定的下游灰度单元中。 + + + +**灰度策略** + +支持丰富的灰度策略,可以按照比例数灰度,也可以按照特定条件灰度。 + + + +## 监控告警 + +### 立体化监控 + +API网关提供360度的立体化监控,从业务指标、机器指标、JVM指标提供7x24小时的专业守护,如下表: + +| | 监控模块 | 主要功能 | +| :--- | :-------------- | :----------------------------------------------------------- | +| 1 | 统一监控Raptor | 实时上报请求调用信息、系统指标,负责应用层(JVM)监控、系统层(CPU、IO、网络)监控 | +| 2 | 链路追踪Mtrace | 负责全链路参数透传、全链路追踪监控 | +| 3 | 日志监控Logscan | 监控本地日志异常关键字:如5xx状态码、空指针异常等 | +| 4 | 远程日志中心 | API请求日志、Debug日志、组件日志等可上报远程日志中心 | +| 5 | 健康检查Scanner | 对网关节点进行心跳检测和API状态检测,及时发现异常节点和异常API | + + + +### 多维度告警 + +有了全面的监控体系,自然少不了配套的告警机制,主要的告警能力包括: + +| | 告警类型 | 触发时机 | +| :--- | :--------------- | :-------------------------------------------------- | +| 1 | 限流告警 | API请求达到限流规则阈值触发限流告警 | +| 2 | 请求失败告警 | 鉴权失败、请求超时、后端服务异常等触发请求失败告警 | +| 3 | 组件异常告警 | 自定义组件处理耗时长、失败率高告警 | +| 4 | API异常告警 | API发布失败、API检查异常时触发API异常告警 | +| 5 | 健康检查失败告警 | API心跳检查失败、网关节点不通时触发健康检查失败告警 | + + + +## 关键设计 + +### 异步外调 + +![API网关-异步外调](images/Solution/API网关-异步外调.png) + +基于Netty实现异步外调主要有两种方式可以实现: + +- **方式一:建立全局Map,上线文传递(不参与远程传输)requestId,响应时使用requestId进行映射上游信息** +- **方式二:直接将上游信息包装成Context进行上线文传递(不参与远程传输)** + +方式一需要独立维护一个全局映射表,同时需要考虑请求超时和丢失的情况,否则会出现内存不断增长问题。 + + + +### 外调链接池化 + +![API网关-外调链接池化](images/Solution/API网关-外调链接池化.png) + +使用Netty实现API网关外调微服务时,因建立连接需要极度消耗资源,所以需要考虑将外调的链接进行池化管理,设计时需要注意以下几点: + +- **初始化适当连接(过多过少都不适合)** +- **考虑连接能随流量增减而进行自动扩缩容** +- **取出的连接需要检查是否可用** +- **连接需要考虑双向心跳探测** + + + +### 释放连接 + +http的链接是独占的,所以在释放的时候要特别小心,一定要等服务端响应完了才能释放,还有就是链接关闭的处理也要小心,总结如下几点: + +- Connection:close +- 空闲超时,关闭链接 +- 读超时关闭链接 +- 写超时,关闭链接 +- Fin,Reset + + + +- **写超时**:writeAndFlush包含Netty的encode时间和从队列里把请求发出去即flush的时间。因此后端超时开始需要在真正flush成功后开始计时,这样才最接近服务端超时时间(还有网络往返时间和内核协议栈处理时间) + + + +### 对象池化设计 + +针对高并发系统,频繁创建对象不仅有分配内存开销,还对gc会造成压力。因此在实现时,会对频繁使用的对象(如线程池的任务task,StringBuffer等)进行重写,减少频繁的申请内存的开销。 + + + +### 上下文切换 + +![API网关-上下文切换](images/Solution/API网关-上下文切换.png) + +整个网关没有涉及到IO操作,但在IO编解码和业务逻辑都用了异步,是有两个原因 + +- 防止开发写的代码有阻塞 +- 业务逻辑打日志可能会比较多 + +在突发的情况下,但是我们在push线程时,支持用Netty的IO线程替代,这里做的工作比较少,这里由异步修改为同步后(通过修改配置调整),CPU的上下文切换减少20%,进而提高了整体的吞吐量,就是不能为了异步而异步,Zuul2的设计类似。 + + + +### 监控告警 + +**协议层** + +- **攻击性请求**。只发头,不发/发部分body,采样落盘,还原现场,并报警 +- **Line or Head or Body过大的请求**。采样落盘,还原现场,并报警 + + + +**应用层** + +- **耗时监控**。有慢请求,超时请求,以及tp99,tp999等 +- **QPS监控和报警** +- **带宽监控和报警**。支持对请求和响应的行、头、body单独监控 +- **响应码监控**。特别是400和404 +- **链接监控**。对接入端的链接,以及和后端服务的链接,后端服务链接上待发送字节大小也都做了监控 +- **失败请求监控** +- **流量抖动报警**。流量抖动要么是出了问题,要么就是出问题的前兆 + + + +## 解决方案 + +![APIGateway产品](images/Solution/APIGateway产品.png) + +### Shepherd API网关 + +![ShepherdAPI网关](images/Solution/ShepherdAPI网关.png) + + + +### Mashape Kong + +**访问地址**:https://github.com/Kong/kong + +![Kong](images/Solution/kong.png) + + + +### Soul + +**访问地址**:https://github.com/Dromara/soul + +![Soul](images/Solution/Soul.png) + + + +### Apiman + +**访问地址**:https://apiman.gitbooks.io/apiman-user-guide/user-guide/gateway/policies.html + +![apiman](images/Solution/apiman.png) + + + +### Gravitee + +**访问地址**:https://docs.gravitee.io/apim_policies_latency.html + +![Gravitee](images/Solution/Gravitee.png) + + + +### Tyk + +**访问地址**:https://tyk.io/docs + + + +### Traefik + +**访问地址**:https://traefik.cn + +Træfɪk 是一个为了让部署微服务更加便捷而诞生的现代HTTP反向代理、负载均衡工具。 它支持多种后台 (Docker, Swarm, Kubernetes, Marathon, Mesos, Consul, Etcd, Zookeeper, BoltDB, Rest API, file…) 来自动化、动态的应用它的配置文件设置。 + +![traefik](images/Solution/traefik.png) + +**功能特性** + +- 它非常快 +- 无需安装其他依赖,通过Go语言编写的单一可执行文件 +- 支持 Rest API +- 多种后台支持:Docker, Swarm, Kubernetes, Marathon, Mesos, Consul, Etcd, 并且还会更多 +- 后台监控, 可以监听后台变化进而自动化应用新的配置文件设置 +- 配置文件热更新。无需重启进程 +- 正常结束http连接 +- 后端断路器 +- 轮询,rebalancer 负载均衡 +- Rest Metrics +- 支持最小化官方docker 镜像 +- 后台支持SSL +- 前台支持SSL(包括SNI) +- 清爽的AngularJS前端页面 +- 支持Websocket +- 支持HTTP/2 +- 网络错误重试 +- 支持Let’s Encrypt (自动更新HTTPS证书) +- 高可用集群模式 + + + +### 小豹API网关 + +**访问地址**:http://www.xbgateway.com + +小豹API网关(企业级API网关),统一解决:认证、鉴权、安全、流量管控、缓存、服务路由,协议转换、服务编排、熔断、灰度发布、监控报警等。 + +![小豹API网关架构](images/Solution/小豹API网关架构.png) + + + +### Others + +- Orange:http://orange.sumory.com +- gateway:https://github.com/fagongzi/gateway + + + +# 服务编排 + +## DSL设计 + +为了实现服务编排,需要定义一个数据结构来描述服务之间的依赖关系、调用顺序、调用服务的入参和出参等等。之后对获取的结果进行处理,也需要在这个数据结构中具体描述对什么样的数据进行怎么样的处理等等。所以需要定义一套DSL(领域特定语言)来描述整个服务编排的蓝图。 + + + +## 架构设计 + +![服务编排](images/Solution/服务编排.png) + +- Facade:对外提供统一接口,供客户端调用 +- Parser:对于输入的DSL进行解析,解析成内部流转的数据结构,同时得到所有的task,并且构建task调用逻辑树 +- Executor:真实发起调用的模块,目前支持平台内部的RPC和HTTP调用方式,同时对HTTP等其它协议有良好的扩展性 +- DataProcessor:数据后处理。这边会把所有接口拿到的数据转换层客服场景这边需要的数据,并且通过设计的一些内部函数,可以支持一些如数据半脱敏等功能 +- 组件插件化:对日志等功能实现可插拔,调用方可以自定义这些组件,即插即用 + + + +## 主要特点 + +主要特点如下: + +- 采用去中心化设计思路,引擎集成在SDK中。方案通用化,每个需要业务数据的场景都可以通过框架直接调用数据提供方 +- 服务编排支持并行和串行调用,使用方可以根据实际场景自己构造服务调用树。通过DSL的方式把之前硬编码组装的逻辑实现了配置化,然后通过框架引擎把能并行调用的服务都执行了并行调用,数据使用方不用再自己处理性能优化 +- 使用JSON DSL 描述整个工作蓝图,简单易学 +- 支持JSONPath语法对服务返回的结果进行取值 +- 支持内置函数和自定义指令(语法参考ftl)对取到的元数据进行处理,得到需要的最终结果 +- 编排服务树可视化 + + + +# 服务降级 + +降级是指将系统中的某些业务或接口的功能降低,只提供部分功能或完全停掉所有功能。 + +什么是服务降级?当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。服务降级的设计架构图如下: + +![ServiceDowngrade](images/Solution/ServiceDowngrade.png) + +**使用场景** + +服务降级主要用于什么场景呢?当整个微服务架构整体的负载超出了预设的上限阈值或即将到来的流量预计将会超过预设的阈值时,为了保证重要或基本的服务能正常运行,我们可以将一些 **不重要** 或 **不紧急** 的服务或任务进行服务的 **延迟使用** 或 **暂停使用**。 + + + +**服务降级要考虑的问题** + +- 核心和非核心服务 +- 是否支持降级,降级策略 +- 业务放通的场景,策略 + + + +# 断路器 + +熔断机制是应对雪崩效应的一种微服务链路保护机制。当链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。服务断路器的设计架构图如下: + +![CircuitBreaker](images/Solution/CircuitBreaker.png) + + + +## 断路器状态 + +服务调用方为每一个调用服务 (调用路径) 维护一个状态机,在这个状态机中有`3`种状态: + +- `CLOSED`:默认状态。断路器观察到请求失败比例没有达到阈值,断路器认为被代理服务状态良好 +- `OPEN`:断路器观察到请求失败比例已经达到阈值,断路器认为被代理服务故障,打开开关,请求不再到达被代理的服务,而是快速失败 +- `HALF OPEN`:断路器打开后,为了能自动恢复对被代理服务的访问,会切换到半开放状态,去尝试请求被代理服务以查看服务是否已经故障恢复。如果成功,会转成`CLOSED`状态,否则转到`OPEN`状态 + + + +## 常见问题 + +使用断路器需要考虑一些问题: + +- 针对不同的异常,定义不同的熔断后处理逻辑 +- 设置熔断的时长,超过这个时长后切换到`HALF OPEN`进行重试 +- 记录请求失败日志,供监控使用 +- 主动重试,比如对于`connection timeout`造成的熔断,可以用异步线程进行网络检测,比如`telenet`,检测到网络畅通时切换到`HALF OPEN`进行重试 +- 补偿接口,断路器可以提供补偿接口让运维人员手工关闭 +- 重试时,可以使用之前失败的请求进行重试,但一定要注意业务上是否允许这样做 + + + +## 使用场景 + +- 服务故障或者升级时,让客户端快速失败 +- 失败处理逻辑容易定义 +- 响应耗时较长,客户端设置的`read timeout`会比较长,防止客户端大量重试请求导致的连接、线程资源不能释放 + + + +# 链路追踪 + +![链路追踪-开源组件对比](images/Solution/链路追踪-开源组件对比.png) + +## ThreadContext + +`NDC`(Nested Diagnostic Context)和`MDC`(Mapped Diagnostic Context)是`log4j`种非常有用的两个类,它们用于存储应用程序的上下文信息(Context Infomation),从而便于在`log`中使用这些上下文信息。`NDC`采用了一个类似栈的机制来push和pop上下文信息,每一个线程都独立地储存上下文信息。比如说一个servlet就可以针对每一个request创建对应的NDC,储存客户端地址等等信息。`MDC`和`NDC`非常相似,所不同的是`MDC`内部使用了类似map的机制来存储信息,上下文信息也是每个线程独立地储存,所不同的是信息都是以它们的key值存储在”map”中。 + +NDC和MDC的原理是用了java的ThreadLocal类。可以针对不同线程存储信息。但是今天在log4j2上使用时发现没有找到NDC和MDC。查找官方文档,原来是换成了ThreadContext。 + +微服务架构中的链路追踪主要是用于快速定位故障点,使用MDC方式来将每一笔交易产生的所有日志都添加上请求ID,从而只需要该ID即可将整个架构中的所有有关日志收集至一出进行分析定位具体问题。 + +### NDC + +NDC采用栈的机制存储上下文,线程独立的,子线程会从父线程拷贝上下文。其调用方法以下: + +- 开始调用 + + `NDC.push(message);` + +- 删除栈顶消息 + + `NDC.pop();` + +- 清除所有的消息,必须在线程退出前显示的调用,不然会致使内存溢出。 + + `NDC.remove();` + +- 输出模板,注意是小写的 `[%x]` + + `log4j.appender.stdout.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ssS}] [%x] : %m%n` + + + +### MDC + +MDC采用Map的方式存储上下文,线程独立的,子线程会从父线程拷贝上下文。其调用方法以下: + +- 保存信息到上下文 + + `MDC.put(key, value);` + +- 从上下文获取设置的信息 + + `MDC.get(key);` + +- 清楚上下文中指定的key的信息 + + `MDC.remove(key);` + +- 清除全部 + + `clear();` + +- 输出模板,注意是大写 `[%X{key}]` + + `log4j.appender.consoleAppender.layout.ConversionPattern = %-4r [%t] %5p %c %x - %m - %X{key}%n` + +在 `log4j 1.x` 中 `MDC` 的使用方式如下: + +```java + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + try { + // 填充数据 + MDC.put(Contents.REQUEST_ID, UUID.randomUUID().toString()); + chain.doFilter(request, response); + } finally { + // 请求结束时清除数据,否则会造成内存泄露问题 + MDC.remove(Contents.REQUEST_ID); + } +} +``` + + + +### ThreadContext + +在 `log4j 2.x` 中,使用 `ThreadContext` 代替了 `MDC` 和 `NDC`,使用方式如下: + +```java + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + try { + // 填充数据 + ThreadContext.put(Contents.REQUEST_ID, UUID.randomUUID().toString()); + chain.doFilter(request, response); + } finally { + // 请求结束时清除数据,否则会造成内存泄露问题 + ThreadContext.remove(Contents.REQUEST_ID); + } +} +``` + + + +### 写日志 + +- `%X` :打印Map中的所有信息 +- `%X{key}` :打印指定的信息 +- `%x` :打印堆栈中的所有信息 + +打印日志的格式案例如下: + +```xml + +``` + + + +## ThreadLocal + +在全链路跟踪框架中,Trace信息的传递功能是基于ThreadLocal的。但实际业务中可能会使用异步调用,这样就会丢失Trace信息,破坏了链路的完整性。 + +### InheritableThreadLocal + +`InheritableThreadLocal` 是 JDK 本身自带的一种线程传递解决方案。顾名思义,由当前线程创建的线程,将会继承当前线程里 ThreadLocal 保存的值。Thread内部为InheritableThreadLocal开辟了一个单独的ThreadLocalMap。在父线程创建一个子线程的时候,会检查这个ThreadLocalMap是否为空,不为空则会浅拷贝给子线程的ThreadLocalMap。 + + + +### TransmittableThreadLocal + +Transmittable ThreadLocal是阿里开源的库,继承了InheritableThreadLocal,优化了在使用线程池等会池化复用线程的情况下传递ThreadLocal的使用。简单来说,有个专门的TtlRunnable和TtlCallable包装类,用于读取原Thread的ThreadLocal对象及值并存于Runnable/Callable中,在执行run或者call方法的时候再将存于Runnable/Callable中的ThreadLocal对象和值读取出来,存入调用run或者call的线程中。 + + + +## Zipkin + +Zipkin 是 Twitter 的一个开源项目,它基于 Google Dapper 实现,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。 + +### Zipkin基本架构 + +![Zipkin架构](images/Solution/Zipkin架构.png) + + + +在服务运行的过程中会产生很多链路信息,产生数据的地方可以称之为Reporter。将链路信息通过多种传输方式如HTTP,RPC,kafka消息队列等发送到Zipkin的采集器,Zipkin处理后最终将链路信息保存到存储器中。运维人员通过UI界面调用接口即可查询调用链信息。 + + + +### Zipkin核心组件 + +![Zipkin核心组件](images/Solution/Zipkin核心组件.png) + +Zipkin有四大核心组件 + +- **Collector** + + 一旦Collector采集线程获取到链路追踪数据,Zipkin就会对其进行验证、存储和索引,并调用存储接口保存数据,以便进行查找。 + +- **Storage** + + Zipkin Storage最初是为了在Cassandra上存储数据而构建的,因为Cassandra是可伸缩的,具有灵活的模式,并且在Twitter中大量使用。除了Cassandra,还支持支持ElasticSearch和MySQL存储,后续可能会提供第三方扩展。 + +- **Query Service** + + 链路追踪数据被存储和索引之后,webui 可以调用query service查询任意数据帮助运维人员快速定位线上问题。query service提供了简单的json api来查找和检索数据。 + +- **Web UI** + + Zipkin 提供了基本查询、搜索的web界面,运维人员可以根据具体的调用链信息快速识别线上问题。 + + + +# 幂等机制 + +## 幂等场景 + +**场景一:前端重复提交** + +用户注册,用户创建商品等操作,前端都会提交一些数据给后台服务,后台需要根据用户提交的数据在数据库中创建记录。如果用户不小心多点了几次,后端收到了好几次提交,这时就会在数据库中重复创建了多条记录。这就是接口没有幂等性带来的 bug。 + + + +**场景二:黑客拦截重放** + +接口请求参数被黑客拦截,然后进行重放。 + + + +**场景三:接口超时重试** + +对于给第三方调用的接口,有可能会因为网络原因而调用失败,这时,一般在设计的时候会对接口调用加上失败重试的机制。如果第一次调用已经执行了一半时,发生了网络异常。这时再次调用时就会因为脏数据的存在而出现调用异常。 + + + +**场景四:消息重复消费** + +在使用消息中间件来处理消息队列,且手动 ack 确认消息被正常消费时。如果消费者突然断开连接,那么已经执行了一半的消息会重新放回队列。当消息被其他消费者重新消费时,如果没有幂等性,就会导致消息重复消费时结果异常,如数据库重复数据,数据库数据冲突,资源重复等。 + + + +## 解决方案 + +### Token机制实现 + +通过token机制实现接口的幂等性,这是一种比较通用性的实现方法。示意图如下: + +![Token机制实现](images/Solution/Token机制实现.jpg) + +具体流程步骤: + +- 客户端会先发送一个请求去获取 token,服务端会生成一个全局唯一的 ID 作为 token 保存在 redis 中,同时把这个 ID 返回给客户端 +- 客户端第二次调用业务请求的时候必须携带这个 token +- 服务端会校验这个 token,如果校验成功,则执行业务,并删除 redis 中的 token +- 如果校验失败,说明 redis 中已经没有对应的 token,则表示重复操作,直接返回指定的结果给客户端 + +注意: + +- 对 redis 中是否存在 token 以及删除的代码逻辑建议用 Lua 脚本实现,保证原子性 +- 全局唯一 ID 可以考虑用百度的 uid-generator、美团的 Leaf 去生成 + + + +### 基于MySQL实现 + +这种实现方式是利用 mysql 唯一索引的特性。示意图如下: + +![基于mysql实现](images/Solution/基于mysql实现.jpg) + +具体流程步骤: + +- 建立一张去重表,其中某个字段需要建立唯一索引 +- 客户端去请求服务端,服务端会将这次请求的一些信息插入这张去重表中 +- 因为表中某个字段带有唯一索引,如果插入成功,证明表中没有这次请求的信息,则执行后续的业务逻辑 +- 如果插入失败,则代表已经执行过当前请求,直接返回 + + + +### 基于Redis实现 + +这种实现方式是基于 SETNX 命令实现的 SETNX key value:将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作。该命令在设置成功时返回 1,设置失败时返回 0。示意图如下: + +![基于redis实现](images/Solution/基于redis实现.jpg) + +具体流程步骤: + +- 客户端先请求服务端,会拿到一个能代表这次请求业务的唯一字段 +- 将该字段以 SETNX 的方式存入 redis 中,并根据业务设置相应的超时时间 +- 如果设置成功,证明这是第一次请求,则执行后续的业务逻辑 +- 如果设置失败,则代表已经执行过当前请求,直接返回 + + + +### 基于业务参数实现 + +**第一阶段**:只要客户端请求有唯一的请求编号,那么就能借用Redis做这个去重:只要这个唯一请求编号在Redis存在,证明处理过,那么就认为是重复的。 + +**第二阶段**:但很多的场景下,请求并不会带这样的唯一编号。先考虑简单的场景,假设请求参数只有一个字段reqParam,我们可以利用以下标识去判断这个请求是否重复:**用户ID:接口名:请求参数** 。 + +**第三阶段**:但我们的接口通常不是这么简单,参数通常是一个JSON。假设我们把请求参数(JSON)按KEY做升序排序,排序后拼成一个字符串作为KEY值,但这可能非常的长,所以可以考虑对这个字符串求一个MD5作为参数的摘要,以这个摘要去取代reqParam的位置。 + +```java +String KEY = "user_opt:U="+userId + "M=" + method + "P=" + reqParamMD5; +``` + +**第四阶段**:上面的问题其实已经是一个很不错的解决方案了,但是实际投入使用的时候可能发现有些问题:某些请求用户短时间内重复的点击了(例如1000毫秒发送了三次请求),但绕过了上面的去重判断(不同的KEY值)。原因是这些请求参数的字段里面,**是带时间字段的**,这个字段标记用户请求的时间,服务端可以借此丢弃掉一些老的请求(例如5秒前)。 + +**总结** + +将业务参数(Query+Body)按KEY(排除时间字段和经纬度字段)做升序排序,排序后将按Query方式逐一拼接成参数字符串,然后将这个字符串进行MD5摘要计算,然后使用以下规则进行KEY值计算: + +```java +String KEY = "前缀标识:U=" + <用户唯一标识> + "M=" + <接口唯一标识> + "P=" + <业务参数MD5签名>; +``` + + + +# 分布式ID + +分布式ID的两大核心需求: + +- **全局唯一** +- **趋势有序** +- **高性能** + +![分布式ID方案](images/Solution/分布式ID方案.jpg) + + + +## UUID + +基于 `UUID` 实现全球唯一的ID。用作订单号`UUID`这样的字符串没有丝毫的意义,看不出和订单相关的有用信息;而对于数据库来说用作业务`主键ID`,它不仅是太长还是字符串,存储性能差查询也很耗时,所以不推荐用作`分布式ID`。 + +**优点** + +- 生成足够简单,本地生成无网络消耗,具有唯一性 + +**缺点** + +- 无序的字符串,不具备趋势自增特性 +- 没有具体的业务含义,看不出和订单相关的有用信息 +- 长度过长16 字节128位,36位长度的字符串,存储以及查询对MySQL的性能消耗较大,MySQL官方明确建议主键要尽量越短越好,作为数据库主键 `UUID` 的无序性会导致数据位置频繁变动,严重影响性能 + +**适用场景** + +- 可以用来生成如token令牌一类的场景,足够没辨识度,而且无序可读,长度足够 +- 可以用于无纯数字要求、无序自增、无可读性要求的场景 + + + +## 数据库自增ID + +基于数据库的 `auto_increment` 自增ID完全可以充当 `分布式ID` 。当我们需要一个ID的时候,向表中插入一条记录返回`主键ID`,但这种方式有一个比较致命的缺点,访问量激增时MySQL本身就是系统的瓶颈,用它来实现分布式服务风险比较大,不推荐。相关SQL如下: + +```mysql +CREATE DATABASE `SEQ_ID`; +CREATE TABLE SEQID.SEQUENCE_ID ( + id bigint(20) unsigned NOT NULL auto_increment, + value char(10) NOT NULL default '', + PRIMARY KEY (id), +) ENGINE=MyISAM; + +insert into SEQUENCE_ID(value) VALUES ('values'); +``` + +**优点** + +- 实现简单,ID单调自增,数值类型查询速度快 + +**缺点** + +- DB单点存在宕机风险,无法扛住高并发场景 + +**适用场景** + +- 小规模的,数据访问量小的业务场景 +- 无高并发场景,插入记录可控的场景 + + + +## 数据库多主模式 + +单点数据库方式不可取,那对上述的方式做一些高可用优化,换成主从模式集群。一个主节点挂掉没法用,那就做双主模式集群,也就是两个Mysql实例都能单独的生产自增ID。 + +**问题**:如果两个MySQL实例的自增ID都从1开始,会生成重复的ID怎么办? + +**解决方案**:设置`起始值`和`自增步长` + +MySQL_1 配置: + +```mysql +set @@auto_increment_offset = 1; -- 起始值 +set @@auto_increment_increment = 2; -- 步长 +-- 自增ID分别为:1、3、5、7、9 ...... +``` + +MySQL_2 配置: + +```mysql +set @@auto_increment_offset = 2; -- 起始值 +set @@auto_increment_increment = 2; -- 步长 +-- 自增ID分别为:2、4、6、8、10 ...... +``` + +那如果集群后的性能还是扛不住高并发咋办?则进行MySQL扩容增加节点: + +![MySQL数据库多主模式](images/Solution/MySQL数据库多主模式.jpg) + +从上图可以看出,水平扩展的数据库集群,有利于解决数据库单点压力的问题,同时为了ID生成特性,将自增步长按照机器数量来设置。增加第三台`MySQL`实例需要人工修改一、二两台`MySQL实例`的起始值和步长,把`第三台机器的ID`起始生成位置设定在比现有`最大自增ID`的位置远一些,但必须在一、二两台`MySQL实例`ID还没有增长到`第三台MySQL实例`的`起始ID`值的时候,否则`自增ID`就要出现重复了,**必要时可能还需要停机修改**。 + +**优点** + +- 解决DB单点问题 + +**缺点** + +- 不利于后续扩容,而且实际上单个数据库自身压力还是大,依旧无法满足高并发场景 + +**适用场景** + +- 数据量不大,数据库不需要扩容的场景 + +这种方案,除了难以适应大规模分布式和高并发的场景,普通的业务规模还是能够胜任的,所以这种方案还是值得积累。 + + + +## 数据库号段模式 + +号段模式是当下分布式ID生成器的主流实现方式之一,可以理解为从数据库批量的获取自增ID,每次从数据库取出一个号段范围,例如 (1,1000] 代表1000个ID,具体的业务服务将本号段,生成1~1000的自增ID并加载到内存。表结构如下: + +```mysql +CREATE TABLE id_generator ( + id int(10) NOT NULL, + max_id bigint(20) NOT NULL COMMENT '当前最大id', + step int(20) NOT NULL COMMENT '号段的步长', + biz_type int(20) NOT NULL COMMENT '业务类型', + version int(20) NOT NULL COMMENT '版本号', + PRIMARY KEY (`id`) +) +``` + +biz_type :代表不同业务类型 + +max_id :当前最大的可用id + +step :代表号段的长度 + +version :是一个乐观锁,每次都更新version,保证并发时数据的正确性 + +| id | biz_type | max_id | step | version | +| ---- | -------- | ------ | ---- | ------- | +| 1 | 101 | 1000 | 2000 | 0 | + +等这批号段ID用完,再次向数据库申请新号段,对`max_id`字段做一次`update`操作,`update max_id= max_id + step`,update成功则说明新号段获取成功,新的号段范围是`(max_id ,max_id +step]`。 + +```mysql +update id_generator set max_id=max_id+${step}, version = version+1 where version=${version} and biz_type=${XXX} +``` + +由于多业务端可能同时操作,所以采用版本号`version`乐观锁方式更新,这种`分布式ID`生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多。 + + + + +## Redis模式 + +`Redis`也同样可以实现,原理就是利用`redis`的 `incr`命令实现ID的原子性自增。 + +```shell + # 初始化自增ID为1 +127.0.0.1:6379> set seq_id 1 +OK + +# 增加1,并返回递增后的数值 +127.0.0.1:6379> incr seq_id +(integer) 2 +``` + +用`redis`实现需要注意一点,要考虑到`redis`持久化的问题。`redis`有两种持久化方式`RDB`和`AOF`: + +- `RDB`:会定时打一个快照进行持久化,假如连续自增但`redis`没及时持久化,而这会`redis`挂掉了,重启`redis`后会出现ID重复的情况 +- `AOF`:会对每条写命令进行持久化,即使`Redis`挂掉了也不会出现ID重复的情况,但由于incr命令的特殊性,会导致`Redis`重启恢复的数据时间过长 + + + +**优点** + +- 有序递增,可读性强 +- 能够满足一定性能 + +**缺点** + +- 强依赖于Redis,可能存在单点问题 +- 占用宽带,而且需要考虑网络延时等问题带来地性能冲击 + +**适用场景** + +- 对性能要求不是太高,而且规模较小业务较轻的场景,而且Redis的运行情况有一定要求,注意网络问题和单点压力问题,如果是分布式情况,那考虑的问题就更多了,所以一帮情况下这种方式用的比较少 + +Redis的方案其实可靠性有待考究,毕竟依赖于网络,延时故障或者宕机都可能导致服务不可用,这种风险是不得不考虑在系统设计内的。 + + + +## 雪花算法(Snowflake) + +雪花算法(Snowflake)是Twitter公司内部分布式项目采用的ID生成算法,开源后广受国内大厂的好评,在该算法影响下各大公司相继开发出各具特色的分布式生成器。 + +![雪花算法(SnowFlake)](images/Solution/雪花算法(SnowFlake).jpg) + +`Snowflake`生成的是Long类型的ID,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特。Snowflake ID组成结构:`正数位`(占1比特)+ `时间戳`(占41比特)+ `机器ID`(占5比特)+ `数据中心`(占5比特)+ `自增值`(占12比特),总共64比特组成的一个Long类型。 + +- **第一个bit位(1bit)**:Java中long的最高位是符号位代表正负,正数是0,负数是1,一般生成ID都为正数,所以默认为0 +- **时间戳部分(41bit)**:毫秒级的时间,不建议存当前时间戳,而是用(当前时间戳 - 固定开始时间戳)的差值,可以使产生的ID从更小的值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年 +- **工作机器id(10bit)**:也被叫做`workId`,这个可以灵活配置,机房或者机器号组合都可以 +- **序列号部分(12bit)**:自增值支持同一毫秒内同一个节点可以生成4096个ID + + + +**优点** + +- 每秒能够生成百万个不同的ID,性能佳 +- 时间戳值在高位,中间是固定的机器码,自增的序列在地位,整个ID是趋势递增的 +- 能够根据业务场景数据库节点布置灵活挑战bit位划分,灵活度高 + +**缺点** + +- **强依赖于机器时钟**,如果时钟回拨,会导致重复的ID生成,所以一般基于此的算法发现时钟回拨,都会抛异常处理,阻止ID生成,这可能导致服务不可用 + +**适用场景** + +- 雪花算法有很明显的缺点就是时钟依赖,如果确保机器不存在时钟回拨情况的话,那使用这种方式生成分布式ID是可行的,当然小规模系统完全是能够使用的 + + + +## 百度(Uid-Generator) + +`uid-generator`是基于`Snowflake`算法实现的,与原始的`snowflake`算法不同在于,`uid-generator`支持自`定义时间戳`、`工作机器ID`和 `序列号` 等各部分的位数,而且`uid-generator`中采用用户自定义`workId`的生成策略。 + +`uid-generator`需要与数据库配合使用,需要新增一个`WORKER_NODE`表。当应用启动时会向数据库表中去插入一条数据,插入成功后返回的自增ID就是该机器的`workId`数据由host,port组成。 + + + +**对于`uid-generator` ID组成结构**: + +`workId`占用了22个bit位,时间占用了28个bit位,序列化占用了13个bit位。这里的时间单位是秒,而不是毫秒,`workId`也不一样,而且同一应用每次重启就会消费一个`workId`。 + + + +## 美团(Leaf) + +`Leaf`同时支持号段模式和`snowflake`算法模式,可以切换使用。 + +### Leaf-segment数据库方案 + +![Leaf-segment数据库方案](images/Solution/Leaf-segment数据库方案.png) + +在建一张表`leaf_alloc`: + +```mysql +DROP TABLE IF EXISTS `leaf_alloc`; +CREATE TABLE `leaf_alloc` ( + `biz_tag` varchar(128) NOT NULL DEFAULT '' COMMENT '业务key', + `max_id` bigint(20) NOT NULL DEFAULT '1' COMMENT '当前已经分配了的最大id', + `step` int(11) NOT NULL COMMENT '初始步长,也是动态调整的最小步长', + `description` varchar(256) DEFAULT NULL COMMENT '业务key的描述', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '数据库维护的更新时间', + PRIMARY KEY (`biz_tag`) +) ENGINE=InnoDB; +``` + +test_tag在第一台Leaf机器上是1~1000的号段,当这个号段用完时,会去加载另一个长度为step=1000的号段,假设另外两台号段都没有更新,这个时候第一台机器新加载的号段就应该是3001~4000。同时数据库对应的biz_tag这条数据的max_id会从3000被更新成4000,更新号段的SQL语句如下: + +```mysql +Begin +UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx +SELECT tag, max_id, step FROM table WHERE biz_tag=xxx +Commit +``` + +**优点** + +- Leaf服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景 +- ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求 +- 容灾性高:Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务 +- 可以自定义max_id的大小,非常方便业务从原有的ID方式上迁移过来 + +**缺点** + +- ID号码不够随机,能够泄露发号数量的信息,不太安全 +- TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,tg999数据会出现偶尔的尖刺 +- DB宕机会造成整个系统不可用 + + + +**双buffer优化** + +针对第二个缺点是因为在号段用完后才会出现,因此可以在消耗完前提前获取下一个号段,从而解决问题: + +![Leaf-segment双buffer优化](images/Solution/Leaf-segment双buffer优化.png) + +采用双buffer的方式,Leaf服务内部有两个号段缓存区segment。当前号段已下发10%时,如果下一个号段未更新,则另启一个更新线程去更新下一个号段。当前号段全部下发完后,如果下个号段准备好了则切换到下个号段为当前segment接着下发,循环往复: + +- 每个biz-tag都有消费速度监控,通常推荐segment长度设置为服务高峰期发号QPS的600倍(10分钟),这样即使DB宕机,Leaf仍能持续发号10-20分钟不受影响 +- 每次请求来临时都会判断下个号段的状态,从而更新此号段,所以偶尔的网络抖动不会影响下个号段的更新 + + + +**Leaf高可用容灾** + +对于第三点“DB可用性”问题,采用一主两从的方式,同时分机房部署,Master和Slave之间采用**半同步方式**同步数据。 + +![Leaf-segment高可用容灾](images/Solution/Leaf-segment高可用容灾.png) + + + + +### Leaf-snowflake方案 + +Leaf-segment方案可以生成趋势递增的ID,同时ID号是可计算的,不适用于订单ID生成场景,比如竞对在两天中午12点分别下单,通过订单id号相减就能大致计算出公司一天的订单量,这个是不能忍受的。面对这一问题,我们提供了 Leaf-snowflake方案。Leaf-snowflake方案完全沿用snowflake方案的bit位设计,即是“1+41+10+12”的方式组装ID号。对于workerID的分配,当服务集群数量较小的情况下,完全可以手动配置。Leaf服务规模较大,动手配置成本太高。所以使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。Leaf-snowflake是按照下面几个步骤启动的: + +- 启动Leaf-snowflake服务,连接Zookeeper,在leaf_forever父节点下检查自己是否已经注册过(是否有该顺序子节点) +- 如果有注册过直接取回自己的workerID(zk顺序节点生成的int类型ID号),启动服务 +- 如果没有注册过,就在该父节点下面创建一个持久顺序节点,创建成功后取回顺序号当做自己的workerID号,启动服务 + +![Leaf-snowflake方案](images/Solution/Leaf-snowflake方案.png) + + + +## 滴滴(TinyID) + +`Tinyid`是基于号段模式原理实现的与`Leaf`如出一辙,每个服务获取一个号段(1000,2000]、(2000,3000]、(3000,4000] + +![滴滴(TinyID)](images/Solution/滴滴(TinyID).png) +`Tinyid`提供`http`和`tinyid-client`两种方式接入。 + + + +### Http方式接入 + +**第一步**:导入Tinyid源码 + +```shell +git clone https://github.com/didi/tinyid.git +``` + +**第二步**:创建数据表 + +```mysql +-- 建表SQL +CREATE TABLE `tiny_id_info` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键', + `biz_type` varchar(63) NOT NULL DEFAULT '' COMMENT '业务类型,唯一', + `begin_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '开始id,仅记录初始值,无其他含义。初始化时begin_id和max_id应相同', + `max_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '当前最大id', + `step` int(11) DEFAULT '0' COMMENT '步长', + `delta` int(11) NOT NULL DEFAULT '1' COMMENT '每次id增量', + `remainder` int(11) NOT NULL DEFAULT '0' COMMENT '余数', + `create_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '更新时间', + `version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本号', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_biz_type` (`biz_type`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT 'id信息表'; + +CREATE TABLE `tiny_id_token` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id', + `token` varchar(255) NOT NULL DEFAULT '' COMMENT 'token', + `biz_type` varchar(63) NOT NULL DEFAULT '' COMMENT '此token可访问的业务类型标识', + `remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注', + `create_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT 'token信息表'; + +-- 添加tiny_id_info +INSERT INTO `tiny_id_info` (`id`, `biz_type`, `begin_id`, `max_id`, `step`, `delta`, `remainder`, `create_time`, `update_time`, `version`) VALUES (1, 'test', 1, 1, 100000, 1, 0, '2018-07-21 23:52:58', '2018-07-22 23:19:27', 1); +INSERT INTO `tiny_id_info` (`id`, `biz_type`, `begin_id`, `max_id`, `step`, `delta`, `remainder`, `create_time`, `update_time`, `version`) VALUES(2, 'test_odd', 1, 1, 100000, 2, 1, '2018-07-21 23:52:58', '2018-07-23 00:39:24', 3); + +-- 添加tiny_id_token +INSERT INTO `tiny_id_token` (`id`, `token`, `biz_type`, `remark`, `create_time`, `update_time`) VALUES(1, '0f673adf80504e2eaa552f5d791b644c', 'test', '1', '2017-12-14 16:36:46', '2017-12-14 16:36:48'); +INSERT INTO `tiny_id_token` (`id`, `token`, `biz_type`, `remark`, `create_time`, `update_time`) VALUES(2, '0f673adf80504e2eaa552f5d791b644c', 'test_odd', '1', '2017-12-14 16:36:46', '2017-12-14 16:36:48'); +``` + +**第三步**:配置数据库 + +```properties +datasource.tinyid.names=primary +datasource.tinyid.primary.driver-class-name=com.mysql.jdbc.Driver +datasource.tinyid.primary.url=jdbc:mysql://ip:port/databaseName?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8 +datasource.tinyid.primary.username=root +datasource.tinyid.primary.password=123456 +``` + +**第四步**:启动`tinyid-server`后测试 + +```properties +# 获取分布式自增ID +http://localhost:9999/tinyid/id/nextIdSimple?bizType=test&token=0f673adf80504e2eaa552f5d791b644c' +返回结果: 3 + +# 批量获取分布式自增ID +http://localhost:9999/tinyid/id/nextIdSimple?bizType=test&token=0f673adf80504e2eaa552f5d791b644c&batchSize=10' +返回结果: 4,5,6,7,8,9,10,11,12,13 +``` + + + +### Java客户端方式接入 + +**第一步**:引入依赖 + +```xml + + com.xiaoju.uemc.tinyid + tinyid-client + ${tinyid.version} + +``` + +**第二步**:配置文件 + +```properties +tinyid.server =localhost:9999 +tinyid.token =0f673adf80504e2eaa552f5d791b644c +``` + +**第三步**:`test` 、`tinyid.token`是在数据库表中预先插入数据,`test` 是具体业务类型,`tinyid.token`表示可访问的业务类型 + +```java +// 获取单个分布式自增ID +Long id = TinyId . nextId( " test " ); +// 按需批量分布式自增ID +List< Long > ids = TinyId . nextId( " test " , 10 ); +``` + + + +# 分布式限流 + +当系统的处理能力不能应对外部请求的突增流量时,为了不让系统奔溃,必须采取限流的措施。 + +**限流目标**: + +- 防止被突发流量冲垮 +- 防止恶意请求和攻击 +- 保证集群服务中心的健康稳定运行(流量整形) +- API经济的细粒度资源量(请求量)控制 + + + +## 限流指标 + +目前主流的限流方法多采用 HPS 作为限流指标。 + +### TPS + +**系统吞吐量是衡量系统性能的关键指标,按照事务的完成数量来限流是最合理的。** + +> 但是对实操性来说,按照事务来限流并不现实。在分布式系统中完成一笔事务需要多个系统的配合。比如我们在电商系统购物,需要订单、库存、账户、支付等多个服务配合完成,有的服务需要异步返回,这样完成一笔事务花费的时间可能会很长。如果按照TPS来进行限流,时间粒度可能会很大大,很难准确评估系统的响应性能。 + + + + + +### HPS + +**每秒请求数,指每秒钟服务端收到客户端的请求数量。** + +> 如果一个请求完成一笔事务,那TPS和HPS是等同的。但在分布式场景下,完成一笔事务可能需要多次请求,所以TPS和HPS指标不能等同看待。 + + + + + +### QPS + +**服务端每秒能够响应的客户端查询请求数量。** + +> 如果后台只有一台服务器,那 HPS 和 QPS 是等同的。但是在分布式场景下,每个请求需要多个服务器配合完成响应。 + + + + + +## 限流方案 + +### 固定窗口计数器(Fixed Window) + +固定窗口计数器(Fixed Window)算法的实现思路非常简单,维护一个固定单位时间内的计数器,如果检测到单位时间已经过去就重置计数器为零。计数限首先维护一个计数器,将单位时间段当做一个窗口,计数器记录这个窗口接收请求的次数。 + +- 当次数少于限流阀值,就允许访问,并且计数器+1 +- 当次数大于限流阀值,就拒绝访问 +- 当前的时间窗口过去之后,计数器清零 + +假设单位时间是1秒,限流阀值为3。在单位时间1秒内,每来一个请求,计数器就加1,如果计数器累加的次数超过限流阀值3,后续的请求全部拒绝。等到1s结束后,计数器清0,重新开始计数。如下图: + +![固定窗口计数器原理](images/Solution/固定窗口计数器原理.png) + +伪代码如下: + +```java + /** + * 固定窗口时间算法 + * @return + */ + boolean fixedWindowsTryAcquire() { + long currentTime = System.currentTimeMillis(); //获取系统当前时间 + if (currentTime - lastRequestTime > windowUnit) { //检查是否在时间窗口内 + counter = 0; // 计数器清0 + lastRequestTime = currentTime; //开启新的时间窗口 + } + if (counter < threshold) { // 小于阀值 + counter++; //计数器加1 + return true; + } + + return false; + } +``` + + + +**存在问题** + +但是,这种算法有一个很明显的**临界问题**:假设限流阀值为5个请求,单位时间窗口是1s,如果我们在单位时间内的前0.8-1s和1-1.2s,分别并发5个请求。虽然都没有超过阀值,但是如果算0.8-1.2s,则并发数高达10,已经**超过单位时间1s不超过5阀值**的定义啦。 + +![固定窗口时间算法-存在问题](images/Solution/固定窗口时间算法-存在问题.png) + + + +### 滑动窗口计数器(Sliding Window) + +滑动窗口计数器(Sliding Window)算法限流解决固定窗口临界值的问题。它将单位时间周期分为n个小周期,分别记录每个小周期内接口的访问次数,并且根据时间滑动删除过期的小周期。 + +一张图解释滑动窗口算法,如下: + +![滑动窗口限流算法](images/Solution/滑动窗口限流算法.png) + +假设单位时间还是1s,滑动窗口算法把它划分为5个小周期,也就是滑动窗口(单位时间)被划分为5个小格子。每格表示0.2s。每过0.2s,时间窗口就会往右滑动一格。然后呢,每个小周期,都有自己独立的计数器,如果请求是0.83s到达的,0.8~1.0s对应的计数器就会加1。 + +我们来看下滑动窗口是如何解决临界问题的? + +假设我们1s内的限流阀值还是5个请求,0.8~1.0s内(比如0.9s的时候)来了5个请求,落在黄色格子里。时间过了1.0s这个点之后,又来5个请求,落在紫色格子里。如果**是固定窗口算法,是不会被限流的**,但是**滑动窗口的话,每过一个小周期,它会右移一个小格**。过了1.0s这个点后,会右移一小格,当前的单位时间段是0.2~1.2s,这个区域的请求已经超过限定的5了,已触发限流啦,实际上,紫色格子的请求都被拒绝啦。 + +**TIPS:** 当滑动窗口的格子周期划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。 + +滑动窗口算法**伪代码实现**如下: + +```java + /** + * 单位时间划分的小周期(单位时间是1分钟,10s一个小格子窗口,一共6个格子) + */ + private int SUB_CYCLE = 10; + + /** + * 每分钟限流请求数 + */ + private int thresholdPerMin = 100; + + /** + * 计数器, k-为当前窗口的开始时间值秒,value为当前窗口的计数 + */ + private final TreeMap counters = new TreeMap<>(); + + /** + * 滑动窗口时间算法实现 + */ + boolean slidingWindowsTryAcquire() { + long currentWindowTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) / SUB_CYCLE * SUB_CYCLE; //获取当前时间在哪个小周期窗口 + int currentWindowNum = countCurrentWindow(currentWindowTime); //当前窗口总请求数 + + //超过阀值限流 + if (currentWindowNum >= thresholdPerMin) { + return false; + } + + //计数器+1 + counters.get(currentWindowTime)++; + return true; + } + + /** + * 统计当前窗口的请求数 + */ + private int countCurrentWindow(long currentWindowTime) { + //计算窗口开始位置 + long startTime = currentWindowTime - SUB_CYCLE* (60s/SUB_CYCLE-1); + int count = 0; + + //遍历存储的计数器 + Iterator> iterator = counters.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + // 删除无效过期的子窗口计数器 + if (entry.getKey() < startTime) { + iterator.remove(); + } else { + //累加当前窗口的所有计数器之和 + count =count + entry.getValue(); + } + } + return count; + } +``` + +滑动窗口算法虽然解决了**固定窗口的临界问题**,但是一旦到达限流后,请求都会直接暴力被拒绝。这样我们会损失一部分请求,这其实对于产品来说,并不太友好。滑动时间窗口的优点是解决了流量计数器算法的缺陷,但是也有 2 个问题: + +- 流量超过就必须抛弃或者走降级逻辑 +- 对流量控制不够精细,不能限制集中在短时间内的流量,也不能削峰填谷 + + + +### 漏桶算法(Leaky Bucket) + +如下图所示,水滴持续滴入漏桶中,底部定速流出。如果水滴滴入的速率大于流出的速率,当存水超过桶的大小的时候就会溢出。规则如下: + +- 请求来了放入桶中 +- 桶内请求量满了拒绝请求 +- 服务定速从桶内拿请求处理 + +![漏桶算法](images/Solution/漏桶算法.png) + +可以看到水滴对应的就是请求。它的特点就是**宽进严出**,无论请求多少,请求的速率有多大,都按照固定的速率流出,对应的就是服务按照固定的速率处理请求。面对突发请求,服务的处理速度和平时是一样的,这其实不是我们想要的,在面对突发流量我们希望在系统平稳的同时,提升用户体验即能更快的处理请求,而不是和正常流量一样,循规蹈矩的处理。而令牌桶在应对突击流量的时候,可以更加的“激进”。 + +漏桶算法伪代码实现如下: + +```java + /** + * 每秒处理数(出水率) + */ + private long rate; + + /** + * 当前剩余水量 + */ + private long currentWater; + + /** + * 最后刷新时间 + */ + private long refreshTime; + + /** + * 桶容量 + */ + private long capacity; + + /** + * 漏桶算法 + * @return + */ + boolean leakybucketLimitTryAcquire() { + long currentTime = System.currentTimeMillis(); //获取系统当前时间 + long outWater = (currentTime - refreshTime) / 1000 * rate; //流出的水量 =(当前时间-上次刷新时间)* 出水率 + long currentWater = Math.max(0, currentWater - outWater); // 当前水量 = 之前的桶内水量-流出的水量 + refreshTime = currentTime; // 刷新时间 + + // 当前剩余水量还是小于桶的容量,则请求放行 + if (currentWater < capacity) { + currentWater++; + return true; + } + + // 当前剩余水量大于等于桶的容量,限流 + return false; + } +``` + + + +### 令牌桶算法(Token Bucket) + +令牌桶和漏桶的原理类似,不过漏桶是**定速地流出**,而令牌桶是**定速地往桶里塞入令牌**,然后请求只有拿到了令牌才能通过,之后再被服务器处理。当然令牌桶的大小也是有限制的,假设桶里的令牌满了之后,定速生成的令牌会丢弃。规则: + +- 定速的往桶内放入令牌 +- 令牌数量超过桶的限制,丢弃 +- 请求来了先向桶内索要令牌,索要成功则通过被处理,反之拒绝 + +![令牌桶算法](images/Solution/令牌桶算法.png) + +可以看出令牌桶在应对突发流量的时候,桶内假如有 100 个令牌,那么这 100 个令牌可以马上被取走,而不像漏桶那样匀速的消费。所以在**应对突发流量的时候令牌桶表现的更佳**。 + +令牌桶算法伪代码实现如下: + +```java + /** + * 每秒处理数(放入令牌数量) + */ + private long putTokenRate; + + /** + * 最后刷新时间 + */ + private long refreshTime; + + /** + * 令牌桶容量 + */ + private long capacity; + + /** + * 当前桶内令牌数 + */ + private long currentToken = 0L; + + /** + * 漏桶算法 + * @return + */ + boolean tokenBucketTryAcquire() { + long currentTime = System.currentTimeMillis(); //获取系统当前时间 + long generateToken = (currentTime - refreshTime) / 1000 * putTokenRate; //生成的令牌 =(当前时间-上次刷新时间)* 放入令牌速率 + currentToken = Math.min(capacity, generateToken + currentToken); // 当前令牌数量 = 之前的桶内令牌数量+放入的令牌数量 + refreshTime = currentTime; // 刷新时间 + + //桶里面还有令牌,请求正常处理 + if (currentToken > 0) { + currentToken--; //令牌数量-1 + return true; + } + + return false; + } +``` + + + +### 分布式限流 + +计数器限流的核心是 `INCRBY` 和 `EXPIRE` 指令,测试用例在此,通常,计数器算法容易出现不平滑的情况,瞬间的 qps 有可能超过系统的承载。 + +```lua +-- 获取调用脚本时传入的第一个 key 值(用作限流的 key) +local key = KEYS[1] +-- 获取调用脚本时传入的第一个参数值(限流大小) +local limit = tonumber(ARGV[1]) +-- 获取计数器的限速区间 TTL +local ttl = tonumber(ARGV[2]) + +-- 获取当前流量大小 +local curentLimit = tonumber(redis.call('get', key) or "0") + +-- 是否超出限流 +if curentLimit + 1 > limit then + -- 返回 (拒绝) + return 0 +else + -- 没有超出 value + 1 + redis.call('INCRBY', key, 1) + -- 如果 key 中保存的并发计数为 0,说明当前是一个新的时间窗口,它的过期时间设置为窗口的过期时间 + if (current_permits == 0) then + redis.call('EXPIRE', key, ttl) + end + -- 返回 (放行) + return 1 +end +``` + +此段 Lua 脚本的逻辑很直观: + +- 通过 `KEYS[1]` 获取传入的 key 参数,为某个限流指标的 key +- 通过 `ARGV[1]` 获取传入的 limit 参数,为限流值 +- 通过 `ARGV[2]` 获取限流区间 ttl +- 通过 `redis.call`,拿到 key 对应的值(默认为 0),接着与 limit 判断,如果超出表示该被限流;否则,使用 `INCRBY` 增加 1,未限流(需要处理初始化的情况,设置 `TTL`) + +不过上面代码是有问题的,如果 key 之前存在且未设置 `TTL`,那么限速逻辑就会永远生效了(触发 limit 值之后),使用时需要注意。 + + + +令牌桶算法也是 Guava 中使用的算法,同样采用计算的方式,将时间和 Token 数目联系起来: + +```lua +-- key +local key = KEYS[1] +-- 最大存储的令牌数 +local max_permits = tonumber(KEYS[2]) +-- 每秒钟产生的令牌数 +local permits_per_second = tonumber(KEYS[3]) +-- 请求的令牌数 +local required_permits = tonumber(ARGV[1]) + +-- 下次请求可以获取令牌的起始时间 +local next_free_ticket_micros = tonumber(redis.call('hget', key, 'next_free_ticket_micros') or 0) + +-- 当前时间 +local time = redis.call('time') +-- time[1] 返回的为秒,time[2] 为 ms +local now_micros = tonumber(time[1]) * 1000000 + tonumber(time[2]) + +-- 查询获取令牌是否超时(传入参数,单位为 微秒) +if (ARGV[2] ~= nil) then + -- 获取令牌的超时时间 + local timeout_micros = tonumber(ARGV[2]) + local micros_to_wait = next_free_ticket_micros - now_micros + if (micros_to_wait> timeout_micros) then + return micros_to_wait + end +end + +-- 当前存储的令牌数 +local stored_permits = tonumber(redis.call('hget', key, 'stored_permits') or 0) +-- 添加令牌的时间间隔(1000000ms 为 1s) +-- 计算生产 1 个令牌需要多少微秒 +local stable_interval_micros = 1000000 / permits_per_second + +-- 补充令牌 +if (now_micros> next_free_ticket_micros) then + local new_permits = (now_micros - next_free_ticket_micros) / stable_interval_micros + stored_permits = math.min(max_permits, stored_permits + new_permits) + -- 补充后,更新下次可以获取令牌的时间 + next_free_ticket_micros = now_micros +end + +-- 消耗令牌 +local moment_available = next_free_ticket_micros +-- 两种情况:required_permits<=stored_permits 或者 required_permits>stored_permits +local stored_permits_to_spend = math.min(required_permits, stored_permits) +local fresh_permits = required_permits - stored_permits_to_spend; +-- 如果 fresh_permits>0,说明令牌桶的剩余数目不够了,需要等待一段时间 +local wait_micros = fresh_permits * stable_interval_micros + +-- Redis 提供了 redis.replicate_commands() 函数来实现这一功能,把发生数据变更的命令以事务的方式做持久化和主从复制,从而允许在 Lua 脚本内进行随机写入 +redis.replicate_commands() +-- 存储剩余的令牌数:桶中剩余的数目 - 本次申请的数目 +redis.call('hset', key, 'stored_permits', stored_permits - stored_permits_to_spend) +redis.call('hset', key, 'next_free_ticket_micros', next_free_ticket_micros + wait_micros) +redis.call('expire', key, 10) + +-- 返回需要等待的时间长度 +-- 返回为 0(moment_available==now_micros)表示桶中剩余的令牌足够,不需要等待 +return moment_available - now_micros +``` + +![redis-token-bucket-dataflow](images/Solution/redis-token-bucket-dataflow.png) + + + +## Nginx限流 + +### 控制速率(limit_req_zone) + +`ngx_http_limit_req_module` 模块提供限制请求处理速率能力,使用`漏桶算法(leaky bucket)`。使用`limit_req_zone` 和`limit_req`两个指令,限制单个IP的请求处理速率。格式:`limit_req_zone key zone rate` + +```nginx +http { + limit_req_zone $binary_remote_addr zone=testRateLimit:10m rate=10r/s; +} +server { + location /test/ { + limit_req zone=testRateLimit burst=20 nodelay; + # 设置(http,server,location)超过限流策略后拒绝请求的响应状态码,默认503 + limit_req_status 555; + # 设置(http,server,location)限流策略后打印的日志级别:info|notice|warn|error + limit_req_log_level warn; + # 设置(http,server,location)启动无过滤模式。启用后不会过滤请求,但仍会记录速率超量的日志,默认为off + limit_req_dry_run off; + proxy_pass http://my_upstream; + } + error_page 555 /555json; + location = /555json { + default_type application/json; + add_header Content-Type 'text/html; charset=utf-8'; + return 200 '{"code": 666, "update":"访问高峰期,请稍后再试"}'; + } +} +``` + +- **key**:定义限流对象,`$binary_remote_addr` 表示基于 `remote_addr` 来做限流,`binary_` 的目的是压缩内存占用量 +- **zone**:定义共享内存区来存储访问信息,`myRateLimit:10m` 表示一个大小为10M,名字为myRateLimit的内存区域。1M能存储16000 IP地址的访问信息,10M可以存储16W IP地址访问信息 +- **rate**:设置最大访问速率,`rate=10r/s` 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一个请求。即上一个请求处理完后,后续100毫秒内又有请求到达,将拒绝处理该请求 +- **burst**:处理突发流量 + - `burst=20` 表示若同时有21个请求到达,Nginx 会处理第1个请求,剩余20个请求将放入队列,然后每隔100ms从队列中获取一个请求进行处理。若请求数大于21,将拒绝处理多余的请求,直接返回503 + - `burst=20 nodelay` 表示20个请求立马处理,不能延迟。不过即使这20个突发请求立马处理结束,后续来请求也不会立马处理。**burst=20** 相当于缓存队列中占了20个坑,即使请求被处理,这20个位置这只能按100ms一个来释放 + + + +### 控制并发连接数(limit_conn_zone) + +`ngx_http_limit_conn_module`提供了限制连接数的能力,利用`limit_conn_zone`和`limit_conn`两个指令即可。下面是Nginx官方例子: + +```nginx +limit_conn_zone $binary_remote_addr zone=test:10m; +limit_conn_zone $server_name zone=demo:10m; + +server { + # 表示限制单个IP同时最多持有10个连接 + limit_conn test 10; + # 表示虚拟主机(server) 同时能处理并发连接的总数为100个 + limit_conn demo 100; + # 设置(http,server,location)超过限流策略后拒绝请求的响应状态码,默认503 + limit_conn_status 555; + # 设置(http,server,location)限流策略后打印的日志级别:info|notice|warn|error + limit_conn_log_level warn; + # 设置(http,server,location)启动无过滤模式。启用后不会过滤请求,但仍会记录速率超量的日志,默认为off + limit_conn_dry_run off; + + error_page 555 /555json; + location = /555json { + default_type application/json; + add_header Content-Type 'text/html; charset=utf-8'; + return 200 '{"code": 666, "update":"访问高峰期,请稍后再试"}'; + } +} +``` + +**注意**:只有当 **request header** 被后端server处理后,这个连接才进行计数。 + + + +### lua限流 + +**第一步**:安装说明 + +环境准备: + +```shell +yum install -y gcc gcc-c++ readline-devel pcre-devel openssl-devel tcl perl +``` + +安装drizzle http://wiki.nginx.org/HttpDrizzleModule: + +```shell +cd /usr/local/src/ +wget http://openresty.org/download/drizzle7-2011.07.21.tar.gz +tar xzvf drizzle-2011.07.21.tar.gz +cd drizzle-2011.07.21/ +./configure --without-server +make libdrizzle-1.0 +make install-libdrizzle-1.0 +export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH +``` + +安装openresty: + +```shell +wget http://openresty.org/download/ngx_openresty-1.7.2.1.tar.gz +tar xzvf ngx_openresty-1.7.2.1.tar.gz +cd ngx_openresty-1.7.2.1/ +./configure --with-http_drizzle_module +gmake +gmake install +``` + +**第二步**:Nginx配置nginx.conf + +`/usr/local/openresty/nginx/conf/nginx.conf`: + +```nginx +# 添加MySQL配置(drizzle) +upstream backend { + drizzle_server 127.0.0.1:3306 dbname=test user=root password=123456 protocol=mysql; + drizzle_keepalive max=200 overflow=ignore mode=single; +} + +server { + listen 80; + server_name localhost; + + location / { + root html; + index index.html index.htm; + } + + location /lua _test{ + default_type text/plain; + content_by_lua 'ngx.say("hello, lua")'; + } + + + location /lua_redis { + default_type text/plain; + content_by_lua_file /usr/local/lua_test/redis_test.lua; + } + + location /lua_mysql { + default_type text/plain; + content_by_lua_file /usr/local/lua_test/mysql_test.lua; + } + + + location @cats-by-name { + set_unescape_uri $name $arg_name; + set_quote_sql_str $name; + drizzle_query 'select * from cats where name=$name'; + drizzle_pass backend; + rds_json on; + } + location @cats-by-id { + set_quote_sql_str $id $arg_id; + drizzle_query 'select * from cats where id=$id'; + drizzle_pass backend; + rds_json on; + } + location = /cats { + access_by_lua ' + if ngx.var.arg_name then + return ngx.exec("@cats-by-name") + end + + if ngx.var.arg_id then + return ngx.exec("@cats-by-id") + end + '; + + rds_json_ret 400 "expecting \"name\" or \"id\" query arguments"; + } + # 通过url匹配出name,并编码防止注入,最后以json格式输出结果 + location ~ '^/mysql/(.*)' { + set $name $1; + set_quote_sql_str $quote_name $name; + set $sql "SELECT * FROM cats WHERE name=$quote_name"; + drizzle_query $sql; + drizzle_pass backend; + rds_json on; + } + # 查看MySQL服务状态 + location /mysql-status { + drizzle_status; + } +} +``` + +**第三步**:lua测试脚本 + +`/usr/local/lua_test/redis_test.lua`: + +```lua +local redis = require "resty.redis" +local cache = redis.new() +cache.connect(cache, '127.0.0.1', '6379') +local res = cache:get("foo") +if res==ngx.null then + ngx.say("This is Null") + return +end +ngx.say(res) +``` + +`/usr/local/lua_test/mysql_test.lua`: + +```lua +local mysql = require "resty.mysql" +local db, err = mysql:new() +if not db then + ngx.say("failed to instantiate mysql: ", err) + return +end + +db:set_timeout(1000) -- 1 sec + +-- or connect to a unix domain socket file listened +-- by a mysql server: +-- local ok, err, errno, sqlstate = +-- db:connect{ +-- path = "/path/to/mysql.sock", +-- database = "ngx_test", +-- user = "ngx_test", +-- password = "ngx_test" } + +local ok, err, errno, sqlstate = db:connect{ + host = "127.0.0.1", + port = 3306, + database = "test", + user = "root", + password = "123456", + max_packet_size = 1024 * 1024 } + +if not ok then + ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) + return +end + +ngx.say("connected to mysql.") + +local res, err, errno, sqlstate = + db:query("drop table if exists cats") +if not res then + ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") + return +end + +res, err, errno, sqlstate = + db:query("create table cats " + .. "(id serial primary key, " + .. "name varchar(5))") +if not res then + ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") + return +end + +ngx.say("table cats created.") + +res, err, errno, sqlstate = + db:query("insert into cats (name) " + .. "values (\'Bob\'),(\'\'),(null)") +if not res then + ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") + return +end + +ngx.say(res.affected_rows, " rows inserted into table cats ", + "(last insert id: ", res.insert_id, ")") + +-- run a select query, expected about 10 rows in +-- the result set: +res, err, errno, sqlstate = + db:query("select * from cats order by id asc", 10) +if not res then + ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") + return +end + +local cjson = require "cjson" +ngx.say("result: ", cjson.encode(res)) + +-- put it into the connection pool of size 100, +-- with 10 seconds max idle timeout +local ok, err = db:set_keepalive(10000, 100) +if not ok then + ngx.say("failed to set keepalive: ", err) + return +end + +-- or just close the connection right away: +-- local ok, err = db:close() +-- if not ok then +-- ngx.say("failed to close: ", err) +-- return +-- end +'; +``` + +**第四步**:验证结果 + +```shell +$ curl 'http://127.0.0.1/lua_test' +hello, lua + +$ redis-cli set foo 'hello,lua-redis' +OK +$ curl 'http://127.0.0.1/lua_redis' +hello,lua-redis + +$ curl 'http://127.0.0.1/lua_mysql' +connected to mysql. +table cats created. +3 rows inserted into table cats (last insert id: 1) +result: [{"name":"Bob","id":"1"},{"name":"","id":"2"},{"name":null,"id":"3"}] + +$ curl 'http://127.0.0.1/cats' +{"errcode":400,"errstr":"expecting \"name\" or \"id\" query arguments"} + +$ curl 'http://127.0.0.1/cats?name=bob' +[{"id":1,"name":"Bob"}] + +$ curl 'http://127.0.0.1/cats?id=2' +[{"id":2,"name":""}] + +$ curl 'http://127.0.0.1/mysql/bob' +[{"id":1,"name":"Bob"}] + +$ curl 'http://127.0.0.1/mysql-status' +worker process: 32261 + +upstream backend + active connections: 0 + connection pool capacity: 0 + servers: 1 + peers: 1 +``` + + + +## API经济 + +**API经济** + +API经济是基于API所产生经济活动的总和,在当今发展阶段主要包括API业务,以及通过API进行的业务功能、性能等方面的商业交易。 + + + +**借贷机制** + +针对两个连续的限流时间窗切换时,如果切换前的时间窗中的请求量已被使用完,则第一个时间窗可以向下一个时间窗预借一小部分的请求量(小于每个时间窗内的资源量,建议运行借贷20%以内的资源)来提前使用,如果该时间窗内预借的资源也消耗完,则触发限流拒绝措施;那么在第二个时间窗内则会少使用被借走的请求量。在预借请求量时,如果当前时间窗”已欠费“(即有被借走的量),则该时间窗不允许向下一个时间窗借贷资源。 + + + +# 分布式缓存 + +## 淘汰算法 + +### 最不经常使用算法(LFU) + +这个缓存算法使用一个计数器来记录条目被访问的频率。通过使用LFU缓存算法,最低访问数的条目首先被移除。这个方法并不经常使用,因为它无法对一个拥有最初高访问率之后长时间没有被访问的条目缓存负责。 + +![最不经常使用算法(LFU)](images/Solution/最不经常使用算法(LFU).png) + + + +### 最近最少使用算法(LRU) + +这个缓存算法将最近使用的条目存放到靠近缓存顶部的位置。当一个新条目被访问时,LRU将它放置到缓存的顶部。当缓存达到极限时,较早之前访问的条目将从缓存底部开始被移除。这里会使用到昂贵的算法,而且它需要记录“年龄位”来精确显示条目是何时被访问的。此外,当一个LRU缓存算法删除某个条目后,“年龄位”将随其他条目发生改变。 + +![最近最少使用算法(LRU)](images/Solution/最近最少使用算法(LRU).png) + + + +### 自适应缓存替换算法(ARC) + +在IBM Almaden研究中心开发,这个缓存算法同时跟踪记录LFU和LRU,以及驱逐缓存条目,来获得可用缓存的最佳使用。 + + + +### 先进先出算法(FIFO) + +FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。 + +![先进先出算法(FIFO)](images/Solution/先进先出算法(FIFO).png) + + + +### 最近最常使用算法(MRU) + +这个缓存算法最先移除最近最常使用的条目。一个MRU算法擅长处理一个条目越久,越容易被访问的情况。 + + + +## 缓存策略 + +缓存更新的策略主要分为三种: + +- **Cache-Aside**:通常会先更新数据库,然后再删除缓存,为了兜底通常还会将数据设置缓存时间 +- **Read/Write through**:一般是由一个 Cache Provider 对外提供读写操作,应用程序不用感知操作的是缓存还是数据库 +- **Write-Behind**:即延迟写入,Cache Provider 每隔一段时间会批量输入数据库,优点是应用程序写入速度非常快 + +### Cache-Aside + +`Cache-Aside(旁路缓存)`的提出是为了尽可能地解决缓存与数据库的数据不一致问题。 + +#### Read Cache Aside + +`Cache-Aside` 的读请求流程如下: + +![Cache-Aside读请求](images/Solution/Cache-Aside读请求.jpg) + +- 读的时候,先读缓存,缓存命中的话,直接返回数据 +- 缓存没有命中的话,就去读数据库,从数据库取出数据,放入缓存后,同时返回响应 + + + +#### Write Cache Aside + +`Cache Aside` 的写请求流程如下: + +![Cache-Aside写请求](images/Solution/Cache-Aside写请求.jpg) + +- 更新的时候,先**更新数据库,然后再删除缓存** + + + +### Read/Write-Through + +`Read/Write-Through(读写穿透)` 模式中,服务端把缓存作为主要数据存储。应用程序跟数据库缓存交互,都是通过**抽象缓存层**完成的。 + +#### Read-Through + +`Read-Through`的简要流程如下 + +![Read-Through简要流程](images/Solution/Read-Through简要流程.png) + +- 从缓存读取数据,读到直接返回 +- 如果读取不到的话,从数据库加载,写入缓存后,再返回响应 + +这个简要流程是不是跟`Cache-Aside`很像呢?其实`Read-Through`就是多了一层`Cache-Provider`而已,流程如下: + +![Read-Through流程](images/Solution/Read-Through流程.png) + +`Read-Through`实际只是在`Cache-Aside`之上进行了一层封装,它会让程序代码变得更简洁,同时也减少数据源上的负载。 + + + +#### Write-Through + +`Write-Through`模式下,当发生写请求时,也是由**缓存抽象层**完成数据源和缓存数据的更新,流程如下: + +![Write-Through](images/Solution/Write-Through.png) + + + +### Write-Behind + +`Write-Behind`在一些地方也被成为`Write back`, 简单理解就是:应用程序更新数据时只更新缓存, `Cache Provider`每隔一段时间将数据刷新到数据库中。说白了就是`延迟写入`。 + +`Write-Behind(异步写入)` 和 `Read/Write-Through` 有相似的地方,都是由`Cache Provider`来负责缓存和数据库的读写。它们又有个很大的不同:`Read/Write-Through`是同步更新缓存和数据的,`Write-Behind`则是只更新缓存,不直接更新数据库,通过**批量异步**的方式来更新数据库。 + +![WriteBehind流程](images/Solution/WriteBehind流程.png) + +这种方式下,缓存和数据库的一致性不强,**对一致性要求高的系统要谨慎使用**。但是它适合频繁写的场景,MySQL的**InnoDB Buffer Pool机制**就使用到这种模式。如上图,应用程序更新两个数据,Cache Provider 会立即写入缓存中,但是隔一段时间才会批量写入数据库中。优缺点如下: + +- **优点**:是数据写入速度非常快,适用于频繁写的场景 +- **缺点**:是缓存和数据库不是强一致性,对一致性要求高的系统慎用 + + + +## 策略选择 + +### 删除or更新 + +操作缓存的时候,到底是删除缓存呢,还是更新缓存?日常开发中,我们一般使用的就是`Cache-Aside`模式。有些小伙伴可能会问, `Cache-Aside`在写入请求的时候,为什么是**删除缓存而不是更新缓存**呢? + +![Cache-Aside写入流程](images/Solution/Cache-Aside写入流程.png) + +我们在操作缓存的时候,到底应该删除缓存还是更新缓存呢?我们先来看个例子: + +![Cache-Aside写入流程案例](images/Solution/Cache-Aside写入流程案例.png) + +1. 线程A先发起一个写操作,第一步先更新数据库 +2. 线程B再发起一个写操作,第二步更新了数据库 +3. 由于网络等原因,线程B先更新了缓存 +4. 线程A更新缓存 + +这时候,缓存保存的是A的数据(老数据),数据库保存的是B的数据(新数据),数据**不一致**了,脏数据出现啦。如果是**删除缓存取代更新缓存**则不会出现这个脏数据问题。**更新缓存相对于删除缓存**,还有两点劣势: + +- 如果你写入的缓存值,是经过复杂计算才得到的话。更新缓存频率高的话,就浪费性能啦 +- 在写数据库场景多,读数据场景少的情况下,数据很多时候还没被读取到,又被更新了,这也浪费了性能呢(实际上,写多的场景,用缓存也不是很划算的) + + + +### 双写顺序 + +双写的情况下,先操作数据库还是先操作缓存?`Cache-Aside` 缓存模式中,有些小伙伴还是会有疑问,在写请求过来的时候,为什么是**先操作数据库呢**?为什么**不先操作缓存**呢?比如一条数据同时存在数据库、缓存,现在你要更新此数据,不管先更新数据库,还是先更新缓存,这两种方式都有问题。 + +**方案一:先更新数据库,后更新缓存** + +![双写顺序-先DB后缓存](images/Solution/双写顺序-先DB后缓存.jpg) + +如下案例会造成数据不一致。 + +A先把数据库更新为 123,由于网络问题,更新缓存的动作慢了。这时,B 去更新数据库了,改为了 456,紧接着把缓存也更新为456。现在A更新缓存的请求到了,把缓存更新为了 123。那么这时数据就不一致了,数据库里是最新的 456,而缓存是 123,是旧数据。因为数据库更新、缓存更新这2个动作不是原子的,在高并发操作时,这2个动作直接会插入其他动作。 + +![双写顺序-先DB后缓存-案例](images/Solution/双写顺序-先DB后缓存-案例.jpg) + + + +**方案二:先更新缓存,再更新数据库** + +![双写顺序-先缓存后DB](images/Solution/双写顺序-先缓存后DB.jpg) + +如下案例也同样可能数据不一致。 + +缓存更新成功,数据为最新的,但数据库更新失败,回滚了,还是旧数据。还是非原子操作的原因。 + +![双写顺序-先缓存后DB-案例](images/Solution/双写顺序-先缓存后DB-案例.jpg) + +**注意**:先操作数据库再操作缓存,不一样也会导致数据不一致嘛?它俩又不是原子性操作的。这个是**会的**,但是这种方式,一般因为删除缓存失败等原因,才会导致脏数据,这个概率就很低。接下来我们再来分析这种**删除缓存失败**的情况,**如何保证一致性**。 + + + +## 缓存问题 + +### 缓存雪崩 + +当**大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机**时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是**缓存雪崩**的问题。 + +![缓存雪崩](images/Solution/缓存雪崩.png) + +发生缓存雪崩的原因及解决方案: + +**① 大量数据同时过期** + +- **均匀设置过期时间**:给缓存数据的过期时间加上一个随机数 +- **互斥锁**:加个互斥锁,保证同一时间内只有一个请求来构建缓存 +- **二级缓存**:每一级缓存的失效时间都不同 +- **队列控制**:使用MQ控制读取数据库的请求数据。即发N个消息,单线程消费 +- **热点数据缓存永远不过期** + - **物理不过期**:针对热点key不设置过期时间 + - **逻辑过期**:把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建 + +**② Redis故障宕机** + +- **服务熔断或请求限流机制**:启动服务熔断机制,暂停业务对缓存服务访问,直接返回错误;或启用限流机制 +- **构建Redis缓存高可靠集群**:通过主从节点的方式构建Redis缓存高可靠集群 +- **开启Redis持久化机制**:一旦重启,能直接从磁盘上自动加载数据恢复内存中的数据 + + + +### 缓存击穿 + +如果缓存中的**某个热点数据过期**了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是**缓存击穿**的问题,如秒杀活动。 + +![缓存击穿](images/Solution/缓存击穿.png) + +缓存击穿跟缓存雪崩很相似,可以认为缓存击穿是缓存雪崩的一个子集。应对缓存击穿可以采取以下两种方案: + +- **互斥锁方案**:保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值 + +- **热点数据缓存永远不过期** + + - **物理不过期**:针对热点key不设置过期时间 + - **逻辑过期**:把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建 + + + +### 缓存穿透 + +当用户访问的数据,**既不在缓存中,也不在数据库中**,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是**缓存穿透**的问题。 + +![缓存穿透](images/Solution/缓存穿透.png) + +缓存穿透的发生一般有这两种情况: + +- **业务误操作**:缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据 +- **黑客恶意攻击**:故意大量访问某些读取不存在数据的业务 + +应对缓存穿透的方案,常见的方案有三种: + +- **拦截非法请求** + +- **布隆过滤器**:构造一个`BloomFilter`过滤器,初始化全量数据,当接到请求时,在`BloomFilter`中判断这个key是否存在,如果不存在,直接返回即可,无需再查询`缓存和DB` + +- **缓存空对象**:查存DB 时,如果数据不存在,预热一个`特殊空值`到缓存中。这样,后续查询都会命中缓存,但是要对特殊值,解析处理 + + ![缓存空对象](images/Solution/缓存空对象.png) + + + +**布隆过滤器工作原理** + +布隆过滤器由「**初始值都为 0 的位图数组**」和「 **N 个哈希函数**」两部分组成。当我们在写入数据库数据时,在布隆过滤器里做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤器,如果查询到数据没有被标记,说明不在数据库中。布隆过滤器会通过 3 个操作完成标记: + +- 第一步:使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值 +- 第二步:将第一步得到的 N 个哈希值对位图数组的长度取模,得到每个哈希值在位图数组的对应位置 +- 第三步:将每个哈希值在位图数组的对应位置的值设置为 1 + +举个例子,假设有一个位图数组长度为 8,哈希函数 3 个的布隆过滤器: + +![布隆过滤器](images/Solution/布隆过滤器.jpg) + +![Guava布隆过滤器](images/Solution/Guava布隆过滤器.jpg) + + + +### 缓存预热 + +缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。如果不进行预热,那么Redis初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。 + +**缓存预热思路:** + +- **数据量不大的时候**:工程启动的时候进行加载缓存动作 +- **数据量大的时候**:设置一个定时任务脚本,进行缓存的刷新 +- **数据量太大的时候**:优先保证热点数据进行提前加载到缓存 + + + +**缓存预热解决方案:** + +- 直接写个缓存刷新页面,上线时手工操作下 +- 数据量不大,可以在项目启动的时候自动进行加载 +- 定时刷新缓存 + + + +### 缓存降级 + +缓存降级是指当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。 + +**降级的最终目的是保证核心服务可用,即使是有损的**。而且有些服务是无法降级的(如加入购物车、结算)。 + + + +**分级降级预案:** + +- **一般**:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级 +- **警告**:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警 +- **错误**:比如可用率低于90%,或数据库连接池被打爆,或访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级 +- **严重错误**:比如因为特殊原因数据错误了,此时需要紧急人工降级 + + + +### 缓存更新 + +除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),还可以根据具体的业务需求进行自定义的缓存淘汰。常见的更新策略如下: + +- **LRU/LFU/FIFO**:都是属于当**缓存不够用**时采用的更新算法 + + 适合内存空间有限,数据长期不变动,基本不存在数据一不致性业务。比如一些一经确定就不允许变更的信息。 + +- **超时剔除**:给缓存数据设置一个过期时间 + + 适合于能够容忍一定时间内数据不一致性的业务,比如促销活动的描述文案。 + +- **主动更新**:如果数据源的数据有更新,则主动更新缓存 + + 对于数据的一致性要求很高,比如交易系统,优惠劵的总张数。 + + + +常见数据更新方式有两大类,其余基本都是这两类的变种: + +**方式一:先删缓存,再更新数据库** + +![缓存更新-先删缓存再更新数据库](images/Solution/缓存更新-先删缓存再更新数据库.png) + +这种做法是遇到数据更新,我们先去删除缓存,然后再去更新DB,如左图。让我们来看一下整个操作的流程: + +- A请求需要更新数据,先删除对应的缓存,还未更新DB +- B请求来读取数据 +- B请求看到缓存里没有,就去读取DB并将旧数据写入缓存(脏数据) +- A请求更新DB + +可以看到B请求将脏数据写入了缓存,如果这是一个读多写少的数据,可能脏数据会存在比较长的时间(要么有后续更新,要么等待缓存过期),这是业务上不能接受的。 + + + +**方式二:先更新数据库,再删除缓存** + +![缓存更新-先更新数据库再删除缓存](images/Solution/缓存更新-先更新数据库再删除缓存.png) + +上图的右侧部分可以看到在A更新DB和删除缓存之间B请求会读取到老数据,因为此时A操作还没有完成,并且这种读到老数据的时间是非常短的,可以满足数据最终一致性要求。 + + + +**删除缓存而非更新缓存原因** + +上图可以看到我们用的是删除缓存,而不是更新缓存,原因如下图: + +![缓存更新-删除缓存原因](images/Solution/缓存更新-删除缓存原因.png) + +上图我用操作代替了删除或更新,当我们做删除操作时,A先删还是B先删没有关系,因为后续读取请求都会从DB加载出最新数据;但是当我们对缓存做的是更新操作时,就会对A先更新缓存还是B先更新缓存敏感了,如果A后更新,那么缓存里就又存在脏数据了,所以 go-zero 只使用删除缓存的方式。 + + + +**缓存更新请求处理流程** + +我们来一起看看完整的请求处理流程: + +![缓存更新-请求处理流程](images/Solution/缓存更新-请求处理流程.png) + +**注意**:不同颜色代表不同请求。 + +- 请求1更新DB +- 请求2查询同一个数据,返回了老的数据,这个短时间内返回旧数据是可以接受的,满足最终一致性 +- 请求1删除缓存 +- 请求3再来请求时缓存里没有,就会查询数据库,并回写缓存再返回结果 +- 后续的请求就会直接读取缓存了 + + + +### 缓存Hot Key + +对于突发事件,大量用户同时去访问热点信息,这个突发热点信息所在的缓存节点就很容易出现过载和卡顿现象,甚至 Crash,我们称之为缓存热点。这个在新浪微博经常遇到,某大V明星出轨、结婚、离婚,瞬间引发数百千万的吃瓜群众围观,访问同一个key,流量集中打在一个缓存节点机器,很容易打爆网卡、带宽、CPU的上限,最终导致缓存不可用。 + +一般使用 **缓存+过期时间** 的策略来加速读写,又保证数据的定期更新,这种模式基本能满足绝大部分需求。但是如果有两个问题同时出现,可能会对应用造成致命的伤害: + +- **当前key是一个hot key**。比如热点娱乐新闻,并发量非常大 +- **重建缓存不能在短时间完成,可能是一个复杂计算**。例如复杂的SQL、多次IO、多个依赖等 + +当缓存失效的瞬间,将会有大量线程来重建缓存,造成后端负载加大,甚至让应该崩溃。 + + + +**如何发现热key?** + +- 预估热key,如秒杀商品,火爆新闻 +- 在客户端进行统计 +- 可用Proxy,如Codis可以在Proxy端收集 +- 利用redis自带命令,monitor,hotkeys。**执行缓慢,不推荐使用** +- 利用流式计算引擎统计访问次数,如Storm、Spark Streaming、Flink + + + +解决方案主要包括以下几种: + +- 首先能先找到这个`热key`来,比如通过`Spark`实时流分析,及时发现新的热点key +- 将集中化流量打散,避免一个缓存节点过载。由于只有一个key,我们可以在key的后面拼上`有序编号`,比如`key#01`、`key#02`......`key#10`多个副本,这些加工后的key位于多个缓存节点上。每次请求时,客户端随机访问一个即可 + +- **本地缓存** + + 变更分布式缓存为本地缓存,减少网络开销,提高吞吐量。 + +- **互斥锁** + + 具体做法是只允许一个线程重建缓存,其它线程等待重建缓存的线程执行完,重新从缓存获取数据即可。 + + - **方案风险**:重建的时间太长或者并发量太大,将会大量的线程阻塞,同样会加大系统负载 + + - **优化方案**:除了重建线程之外,其它线程拿旧值直接返回 + + 比如Google的Guava Cache的refreshAfterWrite采用的就是这种方案避免雪崩效应。 + +- **永不过期** + + 这种就是缓存更新操作是独立的,可以通过跑定时任务来定期更新,或者变更数据时主动更新。 + +- **限流熔断** + + 以上两种方案都是建立在我们事先知道hot key的情况下,如果事先知道哪些是hot key,其实问题都不是很大。问题是我们不知道的情况,既然hot key的危害是因为有大量的重建请求落到了后端,如果后端自己做了限流,只有部分请求落到了后端, 其它的都打回去了。一个hot key只要有一个重建请求处理成功了,后面的请求都是直接走缓存了,问题就解决了。 + + + +### 缓存Big Key + +Big Key指数据量大的key,由于其数据大小远大于其它key,导致经过分片之后,某个具体存储这个Big Key的实例内存使用量远大于其他实例,造成内存不足,拖累整个集群的使用。 + + + +**常见场景** + +- 热门话题下的讨论 +- 大V的粉丝列表 +- 序列化后的图片 +- 没有及时处理的垃圾数据 + + + +**大Key影响** + +- 大key会大量占用内存,在Redis集群中无法均衡 +- Reids性能下降,影响主从复制 +- 在主动删除或过期删除时操作时间过长而引起服务阻塞 + + + +**如何发现大key** + +- redis-cli --bigkeys命令。可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大key。但如果Redis的key比较多,执行该命令会比较慢 +- 获取生产Redis的rdb文件,通过rdbtools分析rdb生成csv文件,再导入MySQL或其他数据库中进行分析统计,根据size_in_bytes统计bigkey + + + +**解决方案** + +优化big key的原则就是string减少字符串长度,而list、hash、set、zset等则减少成员数: + +- string类型的big key,尽量不要存入Redis中,可以使用文档型数据库MongoDB或缓存到CDN上。如果必须用Redis存储,最好单独存储,不要和其他的key一起存储 + +- 设置一个阈值,当value的长度超过阈值时,对内容启动压缩,降低kv的大小 +- 评估`大key`所占的比例,由于很多框架采用`池化技术`,如:Memcache,可以预先分配大对象空间。真正业务请求时,直接拿来即用 +- 颗粒划分,将大key拆分为多个小key,独立维护,成本会降低不少 +- 大key要设置合理的过期时间,尽量不淘汰那些大key + + + +## 数据一致性 + +![缓存双写一致性](images/Solution/缓存双写一致性.png) + +一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。 + +- **强一致性**:这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大 +- **弱一致性**:这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态 +- **最终一致性**:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型 + + + +数据库和缓存数据如何保持强一致?实际上,没办法做到数据库与缓存**绝对的一致性**。 + +- 加锁可以吗?并发写期间加锁,任何读操作不写入缓存? +- 缓存及数据库封装CAS乐观锁,更新缓存时通过lua脚本? +- 分布式事务,3PC?TCC? + +其实,这是由**CAP理论**决定的。缓存系统适用的场景就是非强一致性的场景,它属于CAP中的AP。**个人觉得,追求绝对一致性的业务场景,不适合引入缓存**。 + +> CAP理论,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。 + + + +但是,通过一些方案优化处理,是可以**保证弱一致性,最终一致性**的。有3种方案保证数据库与缓存的一致性:**缓存延时双删、删除缓存重试机制和读取biglog异步删除缓存**。 + + + +### 缓存延时双删 + +有人可能会说,并不一定要先操作数据库呀,采用**缓存延时双删**策略,就可以保证数据的一致性啦。什么是延时双删呢? + +![延时双删流程](images/Solution/延时双删流程.png) + +1. 先删除缓存 +2. 再更新数据库 +3. 休眠一会(比如1秒),再次删除缓存 + +这个休眠一会,一般多久呢?都是1秒? + +> 这个休眠时间 = 读业务逻辑数据的耗时 + 几百毫秒 +> +> 为了确保读请求结束,写请求可以删除读请求可能带来的缓存脏数据。 + +这种方案还算可以,只有休眠那一会(比如就那1秒),可能有脏数据,一般业务也会接受的。但是如果**第二次删除缓存失败**呢?缓存和数据库的数据还是可能不一致,对吧?给Key设置一个自然的expire过期时间,让它自动过期怎样?那业务要接受**过期时间**内,数据的不一致咯?还是有其他更佳方案呢? + + + +### 删除缓存重试机制 + +不管是**延时双删**还是**Cache-Aside的先操作数据库再删除缓存**,都可能会存在第二步的删除缓存失败,导致的数据不一致问题。可以使用这个方案优化:删除失败就多删除几次呀,保证删除缓存成功就可以了呀~ 所以可以引入**删除缓存重试机制** + +![删除缓存重试流程](images/Solution/删除缓存重试流程.png) + +1. 写请求更新数据库 +2. 缓存因为某些原因,删除失败 +3. 把删除失败的key放到消息队列 +4. 消费消息队列的消息,获取要删除的key +5. 重试删除缓存操作 + + + +### 读取biglog异步删除缓存 + +重试删除缓存机制还可以吧,就是会造成好多**业务代码入侵**。其实,还可以这样优化:通过数据库的**binlog来异步淘汰key**。 + +![读取biglog异步删除缓存](images/Solution/读取biglog异步删除缓存.png) + +以mysql为例: + +- 可以使用阿里的canal将binlog日志采集发送到MQ队列里面 +- 然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性 + + + +# 消息队列 + +## 痛点问题 + +### 总耗时长 + +有些复杂业务系统,一次用户请求可能会同步调用N个系统接口,需要等待所有的接口都返回了,才能真正的获取执行结果。 + +![消息队列-痛点1](images/Solution/消息队列-痛点1.png) + +这种同步接口调用的方式`总耗时比较长`,非常影响用户的体验,特别是在网络不稳定的情况下,极容易出现接口超时问题。 + + + +### 耦合性高 + +很多复杂的业务系统,一般都会拆分成多个子系统。我们在这里以用户下单为例,请求会先通过订单系统,然后分别调用:支付系统、库存系统、积分系统 和 物流系统。 + +![消息队列-痛点2](images/Solution/消息队列-痛点2.png) + +系统之间`耦合性太高`,如果调用的任何一个子系统出现异常,整个请求都会异常,对系统的稳定性非常不利。 + + + +### 突发流量 + +有时候为了吸引用户,我们会搞一些活动,比如秒杀等。 + +![消息队列-痛点3-业务场景](images/Solution/消息队列-痛点3-业务场景.png) + +如果用户少还好,不会影响系统的稳定性。但如果用户突增,一时间所有的请求都到数据库,可能会导致数据库无法承受这么大的压力,响应变慢或者直接挂掉。 + +![消息队列-痛点3-请求过多](images/Solution/消息队列-痛点3-请求过多.png) + +对于这种 `突发流量`,无法保证系统的稳定性。 + + + +## 功能特性 + +对于上面传统模式的三类问题,使用MQ就能轻松解决。 + +### 异步 + +同步接口调用导致响应时间长的问题,使用MQ之后,将同步调用改成异步,能够显著减少系统响应时间。 + +![消息队列-功能-异步](images/Solution/消息队列-功能-异步.png) + +系统A作为消息的生产者,在完成本职工作后,就能直接返回结果了。而无需等待消息消费者的返回,它们最终会独立完成所有的业务功能。这样能避免`总耗时比较长`,从而影响用户的体验的问题。 + + + +### 解耦 + +子系统间耦合性太大的问题,使用MQ之后,我们只需要依赖于MQ,避免了各个子系统间的强依赖问题。 + +![消息队列-功能-解耦](images/Solution/消息队列-功能-解耦.png) + +订单系统作为消息生产者,保证它自己没有异常即可,不会受到支付系统等业务子系统的异常影响,并且各个消费者业务子系统之间,也互不影响。这样就把之前复杂的业务子系统的依赖关系,转换为只依赖于MQ的简单依赖,从而显著的降低了系统间的耦合度。 + + + +### 消峰 + +由于突然出现的`突发流量`,导致系统不稳定的问题。使用MQ后,能够起到消峰的作用。 + +![消息队列-功能-消峰](images/Solution/消息队列-功能-消峰.png) + +订单系统接收到用户请求之后,将请求直接发送到MQ,然后订单消费者从MQ中消费消息,做写库操作。如果出现`突发流量`的情况,由于消费者的消费能力有限,会按照自己的节奏来消费消息,多的请求不处理,保留在MQ的队列中,不会对系统的稳定性造成影响。 + + + +## 引发问题 + +引入MQ后让我们子系统间耦合性降低了,异步处理机制减少了系统的响应时间,同时能够有效的应对`突发流量`问题,提升系统的稳定性。但是引入MQ同时也会带来一些问题。 + +### 重复消息 + +重复消费问题可以说是MQ中普遍存在的问题,不管你用哪种MQ都无法避免。有哪些场景会出现重复的消息呢? + +- **消息生产者产生了重复的消息** +- **Kafka和RocketMQ的offset被回调了** +- **消息消费者确认失败** +- **消息消费者确认时超时了** +- **业务系统主动发起重试** + +![消息队列-问题-重复消息问题](images/Solution/消息队列-问题-重复消息问题.png) + +如果重复消息不做正确的处理,会对业务造成很大的影响,产生重复的数据,或者导致数据异常,比如会员系统多开通了一个月的会员。 + + + +### 数据一致性 + +很多时候,如果MQ的消费者业务处理异常的话,就会出现数据一致性问题。比如:一个完整的业务流程是,下单成功之后,送100个积分。下单写库了,但是消息消费者在送积分的时候失败了,就会造成`数据不一致`的情况,即该业务流程的部分数据写库了,另外一部分没有写库。 + +![消息队列-问题-数据一致性问题](images/Solution/消息队列-问题-数据一致性问题.png) + +如果下单和送积分在同一个事务中,要么同时成功,要么同时失败,是不会出现数据一致性问题的。但由于跨系统调用,为了性能考虑,一般不会使用强一致性的方案,而改成达成最终一致性即可。 + + + +### 消息丢失 + +同样消息丢失问题,也是MQ中普遍存在的问题,不管你用哪种MQ都无法避免。有哪些场景会出现消息丢失问题呢? + +- **消息生产者发生消息时,由于网络原因,发生到MQ失败了** +- **MQ服务器持久化时,磁盘出现异常** +- **Kafka和RocketMQ的offset被回调时,略过了很多消息** +- **消息消费者刚读取消息,已经ACK确认了,但业务还没处理完,服务就被重启了** + +导致消息丢失问题的原因挺多的,`生产者`、`MQ服务器`、`消费者` 都有可能产生问题,在这里就不一一列举了。最终的结果会导致消费者无法正确的处理消息,而导致数据不一致的情况。 + + + +### 消息顺序 + +有些业务数据是有状态的,比如订单有:下单、支付、完成、退货等状态,如果订单数据作为消息体,就会涉及顺序问题了。如果消费者收到同一个订单的两条消息,第一条消息的状态是下单,第二条消息的状态是支付,这是没问题的。但如果第一条消息的状态是支付,第二条消息的状态是下单就会有问题了,没有下单就先支付了? + +![消息队列-问题-消息顺序问题](images/Solution/消息队列-问题-消息顺序问题.png) + +消息顺序问题是一个非常棘手的问题,比如: + +- `Kafka`同一个`partition`中能保证顺序,但是不同的`partition`无法保证顺序 +- `RabbitMQ`的同一个`queue`能够保证顺序,但是如果多个消费者同一个`queue`也会有顺序问题 + +如果消费者使用多线程消费消息,也无法保证顺序。如果消费消息时同一个订单的多条消息中,中间的一条消息出现异常情况,顺序将会被打乱。还有如果生产者发送到MQ中的路由规则,跟消费者不一样,也无法保证顺序。 + + + +### 消息堆积 + +如果消息消费者读取消息的速度,能够跟上消息生产者的节奏,那么整套MQ机制就能发挥最大作用。但是很多时候,由于某些批处理,或者其他原因,导致消息消费的速度小于生产的速度。这样会直接导致消息堆积问题,从而影响业务功能。 + +![消息队列-问题-消息堆积](images/Solution/消息队列-问题-消息堆积.png) + +以下单开通会员为例,若消息出现堆积,会导致用户下单后,很久之后才能变成会员,这种情况肯定会引起大量用户投诉。 + + + +### 系统复杂度提升 + +这里说的系统复杂度和系统耦合性是不一样的,比如以前只有:系统A、系统B和系统C 这三个系统,现在引入MQ之后,你除了需要关注前面三个系统之外,还需要关注MQ服务,需要关注的点越多,系统的复杂度越高。 + +![消息队列-问题-系统复杂度提升](images/Solution/消息队列-问题-系统复杂度提升.png) + +MQ的机制需要:生产者、MQ服务器、消费者。有一定的学习成本,需要额外部署MQ服务器,而且有些MQ比如:RocketMQ,功能非常强大,用法有点复杂,如果使用不好,会出现很多问题。有些问题,不像接口调用那么容易排查,从而导致系统的复杂度提升了。 + + + +## 解决引发问题 + +MQ是一种趋势,总体来说对我们的系统是利大于弊的,那么要如何解决这些问题呢? + +### 重复消息问题 + +不管是由于生产者产生的重复消息,还是由于消费者导致的重复消息,我们都可以在消费者中这个问题。这就要求消费者在做业务处理时,要做幂等设计。在这里推荐增加一张消费消息表,来解决MQ的这类问题。消费消息表中,使用`messageId`做`唯一索引`,在处理业务逻辑之前,先根据messageId查询一下该消息有没有处理过,如果已经处理过了则直接返回成功,如果没有处理过,则继续做业务处理。解决方案: + +- **在消费放使用messageId做唯一索引进行幂等处理** +- **在消费方根据业务唯一标识(如订单号、流水号)进行幂等处理 —— 推荐** + +![消息队列-解决-重复消息问题](images/Solution/消息队列-解决-重复消息问题.png) + + + +### 数据一致性问题 + +数据一致性分为强一致性、弱一致性和最终一致性。而MQ为了性能考虑使用的是`最终一致性`,那么必定会出现数据不一致的问题。这类问题大概率是因为消费者读取消息后,业务逻辑处理失败导致的,这时候可以增加`重试机制`。重试分为:`同步重试` 和 `异步重试`。业务处理失败后,可以采用以下三种方式进行重试: + +- **同步重试** + - 立即重试3-5次,若还是失败,则写入`记录表`,人工处理。推荐`消息量小`的场景使用 +- **异步重试** + - 立刻写入`重试表`,使用Job定时重试。推荐在`消息量大`的场景使用 + - 消费失败后,将消息再发至消息队列的同一个Topic中。推荐在`顺序要求不高`的场景 + +![消息队列-解决-数据一致性问题](images/Solution/消息队列-解决-数据一致性问题.png) + +有些消息量比较小的业务场景,可以采用同步重试,在消费消息时如果处理失败,立刻重试3-5次,如何还是失败,则写入到`记录表`中。但如果消息量比较大,则不建议使用这种方式,因为如果出现网络异常,可能会导致大量的消息不断重试,影响消息读取速度,造成`消息堆积`。而消息量比较大的业务场景,建议采用异步重试,在消费者处理失败之后,立刻写入`重试表`,有个`job`专门定时重试。还有一种做法是,如果消费失败,自己给同一个Topic发一条消息,在后面的某个时间点,自己又会消费到那条消息,起到了重试的效果。如果对消息顺序要求不高的场景,可以使用这种方式。 + + + +### 消息丢失问题 + +不管你是否承认有时候消息真的会丢,即使这种概率非常小,也会对业务有影响。生产者、MQ服务器、消费者都有可能会导致消息丢失的问题。 + +为了解决这个问题,可以增加一张`消息发送表`,当生产者发完消息之后,会往该表中写入一条数据,状态status标记为待确认。如果消费者读取消息之后,调用生产者的api更新该消息的status为已确认。有个Job,每隔一段时间检查一次消息发送表,如果5分钟(这个时间可以根据实际情况来定)后还有状态是待确认的消息,则认为该消息已经丢失了,重新发条消息。 + +![消息队列-解决-消息丢失问题](images/Solution/消息队列-解决-消息丢失问题.png) + +这样不管是由于生产者、MQ服务器、还是消费者导致的消息丢失问题,job都会重新发消息。 + + + +### 消息顺序问题 + +消息顺序问题是非常常见的问题,以`Kafka`消费订单消息为例。订单有:下单、支付、完成、退货等状态,这些状态是有先后顺序的,如果顺序错了会导致业务异常。解决这类问题之前,我们先确认一下,消费者是否真的需要知道中间状态,只知道最终状态行不行? + +![消息队列-解决-消息顺序问题1](images/Solution/消息队列-解决-消息顺序问题1.png) + +其实很多时候,只需要知道的是最终状态,这时可以把流程优化一下: + +![消息队列-解决-消息顺序问题2](images/Solution/消息队列-解决-消息顺序问题2.png) + +这种方式可以解决大部分的消息顺序问题。但如果真的有需要保证消息顺序的需求。订单号路由到不同的`partition`,同一个订单号的消息,每次到发到同一个`partition`。 + +![消息队列-解决-消息顺序问题3](images/Solution/消息队列-解决-消息顺序问题3.png) + + + +### 消息堆积问题 + +如果消费者消费消息的速度小于生产者生产消息的速度,将会出现消息堆积问题。那么消息堆积问题该如何解决呢?这个要看消息是否需要保证顺序。如果不需要保证顺序,可以读取消息之后用多线程处理业务逻辑。 + +![消息队列-解决-消息堆积1](images/Solution/消息队列-解决-消息堆积1.png) + +这样就能增加业务逻辑处理速度,解决消息堆积问题。但线程池的核心线程数和最大线程数需要合理配置,不然可能会浪费系统资源。如果需要保证顺序,可以读取消息之后,将消息按照一定规则分发到多个队列中,然后在队列中用单线程处理。 + +![消息队列-解决-消息堆积2](images/Solution/消息队列-解决-消息堆积2.png) + +## 使用场景 + +其实MQ相关的内容还有很多,比如:延迟消息、私信队列、事务问题等等。 + + + +### 延迟队列 + +延迟队列存储的对象肯定是对应的延时消息,所谓”延时消息”是指当消息被发送以后,并不想让消费者立即拿到消息,而是等待指定时间后,消费者才拿到这个消息进行消费。开源RocketMQ 不支持任意时间自定义的延迟消息,仅支持内置预设值的延迟时间间隔的延迟消息。预设值的延迟时间间隔为:1s、 5s、 10s、 30s、 1m、 2m、 3m、 4m、 5m、 6m、 7m、 8m、 9m、 10m、 20m、 30m、 1h、 2h。 + +如电商中,提交一个订单就可以发送一个延时消息,30m后去检查这个订单的状态,如果还未付款就取消订单释放库存。 + +```java + Message msg = new Message("order", "订单001".getBytes()); +// 延迟30分钟 +msg.setDelayTimeLevel(16); +SendResult sendResult = producer.send(msg); +``` + + + +### 死信队列 + +当一条消息初次消费失败,消息队列 RocketMQ 会自动进行消息重试;达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列 RocketMQ 不会立刻将消息丢弃,而是将其发送到该消费者对应的特殊队列中。在消息队列 RocketMQ 中,这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。 + +**死信消息** + +- 不会再被消费者正常消费 +- 有效期与正常消息相同,均为 3 天,3 天后会被自动删除。因此,请在死信消息产生后的 3 天内及时处理 + +**死信队列** + +- 一个死信队列对应一个 Group ID, 而不是对应单个消费者实例 +- 如果一个 Group ID 未产生死信消息,消息队列 RocketMQ 不会为其创建相应的死信队列 +- 一个死信队列包含了对应 Group ID 产生的所有死信消息,不论该消息属于哪个 Topic + + + +死信队列中的数据需要通过新订阅该topic进行消费。每个topic被消费后,如果消费失败超过次数会进入重试队列、死信队列等。名称会以: + +- **%RETRY%消费组名称** +- **%DLQ%消费组名称** + + + +订单失效问题比较麻烦的地方就是如何能够实时获取失效的订单。对于这种问题一般有两种解决方案: + +- **定时任务处理** + 用户下订单后先生成订单信息,然后将该订单加入到定时任务中(30分钟后执行),当到达指定时间后检查订单状态,如果未支付则标识该订单失效。定时去轮询数据库/缓存,看订单的状态。这种方式的问题很明显,当集群部署服务器的时候需要做分布式锁进行协调,而且实时性不高,对数据库会产生压力。 + + ![Job扫描处理超时未支付订单](images/Solution/Job扫描处理超时未支付订单.png) + +- **延时任务处理** + 当用户下订单后,将用户的订单的标识全部发送到延时队列中,30分钟后进去消费队列中被消费,消费时先检查该订单的状态,如果未支付则标识该订单失效。有以下几种延时任务处理方式: + + - **Java自带的DelayedQuene队列** + 这是java本身提供的一种延时队列,如果项目业务复杂性不高可以考虑这种方式。它是使用jvm内存来实现的,停机会丢失数据,扩展性不强。 + + - **使用redis监听key的过期来实现** + 当用户下订单后把订单信息设置为redis的key,30分钟失效,程序编写监听redis的key失效,然后处理订单(我也尝试过这种方式)。这种方式最大的弊端就是只能监听一台redis的key失效,集群下将无法实现,也有人监听集群下的每个redis节点的key,但我认为这样做很不合适。如果项目业务复杂性不高,redis单机部署,就可以考虑这种方式。 + + - **MQ死信队列实现** + + ![死信队列处理超时未支付订单](images/Solution/死信队列处理超时未支付订单.png) + + + +### 事务问题 + +![RocketMQ事务消息](images/Solution/RocketMQ事务消息.png) + + + +# 分布式事务 + +**什么是分布式事务?** + +分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。一个大的操作由N多的小的操作共同完成。而这些小的操作又分布在不同的服务上。针对于这些操作,要么全部成功执行,要么全部不执行 。 + + + +**TCC**和**可靠消息最终一致性方案**是在生产中最常用。一个要求强一致,一个要求最终一致。**TCC**用于强一致主要用于核心模块,例如交易/订单等。**最终一致方案**一般用于边缘模块例如库存,通过mq去通知,保证最终一致性,也可以业务解耦。 + + + +## 分布式理论 + +### 事务一致性 + +**可靠程度**:强一致性 > 顺序一致性 > 因果一致性 > 最终一致性 > 弱一致性。 + +- **强一致性/线性一致性(Linearizability)**:写操作完成后,要求任何读取操作都能读取到最新的值 +- **顺序一致性(Sequential Consistency)**:不保证操作的全局时序,但保证每个客户端操作能按顺序被执行 +- **因果一致性(Causal Consistency)**:只对并发访问中具有因果关系的操作保证顺序 +- **最终一致性(Eventual Consistency)**:不保证在任意时刻任意节点上的同一份数据都是相同的,但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化 +- **弱一致性(Weak Consistency)**:数据更新后,能容忍后续的访问只能访问到部分或者全部访问不到 + + + +### ACID特性 + +`ACID`注重一致性,是传统关系型数据库的设计思路。`ACID`是数据库(MySQL)事务正确执行所必须满足的四个特性: + +- **Atomicity(原子性)**:事务中的操作要么都做,要么都不做 + +- **Consistency(一致性)**:系统必须始终处在强一致状态下 + +- **Isolation(隔离性)**:一个事务的执行不能被其它事务所干扰 + +- **Durability(持久性)**:一个已提交的事务对数据库中数据的改变是永久性的 + + + +### CAP理论 + +`CAP`理论的核心思想是任何基于网络的数据共享系统最多只能满足数据一致性(`Consistency`)、可用性(`Availability`)和网络分区容忍(`Partition Tolerance`)三个特性中的两个。 + +- **一致性(Consistency)**:在分布式系统中的所有数据备份,在同一时刻是否拥有同样的值 + +- **可用性(Availability)**:在集群中一部分节点故障后,集群整体还能响应客户端的读写请求 + +- **分区容错性(Partition Tolerance)**:即使某一部分集群坏掉,另一部分仍能正常工作 + +![CAP](images/Solution/cap.png) + +这三个特性只能满足其中两个,牺牲另一个。大部分系统也都是如此: + +- **一般来说分布式集群都会保证P优先**。即集群部分节点坏死不影响整个集群的使用,然后再去追求C和A。因为如果放弃P,那不如就直接使用多个传统数据库了。事实上,很多微服务分库分表就是这个道理 +- **如果追求强一致性,那么势必会导致可用性下降**。比如在Master-Slave的场景中,Master负责数据写入,然后分发给各个节点,所有节点都写入成功,才算写入,这样保证了强一致性,但是延迟也会随之增加,导致可用性降低 + + + +### BASE理论 + +`BASE`关注高可用性。`BASE`理论是对`CAP`理论的延伸,核心思想是即使无法做到强一致性(`Strong Consistency`,`CAP`的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(`Eventual Consitency`)。`BASE`是分别代表: + +- **基本可用(Basically Available)**:指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用 + +- **软状态( Soft State)**:指允许系统存在中间状态,而该中间状态不会影响系统整体可用性 + +- **最终一致性( Eventual Consistency)**:指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态 + +BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。 + + + +## 分一致性算法 + +### Gossip协议 + +Gossip 协议,顾名思义,就像流言蜚语一样,利用一种随机、带有传染性的方式,将信息传播到整个网络中,并在一定时间内,使得系统内的所有节点数据一致。根据 Base 理论,如果你需要实现最终一致性,那么就可以通过 Gossip 协议实现这个目标。Gossip协议的核心一共是三块内容:直接邮寄(Direct Mail)、反熵(Anti-entropy)和谣言传播(RumRumor mongering)。 + +作为一种异步修复、实现最终一致性的协议,反熵在存储组件中应用广泛,比如 Dynamo、InfluxDB、Cassandra,在需要实现最终一致性时,如果节点都是已知的,一般优先考虑反熵。当集群节点是变化的,或者集群节点数比较多时,这时要采用谣言传播的方式,同步更新数据,实现最终一致。 + + + +#### 直接邮寄(Direct Mail) + +所谓直接邮寄,就是直接发送更新数据,当数据发送失败时,将数据缓存下来,然后重传。比如下图中,节点 A 直接将更新数据发送给了节点 B、D: +![Gossip协议-直接邮寄](images/Solution/Gossip协议-直接邮寄.png) + +虽然直接邮寄实现起来比较容易,数据同步也很及时,但可能会因为缓存队列满了而丢数据。 也就是说,只采用直接邮寄是无法实现最终一致性的。 + + + +#### 反熵(Anti-entropy) + +熵,在物理学中是用来度量体系的混乱程度。所以,反熵就是要消除混乱,Gossip协议通过反熵来异步修复节点之间的数据差异,实现最终一致性。反熵的实现,一共有推、拉、推拉三种。 集群中的节点,每隔一段时间就会随机选择某个其他节点,然后交换自己的已有数据来消除两者之间的差异。但是正因为反熵需要节点间两两交换比对数据,所以执行反熵时的通讯成本会很高,不建议在实际场景中频繁执行反熵,应该通过引入Checksum等机制,降低需要对比的数据量和通讯次数。 + +**推方式** + +推方式,就是将自己的所有副本数据,推给对方,修复对方副本中的熵: +![Gossip协议-推方式](images/Solution/Gossip协议-推方式.png) + + + +**拉方式** + +拉方式,就是拉取对方的所有副本数据,修复自己副本中的熵: +![Gossip协议-拉方式](images/Solution/Gossip协议-拉方式.png) + + + +**推拉方式** + +推拉方式,就是同时修复自己副本和对方副本中的熵: +![Gossip协议-推拉方式](images/Solution/Gossip协议-推拉方式.png) + +虽然反熵很实用,但是执行反熵时,相关的节点都是已知的,而且节点数量不能太多,如果是一个动态变化或节点数比较多的分布式环境,反熵就不适用了。那么当你面临这个情况要怎样实现最终一致性呢?答案就是谣言传播。 + + + +#### 谣言传播(Rumor mongering) + +谣言传播,广泛地散播谣言,它指的是当一个节点有了新数据后,这个节点变成活跃状态,并周期性地联系其他节点向其发送新数据,直到所有的节点都存储了该新数据。比如下图中,节点 A 向节点 B、D 发送新数据,节点 B 收到新数据后,变成活跃节点,然后节点 B 向节点 C、D 发送新数据: +![Gossip协议-谣言传播](images/Solution/Gossip协议-谣言传播.png) + +谣言传播非常具有传染性,它适合动态变化的分布式系统。 + + + +### Quorum NWR算法 + +CAP理论中的一致性一般指的是强一致性,也就是说写操作完成后,任何后续访问都能读到更新后的值。而BASE理论中的一致性指的是最终一致性,也就是说写操作完成后, 任何后续访问可能会读到旧数据,但是整个分布式系统的数据最终会达到一致。那么,如果我们的系统现在是最终一致性模型,也就是AP模型,突然有一天因为业务需要,要临时保证节点间的数据强一致性,有没有办法临时做这样的改造呢?一种办法是重新开发一套系统,但显然成本太高了。另一种办法就是本章要介绍的Quorum NWR算法。通过 Quorum NWR,我们可以自定义一致性级别。 + + + +**Quorum NWR三要素** + +Quorum NWR 中有三个要素:N、W、R,它们是 Quorum NWR 的核心内容,我们就是通过组合这三个要素,实现自定义一致性级别的。 + + + +Quorum NWR 是非常实用的一个算法,能有效弥补 AP 型系统缺乏强一致性的痛点,给业务提供了按需选择一致性级别的灵活度。很多开源框架都利用Quorum NWR实现自定义一致性级别,比如Elasticsearch,就支持“any、one、quorum、all”4 种写一致性级别。 + + + +#### N(副本数) + +N 表示副本数,又叫做复制因子(Replication Factor)。也就是说,N 表示集群中同一份数据有多少个副本。 注意,副本数不等同于节点数。(如果读者对Elasticsearch或Kafka有了解,可以把副本理解成Replica Shard)。比如下图中, DATA-1 有 2 个副本,DATA-2 有 3 个副本,DATA-3 有 1 个副本: + +![QuorumNWR算法-副本数](images/Solution/QuorumNWR算法-副本数.png) + +在实现 Quorum NWR 的时候,我们需要实现自定义副本数的功能,比如,用户可以指定 DATA-1 具有 2 个副本,DATA-2 具有 3 个副本,就像上图中的样子。 + + + +#### W( 写一致性级别 ) + +W,又称写一致性级别(Write Consistency Level),表示对于客户端的一次写操作,只有成功完成 W 个副本的更新,才算写操作成功。以下图中的DATA-2为例,当它的W=2时,如果客户端对 DATA-2 执行写操作,必须完成它的2 个副本的更新,才算完成了写操作: +![QuorumNWR算法-写一致性级别](images/Solution/QuorumNWR算法-写一致性级别.png) + + + +#### R(读一致性级别) + +R,又称读一致性级别(Read Consistency Level),表示对于客户端的一次写操作,需要读 R 个副本,然后最终返回最新的那份数据。以下图中的DATA-2为例,当它的R=2时,如果客户端读取DATA-2的数据,需要读取它的2 个副本中的数据,然后返回最新的那份数据: + +![QuorumNWR算法-读一致性级别](images/Solution/QuorumNWR算法-读一致性级别.png) + +N、W、R 值的不同组合,会产生不同的一致性效果,具体来说,有这么两种效果: + +- 当 W + R > N 的时候,对于客户端来讲,整个系统能保证强一致性,一定能返回更新后的那份数据 +- 当 W + R <= N 的时候,对于客户端来讲,整个系统只能保证最终一致性,可能会返回旧数据 + +我这里以DATA-2为例解释下,为什么W+R>N时,一定可以读到最新的数据。首先,DATA-2的N=3,W=2,R=2,那么当写数据时,必然有2个节点要写成功;此时再读数据,即使读到了一个没有写过的节点,由于要读2个节点的值,另一个必然是写成功的节点,所以最终返回给客户端的还是最新的数据。 + + + +### PBFT算法 + +我在《共识问题》一章中提到过,共识算法一共可以分为两大类:拜占庭容错算法(Byzantine Fault Tolerance,BFT)和故障容错算法(Crash Fault Tolerance,CFT)。Leslie Lamport在论文中提出的口信消息解决方案就属于BFT,需要考虑恶意节点的篡改、攻击等问题。但是,口信消息解决方案在现实场景中很难落地。比如,它并不关心这个共识的结果是什么,这会出现一种情况:现在适合进攻,但将军们达成的共识却是撤退。另外,实际场景中,我们往往需要就提议的一系列值(而不是单值)在拜占庭错误发生的时候,也能被达成共识。那应该怎么做呢?一种方案就是本文要讲解的PBFT算法。 PBFT算法,是一种能在实际场景中落地的BFT算法,它在区块链中应用广泛。 + +Raft 算法完全不适应有人作恶的场景,但PBFT 算法能容忍 (n - 1)/3 个恶意节点 (也可以是故障节点)。另外,相比 PoW 算法,PBFT 的优点是不消耗算力,所以在日常实践中,PBFT 比较适用于相对“可信”的场景中,比如联盟链。 + +此外,虽然PBFT 算法相比口信消息方案已经有了很大的优化,将消息复杂度从 O(n ^ (f + 1)) 降低为 O(n ^ 2),能在实际场景中落地并解决共识问题,但 PBFT 还是需要比较多的消息,比如在 13 节点集群中(f 为 4),一次共识协商需要 237 个消息,所以决定了 PBFT 算法适用于中小型分布式系统。 + + + +#### oral message的问题 + +要理解PBFT算法,首先必须要明白口信消息解决方案(A solution with oral message)到底存在哪些问题?这些问题都是后续众多BFT算法在努力改进和解决的,理解了这些问题,能帮助你更好地理解后来的拜占庭容错算法的思想(包括 PBFT 算法)。oral message方案存在一个致命的缺陷:当将军总数为n,叛将数为f时,算法需要递归协商 f+1 轮,消息复杂度为 O(n ^ (f + 1)),消息数量指数级暴增。你可以想象一下,如果叛将数为 64,消息数已经远远超过 int64 所能表示的了,这是无法想象的。 + + + +**PBFT算法流程** + +PBFT 算法,通过签名(或消息认证码 MAC)约束恶意节点的行为,每个节点都可以通过验证消息签名确认消息的发送来源,一个节点无法伪造另外一个节点的消息。PBFT 算法采用了三阶段协议,基于大多数原则(2f + 1,f表示叛将数)实现共识。另外,与oral message不同的是,PBFT 算法实现的是一系列值的共识,而不是单值的共识。我们先来看看PBFT 算法的流程。为了方便演示,假设一共有A、B、C、D四个节点,那么根据Paxos算法的理论,最多允许存在一个恶意节点((4-1)/3=1),我们假设B是恶意节点,现在客户端发起了一个提议值(进攻),希望被各节点达成共识: + +![PBFT算法流程](images/Solution/PBFT算法流程.png) + +在PBFT算法中,第一个接收到客户端请求的节点,将成为Leader节点,我们假设A节点首先接收了到请求。A接收到客户端请求之后,会执行三阶段协议(Three-phase protocol)。 + + + +#### 预准备阶段 + +首先,A进入预准备(Pre-prepare)阶段,构造包含作战指令的预准备消息,并广播给其他节点(B、C、D): + +![PBFT算法流程-预准备阶段](images/Solution/PBFT算法流程-预准备阶段.png) + + + +#### 准备阶段 + +B、C、D收到消息后 , 不能确认自己接收到指令和其他人的是否相同。比如,D是叛徒,D收到了 2 个指令,然后他给A发送的是其中一个指令,给B、C发送的是另一个指令,这样就会出现无法一致行动的情况。 + +所以, 接收到预准备消息之后,B、C、D会进入准备(Prepare)阶段,并分别广播包含指令的准备消息给其他节点。这里我们假设叛徒D想通过不发送消息,来干扰共识协商: + +![PBFT算法流程-准备阶段](images/Solution/PBFT算法流程-准备阶段.png) + + + +#### 提交阶段 + +然后,当某个节点收到 2f 个包含相同指令的准备消息后,会进入提交(Commit)阶段(这里的f 为叛徒数, 2f 包括自己)。 + +> 在这里,思考一个问题:这个时候节点(比如B)可以直接执行指令吗?答案还是不能,因为B不能确认A、C、D是否收到了 2f 个一致的包含相同指令的准备消息。也就是说,B这时无法确认A、C、D是否准备好了执行指令。 + +进入提交阶段后,各节点分别广播提交消息给其他节点,也就是告诉其他节点:“我已经准备好了,可以执行指令了”: + +![PBFT算法流程-提交阶段](images/Solution/PBFT算法流程-提交阶段.png) + + + +#### 响应 + +最后,当某个节点收到 2f + 1 个验证通过的提交消息后(其中 f 为叛徒数,包括自己),也就是说,大部分的节点们已经达成共识,这时可以执行指令了,那么该节点将执行客户端的指令,执行完毕后发送执行成功的消息给客户端。 + +![PBFT算法流程-响应](images/Solution/PBFT算法流程-响应.png) + +最后,当客户端收到 f+1 个相同的响应(Reply)消息时,说明各个节点已经就指令达成了共识,并执行了指令。 + +在PBFT算法中,共识是否达成,客户端是会做判断的,如果客户端在指定时间内未收到请求对应的 f + 1 相同响应,就认为集群出故障了,共识未达成,客户端会重新发送请求。PBFT 算法通过视图变更(View Change)的方式,来处理主节点作恶,当发现主节点在作恶时,会以“轮流上岗”方式,推举新的主节点。 + + + +### PoW算法 + +谈起比特币,大家至少都应该有所耳闻吧?比特币是基于区块链实现的,而区块链运行在Internet上,这就存在有人试图作恶的情况。之前提到的口信消息解决方案和PBFT算法,虽然能防止坏人作恶,但只能防止少数,也就是 (n-1)/3 个坏人 (其中 n 为节点数)。可由于很多区块链是在公网环境,可能有坏人不断增加节点数,轻松突破 (n - 1) / 3 的限制。解决上述问题的方法就是PoW算法。PoW算法通过工作量证明(Proof of Work)增加了坏人作恶的成本,以此防止坏人作恶。本章,我就来讲讲PoW算法的原理。 + +PoW算法,属于拜占庭容错算法中的一种,能容忍一定比例的作恶行为,所以它在相对开放的场景中应用广泛,比如公链、联盟链。而非拜占庭容错算法(比如 Raft)无法对作恶行为进行容错,主要用于封闭、绝对可信的场景中,比如私链、公司内网的 DevOps 环境。 + + + +#### 工作量证明 + +什么是工作量证明 (Proof Of Work,简称PoW) ?你可以这么理解:就是一份证明,用来确认你做过一定量的工作。比如,你的大学毕业证书就是一份工作量证明,证明你通过 4 年的努力完成了相关课程的学习。 + +那么,回到计算机世界,具体来说就是,客户端需要做一定难度的工作才能得出一个结果,验证方却很容易通过结果来检查出客户端是不是做了相应的工作。 + +比如小肖去Google面试,说自己的编程能力很强,那么她需要做一定难度的工作(比如做个算法题)。根据做题结果,面试官可以判断她是否适合这个岗位。这就是一个现实版的工作量证明。具体的工作量证明过程,就像下图中的样子: +![PoW算法-工作量证明](images/Solution/PoW算法-工作量证明.png) + +**哈希运算** + +既然工作量证明是通过指定的结果,来证明自己做过了一定量的工作。那么在区块链的 PoW 算法中需要做哪些工作呢?答案是哈希运算。哈希运算的核心是哈希函数(Hash Function),也叫散列函数。就是说,你输入一个任意长度的字符串,哈希函数会计算出一个哈希值。比如,我们对任意长度字符串(比如"tutuxiao")执行 SHA256 哈希运算,就会得到一个 32 字节的哈希值,就像下面的样子: + +```java +$ echo -n "tutuxiao" | sha256sum +bb2f0f297fe9d3b8669b6b4cec3bff99b9de596c46af2e4c4a504cfe1372dc52 +``` + +那么我们如何通过哈希运算来证明工作量呢? + +举个例子,我们给出的工作量要求是:基于一个基本的字符串(比如"tutuxiao"),在这个字符串后面添加一个整数值,然后对变更后的字符串进行 SHA256 哈希运算,如果运算后得到的哈希值(16 进制形式)是以"0000"开头的,就验证通过;为了达到这个工作量证明的目标,我们需要不停地递增整数值,对得到的新字符串进行 SHA256 哈希运算。按照这个规则,我们需要经过 35024 次计算,才能找到恰好前 4 位为 0 的哈希值: + +```java +"tutuxiao0" => 01f28c5df06ef0a575fd0e529be9a6f73b1290794762de014ec84182081e118e +"tutuxiao1" => a2567c06fdb5775cb1e3ce17b72754cf146fcc6da75c8f1d87d7ab6a1b8c4523 +... +"tutuxiao35022" => +8afc85049a9e92fe0b6c98b02b27c09fb869fbfe273d0ab84ad8c5ac17b8627e +"tutuxiao35023" => +0000ec5927ba10ea45a6822dcc205050ae74ae1ad2d9d41e978e1ec9762dc404 +``` + +通过上面这个示例可以看到,工作量证明就是通过执行哈希运算,经过一段时间的计算后,得到符合条件的哈希值。在实际场景中,我们可以根据场景特点,制定不同的规则,比如,你可以试试分别运行多少次,才能找到恰好前 3 位和前 5 位为 0 的哈希值。 + + + +#### 区块链 + +区块链也是通过 SHA256 执行哈希运算,通过计算出符合指定条件的哈希值,来证明工作量的。因为在区块链中,PoW 算法是基于区块链中的区块信息来进行哈希运算的。区块链的区块,是由区块头、区块体 2 部分组成的: + +![PoW算法-区块链](images/Solution/PoW算法-区块链.png) + +- 区块头(Block Head):区块头主要由上一个区块的哈希值、区块体的哈希值、4 字节的随机数(nonce)等组成的,共80 字节固定长度 +- 区块体(Block Body):区块包含的交易数据,其中的第一笔交易是 Coinbase 交易,这是一笔激励矿工的特殊交易 + +在区块链中,给出的工作量要求是:对区块头执行 SHA256 哈希运算,得到的结果再执行一个哈希运算,计算出的哈希值,只有小于目标值(target),才是有效的。计算出符合条件的哈希值后,矿工就会把这个信息广播给集群中所有其他节点,其他节点验证通过后,会将这个区块加入到自己的区块链中,最终形成一串区块链,就像下图的样子: + +![PoW算法-区块链串](images/Solution/PoW算法-区块链串.png) + +从上面这种工作量证明要求可以看出,算力越强,系统大概率会越先计算出这个哈希值。这也就意味着, 攻击者能挖掘一条比原链更长的攻击链,并将攻击链向全网广播。而按照约定,节点将接受更长的链,也就是攻击链,丢弃原链。就像下图的样子: + +![PoW算法-攻击链](images/Solution/PoW算法-攻击链.png) + +这样的话,按照比特币的区块链约定——“最长链胜出,其它节点在这条链基础上扩展”,那么如果坏人们掌握了 51% 的算力,就通过优势算力实现对最长链的争夺,也就是发起 51% 攻击,实现双花(Double Spending)。 + +即使攻击者只有 30% 的算力,他也有可能连续计算出多个区块的哈希值,挖掘出更长的攻击链,发动攻击; 另外,即使攻击者拥有 51% 的算力,他也有可能半天无法计算出一个区块的哈希值,也就是攻击失败。也就是说,能否计算出符合条件的哈希值,有一定的概率性,但长久来看,攻击者攻击成功的概率等同于攻击者算力的权重。 + + + +## 强一致性算法 + +**两阶段提交协议** + +两阶段提交系统具有完全的C(一致性),很糟糕的A(可用性),很糟糕的P(容错性)。 +首先,两阶段提交协议保证了副本间是完全一致的,这也是协议的设计目的。再者,协议在一个节点出现异常时,就无法更新数据,其服务可用性较低。最后,一旦协调者与参与者之间网络分化,无法提供服务。 + + + +**Paxos和Raft算法** + +Paxos 协议和Raft算法都是强一致性协议。Paxos只有两种情况下服务不可用:一是超过半数的 Proposer 异常,二是出现活锁。前者可以通过增加 Proposer 的个数来 降低由于 Proposer 异常影响服务的概率,后者本身发生的概率就极低。最后,只要能与超过半数的 Proposer 通信就可以完成协议流程,协议本身具有较好的容忍网络分区的能力。 + + + +### Paxos协议 + +二阶段提交还是三阶段提交都无法很好的解决分布式的一致性问题,直到Paxos算法的提出,Paxos协议由Leslie Lamport最早在1990年提出,目前已经成为应用最广的分布式一致性算法。Google Chubby的作者Mike Burrows说过这个世界上只有一种一致性算法,那就是Paxos,其它的算法都是残次品。 + + + +**为什么在Paxos运行过程中,半数以内的Acceptor失效都能运行?** + +- 如果半数以内的Acceptor失效时 还没确定最终的value,此时,所有Proposer会竞争 提案的权限,最终会有一个提案会 成功提交。之后,会有半过数的Acceptor以这个value提交成功 +- 如果半数以内的Acceptor失效时 已确定最终的value,此时,所有Proposer提交前 必须以 最终的value 提交,此值也可以被获取,并不再修改 + + + +**如何产生唯一的编号呢?** +在《Paxos made simple》中提到的是让所有的Proposer都从不相交的数据集合中进行选择,例如系统有5个Proposer,则可为每一个Proposer分配一个标识j(0~4),则每一个proposer每次提出决议的编号可以为5*i + j(i可以用来表示提出议案的次数)。 + + + +#### 核心思想 + +- 引入了多个Acceptor,单个Acceptor就类似2PC中协调者的单点问题,避免故障 +- Proposer用更大ProposalID来抢占临时的访问权,可以对比2PC协议,防止其中一个Proposer崩溃宕机产生阻塞问题 +- 保证一个N值,只有一个Proposer能进行到第二阶段运行,Proposer按照ProposalID递增的顺序依次运行 +- 新ProposalID的proposer比如认同前面提交的Value值,递增的ProposalID的Value是一个继承关系 + + + +**容错要求** + +- 半数以内的Acceptor失效、任意数量的Proposer 失效,都能运行 +- 一旦value值被确定,即使 半数以内的Acceptor失效,此值也可以被获取,并不再修改 + + + +#### 节点角色 + +Paxos 协议中,有三类节点: + +- **Proposer(提案者)** + + - Proposer 可以有多个,Proposer 提出议案(value)。所谓 value,在工程中可以是任何操作,例如“修改某个变量的值为某个值”、“设置当前 primary 为某个节点”等等。Paxos 协议中统一将这些操作抽象为 value。 + - 不同的 Proposer 可以提出不同的甚至矛盾的 value,例如某个 Proposer 提议“将变量 X 设置为 1”,另一个 Proposer 提议“将变量 X 设置为 2”,但对同一轮 Paxos 过程,最多只有一个 value 被批准。 + +- **Acceptor(批准者)** + + - Acceptor 有 N 个,Proposer 提出的 value 必须获得超过半数(N/2+1)的 + - Acceptor 批准后才能通过。Acceptor 之间完全对等独立 + +- **Learner(学习者)** + + - Learner 学习被批准的 value。所谓学习就是通过读取各个 Proposer 对 value 的选择结果,如果某个 value 被超过半数 Proposer 通过,则 Learner 学习到了这个 value + + - 这里类似 Quorum 议会机制,某个 value 需要获得 W=N/2 + 1 的 Acceptor 批准,Learner 需要至少读取 N/2+1 个 Accpetor,至多读取 N 个 Acceptor 的结果后,能学习到一个通过的 value + + + +#### 选举过程 + +![Paxos选举过程](images/Solution/Paxos选举过程.png) + +- **Phase 1:准备阶段** + - **P1a:Proposer 发送 Prepare请求** + + Proposer 生成全局唯一且递增的ProposalID,向 Paxos 集群的所有机器发送 Prepare请求,这里不携带value,只携带N即ProposalID + + - **P1b:Acceptor 应答 Prepare** + + Acceptor 收到 Prepare请求后,判断收到的ProposalID是否比之前已响应的所有提案的N大。 + + - 如果是 + - 在本地持久化 N,可记为Max_N + - 回复请求,并带上已Accept的提案中N最大的value(若此时还没有已Accept的提案,则返回value为空) + - 做出承诺:不会Accept任何小于Max_N的提案 + + - 如果否,则不回复或回复Error + +- **Phase 2:选举阶段** + - **P2a:Proposer 发送 Accept** + 经过一段时间后,Proposer 收集到一些 Prepare 回复,有下列几种情况: + - 回复数量 > 一半的Acceptor数量,且所有的回复的value都为空,则Porposer发出accept请求,并带上自己指定的value + - 回复数量 > 一半的Acceptor数量,且有的回复value不为空,则Porposer发出accept请求,并带上回复中ProposalID最大的value(作为自己的提案内容) + - 回复数量 ≤ 一半的Acceptor数量,则尝试更新生成更大的ProposalID,再转P1a执行 + - **P2b:Acceptor 应答 Accept** + Accpetor 收到 Accpet请求 后,判断: + - 收到的N >= Max_N (一般情况下是 等于),则回复提交成功,并持久化N和value + - 收到的N < Max_N,则不回复或者回复提交失败 + - **P2c:Proposer 统计投票** + 经过一段时间后,Proposer 收集到一些 Accept 回复提交成功,有几种情况: + - 回复数量 > 一半的Acceptor数量,则表示提交value成功。此时,可以发一个广播给所有Proposer、Learner,通知它们已commit的value + - 回复数量 <= 一半的Acceptor数量,则 尝试 更新生成更大的 ProposalID,再转P1a执行 + - 收到一条提交失败的回复,则尝试更新生成更大的 ProposalID,再转P1a执行 + + + +**最后,经过多轮投票后,达到的结果是:** + +- 所有Proposer都 提交提案成功了,且提交的value是同一个value +- 过半数的 Acceptor都提交成功了,且提交的是 同一个value + + + +#### 约束条件 + +Paxos 协议的几个约束: + +- P1: 一个Acceptor必须接受(accept)第一次收到的提案 +- P2a: 一旦一个具有value v的提案被批准(chosen),那么之后任何Acceptor 再次接受(accept)的提案必须具有value v +- P2b: 一旦一个具有value v的提案被批准(chosen),那么以后任何 Proposer 提出的提案必须具有value v +- P2c: 如果一个编号为n的提案具有value v,那么存在一个多数派,要么他们中所有人都没有接受(accept)编号小于n的任何提案,要么他们已经接受(accpet)的所有编号小于n的提案中编号最大的那个提案具有value v + + + +每轮 Paxos 协议分为准备阶段和批准阶段,在这两个阶段 Proposer 和 Acceptor 有各自的处理流程。Proposer与Acceptor之间的交互主要有4类消息通信,如下图: + +![Paxos协议Proposer与Acceptor交互流程](images/Solution/Paxos协议Proposer与Acceptor交互流程.png) + + 这4类消息对应于paxos算法的两个阶段4个过程: + +- Phase 1 + - proposer向网络内超过半数的acceptor发送prepare消息 + - acceptor正常情况下回复promise消息 +- Phase 2 + - 在有足够多acceptor回复promise消息时,proposer发送accept消息 + - 正常情况下acceptor回复accepted消息 + + + +### Raft协议 + +**三种角色** + +**Raft是一个用于管理日志一致性的协议**。它将分布式一致性分解为多个子问题:**Leader选举(Leader election)、日志复制(Log replication)、安全性(Safety)、日志压缩(Log compaction)等**。同时,Raft算法使用了更强的假设来减少了需要考虑的状态,使之变的易于理解和实现。**Raft将系统中的角色分为领导者(Leader)、跟从者(Follower)和候选者**(Candidate): + +- **Leader(领导)**:接受客户端请求,并向Follower同步请求日志,当日志同步到大多数节点上后告诉Follower提交日志 +- **Follower(群众)**:接受并持久化Leader同步的日志,在Leader告之日志可以提交之后,提交日志 +- **Candidate(候选人)**:Leader选举过程中的临时角色 + +三种状态的转换关系如下: + +[![Raft的三种状态转换](images/Solution/Raft的三种状态转换.png)](https://shuwoom.com/wp-content/uploads/2018/05/raft_change_user.png) + +Raft要求系统在任意时刻最多只有一个Leader,正常工作期间只有Leader和Followers。Raft算法将时间分为一个个的**任期(term)**,每一个term的开始都是Leader选举。在成功选举Leader之后,Leader会在整个term内管理整个集群。如果Leader选举失败,该term就会因为没有Leader而结束。 + + + +**Term(任期)** + +**Raft 算法将时间划分成为任意不同长度的任期(term)**。任期用连续的数字进行表示。每一个任期的开始都是一次选举(election),一个或多个候选人会试图成为领导人。如果一个候选人赢得了选举,它就会在该任期的剩余时间担任领导人。在某些情况下,选票会被瓜分,有可能没有选出领导人,那么,将会开始另一个任期,并且立刻开始下一次选举。**Raft 算法保证在给定的一个任期最多只有一个领导人**。 + + + +**RPC(通信)** + +**Raft 算法中服务器节点之间通信使用远程过程调用(RPC)**,并且基本的一致性算法只需要两种类型的 RPC,为了在服务器之间传输快照增加了第三种 RPC。RPC有三种: + +- **RequestVote RPC**:候选人在选举期间发起 +- **AppendEntries RPC**:领导人发起的一种心跳机制,复制日志也在该命令中完成 +- **InstallSnapshot RPC**: 领导者使用该RPC来发送快照给太落后的追随者 + + + +#### 选举流程 + +**① Leader选举过程** + +**Raft 使用心跳(heartbeat)触发Leader选举**。当服务器启动时,初始化为Follower。**Leader**向所有**Followers**周期性发送**heartbeat**。如果Follower在选举超时时间内没有收到Leader的heartbeat,就会等待一段随机的时间后发起一次Leader选举。每一个follower都有一个时钟,是一个随机的值,表示的是follower等待成为leader的时间,谁的时钟先跑完,则发起leader选举。Follower将其当前term加一然后转换为Candidate。它首先给自己投票并且给集群中的其他服务器发送 RequestVote RPC。结果有以下三种情况: + +- **赢得了多数的选票,成功选举为Leader** +- 收到了Leader的消息,表示有其它服务器已经抢先当选了Leader +- 没有服务器赢得多数的选票,Leader选举失败,等待选举时间**超时后发起下一次选举** + +![Raft的Leader选举过程](images/Solution/Raft的Leader选举过程.jpg) + + + +**② Leader选举的限制** + +**在Raft协议中,所有的日志条目都只会从Leader节点往Follower节点写入,且Leader节点上的日志只会增加,绝对不会删除或者覆盖**。这意味着Leader节点必须包含所有已经提交的日志,即能被选举为Leader的节点一定需要包含所有的已经提交的日志。因为日志只会从Leader向Follower传输,所以如果被选举出的Leader缺少已经Commit的日志,那么这些已经提交的日志就会丢失,显然这是不符合要求的。这就是Leader选举的限制:**能被选举成为Leader的节点,一定包含了所有已经提交的日志条目**。 + + + +#### 日志复制 + +日志复制的目的是为了保证数据一致性。 + +- **日志复制的过程** + + Leader选出后,就开始接收客户端的请求。Leader把请求作为日志条目(Log entries)加入到它的日志中,然后并行的向其他服务器发起 AppendEntries RPC复制日志条目。当这条日志被复制到大多数服务器上,Leader将这条日志应用到它的状态机并向客户端返回执行结果。 + + - 客户端的每一个请求都包含被复制状态机执行的指令 + - leader把这个指令作为一条新的日志条目添加到日志中,然后并行发起 RPC 给其他的服务器,让他们复制这条信息 + - 假如这条日志被安全的复制,领导人就应用这条日志到自己的状态机中,并返回给客户端 + - 如果follower宕机或者运行缓慢或者丢包,leader会不断的重试,直到所有follower最终都复制了所有的日志条目 + + ![Raft日志复制的过程](images/Solution/Raft日志复制的过程.png) + 简而言之,leader选举的过程是:1、增加term号;2、给自己投票;3、重置选举超时计时器;4、发送请求投票的RPC给其它节点。 + +- **日志的组成** + + 日志由有序编号(log index)的日志条目组成**。**每个日志条目包含它被创建时的任期号(term)和用于状态机执行的命令。如果一个日志条目被复制到大多数服务器上,就被认为可以提交(commit)了。 + ![Raft日志的组成](images/Solution/Raft日志的组成.png) + + 上图显示,共有 8 条日志,提交了 7 条。提交的日志都将通过状态机持久化到磁盘中,防止宕机。 + +- **日志的一致性** + + ![Raft日志的一致性](images/Solution/Raft日志的一致性.jpg) + + **① 日志复制的两条保证** + + - 如果不同日志中的两个条目有着**相同的索引和任期号,则它们所存储的命令是相同的**(原因:leader 最多在一个任期里的一个日志索引位置创建一条日志条目,日志条目在日志的位置从来不会改变) + - 如果不同日志中的两个条目有着**相同的索引和任期号,则它们之前的所有条目都是完全一样的**(原因:**每次 RPC 发送附加日志时**,leader 会把这条日志条目的前面的**日志的下标和任期号一起发送给 follower**,如果 **follower 发现和自己的日志不匹配,那么就拒绝接受这条日志**,这个称之为**一致性检查**) + + **② 日志的不正常情况** + + 一般情况下,Leader和Followers的日志保持一致,因此 AppendEntries 一致性检查通常不会失败。然而,Leader崩溃可能会导致日志不一致:**旧的Leader可能没有完全复制完日志中的所有条目**。 + + 下图阐述了一些Followers可能和新的Leader日志不同的情况。**一个Follower可能会丢失掉Leader上的一些条目,也有可能包含一些Leader没有的条目,也有可能两者都会发生**。丢失的或者多出来的条目可能会持续多个任期。 + ![Raft日志的不正常情况](images/Solution/Raft日志的不正常情况.jpg) + + **③ 如何保证日志的正常复制** + + Leader通过强制Followers复制它的日志来处理日志的不一致,Followers上的不一致的日志会被Leader的日志覆盖。Leader为了使Followers的日志同自己的一致,Leader需要找到Followers同它的日志一致的地方,然后覆盖Followers在该位置之后的条目。 + + 具体的操作是:**Leader会从后往前试**,每次AppendEntries失败后尝试前一个日志条目,**直到成功找到每个Follower的日志一致位置点(基于上述的两条保证),然后向后逐条覆盖Followers在该位置之后的条目**。 + + 总结一下就是:**当 leader 和 follower 日志冲突的时候**,leader 将**校验 follower 最后一条日志是否和 leader 匹配**,如果不匹配,**将递减查询,直到匹配,匹配后,删除冲突的日志**。这样就实现了主从日志的一致性。 + + + +#### 安全性 + +Raft增加了如下两条限制以保证安全性: + +- 拥有**最新的已提交的log entry的Follower才有资格成为leader** +- **Leader只能推进commit index来提交当前term的已经复制到大多数服务器上的日志**,旧term日志的提交要等到提交当前term的日志来间接提交(log index 小于 commit index的日志被间接提交) + ![Raft安全性](images/Solution/Raft安全性.jpg) + + + +#### 日志压缩 + +在实际的系统中,**不能让日志无限增长**,否则**系统重启时需要花很长的时间进行回放**,从而影响可用性。Raft采用对整个系统进行snapshot来解决,snapshot之前的日志都可以丢弃(以前的数据已经落盘了)。每个副本独立的对自己的系统状态进行snapshot,并且只能对已经提交的日志记录进行snapshot。 + +![Raft日志压缩](images/Solution/Raft日志压缩.png) + +**Snapshot中包含以下内容**: + +- **日志元数据,最后一条已提交的 log entry的 log index和term**。这两个值在snapshot之后的第一条log entry的AppendEntries RPC的完整性检查的时候会被用上 +- **系统当前状态** + + + +当Leader要发给某个日志落后太多的Follower的log entry被丢弃,Leader会将snapshot发给Follower。或者当新加进一台机器时,也会发送snapshot给它。发送snapshot使用InstalledSnapshot RPC。做snapshot既不要做的太频繁,否则**消耗磁盘带宽**, 也不要做的太不频繁,否则一旦节点重启需要回放大量日志,影响可用性。**推荐当日志达到某个固定的大小做一次snapshot**。做一次snapshot可能耗时过长,会影响正常日志同步。可以通过使用copy-on-write技术避免snapshot过程影响正常日志同步。 + + + +#### 成员变更 + +- **常规处理成员变更存在的问题** + + 可能存在这样的一个时间点,两个不同的领导者在同一个任期里都可以被选举成功(双主问题),一个是通过旧的配置,一个通过新的配置。简而言之,成员变更存在的问题是增加或者减少的成员太多了,导致旧成员组和新成员组没有交集,因此出现了双主。 + +- **解决方案之一阶段成员变更** + + Raft解决方法是每次成员变更只允许增加或删除一个成员(如果要变更多个成员,连续变更多次)。 + + + +#### 常见问题 + +**问题1:Raft分为哪几个部分?** + +主要是分为leader选举、日志复制、日志压缩、成员变更等。 + + + +**问题2:Raft中任何节点都可以发起选举吗?** + +Raft发起选举的情况有如下几种: + +- 刚启动时,所有节点都是follower,这个时候发起选举,选出一个leader +- 当leader挂掉后,**时钟最先跑完的follower发起重新选举操作**,选出一个新的leader +- 成员变更的时候会发起选举操作 + + + +**问题3:Raft中选举中给候选人投票的前提?** + +**Raft确保新当选的Leader包含所有已提交(集群中大多数成员中已提交)的日志条目**。这个保证是在RequestVoteRPC阶段做的,candidate在发送RequestVoteRPC时,会带上自己的**last log entry的term_id和index**,follower在接收到RequestVoteRPC消息时,**如果发现自己的日志比RPC中的更新,就拒绝投票**。日志比较的原则是,如果本地的最后一条log entry的term id更大,则更新,如果term id一样大,则日志更多的更大(index更大)。 + + + +**问题4:Raft网络分区下的数据一致性怎么解决?** + +发生了网络分区或者网络通信故障,**使得Leader不能访问大多数Follwer了,那么Leader只能正常更新它能访问的那些Follower,而大多数的Follower因为没有了Leader,他们重新选出一个Leader**,然后这个 Leader来接受客户端的请求,如果客户端要求其添加新的日志,这个新的Leader会通知大多数Follower。如果这时网络故障修复 了,那么原先的Leader就变成Follower,在失联阶段这个老Leader的任何更新都不能算commit,都回滚,接受新的Leader的新的更新(递减查询匹配日志)。 + + + +**问题5:Raft数据一致性如何实现?** + +主要是通过日志复制实现数据一致性,leader将请求指令作为一条新的日志条目添加到日志中,然后发起RPC 给所有的follower,进行日志复制,进而同步数据。 + + + +**问题6:Raft的日志有什么特点?** + +日志由有序编号(log index)的日志条目组成,每个日志条目包含它被创建时的任期号(term)和用于状态机执行的命令。 + + + +**问题7:Raft和Paxos的区别和优缺点?** + +- Raft的leader有限制,**拥有最新日志的节点才能成为leader**,multi-paxos中对成为Leader的限制比较低,**任何节点都可以成为leader** +- Raft中Leader在每一个任期都有Term号 + + + +**问题8:Raft里面怎么保证数据被commit,leader宕机了会怎样,之前的没提交的数据会怎样?** + +leader会通过RPC向follower发出日志复制,等待所有的follower复制完成,这个过程是阻塞的**。**老的leader里面没提交的数据会回滚,然后同步新leader的数据。 + + + +**问题9:Raft日志压缩是怎么实现的?增加或删除节点呢??** + +在实际的系统中,**不能让日志无限增长**,否则**系统重启时需要花很长的时间进行回放**,从而影响可用性。Raft采用对整个系统进行snapshot来解决,snapshot之前的日志都可以丢弃(以前的数据已经落盘了)**。**snapshot里面主要记录的是日志元数据,即最后一条已提交的 log entry的 log index和term。 + + + +### ZAB协议 + +ZAB 协议全称Zookeeper Atomic Broadcast(Zookeeper 原子广播协议)。Zookeeper 是一个为分布式应用提供高效且可靠的分布式协调服务。在解决分布式一致性方面,Zookeeper 并没有使用 Paxos ,而是采用了 ZAB 协议。 + +ZAB 协议定义:**ZAB 协议是为分布式协调服务 Zookeeper 专门设计的一种支持 `崩溃恢复` 和 `原子广播` 协议**。下面我们会重点讲这两个东西。基于该协议,Zookeeper 实现了一种 `主备模式` 的系统架构来保持集群中各个副本之间`数据一致性`。具体如下图所示: + +![ZAB协议](images/Solution/ZAB协议.png) + +上图显示了 Zookeeper 如何处理集群中的数据。所有客户端写入数据都是写入到 主进程(称为 Leader)中,然后,由 Leader 复制到备份进程(称为 Follower)中。从而保证数据一致性。从设计上看,和 Raft 类似。 + +那么复制过程又是如何的呢?复制过程类似 2PC,ZAB 只需要 Follower 有一半以上返回 Ack 信息就可以执行提交,大大减小了同步阻塞。也提高了可用性。 + + + +#### 消息广播 + +ZAB 协议的消息广播过程使用的是一个原子广播协议,类似一个 **二阶段提交过程**。对于客户端发送的写请求,全部由 Leader 接收,Leader 将请求封装成一个事务 Proposal,将其发送给所有 Follwer ,然后,根据所有 Follwer 的反馈,如果超过半数成功响应,则执行 commit 操作(先提交自己,再发送 commit 给所有 Follwer)。整个广播流程分为 3 步骤: + +**第一步**:将数据都复制到 Follwer 中 + +![ZAB消息广播数据复制至Follwer](images/Solution/ZAB消息广播数据复制至Follwer.png) + +**第二步**:等待 Follwer 回应 Ack,最低超过半数即成功 + +![ZAB消息广播等待Follwer回应ACK](images/Solution/ZAB消息广播等待Follwer回应ACK.png) + +**第三步**:当超过半数成功回应,则执行 commit ,同时提交自己 + +![ZAB消息广播Commit](images/Solution/ZAB消息广播Commit.png) + +通过以上 3 个步骤,就能够保持集群之间数据的一致性。实际上,在 Leader 和 Follwer 之间还有一个消息队列,用来解耦他们之间的耦合,避免同步,实现异步解耦。还有一些细节: + +- Leader 在收到客户端请求之后,会将这个请求封装成一个事务,并给这个事务分配一个全局递增的唯一 ID,称为事务ID(ZXID),ZAB 兮协议需要保证事务的顺序,因此必须将每一个事务按照 ZXID 进行先后排序然后处理 +- 在 Leader 和 Follwer 之间还有一个消息队列,用来解耦他们之间的耦合,解除同步阻塞 +- zookeeper集群中为保证任何所有进程能够有序的顺序执行,只能是 Leader 服务器接受写请求,即使是 Follower 服务器接受到客户端的请求,也会转发到 Leader 服务器进行处理 +- 实际上,这是一种简化版本的 2PC,不能解决单点问题。等会我们会讲述 ZAB 如何解决单点问题(即 Leader 崩溃问题) + + + +#### 崩溃恢复 + +当Leader崩溃,即进入我们开头所说的崩溃恢复模式(崩溃即:Leader失去与过半Follwer的联系)。ZAB定义了2个原则: + +- **ZAB 协议确保那些已经在 Leader 提交的事务最终会被所有服务器提交** +- **ZAB 协议确保丢弃那些只在 Leader 提出/复制,但没有提交的事务** + +所以,ZAB 设计了下面这样一个选举算法:**能够确保提交已经被 Leader 提交的事务,同时丢弃已经被跳过的事务。**针对这个要求,如果让 Leader 选举算法能够保证新选举出来的 Leader 服务器拥有集群总所有机器编号(即 ZXID 最大)的事务,那么就能够保证这个新选举出来的 Leader 一定具有所有已经提交的提案。而且这么做有一个好处是:**可以省去 Leader 服务器检查事务的提交和丢弃工作的这一步操作。** + + + +#### 数据同步 + +当崩溃恢复之后,需要在正式工作之前(接收客户端请求),Leader 服务器首先确认事务是否都已经被过半的 Follwer 提交了,即是否完成了数据同步。目的是为了保持数据一致。当所有的 Follwer 服务器都成功同步之后,Leader 会将这些服务器加入到可用服务器列表中。 + + + +**ZXID 生成** + +在 ZAB 协议的事务编号 ZXID 设计中,ZXID 是一个 64 位的数字,其中低 32 位可以看作是一个简单的递增的计数器,针对客户端的每一个事务请求,Leader 都会产生一个新的事务 Proposal 并对该计数器进行 + 1 操作。而高 32 位则代表了 Leader服务器上取出本地日志中最大事务Proposal的ZXID,并从该ZXID中解析出对应的epoch值,然后再对这个值加一。 + +![ZAB数据同步ZXID](images/Solution/ZAB数据同步ZXID.png) + +高 32 位代表了每代 Leader 的唯一性,低 32 代表了每代 Leader 中事务的唯一性。同时,也能让 Follwer 通过高 32 位识别不同的 Leader。简化了数据恢复流程。基于这样的策略:当 Follower 链接上 Leader 之后,Leader 服务器会根据自己服务器上最后被提交的 ZXID 和 Follower 上的 ZXID 进行比对,比对结果要么回滚,要么和 Leader 同步。 + + + +## 两阶段提交/XA(2PC) + +**核心思路** + +参与者将操作成功或失败结果通知协调者,再由协调者根据所有参与者反馈情况决定参与者是否要提交操作还是中止操作。 + +![二阶段提交协议](images/Solution/2pc.png) + +熟悉MySQL的同学对两阶段提交应该颇为熟悉,MySQL的事务就是通过**「日志系统」** 来完成两阶段提交的。两阶段协议可以用于单机集中式系统,由事务管理器协调多个资源管理器;也可以用于分布式系统,**「由一个全局的事务管理器协调各个子系统的局部事务管理器完成两阶段提交」** 。 + + + +### 第一阶段:投票阶段 + +![2PC第一阶段](images/Solution/2PC第一阶段.jpg) + +这个协议有 **「两个角色」** ,A节点是事务的协调者,B和C是事务的参与者。事务的提交分成两个阶段: + +- 第一个阶段是 **「投票阶段」** + - 协调者首先将命令 **「写入日志」** + - **「发一个prepare命令」** 给B和C节点这两个参与者 + - B和C收到消息后,根据自己的实际情况,**「判断自己的实际情况是否可以提交」** + - 将处理结果 **「记录到日志」** 系统 + - 将结果 **「返回」** 给协调者 + + + +### 第二阶段:决定阶段 + +![2PC第二阶段](images/Solution/2PC第二阶段.jpg) + +- 第二个阶段是 **「决定阶段」** + + 当A节点收到B和C参与者所有的确认消息后 + + - **「判断」** 所有协调者 **「是否都可以提交」** + - 如果可以则 **「写入日志」** 并且发起commit命令 + - 有一个不可以则 **「写入日志」** 并且发起abort命令 + - 参与者收到协调者发起的命令,**「执行命令」** + - 将执行命令及结果 **「写入日志」** + - **「返回结果」** 给协调者 + + + +### 两阶段提交缺点 + +- **单点故障**:一旦事务管理器出现故障,整个系统不可用 +- **数据不一致**:在阶段二,如果事务管理器只发送了部分 commit 消息,此时网络发生异常,那么只有部分参与者接收到 commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致 +- **响应时间较长**:整个消息链路是串行的,要等待响应结果,不适合高并发的场景 +- **不确定性**:当事务管理器发送 commit 之后,并且此时只有一个参与者收到了 commit,那么当该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功 + + + +### 无法解决的问题 + +当协调者和参与者同时出现故障时,两阶段提交无法保证事务的完整性。如果调者在发出commit消息之后宕机,而唯一接收到commit消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,因为没人知道事务是否已经被提交。 + + + +## 三阶段提交(3PC) + +3PC针对2PC做了改进: + +- **引入超时机制**:在2PC中,只有协调者拥有超时机制,3PC同时在协调者和参与者中都引入超时机制 +- **在2PC的第一阶段和第二阶段中插入一个准备阶段**:保证了在最后提交阶段之前各参与节点的状态是一致的 + +![三阶段提交协议](images/Solution/3pc.png) + +### 第一阶段:CanCommit + +协调者向参与者发送事务执行请求CanCommit,参与者如果可以提交就返回YES响应,否则就返回NO响应。 + + + +### 第二阶段:PreCommit + +协调者根据参与者反馈的结果来决定是否继续执行事务的PreCommit操作,根据协调者反馈的结果,有以下两种可能: + +- 假如协调者收到参与者的反馈结果都是YES,那么就会执行PreCommit操作 + - **发送预提交请求**:协调者向参与者发送PreCommit请求,并进入Prepared阶段 + - **事务预提交**:参与者接收到PreCommit请求后,执行事务操作 + - **响应反馈**:事务操作执行成功,则返回ACK响应,然后等待协调者的下一步通知 +- 假如有任何一个参与者向协调者发送了NO响应,或者等待超时之后,协调者没有收到参与者的响应,那么就中断事务 + - **发送中断请求**:协调者向所有参与者发送中断请求 + - **中断事务**:参与者收到中断请求之后(或超时之后,仍未收到协调者的请求),执行事务中断操作 + + + +### 第三阶段:DoCommit + +- **执行提交** + - **发送提交请求**:协调者收到ACK之后,向所有的参与者发送DoCommit请求 + - **事务提交**:参与者收到DoCommit请求之后,提交事务 + - **响应反馈**:事务提交之后,向协调者发送ACK响应 + - **完成事务**:协调者收到ACK响应之后,完成事务 +- **中断事务** + - 在第二阶段中,协调者没有收到参与者发送的ACK响应,那么就会执行中断事务 + + + +## 补偿机制(TCC) + +两阶段提交(2PC)和三阶段提交(3PC)并不适用于并发量大的业务场景。TCC事务机制相比于2PC、3PC,不会锁定整个资源,而是通过引入补偿机制,将资源转换为业务逻辑形式,锁的粒度变小。**核心思想**:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。TCC分为三个阶段: + +- **Try**:这个阶段对各个服务的资源做检测以及对资源进行锁定或者预留 +- **Confirm** :执行真正的业务操作,不作任何业务检查,只使用Try阶段预留的业务资源,Confirm操作要求具备幂等设计,Confirm失败后需要进行重试 +- **Cancel**:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,即执行回滚操作,释放Try阶段预留的业务资源 ,Cancel操作要求具备幂等设计,Cancel失败后需要进行重试 + ![Try-Confirm-Cancel](images/Solution/Try-Confirm-Cancel.png) + +TCC 事务机制相比于上面介绍的2PC,解决了其几个缺点: + +- **协调者单点**。由主业务方发起并完成这个业务活动。业务活动管理器也变成多点,引入集群 +- **同步阻塞**。引入超时,超时后进行补偿,并且不会锁定整个资源,将资源转换为业务逻辑形式,粒度变小 +- **数据一致性**。有了补偿机制之后,由业务活动管理器控制一致性 + +总之,TCC 就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,并且很大程度的**增加**了业务代码的**复杂度**,因此,这种模式并不能很好地被复用。 + + +**TCC案例场景** + +TCC将一次事务操作分为三个阶段:Try、Confirm、Cancel,我们通过一个订单/库存的示例来理解。假设我们的分布式系统一共包含4个服务:订单服务、库存服务、积分服务、仓储服务,每个服务有自己的数据库,如下图: +![TCC案例场景-订单服务](images/Solution/TCC案例场景-订单服务.png) + +从正常流程上讲,TCC仍是一个两阶段提交协议。但在执行出现问题的时候,有一定的自我修复能力,如果任何一个事务参与者出现了问题,协调者可以通过执行逆操作来取消之前的操作,达到最终的一致状态(比如冲正交易、查询交易)。从TCC的执行流程也可以看出,服务提供方需要提供额外的补偿逻辑,那么原来一个服务接口,引入TCC后可能要改造成3种逻辑: + +- **Try**:先是服务调用链路依次执行Try逻辑 +- **Confirm**:如果都正常的话,TCC分布式事务框架推进执行Confirm逻辑,完成整个事务 +- **Cancel**:如果某个服务的Try逻辑有问题,TCC分布式事务框架感知到之后就会推进执行各个服务的Cancel逻辑,撤销之前执行的各种操作 + +> 注意:在设计TCC事务时,接口的Cancel和Confirm操作都必须满足幂等设计。 + + + +### Try + +Try阶段一般用于锁定某个资源,设置一个预备状态或冻结部分数据。对于示例中的每一个服务,Try阶段所做的工作如下: + +- 订单服务:先置一个中间状态“UPDATING”,而不是直接设置“支付成功”状态 +- 库存服务:先用一个冻结库存字段保存冻结库存数,而不是直接扣掉库存 +- 积分服务:预增加会员积分 +- 仓储服务:创建销售出库单,但状态是UNKONWN + +![TCC-Try](images/Solution/TCC-Try.png) + + + +### Confirm + +根据Try阶段的执行情况,Confirm分为两种情况: + +- **理想情况下,所有Try全部执行成功,则执行各个服务的Confirm逻辑** +- **部分服务Try执行失败,则执行第三阶段——Cancel** + +Confirm阶段一般需要各个服务自己实现Confirm逻辑: + +- 订单服务:confirm逻辑可以是将订单的中间状态变更为PAYED-支付成功 +- 库存服务:将冻结库存数清零,同时扣减掉真正的库存 +- 积分服务:将预增加积分清零,同时增加真实会员积分 +- 仓储服务:修改销售出库单的状态为已创建-CREATED + ![TCC-Confirm](images/Solution/TCC-Confirm.png) + +> Confirm阶段的各个服务本身可能出现问题,这时候一般就需要TCC框架了(比如ByteTCC,tcc-transaction,himly),TCC事务框架一般会记录一些分布式事务的活动日志,保存事务运行的各个阶段和状态,从而保证整个分布式事务的最终一致性。 + + + +### Cancel + +如果Try阶段执行异常,就会执行Cancel阶段。比如:对于订单服务,可以实现的一种Cancel逻辑就是:将订单的状态设置为“CANCELED”;对于库存服务,Cancel逻辑就是:将冻结库存扣减掉,加回到可销售库存里去。 +![TCC-Cancel](images/Solution/TCC-Cancel.png) + +> 许多公司为了简化TCC使用,通常会将一个服务的某个核心接口拆成两个,如库存服务的扣减库存接口,拆成两个子接口:①扣减接口 ②回滚扣减库存接口,由TCC框架来保证当某个接口执行失败后去执行对应的rollback接口。 + + + +## 可靠消息最终一致性方案 + +该方案其实就是在分布式系统当中,把一个业务操作转换成一个消息,然后利用消息来实现事务的最终一致性。 + +> 比如从A账户向B账户转账的操作,当服务A从A账户扣除完金额后,通过消息中间件向服务B发一个消息,服务B收到这条消息后,进行B账户的金额增加操作。 + +可靠消息最终一致性方案一般有两种实现方式,原理其实是一样的: + +- **基于本地消息表** +- **基于支持分布式事务的消息中间件,如RocketMQ等** + + + +可靠消息最终一致性方案,一般适用于异步的服务调用,比如支付成功后,调用积分服务进行积分累加、调用库存服务进行发货等等。总结一下,可靠消息最终一致性方案其实最基本的思想就两点: + +- **通过引入消息中间件,保证生产者对消息的100%可靠投递** +- **通过引入Zookeeper,保证消费者能够对未成功消费的消息进行重新消费(消费者要保证自身接口的幂等性)** + + + +可靠消息最终一致性方案是目前业务主流的分布式事务落地方案,其优缺点主要如下: + +- **优点**:消息数据独立存储,降低业务系统与消息系统间的耦合 + +- **缺点**:一次消息发送需要两次请求,业务服务需要提供消息状态查询的回调接口 + + + +一般来讲,99%的分布式接口调用不需要做分布式事务,通过监控(邮件、短信告警)、记录日志,就可以事后快速定位问题,然后就是排查、出解决方案、修复数据。因为用分布式事务一定是有成本的,而且这个成本会比较高,特别是对于一些中小型公司。同时,引入分布式事务后,代码复杂度、开发周期会大幅上升,系统性能和吞吐量会大幅下跌,这就导致系统更加更加脆弱,更容易出bug。当然,如果有资源能够持续投入,分布式事务做好了的话,好处就是可以100%保证数据一致性不会出错。 + + + +### 本地消息表 + +![分布式事务-本地消息表](images/Solution/分布式事务-本地消息表.png) + +基于本地消息表的分布式事务,是最简便的实现方式,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于eBay。我们来看下面这张图,基于本地消息服务的分布式事务分为三大部分: + +- **可靠消息服务**:存储消息,因为通常通过数据库存储,所以也叫本地消息表 +- **生产者(上游服务)**:生产者是接口的调用方,生产消息 +- **消费者(下游服务)**:消费者是接口的服务方,消费消息 + ![本地消息表](images/Solution/本地消息表.png) + +#### 可靠消息服务 + +可靠消息服务就是一个单独的服务,有自己的数据库,其主要作用就是存储消息(包含接口调用信息,全局唯一的消息编号),消息通常包含以下状态: + +- **待确认**:上游服务发送待确认消息 +- **已发送**:上游服务发送确认消息 +- **已取消(终态**):上游服务发送取消消息 +- **已完成(终态)**:下游服务确认接口执行完成 + + + +#### 生产者 + +服务调用方(消息生产者)需要调用下游接口时,不直接通过RPC之类的方式调用,而是先生成一条消息,其主要步骤如下: + +- 生产者调用接口前,先发送一条待确认消息(一般称为half-msg,包含接口调用信息)给可靠消息服务,可靠消息服务会将这条记录存储到自己的数据库(或本地磁盘),状态为【待确认】 +- 生产者执行本地事务,本地事务执行成功并提交后,向可靠消息服务发送一条确认消息;如果本地执行失败,则向消息服务发送一条取消消息 +- 可靠消息服务如果收到消息后,修改本地数据库中的那条消息记录的状态改为【已发送】或【已取消】。如果是确认消息,则将消息投递到MQ消息队列;(修改消息状态和投递MQ必须在一个事务里,保证要么都成功要么都失败) + +> 为了防止出现:生产者的本地事务执行成功,但是发送确认/取消消息超时的情况。可靠消息服务里一般会提供一个后台定时任务,不停的检查消息表中那些【待确认】的消息,然后回调生产者(上游服务)的一个接口,由生产者确认到底是取消这条消息,还是确认并发送这条消息。 + +![本地消息表-生产者](images/Solution/本地消息表-生产者.png) + +通过上面这套机制,可以保证生产者对消息的100%可靠投递。 + + + +#### 消费者 + +服务提供方(消息消费者),从MQ消费消息,然后执行本地事务。执行成功后,反过来通知可靠消息服务,说自己处理成功了,然后可靠消息服务就会把本地消息表中的消息状态置为最终状态【已完成】 。这里要注意两种情况: + +- 消费者消费消息失败,或者消费成功但执行本地事务失败。 + 针对这种情况,可靠消息服务可以提供一个后台定时任务,不停的检查消息表中那些【已发送】但始终没有变成【已完成】的消息,然后再次投递到MQ,让下游服务来再次处理。也可以引入zookeeper,由消费者通知zookeeper,生产者监听到zookeeper上节点变化后,进行消息的重新投递 +- 如果消息重复投递,消息者的接口逻辑需要实现幂等性,保证多次处理一个消息不会插入重复数据或造成业务数据混乱。 + 针对这种情况,消费者可以准备一张消息表,用于判重。消费者消费消息后,需要去本地消息表查看这条消息有没处理成功,如果处理成功直接返回成功 + + +![本地消息表-消费者](images/Solution/本地消息表-消费者.png) + +**总结** + +这个方案的优点是简单,但最大的问题在于可靠消息服务是严重依赖于数据库的,即通过数据库的消息表来管理事务,不太适合并发量很高的场景。 + + + +### 分布式消息中间件 + +许多开源的消息中间件都支持分布式事务,比如RocketMQ、Kafka。其思想几乎是和本地消息表/服务实一样的,只不过是将可靠消息服务和MQ功能封装在一起,屏蔽了底层细节,从而更方便用户的使用。这种方案有时也叫做可靠消息最终一致性方案。以RocketMQ为例,消息的发送分成2个阶段:**Prepare阶段**和**确认阶段**。 +![分布式消息中间件](images/Solution/分布式消息中间件.png) + +#### prepare阶段 + +- 生产者发送一个不完整的事务消息——HalfMsg到消息中间件,消息中间件会为这个HalfMsg生成一个全局唯一标识,生产者可以持有标识,以便下一阶段找到这个HalfMsg +- 生产者执行本地事务 + +> 注意:消费者无法立刻消费HalfMsg,生产者可以对HalfMsg进行Commit或者Rollback来终结事务。只有当Commit了HalfMsg后,消费者才能消费到这条消息。 + + + +#### 确认阶段 + +- 如果生产者执行本地事务成功,就向消息中间件发送一个Commit消息(包含之前HalfMsg的唯一标识),中间件修改HalfMsg的状态为【已提交】,然后通知消费者执行事务 +- 如果生产者执行本地事务失败,就向消息中间件发送一个Rollback消息(包含之前HalfMsg的唯一标识),中间件修改HalfMsg的状态为【已取消】 + +> 消息中间件会定期去向生产者询问,是否可以Commit或者Rollback那些由于错误没有被终结的HalfMsg,以此来结束它们的生命周期,以达成事务最终的一致。之所以需要这个询问机制,是因为生产者可能提交完本地事务,还没来得及对HalfMsg进行Commit或者Rollback,就挂掉了,这样就会处于一种不一致状态。 + + + +#### ACK机制 + +消费者消费完消息后,可能因为自身异常,导致业务执行失败,此时就必须要能够重复消费消息。RocketMQ提供了ACK机制,即RocketMQ只有收到服务消费者的ack message后才认为消费成功。所以,服务消费者可以在自身业务员逻辑执行成功后,向RocketMQ发送ack message,保证消费逻辑执行成功。 + + + +### 应用案例 + +我们最后以一个电子商务支付系统的核心交易链路为示例,来更好的理解下可靠消息最终一致性方案。 + +**交易链路** + +假设我们的系统的核心交易链路如下图。用户支付订单时,首先调用订单服务的对外接口服务,然后开始核心交易链路的调用,依次经过订单业务服务、库存服务、积分服务,全部成功后再通过MQ异步调用仓储服务: +![最终一致性-场景案例-交易链路](images/Solution/最终一致性-场景案例-交易链路.png) + +上图中,订单业务服务、库存服务、积分服务都是同步调用的,由于是核心链路,我们可以通过上一章中讲解的TCC分布式事务来保证分布式事务的一致性。而调用仓储服务可以异步执行,所以我们依赖RocketMQ来实现分布式事务。 + + + +**事务执行** + +接着,我们来看下引入RocketMQ来实现分布式事务后,整个系统的业务执行流程发生了哪些变化,整个流程如下图: +![最终一致性-场景案例-事务执行](images/Solution/最终一致性-场景案例-事务执行.png) + +- 当用户针对订单发起支付时,首先订单接口服务先发送一个half-msg消息给RocketMQ,收到RocketMQ的成功响应(注意,此时仓储服务还不能消费消息,因为half-msg还没有确认) +- 然后,订单接口服务调用核心交易链路,如果其中任一服务执行失败,则先执行内部的TCC事务回滚 +- 如果订单接口服务收到链路失败的响应,则向MQ投递一个rollback消息,取消之前的half-msg +- 如果订单接口服务收到链路成功的响应,则向MQ投递一个commit消息,确认之前的half-msg,那仓库服务就可消费消息 +- 仓储服务消费消息成功并执行完自身的逻辑后,会向RocketMQ投递一个ack message,以确保消费成功 + +> 注意,如果因为网络原因,导致RocketMQ始终没有收到订单接口服务对half-msg的commit或rollback消息,RocketMQ就会回调订单接口服务的某个接口,以查询该half-msg究竟是进行commit还是rollback。 + + + +## 最大努力通知 + +最大努力通知的方案实现比较简单,适用于一些最终一致性要求较低的业务。执行流程: + +- 系统 A 本地事务执行完之后,发送个消息到 MQ +- 这里会有个专门消费 MQ 的服务,这个服务会消费 MQ 并调用系统 B 的接口 +- 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B, 反复 N 次,最后还是不行就放弃 + + + +## Sagas事务模型 + +Saga事务模型又叫做长时间运行的事务。其核心思想是**「将长事务拆分为多个本地短事务」**,由Saga事务协调器协调,如果正常结束那就正常完成,如果**「某个步骤失败,则根据相反顺序一次调用补偿操作」**。 + + + +## Seate + +**XA和Seata区别** + +![XA和Seata的区别](images/Solution/xa-seata.png) + +![XA过程](images/Solution/XA过程.png) + +![seata过程](images/Solution/seata过程.png) + +Seata 中有三大模块,分别是 TM、RM 和 TC。 其中 TM 和 RM 是作为 Seata 的客户端与业务系统集成在一起,TC 作为 Seata 的服务端独立部署。 + +![seata流程](images/Solution/seata流程.png) + +在 Seata 中,分布式事务的执行流程: + +- TM 开启分布式事务(TM 向 TC 注册全局事务记录) +- 按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 ) +- TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务) +- TC 汇总事务信息,决定分布式事务是提交还是回滚 +- TC 通知所有 RM 提交/回滚 资源,事务二阶段结束 + +![seata](images/Solution/seata.png) + +- Transaction Coordinator(TC) + +事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。 + +- Transaction Manager(TM) + +控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。 + +- Resource Manager(RM) + +控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。 + + + +Seata 会有 4 种分布式事务解决方案,分别是:AT 模式、TCC 模式、Saga 模式和 XA 模式。 + +![seata发展历程](images/Solution/seata发展历程.png) + + + +### AT模式 + +2019年1月,Seata 开源了 AT 模式。AT 模式是一种无侵入的分布式事务解决方案。在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。 + +![AT 模式](images/Solution/AT模式.png) + +**AT模式如何做到对业务的无侵入 :** + +- **一阶段** + +在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。 + +![AT模式一阶段](images/Solution/AT模式一阶段.png) + +- **二阶段提交** + +二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。 + +![AT模式二阶段提交](images/Solution/AT模式二阶段提交.png) + +- **二阶段回滚** + +二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。 + +![AT模式二阶段回滚](images/Solution/AT模式二阶段回滚.png) + +AT 模式的一阶段、二阶段提交和回滚均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。 + + + +### TCC模式 + +2019 年 3 月份,Seata 开源了 TCC 模式,该模式由蚂蚁金服贡献。TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段 执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。 + +![TCC模式](images/Solution/TCC模式.png) + +TCC 三个方法描述: + +- Try:资源的检测和预留 +- Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功 +- Cancel:预留资源释放 + + + +**业务模型分 2 阶段设计:** + +用户接入 TCC ,最重要的是考虑如何将自己的业务模型拆成两阶段来实现。 + +以“扣钱”场景为例,在接入 TCC 前,对 A 账户的扣钱,只需一条更新账户余额的 SQL 便能完成;但是在接入 TCC 之后,用户就需要考虑如何将原来一步就能完成的扣钱操作,拆成两阶段,实现成三个方法,并且保证一阶段 Try 成功的话 二阶段 Confirm 一定能成功。 + +![kqcq](images/Solution/kqcq.png) + +如上图所示,Try 方法作为一阶段准备方法,需要做资源的检查和预留。在扣钱场景下,Try 要做的事情是就是检查账户余额是否充足,预留转账资金,预留的方式就是冻结 A 账户的 转账资金。Try 方法执行之后,账号 A 余额虽然还是 100,但是其中 30 元已经被冻结了,不能被其他事务使用。 + +二阶段 Confirm 方法执行真正的扣钱操作。Confirm 会使用 Try 阶段冻结的资金,执行账号扣款。Confirm 方法执行之后,账号 A 在一阶段中冻结的 30 元已经被扣除,账号 A 余额变成 70 元 。 + +如果二阶段是回滚的话,就需要在 Cancel 方法内释放一阶段 Try 冻结的 30 元,使账号 A 的回到初始状态,100 元全部可用。 + +用户接入 TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方法,并且保证 Try 成功 Confirm 一定能成功。相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。 + + + +### Saga模式 + +Saga 模式是 Seata 即将开源的长事务解决方案,将由蚂蚁金服主要贡献。在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。 + +分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会去退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。 + +![Saga模式](images/Solution/Saga模式.png) + +Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga 模式是一种长事务解决方案。 + + + +### XA模式 + +XA 模式是 Seata 将会开源的另一种无侵入的分布式事务解决方案,任何实现了 XA 协议的数据库都可以作为资源参与到分布式事务中,目前主流数据库,例如 MySql、Oracle、DB2、Oceanbase 等均支持 XA 协议。 + +XA 协议有一系列的指令,分别对应一阶段和二阶段操作。“xa start”和 “xa end”用于开启和结束XA 事务;“xa prepare” 用于预提交 XA 事务,对应一阶段准备;“xa commit”和“xa rollback”用于提交、回滚 XA 事务,对应二阶段提交和回滚。 + +在 XA 模式下,每一个 XA 事务都是一个事务参与者。分布式事务开启之后,首先在一阶段执行“xa start”、“业务 SQL”、“xa end”和 “xa prepare” 完成 XA 事务的执行和预提交;二阶段如果提交的话就执行 “xa commit”,如果是回滚则执行“xa rollback”。这样便能保证所有 XA 事务都提交或者都回滚。 + +![XA模式](images/Solution/XA模式.png) + +XA 模式下,用户只需关注自己的“业务 SQL”,Seata 框架会自动生成一阶段、二阶段操作;XA 模式的实现如下: + +![XA模式实现过程](images/Solution/XA模式实现过程.png) + +- **一阶段** + +在 XA 模式的一阶段,Seata 会拦截“业务 SQL”,在“业务 SQL”之前开启 XA 事务(“xa start”),然后执行“业务 SQL”,结束 XA 事务“xa end”,最后预提交 XA 事务(“xa prepare”),这样便完成 “业务 SQL”的准备操作。 + +- **二阶段提交** + +执行“xa commit”指令,提交 XA 事务,此时“业务 SQL”才算真正的提交至数据库。 + +- **二阶段回滚** + +执行“xa rollback”指令,回滚 XA 事务,完成“业务 SQL”回滚,释放数据库锁资源。 + +XA 模式下,用户只需关注“业务 SQL”,Seata 会自动生成一阶段、二阶段提交和二阶段回滚操作。XA 模式和 AT 模式一样是一种对业务无侵入性的解决方案;但与 AT 模式不同的是,XA 模式将快照数据和行锁等通过 XA 指令委托给了数据库来完成,这样 XA 模式实现更加轻量化。 + + + +### 性能优化 + +**优化一:同库模式** + +通常一个 TM 会产生一笔主事务日志,一个 RM 会产生一条分支事务日志,每个分布式事务由一个 TM 和若干 RM 组成,一个分布式事务总共会有 1+N 条事务日志(N 为 RM 个数)。 + +在默认情况下,分布式事务执行过程中客户端将事务日志发送给服务端,服务端再将事务日志存储至数据库中,一条事务日志的存储链路会有 2 次 TCP ,分别是“客户端到服务端”和“服务端到数据库”, 我们称这种模式为异库模式。 + +在异库模式下,分布式事务存储事务日志总共需要 2*(1+N) 次左右的 TCP 通信。在 RM 数量较少的业务场景下,分布式事务性能还能接受,但有些业务场景下 RM 数量较多,此时事务内 TCP 数量也会增多,分布式事务性能急剧下降。 + +![异库模式与同库模式](images/Solution/异库模式与同库模式.png) + +在事务执行过程中,客户端和服务端进行通信的目的是为了存储事务日志。如果客户端在存储事务日志时,绕过服务端直接将事务日志写入数据库(如上图“同库模式”所示),那么一笔事务日志的存储链路就由原来的 2 次 TCP 变成只需访问一次数据库便可,每条事务日志的存储减少了一次 TCP 通信,整个分布式事务就减少了 N+2 次 TCP 请求,分布式事务的性能大幅提升。**我们将客户端直接将事务日志存储至数据库的模式称为同库模式。** + + + +**优化二:二阶段异步执行** + +通常情况下,分布式事务发起方会依次执行一阶段和二阶段方法,然后结束分布式事务,返回结果。如果让分布式事务发起方执行完一阶段之后马上结束并返回结果,二阶段交由独立的线程或者进程异步执行,这样分布式事务的二阶段会晚几秒钟或者若干分钟执行,但事务的最终结果不会有任何改变。 + +二阶段异步执行之后,分布式事务的最终结果不会有任何影响,但是事务发起方要执行的内容减少一半(一阶段和二阶段都执行变成只执行一阶段),直观的用户感受是分布式事务的性能提升了 50%。 + +![二阶段异步执行](images/Solution/二阶段异步执行.png) + + + +## 应用案例 + +### 订单库存案例 + +**电商经典模块** + +![电商经典模块](images/Solution/电商经典模块.jpg) + +**下单流程图** + +![下单流程图](images/Solution/下单流程图.jpg) + +**创建订单和扣件库存场景** + +![分布式事务-订单库存](images/Solution/分布式事务-订单库存.jpg) + +**分布式事务-技术方案** + +![分布式事务-技术方案](images/Solution/分布式事务-技术方案.jpg) + + + +#### MQ消息事务-RocketMQ + +先说说MQ的分布式事务,RocketMq在4.3版本已经正式宣布支持分布式事务,在选择Rokcetmq做分布式事务请务必选择4.3以上的版本。 + +事务消息作为一种异步确保型事务, 将两个事务分支通过 MQ 进行异步解耦,RocketMQ 事务消息的设计流程同样借鉴了两阶段提交理论,整体交互流程如下图所示: + +![MQ消息事务-RocketMQ](images/Solution/MQ消息事务-RocketMQ.png) + +这个时候我们基本可以认为,只有MQ发送方自己的本地事务执行完毕,那么MQ的订阅方必定百分百能够接收到消息,我们再对下单减库存的步骤进行改造。这里涉及到一个异步化的改造,我们理一下如果是同步流程中的各个步骤: + +1. 查看商品详情(或购物车) +2. 计算商品价格和目前商品存在库存(生成订单详情) +3. 商品扣库存(调用商品库存服务) +4. 订单确认(生成有效订单) + +订单创建完成后,发布一个事件“orderCreate” 到消息队列中,然后由MQ转发给订阅该消息的服务,因为是基于消息事务,我们可以认为订阅该消息的商品模块是百分百能收到这个消息的。 + +![创建订单-发送MQ消息](images/Solution/创建订单-发送MQ消息.jpg) + +![创建订单-消费MQ消息](images/Solution/创建订单-消费MQ消息.jpg) + +商品服务接受到orderCreate消息后就执行扣减库存的操作,注意⚠️,这里可能会有一些不可抗的因素导致扣减库存失败,无论成功或失败,商品服务都将发送一个扣减库存结果的消息“stroeReduce”到消息队列中,订单服务会订阅扣减库存的结果。订单服务收到消息后有两种可能: + +- 如果扣减库存成功,将订单状态改为 “确认订单” ,下单成功 +- 如果扣减库存失败,将订单状态改为 “失效订单” ,下单失败 + +![创建订单-回执MQ消息](images/Solution/创建订单-回执MQ消息.jpg) + +这种模式将确认订单的流程变成异步化,**非常适合在高并发的使用**,但是,切记了,这个需要前端用户体验的一些改变,要配合产品来涉及流程。 + +上面使用MQ的方式确实是可以完成A和B操作,但是A和B并不是严格一致性,而是最终一致性,我们牺牲掉严格一致性,换来性能的提升,这种很适合在大促高并发场景总使用,但是如果B一直执行不成功,那么一致性也会被破坏,后续应该考虑到更多的兜底方案,方案越细系统就将越复杂。 + + + +#### TCC方案 + +TCC是服务化的二阶段变成模型,每个业务服务都必须实现 try,confirm,calcel三个方法,这三个方式可以对应到SQL事务中Lock,Commit,Rollback。 + +- try阶段 try只是一个初步的操作,进行初步的确认,它的主要职责是完成所有业务的检查,预留业务资源 +- confirm阶段 confirm是在try阶段检查执行完毕后,继续执行的确认操作,必须满足幂等性操作,如果confirm中执行失败,会有事务协调器触发不断的执行,直到满足为止 +- cancel是取消执行,在try没通过并释放掉try阶段预留的资源,也必须满足幂等性,跟confirm一样有可能被不断执行 + +接下来看看,我们的下单扣减库存的流程怎么加入TCC: + +![TCC-try-confirm](images/Solution/TCC-try-confirm.jpg) + +在try的时候,会让库存服务预留n个库存给这个订单使用,让订单服务产生一个“未确认”订单,同时产生这两个预留的资源, 在confirm的时候,会使用在try预留的资源,在TCC事务机制中认为,如果在try阶段能正常预留的资源,那么在confirm一定能完整的提交: + +![TCC-try-cancel](images/Solution/TCC-try-cancel.jpg) + +在try的时候,有任务一方为执行失败,则会执行cancel的接口操作,将在try阶段预留的资源进行释放。 + + + +### 下单扣减库存 + +**传统模式** + +![Seata-下单扣减库存-传统模式](images/Solution/Seata-下单扣减库存-传统模式.png) + +**分库分表** + +![Seata-下单扣减库存-分库分表](images/Solution/Seata-下单扣减库存-分库分表.png) + +**Seata 优势** + +实现分布式事务的方案比较多,常见的比如基于 `XA` 协议的 `2PC`、`3PC`,基于业务层的 `TCC`,还有应用消息队列 + 消息表实现的最终一致性方案,还有今天要说的 `Seata` 中间件,下边看看各个方案的优缺点。 + + + +#### 2PC + +基于 XA 协议实现的分布式事务,XA 协议中分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如 Oracle、MYSQL 这些数据库都实现了 XA 接口,而事务管理器则作为一个全局的调度者。 + +两阶段提交(`2PC`),对业务侵⼊很小,它最⼤的优势就是对使⽤⽅透明,用户可以像使⽤本地事务⼀样使⽤基于 XA 协议的分布式事务,能够严格保障事务 ACID 特性。 + +![Seata-下单扣减库存-2PC第一阶段](images/Solution/Seata-下单扣减库存-2PC第一阶段.png) + +可 `2PC`的缺点也是显而易见,它是一个强一致性的同步阻塞协议,事务执⾏过程中需要将所需资源全部锁定,也就是俗称的 `刚性事务`。所以它比较适⽤于执⾏时间确定的短事务,整体性能比较差。 + +一旦事务协调者宕机或者发生网络抖动,会让参与者一直处于锁定资源的状态或者只有一部分参与者提交成功,导致数据的不一致。因此,在⾼并发性能⾄上的场景中,基于 XA 协议的分布式事务并不是最佳选择。 + +![Seata-下单扣减库存-2PC第二阶段](images/Solution/Seata-下单扣减库存-2PC第二阶段.png) + + + +#### 3PC + +三段提交(`3PC`)是二阶段提交(`2PC`)的一种改进版本 ,为解决两阶段提交协议的阻塞问题,上边提到两段提交,当协调者崩溃时,参与者不能做出最后的选择,就会一直保持阻塞锁定资源。 + +`2PC` 中只有协调者有超时机制,`3PC` 在协调者和参与者中都引入了超时机制,协调者出现故障后,参与者就不会一直阻塞。而且在第一阶段和第二阶段中又插入了一个准备阶段(如下图,看着有点啰嗦),保证了在最后提交阶段之前各参与节点的状态是一致的。 + +![Seata-下单扣减库存-3PC](images/Solution/Seata-下单扣减库存-3PC.png) + +虽然 `3PC` 用超时机制,解决了协调者故障后参与者的阻塞问题,但与此同时却多了一次网络通信,性能上反而变得更差,也不太推荐。 + + + +#### TCC + +所谓的 `TCC` 编程模式,也是两阶段提交的一个变种,不同的是 `TCC` 为在业务层编写代码实现的两阶段提交。`TCC` 分别指 `Try`、`Confirm`、`Cancel` ,一个业务操作要对应的写这三个方法。 + +以下单扣库存为例,`Try` 阶段去占库存,`Confirm` 阶段则实际扣库存,如果库存扣减失败 `Cancel` 阶段进行回滚,释放库存。 + +TCC 不存在资源阻塞的问题,因为每个方法都直接进行事务的提交,一旦出现异常通过则 `Cancel` 来进行回滚补偿,这也就是常说的补偿性事务。 + +原本一个方法,现在却需要三个方法来支持,可以看到 TCC 对业务的侵入性很强,而且这种模式并不能很好地被复用,会导致开发量激增。还要考虑到网络波动等原因,为保证请求一定送达都会有重试机制,所以考虑到接口的幂等性。 + + + +#### 消息事务 + +消息事务(最终一致性)其实就是基于消息中间件的两阶段提交,将本地事务和发消息放在同一个事务里,保证本地操作和发送消息同时成功。下单扣库存原理图: + +![Seata-下单扣减库存-消息事务](images/Solution/Seata-下单扣减库存-消息事务.png) + +- 订单系统向 `MQ` 发送一条预备扣减库存消息, `MQ` 保存预备消息并返回成功 `ACK` +- 接收到预备消息执行成功 `ACK`,订单系统执行本地下单操作,为防止消息发送成功而本地事务失败,订单系统会实现 `MQ` 的回调接口,其内不断的检查本地事务是否执行成功,如果失败则 `rollback` 回滚预备消息;成功则对消息进行最终 `commit` 提交。 +- 库存系统消费扣减库存消息,执行本地事务,如果扣减失败,消息会重新投,一旦超出重试次数,则本地表持久化失败消息,并启动定时任务做补偿。 + +基于消息中间件的两阶段提交方案,通常用在高并发场景下使用,牺牲数据的强一致性换取性能的大幅提升,不过实现这种方式的成本和复杂度是比较高的,还要看实际业务情况。 + + + +#### Seata + +`Seata` 也是从两段提交演变而来的一种分布式事务解决方案,提供了 `AT`、`TCC`、`SAGA` 和 `XA` 等事务模式,这里重点介绍 `AT`模式。既然 `Seata` 是两段提交,那我们看看它在每个阶段都做了点啥?下边我们还以下单扣库存、扣余额举例。 + +![Seata-下单扣减库存-Seata](images/Solution/Seata-下单扣减库存-Seata.png) + +先介绍 `Seata` 分布式事务的几种角色: + +- `Transaction Coordinator(TC)`: 全局事务协调者,用来协调全局事务和各个分支事务(不同服务)的状态, 驱动全局事务和各个分支事务的回滚或提交 +- `Transaction Manager™`: 事务管理者,业务层中用来开启/提交/回滚一个整体事务(在调用服务的方法中用注解开启事务) +- `Resource Manager(RM)`: 资源管理者,一般指业务数据库代表了一个分支事务(`Branch Transaction`),管理分支事务与 `TC` 进行协调注册分支事务并且汇报分支事务的状态,驱动分支事务的提交或回滚 + +Seata 实现分布式事务,设计了一个关键角色 `UNDO_LOG` (回滚日志记录表),我们在每个应用分布式事务的业务库中创建这张表,这个表的核心作用就是,将业务数据在更新前后的数据镜像组织成回滚日志,备份在 `UNDO_LOG` 表中,以便业务异常能随时回滚。 + + + +**第一个阶段** + +比如:下边我们更新 `user` 表的 `name` 字段。 + +```mysql +update user set name = '小富最帅' where name = '程序员内点事' +``` + +首先 Seata 的 `JDBC` 数据源代理通过对业务 SQL 解析,提取 SQL 的元数据,也就是得到 SQL 的类型(`UPDATE`),表(`user`),条件(`where name = '程序员内点事'`)等相关的信息。 + +![Seata-第一阶段](images/Solution/Seata-第一阶段.png) + +先查询数据前镜像,根据解析得到的条件信息,生成查询语句,定位一条数据。 + +```mysql +select name from user where name = '程序员内点事' +``` + +![Seata-第一阶段-数据前镜像](images/Solution/Seata-第一阶段-数据前镜像.png) + +紧接着执行业务 SQL,根据前镜像数据主键查询出后镜像数据 + +```mysql +select name from user where id = 1 +``` + +![Seata-第一阶段-数据后镜像](images/Solution/Seata-第一阶段-数据后镜像.png) + +把业务数据在更新前后的数据镜像组织成回滚日志,将业务数据的更新和回滚日志在同一个本地事务中提交,分别插入到业务表和 `UNDO_LOG` 表中。 + +回滚记录数据格式如下:包括 `afterImage` 前镜像、`beforeImage` 后镜像、 `branchId` 分支事务ID、`xid` 全局事务ID + +```json +{ + "branchId":641789253, + "xid":"xid:xxx", + "undoItems":[ + { + "afterImage":{ + "rows":[ + { + "fields":[ + { + "name":"id", + "type":4, + "value":1 + } + ] + } + ], + "tableName":"product" + }, + "beforeImage":{ + "rows":[ + { + "fields":[ + { + "name":"id", + "type":4, + "value":1 + } + ] + } + ], + "tableName":"product" + }, + "sqlType":"UPDATE" + } + ] +} +``` + +这样就可以保证,任何提交的业务数据的更新一定有相应的回滚日志。 + +在本地事务提交前,各分支事务需向 `全局事务协调者` TC 注册分支 ( `Branch Id`) ,为要修改的记录申请 **全局锁** ,要为这条数据加锁,利用 `SELECT FOR UPDATE` 语句。而如果一直拿不到锁那就需要回滚本地事务。TM 开启事务后会生成全局唯一的 `XID`,会在各个调用的服务间进行传递。 + +有了这样的机制,本地事务分支(`Branch Transaction`)便可以在全局事务的第一阶段提交,并马上释放本地事务锁定的资源。相比于传统的 `XA` 事务在第二阶段释放资源,`Seata` 降低了锁范围提高效率,即使第二阶段发生异常需要回滚,也可以快速 从`UNDO_LOG` 表中找到对应回滚数据并反解析成 SQL 来达到回滚补偿。 + +最后本地事务提交,业务数据的更新和前面生成的 UNDO LOG 数据一并提交,并将本地事务提交的结果上报给全局事务协调者 TC。 + + + +**第二个阶段** + +第二阶段是根据各分支的决议做提交或回滚: + +如果决议是全局提交,此时各分支事务已提交并成功,这时 `全局事务协调者(TC)` 会向分支发送第二阶段的请求。收到 TC 的分支提交请求,该请求会被放入一个异步任务队列中,并马上返回提交成功结果给 TC。异步队列中会异步和批量地根据 `Branch ID` 查找并删除相应 `UNDO LOG` 回滚记录。 + +![Seata-第二阶段](images/Solution/Seata-第二阶段.png) + +如果决议是全局回滚,过程比全局提交麻烦一点,`RM` 服务方收到 `TC` 全局协调者发来的回滚请求,通过 `XID` 和 `Branch ID` 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。 + +注意:这里删除回滚日志记录操作,一定是在本地业务事务执行之后 + +![Seata-第二阶段-分支回滚](images/Solution/Seata-第二阶段-分支回滚.png) + +上边说了几种分布式事务各自的优缺点,下边实践一下分布式事务中间 Seata 感受一下。 + + + +### Seata 实践 + +Seata 是一个需独立部署的中间件,所以先搭 Seata Server,这里以最新的 `seata-server-1.4.0` 版本为例,下载地址:`https://seata.io/en-us/blog/download.html`。解压后的文件我们只需要关心 `\seata\conf` 目录下的 `file.conf` 和 `registry.conf` 文件。 + + + +#### Seata Server + +**file.conf** + +`file.conf` 文件用于配置持久化事务日志的模式,目前提供 `file`、`db`、`redis` 三种方式。 + +![SeataServer-file.conf](images/Solution/SeataServer-file.conf.png) + +**注意**:在选择 `db` 方式后,需要在对应数据库创建 `globalTable`(持久化全局事务)、`branchTable`(持久化各提交分支的事务)、 `lockTable`(持久化各分支锁定资源事务)三张表。 + +```mysql +-- the table to store GlobalSession data +-- 持久化全局事务 +CREATE TABLE IF NOT EXISTS `global_table` +( + `xid` VARCHAR(128) NOT NULL, + `transaction_id` BIGINT, + `status` TINYINT NOT NULL, + `application_id` VARCHAR(32), + `transaction_service_group` VARCHAR(32), + `transaction_name` VARCHAR(128), + `timeout` INT, + `begin_time` BIGINT, + `application_data` VARCHAR(2000), + `gmt_create` DATETIME, + `gmt_modified` DATETIME, + PRIMARY KEY (`xid`), + KEY `idx_gmt_modified_status` (`gmt_modified`, `status`), + KEY `idx_transaction_id` (`transaction_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8; + +-- the table to store BranchSession data +-- 持久化各提交分支的事务 +CREATE TABLE IF NOT EXISTS `branch_table` +( + `branch_id` BIGINT NOT NULL, + `xid` VARCHAR(128) NOT NULL, + `transaction_id` BIGINT, + `resource_group_id` VARCHAR(32), + `resource_id` VARCHAR(256), + `branch_type` VARCHAR(8), + `status` TINYINT, + `client_id` VARCHAR(64), + `application_data` VARCHAR(2000), + `gmt_create` DATETIME(6), + `gmt_modified` DATETIME(6), + PRIMARY KEY (`branch_id`), + KEY `idx_xid` (`xid`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8; + +-- the table to store lock data +-- 持久化每个分支锁表事务 +CREATE TABLE IF NOT EXISTS `lock_table` +( + `row_key` VARCHAR(128) NOT NULL, + `xid` VARCHAR(96), + `transaction_id` BIGINT, + `branch_id` BIGINT NOT NULL, + `resource_id` VARCHAR(256), + `table_name` VARCHAR(32), + `pk` VARCHAR(36), + `gmt_create` DATETIME, + `gmt_modified` DATETIME, + PRIMARY KEY (`row_key`), + KEY `idx_branch_id` (`branch_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8; +``` + + + +**registry.conf** + +`registry.conf` 文件设置 注册中心 和 配置中心: + +目前注册中心支持 `nacos` 、`eureka`、`redis`、`zk`、`consul`、`etcd3`、`sofa` 七种,这里我使用的 `eureka`作为注册中心 ;配置中心支持 `nacos` 、`apollo`、`zk`、`consul`、`etcd3` 五种方式。 + +![SeataServer-registry.conf](images/Solution/SeataServer-registry.conf.png) + +配置完以后在 `\seata\bin` 目录下启动 `seata-server` 即可,到这 `Seata` 的服务端就搭建好了。 + + + +#### Seata Client + +`Seata Server` 环境搭建完,接下来我们新建三个服务 `order-server`(下单服务)、`storage-server`(扣减库存服务)、`account-server`(账户金额服务),分别服务注册到 `eureka`。 + +每个服务的大体核心配置如下: + +```yaml +spring: + application: + name: storage-server + cloud: + alibaba: + seata: + tx-service-group: my_test_tx_group + datasource: + driver-class-name: com.mysql.jdbc.Driver + url: jdbc:mysql://47.93.6.1:3306/seat-storage + username: root + password: root + +# eureka 注册中心 +eureka: + client: + serviceUrl: + defaultZone: http://${eureka.instance.hostname}:8761/eureka/ + instance: + hostname: 47.93.6.5 + prefer-ip-address: true +``` + +业务大致流程:用户发起下单请求,本地 order 订单服务创建订单记录,并通过 `RPC` 远程调用 `storage` 扣减库存服务和 `account` 扣账户余额服务,只有三个服务同时执行成功,才是一个完整的下单流程。如果某个服执行失败,则其他服务全部回滚。Seata 对业务代码的侵入性非常小,代码中使用只需用 `@GlobalTransactional` 注解开启一个全局事务即可。 + +```java +@Override +@GlobalTransactional(name = "create-order", rollbackFor = Exception.class) +public void create(Order order) { + + String xid = RootContext.getXID(); + + LOGGER.info("------->交易开始"); + //本地方法 + orderDao.create(order); + + //远程方法 扣减库存 + storageApi.decrease(order.getProductId(), order.getCount()); + + //远程方法 扣减账户余额 + LOGGER.info("------->扣减账户开始order中"); + accountApi.decrease(order.getUserId(), order.getMoney()); + LOGGER.info("------->扣减账户结束order中"); + + LOGGER.info("------->交易结束"); + LOGGER.info("全局事务 xid: {}", xid); +} +``` + +前边说过 Seata AT 模式实现分布式事务,必须在相关的业务库中创建 `undo_log` 表来存数据回滚日志,表结构如下: + +```mysql +-- for AT mode you must to init this sql for you business database. the seata server not need it. +CREATE TABLE IF NOT EXISTS `undo_log` +( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'increment id', + `branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id', + `xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id', + `context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization', + `rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info', + `log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status', + `log_created` DATETIME NOT NULL COMMENT 'create datetime', + `log_modified` DATETIME NOT NULL COMMENT 'modify datetime', + PRIMARY KEY (`id`), + UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table'; +``` + + + +#### 测试Seata + +项目中的服务调用过程如下图: + +![SeataServer-服务调用过程](images/Solution/SeataServer-服务调用过程.png) + +启动各个服务后,我们直接请求下单接口看看效果,只要 `order` 订单表创建记录成功,`storage` 库存表 `used` 字段数量递增、`account` 余额表 `used` 字段数量递增则表示下单流程成功。 + +![SeataServer-测试表-原始数据](images/Solution/SeataServer-测试表-原始数据.png) + +请求后正向流程是没问题的,数据和预想的一样 + +![SeataServer-测试表-下单数据](images/Solution/SeataServer-测试表-下单数据.png)而且发现 `TM` 事务管理者 `order-server` 服务的控制台也打印出了两阶段提交的日志 + +![SeataServer-控制台两次提交](images/Solution/SeataServer-控制台两次提交.png)那么再看看如果其中一个服务异常,会正常回滚呢?在 `account-server` 服务中模拟超时异常,看能否实现全局事务回滚。 + +![SeataServer-全局事务回滚](images/Solution/SeataServer-全局事务回滚.png) + +发现数据全没执行成功,说明全局事务回滚也成功了 + +![SeataServer-回滚后数据表](images/Solution/SeataServer-回滚后数据表.png) + +那看一下 `undo_log` 回滚记录表的变化情况,由于 `Seata` 删除回滚日志的速度很快,所以要想在表中看见回滚日志,必须要在某一个服务上打断点才看的更明显。 + +![SeataServer-回滚记录](images/Solution/SeataServer-回滚记录.png) + + + +**总结** + +上边简单介绍了 `2PC`、`3PC`、`TCC`、`MQ`、`Seata` 这五种分布式事务解决方案,还详细的实践了 `Seata` 中间件。但不管我们选哪一种方案,在项目中应用都要谨慎再谨慎,除特定的数据强一致性场景外,能不用尽量就不要用,因为无论它们性能如何优越,一旦项目套上分布式事务,整体效率会几倍的下降,在高并发情况下弊端尤为明显。 + + + +# 分库分表 + +随着公司业务快速发展,数据库中的数据量猛增,访问性能也变慢了,优化迫在眉睫。原因在于关系型数据库本身比较容易成为系统瓶颈,单机存储容量、连接数、处理能力都有限。当单表的数据量达到 **1000W** 或 **100G** 以后,由于查询维度较多,即使添加从库、优化索引,做很多操作时性能仍下降严重。解决上述问题通常有以下两种方案: + +- **通过提升服务器硬件能力来提高数据处理能力,比如增加存储容量 、CPU 等,这种方案成本很高,并且如果瓶颈在 MySQL 本身那么提高硬件也是有很的** + +- **把数据分散在不同的数据库中,使得单一数据库的数据量变小来缓解单一数据库的性能问题,从而达到提升数据库性能的目的** + + + +**解决方案** + +- **垂直分表:** 可以把一个宽表的字段按访问频次、是否是大字段的原则拆分为多个表,这样既能使业务清晰,还能提升部分性能。拆分后,尽量从业务角度避免联查,否则性能方面将得不偿失 + +- **垂直分库:** 可以把多个表按业务耦合松紧归类,分别存放在不同的库,这些库可以分布在不同服务器,从而使访问压力被多服务器负载,大大提升性能,同时能提高整体架构的业务清晰度,不同的业务库可根据自身情况定制优化方案。但是它需要解决跨库带来的所有复杂问题 + +- **水平分库:** 可以把一个表的数据(按数据行)分到多个不同的库,每个库只有这个表的部分数据,这些库可以分布在不同服务器,从而使访问压力被多服务器负载,大大提升性能。它不仅需要解决跨库带来的所有复杂问题,还要解决数据路由的问题 + +- **水平分表:** 可以把一个表的数据(按数据行)分到多个同一个数据库的多张表中,每个表只有这个表的部分数据,这样做能小幅提升性能,它仅仅作为水平分库的一个补充优化 + +一般来说,在系统设计阶段就应该根据业务耦合松紧来确定垂直分库,垂直分表方案,在数据量及访问压力不是特别大的情况,首先考虑缓存、读写分离、索引技术等方案。若数据量极大,且持续增长,再考虑水平分库水平分表方案。 + + + +**拆分目的** + +- **垂直拆分:业务数据解耦** + +- **水平拆分:解决容量和性能压力** + + + +**分库分表前的问题** + +- 用户请求量太大 + - 因为单服务器TPS,内存,IO都是有限的 + - **解决方法**:分散请求到多个服务器上; 其实用户请求和执行一个sql查询是本质是一样的,都是请求一个资源,只是用户请求还会经过网关,路由,http服务器等 + +- 单库太大 + - 单个数据库处理能力有限;单库所在服务器上磁盘空间不足;单库上操作的IO瓶颈 + - **解决方法**:切分成更多更小的库 + +- 单表太大 + - CRUD都成问题;索引膨胀,查询超时 + - **解决方法**:切分成多个数据集更小的表 + + + +## 垂直拆分 + +### 垂直分表 + +**垂直分表:将一个表按照字段分成多表,每个表存储其中一部分字段。** + +我们拿网上商城举例:用户在浏览商品列表时,通常只会快速浏览商品名称、商品图片、商品价格等其他字段信息,这些字段数据访问频次较高。当只有对某商品感兴趣时才会查看该商品的详细描述。因此,商品信息中商品描述字段访问频次较低,且该字段存储占用空间较大,访问单个数据 IO 时间较长。 + +由于这两种数据的访问频次的不同,我们可以将商品信息表分成如下 2 张表: + +![垂直分表](images/Solution/垂直分表.jpg) + +**优化提升:** + +1. 为了避免 IO 争抢并减少锁表的几率,查看详情的用户与商品信息浏览互不影响 +2. 充分发挥热门数据的操作效率,商品信息的操作的高效率不会被商品描述的低效率所拖累 + +**拆分原则:** + +1. 把不常用的字段单独放在一张表 +2. 把text,blob等大字段拆分出来放在附表中 +3. 经常组合查询的列放在一张表中 + + + +### 垂直分库 + +**垂直分库:按照业务将表进行分类,分布到不同数据库上面,每个库可以放在不同服务器上,它的核心理念是专库专用。** + +通过垂直分表性能得到了一定程度的提升,但是还没有达到要求,并且磁盘空间也快不够了,因为数据还是始终限制在一台服务器,库内垂直分表只解决了单一表数据量过大的问题,但没有将表分布到不同的服务器上,因此每个表还是竞争同一个物理机的CPU、内存、网络IO、磁盘。 + +继续拿商城举例:一个商城系统通常都包含用户信息表和商品信息表,这两张表在业务上是独立的,因此我们可以将它们拆开分到2个不同的库中。 + +![垂直分库](images/Solution/垂直分库.jpg) + +**优化提升:** + +1. 解决业务层面的耦合,业务清晰 +2. 能对不同业务的数据进行分级管理、维护、监控、扩展等 +3. 高并发场景下,垂直分库一定程度的提升IO、数据库连接数、降低单机硬件资源的瓶颈 + + + +## 水平拆分 + +### 水平分库 + +**水平分库:把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。** + +经过垂直分库后,数据库性能问题得到一定程度的解决,但是随着业务量的增长,单库存储数据已经超出预估。单台服务器已经无法支撑。此时该如何优化?垂直拆分已达到极限,只能从水平维度拆分。 + +继续拿商城举例:我们要查询某个商品信息时,需要分析这条商品信息的ID。如果ID为双数,将此操作映射至DB_1(商品库1)。如果店铺ID为单数,将操作映射至DB_2(商品库2)。此操作要访问数据库名称的表达式为DB_[商品信息ID % 2 + 1]。 + +![水平分库](images/Solution/水平分库.jpg) + +**优化提升:** + +1. 解决了单库大数据,高并发的性能瓶颈 +2. 提高了系统的稳定性及可用性 + + + +### 水平分表 + +**水平分表:在同一个数据库内,把同一个表的数据按一定规则拆到多个表中。** + +即便水平分库,随着业务的增长还是会出现单表数量大导致查询效率下降的问题。 + +按照水平分库的思路,我们可以对单表进行水平拆分: + +![水平分表](images/Solution/水平分表.jpg) + +**优化提升:** + +1. 优化单一表数据量过大而产生的性能问题 +2. 避免IO争抢并减少锁表的几率 + + + +### 切分规则 + +#### Hash取模 + +![分库分表-Hash取模](images/Solution/分库分表-Hash取模.png) + +1. **优点**:经过 hash 取模之后,分到库和分到表中的数据,都是均衡的,所以,不会出现资源倾斜的问题 +2. **缺点**:若后续遇到业务暴增,没有在我们预估范围内,则要涉及到数据迁移,那就需要重新hash , 迁移数据,修改路由等 + + + +#### Range划分 + +![分库分表-Range划分](images/Solution/分库分表-Range划分.png) + +简单说,就是把数据划分范围,挨个存储,存满一个再存另一个。 + +1. **优点**:不需要数据迁移,后续数据即时增长很多也没问题 +2. **缺点**:数据倾斜严重,比如上图,很长一段时间,都会只用到 1 个库,几个表 + + + +#### 一致性Hash + +![分库分表-一致性Hash](images/Solution/分库分表-一致性Hash.png) + +一致性 hash 环的节点一般按 2^32-1 来算,但是一般如果业务 ID 足够均衡,则可以降一些节点,如 4096 等等,4 个库的话,则均衡的分布在图上的位置,而数据通过 hash 计算,对应到外环的虚拟节点,然后归属于真实的库,对于表也可以同样处理。或者,直接把表节点部署在外环上,直接将数据归属于表。 + +1. **优点**:更加均匀,并且在需要扩容时,数据迁移的量级更小,只需要迁移 1/N 的数据即可 +2. **缺点**:路由算法要复杂,但是对于能得到的好处,这点复杂度就可以忽略了 + + + +#### 地理区域划分 + +比如按照华东,华南,华北这样来区分业务,七牛云应该就是如此。 + + + +#### 时间范围划分 + +按照时间切分,就是将6个月前,甚至一年前的数据切出去放到另外的一张表,因为随着时间流逝,这些表的数据 被查询的概率变小,所以没必要和“热数据”放在一起,这个也是“冷热数据分离”。 + + + +## 面临问题 + +### 分布式事务问题 + +使用分布式事务中间件解决,具体是通过最终一致性还是强一致性分布式事务,看业务需求。 + + + +### 跨节点关联查询Join问题 + +切分之前,我们可以通过Join来完成。而切分之后,数据可能分布在不同的节点上,此时Join带来的问题就比较麻烦了,考虑到性能,尽量避免使用Join查询。解决这个问题的一些方法: + +- **全局表** + + 全局表,也可看做是 "**数据字典表**",就是系统中所有模块都可能依赖的一些表,为了避免跨库Join查询,可以将 **这类表在每个数据库中都保存一份**。这些数据通常很少会进行修改,所以也不担心一致性的问题。 + +- **字段冗余** + + **利用空间换时间,为了性能而避免join查询**。例:订单表保存userId时候,也将userName冗余保存一份,这样查询订单详情时就不需要再去查询"买家user表"了。 + +- **数据组装** + + **在系统层面,分两次查询**。第一次查询的结果集中找出关联数据id,然后根据id发起第二次请求得到关联数据。最后将获得到的数据进行字段拼装。 + + + +### 跨节点分页、排序函数问题 + +跨节点多库进行查询时,会出现Limit分页、Order by排序等问题。分页需要按照指定字段进行排序,当排序字段就是分片字段时,通过分片规则就比较容易定位到指定的分片;当排序字段非分片字段时,就变得比较复杂了。需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序,最终返回给用户。 + + + +### 全局主键避重问题 + +如果都用`主键自增`肯定不合理,如果用`UUID`那么无法做到根据主键排序,所以我们可以考虑通过`雪花ID`来作为数据库的主键,有关雪花ID可以参考我之前写的博客:静态内部类单例模式实现雪花算法。 + + + +### 数据迁移问题 + +采用`双写的方式`,修改代码,所有涉及到分库分表的表的增、删、改的代码,都要对新库进行增删改。同时,再有一个数据抽取服务,不断地从老库抽数据,往新库写,边写边按时间比较数据是不是最新的。 + + + +### 公共表 + +参数表、数据字典表等都是数据量较小,变动少的公共表,属于高频联合查询的依赖表。分库分表后,我们需要将这类表在每个数据库都保存一份,所有对公共表的更新操作都同时发送到所有分库执行。 + + + +## ShardingSphere + +Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。 + +Apache ShardingSphere 旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 关系型数据库当今依然占有巨大市场份额,是企业核心系统的基石,未来也难于撼动,我们更加注重在原有基础上提供增量,而非颠覆。 + +Apache ShardingSphere 5.x 版本开始致力于可插拔架构,项目的功能组件能够灵活的以可插拔的方式进行扩展。 目前,数据分片、读写分离、数据加密、影子库压测等功能,以及对 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目。 开发者能够像使用积木一样定制属于自己的独特系统。Apache ShardingSphere 目前已提供数十个 SPI 作为系统的扩展点,而且仍在不断增加中。 + +![ShardingSphere Scope](images/Solution/shardingsphere-scope_cn.png) + +| | ShardingSphere-JDBC | ShardingSphere-Proxy | ShardingSphere-Sidecar | +| :--------- | :------------------ | :------------------- | ---------------------- | +| 数据库 | 任意 | MySQL/PostgreSQL | MySQL/PostgreSQL | +| 连接消耗数 | 高 | 低 | 高 | +| 异构语言 | 仅 Java | 任意 | 任意 | +| 性能 | 损耗低 | 损耗略高 | 损耗低 | +| 无中心化 | 是 | 否 | 是 | +| 静态入口 | 无 | 有 | 无 | + + + +### ShardingSphere-JDBC + +定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。 + +- 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC +- 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等 +- 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库 + +![ShardingSphere-JDBC Architecture](images/Solution/shardingsphere-jdbc-brief.png) + +1. 引入 maven 依赖 + + ```xml + + org.apache.shardingsphere + shardingsphere-jdbc-core + ${latest.release.version} + + ``` + +2. 规则配置 + + ShardingSphere-JDBC 可以通过 `Java`,`YAML`,`Spring 命名空间`和 `Spring Boot Starter` 这 4 种方式进行配置,开发者可根据场景选择适合的配置方式。 详情请参见[配置手册](https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/configuration/)。 + +3. 创建数据源 + + 通过 `ShardingSphereDataSourceFactory` 工厂和规则配置对象获取 `ShardingSphereDataSource`。 该对象实现自 JDBC 的标准 DataSource 接口,可用于原生 JDBC 开发,或使用 JPA, MyBatis 等 ORM 类库。 + + ```java + DataSource dataSource = ShardingSphereDataSourceFactory.createDataSource(dataSourceMap, configurations, properties); + ``` + + + +### ShardingSphere-Proxy + +定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。 目前提供 MySQL 和 PostgreSQL 版本,它可以使用任何兼容 MySQL/PostgreSQL 协议的访问客户端(如:MySQL Command Client, MySQL Workbench, Navicat 等)操作数据,对 DBA 更加友好。 + +- 向应用程序完全透明,可直接当做 MySQL/PostgreSQL 使用 +- 适用于任何兼容 MySQL/PostgreSQL 协议的的客户端 + +![ShardingSphere-Proxy Architecture](images/Solution/shardingsphere-proxy-brief.png) + +1. 规则配置 + - 编辑`%SHARDINGSPHERE_PROXY_HOME%/conf/config-xxx.yaml`。详情请参见[配置手册](https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-proxy/configuration/) + - 编辑`%SHARDINGSPHERE_PROXY_HOME%/conf/server.yaml`。详情请参见[配置手册](https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-proxy/configuration/) + +2. 引入依赖 + + 如果后端连接 PostgreSQL 数据库,不需要引入额外依赖。如果后端连接 MySQL 数据库,请下载 `mysql-connector-java-5.1.47.jar`,并将其放入 `%SHARDINGSPHERE_PROXY_HOME%/lib` 目录。 + +3. 启动服务 + + - 使用默认配置项 + + ```shell + sh %SHARDINGSPHERE_PROXY_HOME%/bin/start.sh + ``` + + 默认启动端口为 `3307`,默认配置文件目录为:`%SHARDINGSPHERE_PROXY_HOME%/conf/`。 + + - 自定义端口和配置文件目录 + + ```shell + sh %SHARDINGSPHERE_PROXY_HOME%/bin/start.sh ${proxy_port} ${proxy_conf_directory} + ``` + +4. 使用ShardingSphere-Proxy + + 执行 MySQL 或 PostgreSQL的客户端命令直接操作 ShardingSphere-Proxy 即可。以 MySQL 举例: + + ```shell + mysql -u${proxy_username} -p${proxy_password} -h${proxy_host} -P${proxy_port} + ``` + + + +### ShardingSphere-Sidecar(TODO) + +定位为 Kubernetes 的云原生数据库代理,以 Sidecar 的形式代理所有对数据库的访问。 通过无中心、零侵入的方案提供与数据库交互的啮合层,即 `Database Mesh`,又可称数据库网格。 + +Database Mesh 的关注重点在于如何将分布式的数据访问应用与数据库有机串联起来,它更加关注的是交互,是将杂乱无章的应用与数据库之间的交互进行有效地梳理。 使用 Database Mesh,访问数据库的应用和数据库终将形成一个巨大的网格体系,应用和数据库只需在网格体系中对号入座即可,它们都是被啮合层所治理的对象。 + +![ShardingSphere-Sidecar Architecture](images/Solution/shardingsphere-sidecar-brief.png) + +1. 规则配置 + + 编辑`%SHARDINGSPHERE_SCALING_HOME%/conf/server.yaml`。详情请参见[使用手册](https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-scaling/usage/)。 + +2. 引入依赖 + + 如果后端连接 PostgreSQL 数据库,不需要引入额外依赖。如果后端连接 MySQL 数据库,请下载 `mysql-connector-java-5.1.47.jar`,并将其放入 `%SHARDINGSPHERE_SCALING_HOME%/lib` 目录。 + +3. 启动服务 + + ```shell + sh %SHARDINGSPHERE_SCALING_HOME%/bin/start.sh + ``` + +4. 任务管理 + + 通过相应的 HTTP 接口管理迁移任务。详情参见[使用手册](https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-scaling/usage/)。 + + + +### 混合架构 + +ShardingSphere-JDBC 采用无中心化架构,适用于 Java 开发的高性能的轻量级 OLTP 应用;ShardingSphere-Proxy 提供静态入口以及异构语言的支持,适用于 OLAP 应用以及对分片数据库进行管理和运维的场景。 + +Apache ShardingSphere 是多接入端共同组成的生态圈。 通过混合使用 ShardingSphere-JDBC 和 ShardingSphere-Proxy,并采用同一注册中心统一配置分片策略,能够灵活的搭建适用于各种场景的应用系统,使得架构师更加自由地调整适合与当前业务的最佳系统架构。 + +![ShardingSphere Hybrid Architecture](images/Solution/shardingsphere-hybrid.png) + +**功能列表** + +- 数据分片 + - 分库 & 分表 + - 读写分离 + - 分片策略定制化 + - 无中心化分布式主键 + +- 分布式事务 + - 标准化事务接口 + - XA 强一致事务 + - 柔性事务 + +- 数据库治理 + - 分布式治理 + - 弹性伸缩 + - 可视化链路追踪 + - 数据加密 + + + +# 全链路压测 + +## 达达全链路压测探索与实战 + +### 业界全链路压测 + +达达原压测方案是: 搭建一套跟线上 1:1 的压测环境, 在这套环境中进行压测, 此方案技术难度低, 实现简单, 但弊端也明显: 人力及机器成本随着生产环境规模的变大而变大; 于是我们调研了业界主流的 【流量打标】方案, 该方案原理是: 在请求的流量(HTTP,RPC,MQ)上打标,所打的标示随着请求在各个服务之间流转, 从而使得压测流量与线上流量隔离,在数据隔离这块: + +- DB 层: 使用 影子库/影子表 隔离数据 +- Cache 层: 使用 影子缓存 隔离数据 +- MQ 层: 使用 影子队列 隔离数据 + +![达达-业界全链路压测-流量打标](images/Solution/达达-业界全链路压测-流量打标.png) + +但这个方案适用于中间件统一的场景;而达达内部使用了各种类型的中间件, 比如 ORM 就有 Mybatis, Hiberante, JPA, 版本也不一致; 并且存在大量异构程序, 有 Java 的, 有Python 的; 若要实现【流量打标】方案, 势必会有大量业务改造, 因此我们放弃此方案。 + + + +### 达达全链路压测 + +在分析自身架构特点后, 我们在 2019年一季度研发了基于【机器打标】的压测方案 (数据使用 影子DB&Redis&MQ 隔离)。 + +![达达全链路压测](images/SOlution/达达全链路压测.png) + +以下是实现此方案的流程: + +- **机器抽象化**: 所有 DB&Redis&ES 机器抽象成一个一个节点; 节点信息如下图 +- **机器信息注册到注册中心**: 所有节点信息注册到注册中心 +- **服务接入链路治理SDK**: 所有服务接入 "链路治理SDK", "链路治理SDK" 具有根据链路路由请求的能力 + +![达达全链路压测-节点信息](images/Solution/达达全链路压测-节点信息.png) + +节点信息说明: + +- **cloud_name**: 资源名称, 全局唯一, 一个资源可包含多个节点, 比如 mysql_cluster001 就可以包含一个主节点, 多个从节点 +- **node_name**: 节点名称, 全局唯一, 通常使用 host+port +- **host**: host 信息 +- **port**: 端口信息 +- **role**: 角色, w: 写节点, r: 读节点 +- **dada_type**: 资源类别, 有 mysql, redis, es 等 +- **data_link**: 链路类别, 有 benchmark(压测), base(生产) + +方案中最重要的是 "链路治理SDK", 它的职责是: 根据链路类别路由请求流量; 如下图所示: + +![达达全链路压测-链路治理SDK](images/Solution/达达全链路压测-链路治理SDK.png) + +它的启动流程如下: + +1. 注册服务节点信息 +2. 根据本机的链路类别, 获取对应链路的存储节点信息 +3. 根据存储节点信息, 建立起 DB,Redis, MQ 连接 + +最终线上环境, 在机器维度形成两条链路, 处理生产流量的生产链路和处理压测流量的压测链路。 + + + +**方案比较** + +下图是对【流量打标】【机器打标】的比较, 两种方案都有优缺点, 达达从安全及系统改造成本出发, 最终选择了【机器打标】方案。 + +![打标-方案比较](images/Solution/打标-方案比较.png) + + + +### 压测平台 + +原压测方案使用 jmeter 进行压测, jmeter 属于老牌压测工具, 稳定性高且支持分布式; 但在压测场景复杂时,使用不够灵活; 达达的压测多数为复杂场景压测, 所以放弃原方案, 转而基于 jmeter 内核开发自己的压测平台; 以下是压测平台的整体架构: + +![达达-压测平台](images/Solution/达达-压测平台.png) + +主要由以下几个核心模块构成: + +- **前端服务**: 提供 压测任务填写, 压测任务启动/停止, 压测结果展示等功能。 +- **压测任务解析器**: 主要负责压测任务的解析, 存储。 +- **压测引擎**: 负责将压测任务调度到执行器上执行 (定时执行 & 立即执行)。 +- **压测结果处理器**: 负责对压测接口返回值解析, 统计, 异常处理, 并生成报表。 + +新压测平台的优势在于: 具有可视化的操作界面, 压测结果实时动态产出; 研发人员在压测平台配置 "发压性能参数", "造数据脚本" 就能直接执行。 + +![达达-压测平台操作界面](images/Solution/达达-压测平台操作界面.png) + +压测引擎在施压时, 对应压测结果 (TPS&响应&错误率) 会实时展示到前端界面。 + +![压测平台结果输出](images/Solution/压测平台结果输出.png) + + + +### 全链路压测落地 + +整个全链路压测的落地分成: 压测前, 压测中, 压测后: + +![达达-压测落地方案](images/Solution/达达-压测落地方案.png) + +其中 **压测前** 有三个关键点: 压测链路梳理, 优化预案设定, 精细化压测模型。 + + + +#### 链路梳理 + +链路的梳理非常重要, 它决定着压测链路需要部署哪些服务, 压测时哪些服务需要被关注。 + +以前达达通过人肉的方式梳理链路, 但是这种方案效率低, 不准确, 工作量大, 且当生产环境链路变更时, 我们不能即时感知到; 后面引入 APM(PinPoint), APM 有梳理链路的功能。 + +![达达-链路梳理](images/Solution/达达-链路梳理.png) + +但此方案还是没解决: "实时感知链路变更" 的问题; 为此我们在开发环境拉了条链路, 定时发请求以检测是否链路通畅; 若链路依赖有变化, 我们就能立刻知道。 + + + +#### 优化预案设定 + +俗话说: 不打无准备之战, 压测就是为了提前发现高负载时系统可能出现的性能隐患, 那如何解决性能隐患呢? 通常在压测之前我们会准备一些性能优化预案, 常用预案如下: + +- **线程池/连接池 打满**: 扩容线程池大小(服务CPU未超过阈值时) / 扩容业务服务 +- **Mysql 主从延迟**: Mysql BinLog 调优 -> 升级机器配置 -> 垂直拆库 -> 水平拆库 +- **Redis 带宽打满**: 带宽自动扩容 +- **MQ 消息堆积**: 扩容消费消息的服务方 + +因过去几年的业务的发展, 导致生产环境数据库单表单库的数据量一直在增长, 达达物流系统多次碰到 **Mysql 主从延迟** ; 目前最常用的优化方案是 "Mysql BinLog 调优", 此方案主要调优以下两个参数: + +- **binlog_group_commit_sync_delay**: 表示事务提交后, 等待多少时间, binlog 再同步到磁盘, 默认0, 表示不等待(单位微秒) +- **binlog_group_commit_sync_no_delay_count**: 表示等待多少事务提交后, binlog 再同步到磁盘 + +但此调优方案也有弊端: 参数调优后, 接口响应会有一定提升(如下图); 所以调优时需考虑业务能否容忍接口响应的上升。 + +![达达-接口响应上升](images/Solution/达达-接口响应上升.png) + + + +#### 精细化压测模型构建 + +随着业务的发展, 压测模型也在不断演进迭代中; 从一开始 "使用虚拟骑士, 单一按照接口TPS目标值压测" 到现在 "模拟生产活跃骑士, 引入时间&空间因素 构建压测模型", 模型越发精准。模型分成两类: + +- **数据模型**: 骑士数据, 商户数据, 订单数据。 +- **流量模型**: 订单下发, 配送履约。 + +![达达-压测模型](images/Solution/达达-压测模型.png) + + + +**数据模型** + + 把生产上活跃骑士&商户&订单导入影子库, 对这批数据进行清洗, 去除敏感信息(手机号, 地址信息等), 用这些数据进行压测; 此方案简单且能最真实的还原生产环境。 + + + +**流量模型** + +业界常用 【流量回放】方案, 将大促时生产环境的流量存储下来, 然后在压测环境进行回放。 + +![达达-流量回放](images/Solution/达达-流量回放.png) + +此方案适用于读接口的压测, 但是达达的业务场景复杂, 主流程接口多写少读; 所以直接使用此方案肯定不行。 + +![达达-业务主流程](images/Solution/达达-业务主流程.png) + +鉴于不能直接使用【流量回放】, 达达选择了【人工构造流量】。影响构造流量真实性主要有两大因素: 时间, 空间。 + + + +**时间** + +在时间上, 达达一天有三个高峰期: 早&午&晚高峰, 每个高峰期 各个接口处于不同状态: + +- **早高峰**: 发单, 接单, 订单详情 的请求量处于高峰, 其他接口请求量一般。 +- **午&晚高峰**: 订单详情, 取货, 完成 的请求量处于高峰, 其他接口请求量一般。 + +![达达-主流程接口请求量分布](images/Solution/达达-主流程接口请求量分布.png) + +所以压测时, 达达根据业务接口三个高峰期的特点, 设计两个压测场景(午高峰与晚高峰接口状态类似, 合并成一个压测场景), 进行压测。 + + + +**空间** + +在空间上, 订单与骑士不均匀分布在各地, 有些区域人多单少, 有些区域人少单多, 而对系统影响最大的是人多单多的热点区域。 + +![达达-订单热力分布](images/Solution/达达-订单热力分布.png) + +达达有个 **查看周围X公里订单** 的接口, 这个接口的性能跟 热点区域的个数, 每个热点区域内的单量, 每个热点区域内的运力 关系比较大; 为了搞清这几点, 我们分析大促时生产的流量, 根据 geohash 把全国划分成一个个正方形区域, 统计每个正方形区域内的订单及运力; 最后再在压测环境还原并放大。 + +然后就是 **压测中**, 主要有这几块: 接口验证, 压测预热, 压测实施, 性能指标观察, 性能问题记录。其中压测预热非常重要, 它决定了压测结果的准确性。 + + + +#### 压测预热 + +早期每次压测得到的接口响应都比生产环境慢一点, 后面发现: 生产环境的部分数据是热数据, 而压测环境全是冷数据, 这导致压测刚开始进行时, 接口响应偏高, 等过了一分钟后, 响应逐渐降低并趋于平稳; 后面引入压测预热, 使得压测环境的数据, 部分是热数据, 部分是冷数据, 以达到跟生产环境数据一致的效果。 + +最后就是 **压测后**; 压测后主要是: 压测报告的生成, 性能隐患定位与优化, 系统容量预估, 压测复盘。下面说一下压测复盘。 + + + +#### 压测复盘 + +每次的大促复盘, 都能找到下次压测优化的方向; 复盘中最重要的是: 比较 生产与压测环境的 接口TPS&响应,中间件核心指标; 通过这些数据的比较来验证并优化 压测模型。 + + + +#### 总结与收益 + +全链路压测从 2019年一季度立项, 已经历了 4次大促压测考验, 并且完成了目标; 而在全链路压测实施的过程中, 我们认为有三大关键: + +- **流量的隔离**: 基于【流量打标】, 达达研发出【机器打标】的隔离方案, 使得压测流量与生产流量完全隔离。 +- **数据的隔离**: 基于安全考虑, 达达选择 影子库, 影子缓存, 影子队列, 从而实现生产与压测数据的彻底隔离; 得益于这一点, 压测的实施在白天/晚上随时可进行。 +- **精细化压测模型构建**: 压测模型是否跟生产环境相近, 直接影响了压测结果的准确性; 我们参考生产环境大促高峰期的流量, 从时间&空间维度分析, 制作出与大促时相近的压测流量, 从而保证数据模型的真实性。 + +整个项目的收益也非常明显, 具体从以下两方面分别来看: + +- **稳定性**: 目前连续两年大促, 全链路压测每次都能挖掘出10 多项性能隐患, 从而保障了大促的平稳度过。 +- **效率**: 相比原先搭建一套独立压测环境的方案, 现在的方案在机器成本上降低 40%, 人效上提升 65%。 + +当然此方案还有待提升的点, 比如 成本问题; 目前从安全角度出发, 达达通过影子库/缓存实现数据的隔离, 但影子库相比影子表方案机器成本高, 那如何在 安全与成本之间找到平衡, 是压测优化的方向; 另外, 除了挖掘系统隐患点, 全链路压测能否给 "智能运力调度" "运力的合理配置" 提出更多建议, 这也是我们思考的。 + + + +# 配置中心 + +产品功能特点比较: + +| 功能点 | Spring Cloud Config | Apollo | Nacos | +| ------------ | ------------------------------------------------------ | ----------------------------------------- | ------------------------------------ | +| 开源时间 | 2014.9 | 2016.5 | 2018.6 | +| 配置实时推送 | 支持(Spring Cloud Bus) | 支持(HTTP长轮询1S内) | 支持(HTTP长轮询1S内) | +| 版本管理 | 支持(Git) | 支持 | 支持 | +| 配置回滚 | 支持(Git) | 支持 | 支持 | +| 灰度发布 | 支持 | 支持 | 待支持 | +| 权限管理 | 支持 | 支持 | 待支持 | +| 多集群 | 支持 | 支持 | 支持 | +| 多环境 | 支持 | 支持 | 支持 | +| 监听查询 | 支持 | 支持 | 支持 | +| 多语言 | 只支持java | Go、C++、java、Python、PHP、.net、OpenAPI | Python、Java、Node.js、OpenAPI | +| 单机部署 | Config-server+Git+Spring Cloud Bus(支持配置实时推送) | Apollo-quikstart+MySQL | Nacos单节点 | +| 分布式部署 | Config-server+Git+MQ(部署复杂) | Config+Admin+Portal+MySQL(部署复杂) | Nacos+MySQL(部署简单) | +| 配置格式校验 | 不支持 | 支持 | 支持 | +| 通信协议 | HTTP和AMQP | HTTP | HTTP | +| 数据一致性 | Git保证数据一致性,Config-server从Git读数据 | 数据库模拟消息队列,Apollo定时读消息 | HTTP异步通知 | +| 单机读 | 7(限流所致) | 9000 | 15000 | +| 单机写 | 5(限流所致) | 1100 | 1800 | +| 3节点读 | 21(限流所致) | 27000 | 45000 | +| 3节点写 | 5(限流所致) | 3300 | 5600 | +| 文档 | 详细 | 详细 | 有待完善(目前只有java开发相关文档) | + + + +# 服务框架 + +![DubboArchitecture](images/Solution/DubboArchitecture.png) + +## 注册中心 + +## 集群容错 + +## 负载均衡 + +## 服务路由 + +## 服务治理 + + + diff --git a/images/Algorithm/1117043-20170407105053816-427306966.png b/images/Algorithm/1117043-20170407105053816-427306966.png new file mode 100644 index 0000000..916946d Binary files /dev/null and b/images/Algorithm/1117043-20170407105053816-427306966.png differ diff --git a/images/Algorithm/1117043-20170407105111300-518814658.png b/images/Algorithm/1117043-20170407105111300-518814658.png new file mode 100644 index 0000000..22c3859 Binary files /dev/null and b/images/Algorithm/1117043-20170407105111300-518814658.png differ diff --git a/images/Algorithm/ArrayList表示FIFO队列解法.gif b/images/Algorithm/ArrayList表示FIFO队列解法.gif new file mode 100644 index 0000000..ba1f8c4 Binary files /dev/null and b/images/Algorithm/ArrayList表示FIFO队列解法.gif differ diff --git a/images/Algorithm/BucketSort.gif b/images/Algorithm/BucketSort.gif new file mode 100644 index 0000000..c4770a0 Binary files /dev/null and b/images/Algorithm/BucketSort.gif differ diff --git a/images/Algorithm/Deque滑动窗口最大值.gif b/images/Algorithm/Deque滑动窗口最大值.gif new file mode 100644 index 0000000..a263ad7 Binary files /dev/null and b/images/Algorithm/Deque滑动窗口最大值.gif differ diff --git a/images/Algorithm/Queue表示FIFO队列解法.gif b/images/Algorithm/Queue表示FIFO队列解法.gif new file mode 100644 index 0000000..5c63d0a Binary files /dev/null and b/images/Algorithm/Queue表示FIFO队列解法.gif differ diff --git a/images/Algorithm/SortAlgorithm.png b/images/Algorithm/SortAlgorithm.png new file mode 100644 index 0000000..a3ccb3f Binary files /dev/null and b/images/Algorithm/SortAlgorithm.png differ diff --git a/images/Algorithm/Stack.png b/images/Algorithm/Stack.png new file mode 100644 index 0000000..c068927 Binary files /dev/null and b/images/Algorithm/Stack.png differ diff --git a/images/Algorithm/Stack判断字符串是否有效.gif b/images/Algorithm/Stack判断字符串是否有效.gif new file mode 100644 index 0000000..303b592 Binary files /dev/null and b/images/Algorithm/Stack判断字符串是否有效.gif differ diff --git a/images/Algorithm/Stack每日温度.gif b/images/Algorithm/Stack每日温度.gif new file mode 100644 index 0000000..db2001b Binary files /dev/null and b/images/Algorithm/Stack每日温度.gif differ diff --git a/images/Algorithm/leftBraceNumber加减.gif b/images/Algorithm/leftBraceNumber加减.gif new file mode 100644 index 0000000..26cafd0 Binary files /dev/null and b/images/Algorithm/leftBraceNumber加减.gif differ diff --git a/images/Algorithm/中序遍历.gif b/images/Algorithm/中序遍历.gif new file mode 100644 index 0000000..89358b3 Binary files /dev/null and b/images/Algorithm/中序遍历.gif differ diff --git a/images/Algorithm/二分查找.gif b/images/Algorithm/二分查找.gif new file mode 100644 index 0000000..f1c00aa Binary files /dev/null and b/images/Algorithm/二分查找.gif differ diff --git a/images/Algorithm/二分查找mid.png b/images/Algorithm/二分查找mid.png new file mode 100644 index 0000000..cef8668 Binary files /dev/null and b/images/Algorithm/二分查找mid.png differ diff --git a/images/Algorithm/二叉搜索中第K小的元素.gif b/images/Algorithm/二叉搜索中第K小的元素.gif new file mode 100644 index 0000000..63bfc1b Binary files /dev/null and b/images/Algorithm/二叉搜索中第K小的元素.gif differ diff --git a/images/Algorithm/二叉树的层次遍历.png b/images/Algorithm/二叉树的层次遍历.png new file mode 100644 index 0000000..f88514a Binary files /dev/null and b/images/Algorithm/二叉树的层次遍历.png differ diff --git a/images/Algorithm/代理模式.png b/images/Algorithm/代理模式.png new file mode 100644 index 0000000..64dc72d Binary files /dev/null and b/images/Algorithm/代理模式.png differ diff --git a/images/Algorithm/优先队列-向上筛选.gif b/images/Algorithm/优先队列-向上筛选.gif new file mode 100644 index 0000000..3ac76ed Binary files /dev/null and b/images/Algorithm/优先队列-向上筛选.gif differ diff --git a/images/Algorithm/优先队列-向下筛选.gif b/images/Algorithm/优先队列-向下筛选.gif new file mode 100644 index 0000000..a066f5f Binary files /dev/null and b/images/Algorithm/优先队列-向下筛选.gif differ diff --git a/images/Algorithm/冒泡排序.gif b/images/Algorithm/冒泡排序.gif new file mode 100644 index 0000000..9dd0ed4 Binary files /dev/null and b/images/Algorithm/冒泡排序.gif differ diff --git a/images/Algorithm/冒泡排序.jpg b/images/Algorithm/冒泡排序.jpg new file mode 100644 index 0000000..7f47a26 Binary files /dev/null and b/images/Algorithm/冒泡排序.jpg differ diff --git a/images/Algorithm/判断字符串括号是否合法.gif b/images/Algorithm/判断字符串括号是否合法.gif new file mode 100644 index 0000000..9d822d8 Binary files /dev/null and b/images/Algorithm/判断字符串括号是否合法.gif differ diff --git a/images/Algorithm/前序遍历.gif b/images/Algorithm/前序遍历.gif new file mode 100644 index 0000000..a4ccb18 Binary files /dev/null and b/images/Algorithm/前序遍历.gif differ diff --git a/images/Algorithm/前缀树.png b/images/Algorithm/前缀树.png new file mode 100644 index 0000000..8992ab3 Binary files /dev/null and b/images/Algorithm/前缀树.png differ diff --git a/images/Algorithm/单例模式.png b/images/Algorithm/单例模式.png new file mode 100644 index 0000000..c26872c Binary files /dev/null and b/images/Algorithm/单例模式.png differ diff --git a/images/Algorithm/单向链表.png b/images/Algorithm/单向链表.png new file mode 100644 index 0000000..a6576ef Binary files /dev/null and b/images/Algorithm/单向链表.png differ diff --git a/images/Algorithm/单循环链表.png b/images/Algorithm/单循环链表.png new file mode 100644 index 0000000..3a7d5ce Binary files /dev/null and b/images/Algorithm/单循环链表.png differ diff --git a/images/Algorithm/双向链表.png b/images/Algorithm/双向链表.png new file mode 100644 index 0000000..15ee411 Binary files /dev/null and b/images/Algorithm/双向链表.png differ diff --git a/images/Algorithm/双循环链表.png b/images/Algorithm/双循环链表.png new file mode 100644 index 0000000..b19b8a5 Binary files /dev/null and b/images/Algorithm/双循环链表.png differ diff --git a/images/Algorithm/右右节点旋转.png b/images/Algorithm/右右节点旋转.png new file mode 100644 index 0000000..85dda7b Binary files /dev/null and b/images/Algorithm/右右节点旋转.png differ diff --git a/images/Algorithm/右左节点旋转.png b/images/Algorithm/右左节点旋转.png new file mode 100644 index 0000000..8c22043 Binary files /dev/null and b/images/Algorithm/右左节点旋转.png differ diff --git a/images/Algorithm/右左节点旋转步骤.jpeg b/images/Algorithm/右左节点旋转步骤.jpeg new file mode 100644 index 0000000..380e8c8 Binary files /dev/null and b/images/Algorithm/右左节点旋转步骤.jpeg differ diff --git a/images/Algorithm/右旋.jpg b/images/Algorithm/右旋.jpg new file mode 100644 index 0000000..0ebdfb5 Binary files /dev/null and b/images/Algorithm/右旋.jpg differ diff --git a/images/Algorithm/右旋条件情况1.png b/images/Algorithm/右旋条件情况1.png new file mode 100644 index 0000000..046f725 Binary files /dev/null and b/images/Algorithm/右旋条件情况1.png differ diff --git a/images/Algorithm/右旋条件情况1流程.gif b/images/Algorithm/右旋条件情况1流程.gif new file mode 100644 index 0000000..a93aa19 Binary files /dev/null and b/images/Algorithm/右旋条件情况1流程.gif differ diff --git a/images/Algorithm/右旋条件情况2.png b/images/Algorithm/右旋条件情况2.png new file mode 100644 index 0000000..00751f4 Binary files /dev/null and b/images/Algorithm/右旋条件情况2.png differ diff --git a/images/Algorithm/右旋条件情况2流程.gif b/images/Algorithm/右旋条件情况2流程.gif new file mode 100644 index 0000000..1280c0d Binary files /dev/null and b/images/Algorithm/右旋条件情况2流程.gif differ diff --git a/images/Algorithm/后序遍历.gif b/images/Algorithm/后序遍历.gif new file mode 100644 index 0000000..6bbf2b4 Binary files /dev/null and b/images/Algorithm/后序遍历.gif differ diff --git a/images/Algorithm/命令模式.png b/images/Algorithm/命令模式.png new file mode 100644 index 0000000..ea80a6e Binary files /dev/null and b/images/Algorithm/命令模式.png differ diff --git a/images/Algorithm/基数排序.gif b/images/Algorithm/基数排序.gif new file mode 100644 index 0000000..2a55695 Binary files /dev/null and b/images/Algorithm/基数排序.gif differ diff --git a/images/Algorithm/堆排序.gif b/images/Algorithm/堆排序.gif new file mode 100644 index 0000000..783010a Binary files /dev/null and b/images/Algorithm/堆排序.gif differ diff --git a/images/Algorithm/大鱼吃小鱼.gif b/images/Algorithm/大鱼吃小鱼.gif new file mode 100644 index 0000000..2ba5ddd Binary files /dev/null and b/images/Algorithm/大鱼吃小鱼.gif differ diff --git a/images/Algorithm/工厂模式.png b/images/Algorithm/工厂模式.png new file mode 100644 index 0000000..09964d1 Binary files /dev/null and b/images/Algorithm/工厂模式.png differ diff --git a/images/Algorithm/左右节点旋转.png b/images/Algorithm/左右节点旋转.png new file mode 100644 index 0000000..c67523d Binary files /dev/null and b/images/Algorithm/左右节点旋转.png differ diff --git a/images/Algorithm/左左节点旋转.png b/images/Algorithm/左左节点旋转.png new file mode 100644 index 0000000..e9f29c8 Binary files /dev/null and b/images/Algorithm/左左节点旋转.png differ diff --git a/images/Algorithm/左左节点旋转步骤.jpeg b/images/Algorithm/左左节点旋转步骤.jpeg new file mode 100644 index 0000000..9296510 Binary files /dev/null and b/images/Algorithm/左左节点旋转步骤.jpeg differ diff --git a/images/Algorithm/左旋.jpg b/images/Algorithm/左旋.jpg new file mode 100644 index 0000000..f10977a Binary files /dev/null and b/images/Algorithm/左旋.jpg differ diff --git a/images/Algorithm/左旋条件情况1.png b/images/Algorithm/左旋条件情况1.png new file mode 100644 index 0000000..646f1a1 Binary files /dev/null and b/images/Algorithm/左旋条件情况1.png differ diff --git a/images/Algorithm/左旋条件情况1流程.gif b/images/Algorithm/左旋条件情况1流程.gif new file mode 100644 index 0000000..6b58833 Binary files /dev/null and b/images/Algorithm/左旋条件情况1流程.gif differ diff --git a/images/Algorithm/左旋条件情况2.png b/images/Algorithm/左旋条件情况2.png new file mode 100644 index 0000000..f69b1d0 Binary files /dev/null and b/images/Algorithm/左旋条件情况2.png differ diff --git a/images/Algorithm/左旋条件情况2流程.gif b/images/Algorithm/左旋条件情况2流程.gif new file mode 100644 index 0000000..fd9cc62 Binary files /dev/null and b/images/Algorithm/左旋条件情况2流程.gif differ diff --git a/images/Algorithm/希尔排序.gif b/images/Algorithm/希尔排序.gif new file mode 100644 index 0000000..bbb3bea Binary files /dev/null and b/images/Algorithm/希尔排序.gif differ diff --git a/images/Algorithm/广度优先搜索.jpg b/images/Algorithm/广度优先搜索.jpg new file mode 100644 index 0000000..39f7c2c Binary files /dev/null and b/images/Algorithm/广度优先搜索.jpg differ diff --git a/images/Algorithm/建造者模式.png b/images/Algorithm/建造者模式.png new file mode 100644 index 0000000..093abd6 Binary files /dev/null and b/images/Algorithm/建造者模式.png differ diff --git a/images/Algorithm/归并排序.gif b/images/Algorithm/归并排序.gif new file mode 100644 index 0000000..a29ca19 Binary files /dev/null and b/images/Algorithm/归并排序.gif differ diff --git a/images/Algorithm/归并排序.jpg b/images/Algorithm/归并排序.jpg new file mode 100644 index 0000000..8787d2c Binary files /dev/null and b/images/Algorithm/归并排序.jpg differ diff --git a/images/Algorithm/循环队列k+1个元素空间.png b/images/Algorithm/循环队列k+1个元素空间.png new file mode 100644 index 0000000..2d06a87 Binary files /dev/null and b/images/Algorithm/循环队列k+1个元素空间.png differ diff --git a/images/Algorithm/循环队列k个元素空间.png b/images/Algorithm/循环队列k个元素空间.png new file mode 100644 index 0000000..614a376 Binary files /dev/null and b/images/Algorithm/循环队列k个元素空间.png differ diff --git a/images/Algorithm/快速排序.gif b/images/Algorithm/快速排序.gif new file mode 100644 index 0000000..ad88d35 Binary files /dev/null and b/images/Algorithm/快速排序.gif differ diff --git a/images/Algorithm/排序算法.png b/images/Algorithm/排序算法.png new file mode 100644 index 0000000..d09b5c0 Binary files /dev/null and b/images/Algorithm/排序算法.png differ diff --git a/images/Algorithm/插值查找mid.png b/images/Algorithm/插值查找mid.png new file mode 100644 index 0000000..a21efd4 Binary files /dev/null and b/images/Algorithm/插值查找mid.png differ diff --git a/images/Algorithm/插入排序.gif b/images/Algorithm/插入排序.gif new file mode 100644 index 0000000..2702b14 Binary files /dev/null and b/images/Algorithm/插入排序.gif differ diff --git a/images/Algorithm/插入排序.jpg b/images/Algorithm/插入排序.jpg new file mode 100644 index 0000000..d2df145 Binary files /dev/null and b/images/Algorithm/插入排序.jpg differ diff --git a/images/Algorithm/数据结构-array.png b/images/Algorithm/数据结构-array.png new file mode 100644 index 0000000..6a3b098 Binary files /dev/null and b/images/Algorithm/数据结构-array.png differ diff --git a/images/Algorithm/数据结构-graph.png b/images/Algorithm/数据结构-graph.png new file mode 100644 index 0000000..020f989 Binary files /dev/null and b/images/Algorithm/数据结构-graph.png differ diff --git a/images/Algorithm/数据结构-hash_table.png b/images/Algorithm/数据结构-hash_table.png new file mode 100644 index 0000000..4d03ac9 Binary files /dev/null and b/images/Algorithm/数据结构-hash_table.png differ diff --git a/images/Algorithm/数据结构-queue.png b/images/Algorithm/数据结构-queue.png new file mode 100644 index 0000000..bb9ca34 Binary files /dev/null and b/images/Algorithm/数据结构-queue.png differ diff --git a/images/Algorithm/数据结构-stack.png b/images/Algorithm/数据结构-stack.png new file mode 100644 index 0000000..a7620f6 Binary files /dev/null and b/images/Algorithm/数据结构-stack.png differ diff --git a/images/Algorithm/斐波那契查找.png b/images/Algorithm/斐波那契查找.png new file mode 100644 index 0000000..d6d3ca0 Binary files /dev/null and b/images/Algorithm/斐波那契查找.png differ diff --git a/images/Algorithm/旋转数组.png b/images/Algorithm/旋转数组.png new file mode 100644 index 0000000..395867b Binary files /dev/null and b/images/Algorithm/旋转数组.png differ diff --git a/images/Algorithm/最小堆-前K个高频元素.jpg b/images/Algorithm/最小堆-前K个高频元素.jpg new file mode 100644 index 0000000..a4d0be8 Binary files /dev/null and b/images/Algorithm/最小堆-前K个高频元素.jpg differ diff --git a/images/Algorithm/栈的使用.png b/images/Algorithm/栈的使用.png new file mode 100644 index 0000000..932d0c7 Binary files /dev/null and b/images/Algorithm/栈的使用.png differ diff --git a/images/Algorithm/桥梁模式-1.png b/images/Algorithm/桥梁模式-1.png new file mode 100644 index 0000000..8571cf4 Binary files /dev/null and b/images/Algorithm/桥梁模式-1.png differ diff --git a/images/Algorithm/桥梁模式-2.png b/images/Algorithm/桥梁模式-2.png new file mode 100644 index 0000000..33c9d58 Binary files /dev/null and b/images/Algorithm/桥梁模式-2.png differ diff --git a/images/Algorithm/桶排序.png b/images/Algorithm/桶排序.png new file mode 100644 index 0000000..008bbb4 Binary files /dev/null and b/images/Algorithm/桶排序.png differ diff --git a/images/Algorithm/模板方法模式.png b/images/Algorithm/模板方法模式.png new file mode 100644 index 0000000..c7f1619 Binary files /dev/null and b/images/Algorithm/模板方法模式.png differ diff --git a/images/Algorithm/深度优先搜索.jpg b/images/Algorithm/深度优先搜索.jpg new file mode 100644 index 0000000..39f7c2c Binary files /dev/null and b/images/Algorithm/深度优先搜索.jpg differ diff --git a/images/Algorithm/滑动窗口的最大值.png b/images/Algorithm/滑动窗口的最大值.png new file mode 100644 index 0000000..14fe519 Binary files /dev/null and b/images/Algorithm/滑动窗口的最大值.png differ diff --git a/images/Algorithm/状态模式.png b/images/Algorithm/状态模式.png new file mode 100644 index 0000000..808b386 Binary files /dev/null and b/images/Algorithm/状态模式.png differ diff --git a/images/Algorithm/用链表实现栈.png b/images/Algorithm/用链表实现栈.png new file mode 100644 index 0000000..dd32c28 Binary files /dev/null and b/images/Algorithm/用链表实现栈.png differ diff --git a/images/Algorithm/用链表实现队列.png b/images/Algorithm/用链表实现队列.png new file mode 100644 index 0000000..b4fa119 Binary files /dev/null and b/images/Algorithm/用链表实现队列.png differ diff --git a/images/Algorithm/策略模式.png b/images/Algorithm/策略模式.png new file mode 100644 index 0000000..f0669f2 Binary files /dev/null and b/images/Algorithm/策略模式.png differ diff --git a/images/Algorithm/简单工厂模式.png b/images/Algorithm/简单工厂模式.png new file mode 100644 index 0000000..a548f5c Binary files /dev/null and b/images/Algorithm/简单工厂模式.png differ diff --git a/images/Algorithm/算法思想.jpg b/images/Algorithm/算法思想.jpg new file mode 100644 index 0000000..10ddca1 Binary files /dev/null and b/images/Algorithm/算法思想.jpg differ diff --git a/images/Algorithm/红黑树.jpg b/images/Algorithm/红黑树.jpg new file mode 100644 index 0000000..15fbb61 Binary files /dev/null and b/images/Algorithm/红黑树.jpg differ diff --git a/images/Algorithm/线段树.png b/images/Algorithm/线段树.png new file mode 100644 index 0000000..82d94a0 Binary files /dev/null and b/images/Algorithm/线段树.png differ diff --git a/images/Algorithm/组合模式.png b/images/Algorithm/组合模式.png new file mode 100644 index 0000000..b51be9f Binary files /dev/null and b/images/Algorithm/组合模式.png differ diff --git a/images/Algorithm/翻转字符串algorithm.gif b/images/Algorithm/翻转字符串algorithm.gif new file mode 100644 index 0000000..84677c2 Binary files /dev/null and b/images/Algorithm/翻转字符串algorithm.gif differ diff --git a/images/Algorithm/观察者模式.png b/images/Algorithm/观察者模式.png new file mode 100644 index 0000000..d313673 Binary files /dev/null and b/images/Algorithm/观察者模式.png differ diff --git a/images/Algorithm/计数排序.gif b/images/Algorithm/计数排序.gif new file mode 100644 index 0000000..2479350 Binary files /dev/null and b/images/Algorithm/计数排序.gif differ diff --git a/images/Algorithm/责任链模式.png b/images/Algorithm/责任链模式.png new file mode 100644 index 0000000..fbbce52 Binary files /dev/null and b/images/Algorithm/责任链模式.png differ diff --git a/images/Algorithm/适配器模式-对象适配器.png b/images/Algorithm/适配器模式-对象适配器.png new file mode 100644 index 0000000..a59284d Binary files /dev/null and b/images/Algorithm/适配器模式-对象适配器.png differ diff --git a/images/Algorithm/适配器模式-类继承.png b/images/Algorithm/适配器模式-类继承.png new file mode 100644 index 0000000..d1ddac1 Binary files /dev/null and b/images/Algorithm/适配器模式-类继承.png differ diff --git a/images/Algorithm/适配器模式-默认适配器.png b/images/Algorithm/适配器模式-默认适配器.png new file mode 100644 index 0000000..59c2f81 Binary files /dev/null and b/images/Algorithm/适配器模式-默认适配器.png differ diff --git a/images/Algorithm/选择排序.gif b/images/Algorithm/选择排序.gif new file mode 100644 index 0000000..353459b Binary files /dev/null and b/images/Algorithm/选择排序.gif differ diff --git a/images/Algorithm/选择排序.jpg b/images/Algorithm/选择排序.jpg new file mode 100644 index 0000000..ea7ed3e Binary files /dev/null and b/images/Algorithm/选择排序.jpg differ diff --git a/images/Algorithm/链表(LinkedList).png b/images/Algorithm/链表(LinkedList).png new file mode 100644 index 0000000..479abef Binary files /dev/null and b/images/Algorithm/链表(LinkedList).png differ diff --git a/images/Algorithm/门面模式.png b/images/Algorithm/门面模式.png new file mode 100644 index 0000000..692e284 Binary files /dev/null and b/images/Algorithm/门面模式.png differ diff --git a/images/Architecture/1a9f1463d439bc07a6b44df1ce973916.png b/images/Architecture/1a9f1463d439bc07a6b44df1ce973916.png new file mode 100644 index 0000000..9b367d2 Binary files /dev/null and b/images/Architecture/1a9f1463d439bc07a6b44df1ce973916.png differ diff --git a/images/Architecture/2cabf15de992c630b80b55b7734744bf.png b/images/Architecture/2cabf15de992c630b80b55b7734744bf.png new file mode 100644 index 0000000..f0882bb Binary files /dev/null and b/images/Architecture/2cabf15de992c630b80b55b7734744bf.png differ diff --git a/images/Architecture/468036d6bd7316bc39d8c783af042fc8.png b/images/Architecture/468036d6bd7316bc39d8c783af042fc8.png new file mode 100644 index 0000000..9b8b607 Binary files /dev/null and b/images/Architecture/468036d6bd7316bc39d8c783af042fc8.png differ diff --git a/images/Architecture/788b54bf0bb97d401f13a0fdc20bc9c1.png b/images/Architecture/788b54bf0bb97d401f13a0fdc20bc9c1.png new file mode 100644 index 0000000..151fa6b Binary files /dev/null and b/images/Architecture/788b54bf0bb97d401f13a0fdc20bc9c1.png differ diff --git a/images/Architecture/Apollo配置中心.jpg b/images/Architecture/Apollo配置中心.jpg new file mode 100644 index 0000000..92af044 Binary files /dev/null and b/images/Architecture/Apollo配置中心.jpg differ diff --git a/images/Architecture/BITOP.png b/images/Architecture/BITOP.png new file mode 100644 index 0000000..c32dc26 Binary files /dev/null and b/images/Architecture/BITOP.png differ diff --git a/images/Architecture/Bitmap.png b/images/Architecture/Bitmap.png new file mode 100644 index 0000000..475ecfb Binary files /dev/null and b/images/Architecture/Bitmap.png differ diff --git a/images/Architecture/Blackboard-pattern.png b/images/Architecture/Blackboard-pattern.png new file mode 100644 index 0000000..df2ad44 Binary files /dev/null and b/images/Architecture/Blackboard-pattern.png differ diff --git a/images/Architecture/Broker-pattern.png b/images/Architecture/Broker-pattern.png new file mode 100644 index 0000000..7dc50ba Binary files /dev/null and b/images/Architecture/Broker-pattern.png differ diff --git a/images/Architecture/CSRF攻击案例.jpg b/images/Architecture/CSRF攻击案例.jpg new file mode 100644 index 0000000..60ccab0 Binary files /dev/null and b/images/Architecture/CSRF攻击案例.jpg differ diff --git a/images/Architecture/Client-server-pattern.png b/images/Architecture/Client-server-pattern.png new file mode 100644 index 0000000..b8e4f7c Binary files /dev/null and b/images/Architecture/Client-server-pattern.png differ diff --git a/images/Architecture/Dapper.jpg b/images/Architecture/Dapper.jpg new file mode 100644 index 0000000..467fe3d Binary files /dev/null and b/images/Architecture/Dapper.jpg differ diff --git a/images/Architecture/Event-bus-pattern.png b/images/Architecture/Event-bus-pattern.png new file mode 100644 index 0000000..77a9d4e Binary files /dev/null and b/images/Architecture/Event-bus-pattern.png differ diff --git a/images/Architecture/Hystrix调用流程.jpg b/images/Architecture/Hystrix调用流程.jpg new file mode 100644 index 0000000..def4835 Binary files /dev/null and b/images/Architecture/Hystrix调用流程.jpg differ diff --git a/images/Architecture/InterProcessMutex.png b/images/Architecture/InterProcessMutex.png new file mode 100644 index 0000000..554540f Binary files /dev/null and b/images/Architecture/InterProcessMutex.png differ diff --git a/images/Architecture/Interpreter-pattern.png b/images/Architecture/Interpreter-pattern.png new file mode 100644 index 0000000..759d520 Binary files /dev/null and b/images/Architecture/Interpreter-pattern.png differ diff --git a/images/Architecture/JSON序列化漏洞复现.jpg b/images/Architecture/JSON序列化漏洞复现.jpg new file mode 100644 index 0000000..a9d1bc2 Binary files /dev/null and b/images/Architecture/JSON序列化漏洞复现.jpg differ diff --git a/images/Architecture/JSON序列化过程.jpg b/images/Architecture/JSON序列化过程.jpg new file mode 100644 index 0000000..9437b2b Binary files /dev/null and b/images/Architecture/JSON序列化过程.jpg differ diff --git a/images/Architecture/Layered-pattern.png b/images/Architecture/Layered-pattern.png new file mode 100644 index 0000000..a13c995 Binary files /dev/null and b/images/Architecture/Layered-pattern.png differ diff --git a/images/Architecture/List最新列表.png b/images/Architecture/List最新列表.png new file mode 100644 index 0000000..d1aa2ef Binary files /dev/null and b/images/Architecture/List最新列表.png differ diff --git a/images/Architecture/Master-slave-pattern.png b/images/Architecture/Master-slave-pattern.png new file mode 100644 index 0000000..bb64d68 Binary files /dev/null and b/images/Architecture/Master-slave-pattern.png differ diff --git a/images/Architecture/Mock-Server.png b/images/Architecture/Mock-Server.png new file mode 100644 index 0000000..f6114f0 Binary files /dev/null and b/images/Architecture/Mock-Server.png differ diff --git a/images/Architecture/Model-view-controller-pattern.png b/images/Architecture/Model-view-controller-pattern.png new file mode 100644 index 0000000..e5ac760 Binary files /dev/null and b/images/Architecture/Model-view-controller-pattern.png differ diff --git a/images/Architecture/Netflix的路由发现体系.jpg b/images/Architecture/Netflix的路由发现体系.jpg new file mode 100644 index 0000000..3f5ddae Binary files /dev/null and b/images/Architecture/Netflix的路由发现体系.jpg differ diff --git a/images/Architecture/OAuth2.0-令牌怎么用.png b/images/Architecture/OAuth2.0-令牌怎么用.png new file mode 100644 index 0000000..0dd9d54 Binary files /dev/null and b/images/Architecture/OAuth2.0-令牌怎么用.png differ diff --git a/images/Architecture/OAuth2.0-授权码.png b/images/Architecture/OAuth2.0-授权码.png new file mode 100644 index 0000000..b6be7f2 Binary files /dev/null and b/images/Architecture/OAuth2.0-授权码.png differ diff --git a/images/Architecture/Peer-to-peer-pattern.png b/images/Architecture/Peer-to-peer-pattern.png new file mode 100644 index 0000000..b5e52dd Binary files /dev/null and b/images/Architecture/Peer-to-peer-pattern.png differ diff --git a/images/Architecture/Pipe-filter-pattern.png b/images/Architecture/Pipe-filter-pattern.png new file mode 100644 index 0000000..5ddc937 Binary files /dev/null and b/images/Architecture/Pipe-filter-pattern.png differ diff --git a/images/Architecture/RBAC.png b/images/Architecture/RBAC.png new file mode 100644 index 0000000..598a8d2 Binary files /dev/null and b/images/Architecture/RBAC.png differ diff --git a/images/Architecture/RBAC0.png b/images/Architecture/RBAC0.png new file mode 100644 index 0000000..0288068 Binary files /dev/null and b/images/Architecture/RBAC0.png differ diff --git a/images/Architecture/RBAC1.png b/images/Architecture/RBAC1.png new file mode 100644 index 0000000..58397c4 Binary files /dev/null and b/images/Architecture/RBAC1.png differ diff --git a/images/Architecture/RBAC2.png b/images/Architecture/RBAC2.png new file mode 100644 index 0000000..3b98ad7 Binary files /dev/null and b/images/Architecture/RBAC2.png differ diff --git a/images/Architecture/RBAC3.png b/images/Architecture/RBAC3.png new file mode 100644 index 0000000..ef95d1e Binary files /dev/null and b/images/Architecture/RBAC3.png differ diff --git a/images/Architecture/RBAC用户组.png b/images/Architecture/RBAC用户组.png new file mode 100644 index 0000000..b6c9d22 Binary files /dev/null and b/images/Architecture/RBAC用户组.png differ diff --git a/images/Architecture/RedisObject.png b/images/Architecture/RedisObject.png new file mode 100644 index 0000000..7eb7431 Binary files /dev/null and b/images/Architecture/RedisObject.png differ diff --git a/images/Architecture/SDS.png b/images/Architecture/SDS.png new file mode 100644 index 0000000..31200cc Binary files /dev/null and b/images/Architecture/SDS.png differ diff --git a/images/Architecture/SOA架构.png b/images/Architecture/SOA架构.png new file mode 100644 index 0000000..7af6046 Binary files /dev/null and b/images/Architecture/SOA架构.png differ diff --git a/images/Architecture/SQL注入业务场景.jpg b/images/Architecture/SQL注入业务场景.jpg new file mode 100644 index 0000000..506aa0c Binary files /dev/null and b/images/Architecture/SQL注入业务场景.jpg differ diff --git a/images/Architecture/Serverless.jpg b/images/Architecture/Serverless.jpg new file mode 100644 index 0000000..2ff98d5 Binary files /dev/null and b/images/Architecture/Serverless.jpg differ diff --git a/images/Architecture/XSS攻击-Html.jpg b/images/Architecture/XSS攻击-Html.jpg new file mode 100644 index 0000000..a5cebb4 Binary files /dev/null and b/images/Architecture/XSS攻击-Html.jpg differ diff --git a/images/Architecture/XSS攻击-Html结果.jpg b/images/Architecture/XSS攻击-Html结果.jpg new file mode 100644 index 0000000..3ec1ff6 Binary files /dev/null and b/images/Architecture/XSS攻击-Html结果.jpg differ diff --git a/images/Architecture/XSS攻击-反射案例.jpg b/images/Architecture/XSS攻击-反射案例.jpg new file mode 100644 index 0000000..25cf0f3 Binary files /dev/null and b/images/Architecture/XSS攻击-反射案例.jpg differ diff --git a/images/Architecture/XSS攻击-转义字符.jpg b/images/Architecture/XSS攻击-转义字符.jpg new file mode 100644 index 0000000..bce2ea2 Binary files /dev/null and b/images/Architecture/XSS攻击-转义字符.jpg differ diff --git a/images/Architecture/cdefa098def4fa1cb6c181f700df4c49.png b/images/Architecture/cdefa098def4fa1cb6c181f700df4c49.png new file mode 100644 index 0000000..d65ec5b Binary files /dev/null and b/images/Architecture/cdefa098def4fa1cb6c181f700df4c49.png differ diff --git a/images/Architecture/dc291356663fc69dd4e8f7cfe7be63b6.png b/images/Architecture/dc291356663fc69dd4e8f7cfe7be63b6.png new file mode 100644 index 0000000..472bce1 Binary files /dev/null and b/images/Architecture/dc291356663fc69dd4e8f7cfe7be63b6.png differ diff --git a/images/Architecture/georadius-range.jpg b/images/Architecture/georadius-range.jpg new file mode 100644 index 0000000..7a0c3a7 Binary files /dev/null and b/images/Architecture/georadius-range.jpg differ diff --git a/images/Architecture/georadius.jpg b/images/Architecture/georadius.jpg new file mode 100644 index 0000000..c66a730 Binary files /dev/null and b/images/Architecture/georadius.jpg differ diff --git a/images/Architecture/mysqllock-lock-java.png b/images/Architecture/mysqllock-lock-java.png new file mode 100644 index 0000000..0507922 Binary files /dev/null and b/images/Architecture/mysqllock-lock-java.png differ diff --git a/images/Architecture/mysqllock-lock-sql.png b/images/Architecture/mysqllock-lock-sql.png new file mode 100644 index 0000000..2e91e8e Binary files /dev/null and b/images/Architecture/mysqllock-lock-sql.png differ diff --git a/images/Architecture/mysqllock-trylock-timeout.png b/images/Architecture/mysqllock-trylock-timeout.png new file mode 100644 index 0000000..cd18c08 Binary files /dev/null and b/images/Architecture/mysqllock-trylock-timeout.png differ diff --git a/images/Architecture/mysqllock-trylock.png b/images/Architecture/mysqllock-trylock.png new file mode 100644 index 0000000..2c7412f Binary files /dev/null and b/images/Architecture/mysqllock-trylock.png differ diff --git a/images/Architecture/mysqllock-unlock.png b/images/Architecture/mysqllock-unlock.png new file mode 100644 index 0000000..c0d9cc3 Binary files /dev/null and b/images/Architecture/mysqllock-unlock.png differ diff --git a/images/Architecture/mysqllock.png b/images/Architecture/mysqllock.png new file mode 100644 index 0000000..f36aa83 Binary files /dev/null and b/images/Architecture/mysqllock.png differ diff --git a/images/Architecture/zookeeperlock.png b/images/Architecture/zookeeperlock.png new file mode 100644 index 0000000..1c0dc18 Binary files /dev/null and b/images/Architecture/zookeeperlock.png differ diff --git a/images/Architecture/三种服务发现模式.png b/images/Architecture/三种服务发现模式.png new file mode 100644 index 0000000..e8e32af Binary files /dev/null and b/images/Architecture/三种服务发现模式.png differ diff --git a/images/Architecture/三种测试.png b/images/Architecture/三种测试.png new file mode 100644 index 0000000..eef8487 Binary files /dev/null and b/images/Architecture/三种测试.png differ diff --git a/images/Architecture/主机独立Lb模式.jpg b/images/Architecture/主机独立Lb模式.jpg new file mode 100644 index 0000000..0f66714 Binary files /dev/null and b/images/Architecture/主机独立Lb模式.jpg differ diff --git a/images/Architecture/交集-共同好友.png b/images/Architecture/交集-共同好友.png new file mode 100644 index 0000000..93de67b Binary files /dev/null and b/images/Architecture/交集-共同好友.png differ diff --git a/images/Architecture/传统Lb模式.jpg b/images/Architecture/传统Lb模式.jpg new file mode 100644 index 0000000..bc646e8 Binary files /dev/null and b/images/Architecture/传统Lb模式.jpg differ diff --git a/images/Architecture/典型主流的监控架构.jpg b/images/Architecture/典型主流的监控架构.jpg new file mode 100644 index 0000000..471d7bd Binary files /dev/null and b/images/Architecture/典型主流的监控架构.jpg differ diff --git a/images/Architecture/分层设计.png b/images/Architecture/分层设计.png new file mode 100644 index 0000000..1173dfe Binary files /dev/null and b/images/Architecture/分层设计.png differ diff --git a/images/Architecture/分布式架构.png b/images/Architecture/分布式架构.png new file mode 100644 index 0000000..806d385 Binary files /dev/null and b/images/Architecture/分布式架构.png differ diff --git a/images/Architecture/分析问题-日志分析.png b/images/Architecture/分析问题-日志分析.png new file mode 100644 index 0000000..af38cb4 Binary files /dev/null and b/images/Architecture/分析问题-日志分析.png differ diff --git a/images/Architecture/功能权限的Demo.png b/images/Architecture/功能权限的Demo.png new file mode 100644 index 0000000..3ce8d8f Binary files /dev/null and b/images/Architecture/功能权限的Demo.png differ diff --git a/images/Architecture/功能权限的实体关系图.png b/images/Architecture/功能权限的实体关系图.png new file mode 100644 index 0000000..baef7de Binary files /dev/null and b/images/Architecture/功能权限的实体关系图.png differ diff --git a/images/Architecture/功能权限的继承.png b/images/Architecture/功能权限的继承.png new file mode 100644 index 0000000..7882c0d Binary files /dev/null and b/images/Architecture/功能权限的继承.png differ diff --git a/images/Architecture/功能权限的继承层级.png b/images/Architecture/功能权限的继承层级.png new file mode 100644 index 0000000..00b8485 Binary files /dev/null and b/images/Architecture/功能权限的继承层级.png differ diff --git a/images/Architecture/单体应用架构.png b/images/Architecture/单体应用架构.png new file mode 100644 index 0000000..28b7855 Binary files /dev/null and b/images/Architecture/单体应用架构.png differ diff --git a/images/Architecture/原始数据.png b/images/Architecture/原始数据.png new file mode 100644 index 0000000..0b4168c Binary files /dev/null and b/images/Architecture/原始数据.png differ diff --git a/images/Architecture/取整.png b/images/Architecture/取整.png new file mode 100644 index 0000000..fb27061 Binary files /dev/null and b/images/Architecture/取整.png differ diff --git a/images/Architecture/垂直应用架构.png b/images/Architecture/垂直应用架构.png new file mode 100644 index 0000000..740316c Binary files /dev/null and b/images/Architecture/垂直应用架构.png differ diff --git a/images/Architecture/定位问题-taceId.png b/images/Architecture/定位问题-taceId.png new file mode 100644 index 0000000..6025d74 Binary files /dev/null and b/images/Architecture/定位问题-taceId.png differ diff --git a/images/Architecture/定位问题-链路跟踪.png b/images/Architecture/定位问题-链路跟踪.png new file mode 100644 index 0000000..18d2cb5 Binary files /dev/null and b/images/Architecture/定位问题-链路跟踪.png differ diff --git a/images/Architecture/客户端模式.png b/images/Architecture/客户端模式.png new file mode 100644 index 0000000..1f067c7 Binary files /dev/null and b/images/Architecture/客户端模式.png differ diff --git a/images/Architecture/密码模式.png b/images/Architecture/密码模式.png new file mode 100644 index 0000000..9652f6d Binary files /dev/null and b/images/Architecture/密码模式.png differ diff --git a/images/Architecture/对称加密.png b/images/Architecture/对称加密.png new file mode 100644 index 0000000..f32548e Binary files /dev/null and b/images/Architecture/对称加密.png differ diff --git a/images/Architecture/对账业务流程.png b/images/Architecture/对账业务流程.png new file mode 100644 index 0000000..204d9ca Binary files /dev/null and b/images/Architecture/对账业务流程.png differ diff --git a/images/Architecture/差集-每日新增好友数.png b/images/Architecture/差集-每日新增好友数.png new file mode 100644 index 0000000..9e3d6ba Binary files /dev/null and b/images/Architecture/差集-每日新增好友数.png differ diff --git a/images/Architecture/平均值.png b/images/Architecture/平均值.png new file mode 100644 index 0000000..187c25e Binary files /dev/null and b/images/Architecture/平均值.png differ diff --git a/images/Architecture/库存超卖-解决方案.jpg b/images/Architecture/库存超卖-解决方案.jpg new file mode 100644 index 0000000..4127911 Binary files /dev/null and b/images/Architecture/库存超卖-解决方案.jpg differ diff --git a/images/Architecture/库存超卖-问题.png b/images/Architecture/库存超卖-问题.png new file mode 100644 index 0000000..ff8acaf Binary files /dev/null and b/images/Architecture/库存超卖-问题.png differ diff --git a/images/Architecture/库存超卖.png b/images/Architecture/库存超卖.png new file mode 100644 index 0000000..667b989 Binary files /dev/null and b/images/Architecture/库存超卖.png differ diff --git a/images/Architecture/康威定律-单块应用.jpg b/images/Architecture/康威定律-单块应用.jpg new file mode 100644 index 0000000..5214857 Binary files /dev/null and b/images/Architecture/康威定律-单块应用.jpg differ diff --git a/images/Architecture/康威定律-微服务解决方案.jpg b/images/Architecture/康威定律-微服务解决方案.jpg new file mode 100644 index 0000000..afe58e9 Binary files /dev/null and b/images/Architecture/康威定律-微服务解决方案.jpg differ diff --git a/images/Architecture/微信支付-API秘钥.png b/images/Architecture/微信支付-API秘钥.png new file mode 100644 index 0000000..2b2b904 Binary files /dev/null and b/images/Architecture/微信支付-API秘钥.png differ diff --git a/images/Architecture/微信支付-JS接口安全域名.png b/images/Architecture/微信支付-JS接口安全域名.png new file mode 100644 index 0000000..829418e Binary files /dev/null and b/images/Architecture/微信支付-JS接口安全域名.png differ diff --git a/images/Architecture/微信支付-产品大全.png b/images/Architecture/微信支付-产品大全.png new file mode 100644 index 0000000..ba942ef Binary files /dev/null and b/images/Architecture/微信支付-产品大全.png differ diff --git a/images/Architecture/微信支付-公众号.png b/images/Architecture/微信支付-公众号.png new file mode 100644 index 0000000..245e5fc Binary files /dev/null and b/images/Architecture/微信支付-公众号.png differ diff --git a/images/Architecture/微信支付-开发配置.png b/images/Architecture/微信支付-开发配置.png new file mode 100644 index 0000000..6d40dec Binary files /dev/null and b/images/Architecture/微信支付-开发配置.png differ diff --git a/images/Architecture/微信支付-微信商户平台.png b/images/Architecture/微信支付-微信商户平台.png new file mode 100644 index 0000000..d3ba1d2 Binary files /dev/null and b/images/Architecture/微信支付-微信商户平台.png differ diff --git a/images/Architecture/微信支付-支付流程.png b/images/Architecture/微信支付-支付流程.png new file mode 100644 index 0000000..c74d547 Binary files /dev/null and b/images/Architecture/微信支付-支付流程.png differ diff --git a/images/Architecture/微信支付-支付状态查询.png b/images/Architecture/微信支付-支付状态查询.png new file mode 100644 index 0000000..a8446b5 Binary files /dev/null and b/images/Architecture/微信支付-支付状态查询.png differ diff --git a/images/Architecture/微信支付-新增授权.png b/images/Architecture/微信支付-新增授权.png new file mode 100644 index 0000000..f739b6b Binary files /dev/null and b/images/Architecture/微信支付-新增授权.png differ diff --git a/images/Architecture/微信支付-服务器配置.png b/images/Architecture/微信支付-服务器配置.png new file mode 100644 index 0000000..b927d2c Binary files /dev/null and b/images/Architecture/微信支付-服务器配置.png differ diff --git a/images/Architecture/微信支付-白名单配置.png b/images/Architecture/微信支付-白名单配置.png new file mode 100644 index 0000000..9bfbd6f Binary files /dev/null and b/images/Architecture/微信支付-白名单配置.png differ diff --git a/images/Architecture/微信支付-绑定商户.png b/images/Architecture/微信支付-绑定商户.png new file mode 100644 index 0000000..c2c258f Binary files /dev/null and b/images/Architecture/微信支付-绑定商户.png differ diff --git a/images/Architecture/微信支付-设置API秘钥.png b/images/Architecture/微信支付-设置API秘钥.png new file mode 100644 index 0000000..f980ffb Binary files /dev/null and b/images/Architecture/微信支付-设置API秘钥.png differ diff --git a/images/Architecture/微信支付最佳实践.png b/images/Architecture/微信支付最佳实践.png new file mode 100644 index 0000000..8f73d86 Binary files /dev/null and b/images/Architecture/微信支付最佳实践.png differ diff --git a/images/Architecture/微服务-API网关-定义.jpg b/images/Architecture/微服务-API网关-定义.jpg new file mode 100644 index 0000000..691b0aa Binary files /dev/null and b/images/Architecture/微服务-API网关-定义.jpg differ diff --git a/images/Architecture/微服务-API网关-架构.jpg b/images/Architecture/微服务-API网关-架构.jpg new file mode 100644 index 0000000..925c8d1 Binary files /dev/null and b/images/Architecture/微服务-API网关-架构.jpg differ diff --git a/images/Architecture/微服务-API网关-案例.jpg b/images/Architecture/微服务-API网关-案例.jpg new file mode 100644 index 0000000..096e9e3 Binary files /dev/null and b/images/Architecture/微服务-API网关-案例.jpg differ diff --git a/images/Architecture/微服务-API网关.png b/images/Architecture/微服务-API网关.png new file mode 100644 index 0000000..acd0117 Binary files /dev/null and b/images/Architecture/微服务-API网关.png differ diff --git a/images/Architecture/微服务-BFF.png b/images/Architecture/微服务-BFF.png new file mode 100644 index 0000000..4a31d83 Binary files /dev/null and b/images/Architecture/微服务-BFF.png differ diff --git a/images/Architecture/微服务-Saga.png b/images/Architecture/微服务-Saga.png new file mode 100644 index 0000000..4a31d83 Binary files /dev/null and b/images/Architecture/微服务-Saga.png differ diff --git a/images/Architecture/微服务-Strangler.png b/images/Architecture/微服务-Strangler.png new file mode 100644 index 0000000..01d894d Binary files /dev/null and b/images/Architecture/微服务-Strangler.png differ diff --git a/images/Architecture/微服务-事件源.png b/images/Architecture/微服务-事件源.png new file mode 100644 index 0000000..42511ab Binary files /dev/null and b/images/Architecture/微服务-事件源.png differ diff --git a/images/Architecture/微服务-断路器.png b/images/Architecture/微服务-断路器.png new file mode 100644 index 0000000..e0447fc Binary files /dev/null and b/images/Architecture/微服务-断路器.png differ diff --git a/images/Architecture/微服务-独享数据库.png b/images/Architecture/微服务-独享数据库.png new file mode 100644 index 0000000..79135f6 Binary files /dev/null and b/images/Architecture/微服务-独享数据库.png differ diff --git a/images/Architecture/微服务-简单CQRS.png b/images/Architecture/微服务-简单CQRS.png new file mode 100644 index 0000000..b6c2c78 Binary files /dev/null and b/images/Architecture/微服务-简单CQRS.png differ diff --git a/images/Architecture/微服务-聚合服务.jpg b/images/Architecture/微服务-聚合服务.jpg new file mode 100644 index 0000000..ed90777 Binary files /dev/null and b/images/Architecture/微服务-聚合服务.jpg differ diff --git a/images/Architecture/微服务-高级CQRS.png b/images/Architecture/微服务-高级CQRS.png new file mode 100644 index 0000000..1c1883d Binary files /dev/null and b/images/Architecture/微服务-高级CQRS.png differ diff --git a/images/Architecture/微服务分层.jpg b/images/Architecture/微服务分层.jpg new file mode 100644 index 0000000..4a55950 Binary files /dev/null and b/images/Architecture/微服务分层.jpg differ diff --git a/images/Architecture/微服务分层监控.jpg b/images/Architecture/微服务分层监控.jpg new file mode 100644 index 0000000..7f45700 Binary files /dev/null and b/images/Architecture/微服务分层监控.jpg differ diff --git a/images/Architecture/微服务利弊.jpg b/images/Architecture/微服务利弊.jpg new file mode 100644 index 0000000..97fda1a Binary files /dev/null and b/images/Architecture/微服务利弊.jpg differ diff --git a/images/Architecture/微服务定义.jpg b/images/Architecture/微服务定义.jpg new file mode 100644 index 0000000..eb6903a Binary files /dev/null and b/images/Architecture/微服务定义.jpg differ diff --git a/images/Architecture/微服务技术架构体系.jpg b/images/Architecture/微服务技术架构体系.jpg new file mode 100644 index 0000000..7c9d32e Binary files /dev/null and b/images/Architecture/微服务技术架构体系.jpg differ diff --git a/images/Architecture/微服务架构.png b/images/Architecture/微服务架构.png new file mode 100644 index 0000000..8f43f9f Binary files /dev/null and b/images/Architecture/微服务架构.png differ diff --git a/images/Architecture/微服务治理.jpg b/images/Architecture/微服务治理.jpg new file mode 100644 index 0000000..bc9f234 Binary files /dev/null and b/images/Architecture/微服务治理.jpg differ diff --git a/images/Architecture/微服务监控点.jpg b/images/Architecture/微服务监控点.jpg new file mode 100644 index 0000000..79a3b97 Binary files /dev/null and b/images/Architecture/微服务监控点.jpg differ diff --git a/images/Architecture/截断方式.png b/images/Architecture/截断方式.png new file mode 100644 index 0000000..bd509ad Binary files /dev/null and b/images/Architecture/截断方式.png differ diff --git a/images/Architecture/找回密码流程.png b/images/Architecture/找回密码流程.png new file mode 100644 index 0000000..b15ae58 Binary files /dev/null and b/images/Architecture/找回密码流程.png differ diff --git a/images/Architecture/授权码模式.png b/images/Architecture/授权码模式.png new file mode 100644 index 0000000..d99956b Binary files /dev/null and b/images/Architecture/授权码模式.png differ diff --git a/images/Architecture/授权码模式优缺点.png b/images/Architecture/授权码模式优缺点.png new file mode 100644 index 0000000..2054771 Binary files /dev/null and b/images/Architecture/授权码模式优缺点.png differ diff --git a/images/Architecture/支付掉单-定时任务补偿.jpg b/images/Architecture/支付掉单-定时任务补偿.jpg new file mode 100644 index 0000000..61b2c1c Binary files /dev/null and b/images/Architecture/支付掉单-定时任务补偿.jpg differ diff --git a/images/Architecture/支付掉单-延迟消息补偿方案.jpg b/images/Architecture/支付掉单-延迟消息补偿方案.jpg new file mode 100644 index 0000000..66934c0 Binary files /dev/null and b/images/Architecture/支付掉单-延迟消息补偿方案.jpg differ diff --git a/images/Architecture/支付系统异常处理-内部掉单.jpg b/images/Architecture/支付系统异常处理-内部掉单.jpg new file mode 100644 index 0000000..ac1fad6 Binary files /dev/null and b/images/Architecture/支付系统异常处理-内部掉单.jpg differ diff --git a/images/Architecture/支付系统异常处理-定时查询.jpg b/images/Architecture/支付系统异常处理-定时查询.jpg new file mode 100644 index 0000000..22c5874 Binary files /dev/null and b/images/Architecture/支付系统异常处理-定时查询.jpg differ diff --git a/images/Architecture/支付系统异常处理-携程.jpg b/images/Architecture/支付系统异常处理-携程.jpg new file mode 100644 index 0000000..291fbda Binary files /dev/null and b/images/Architecture/支付系统异常处理-携程.jpg differ diff --git a/images/Architecture/支付系统异常处理-支付平台.jpg b/images/Architecture/支付系统异常处理-支付平台.jpg new file mode 100644 index 0000000..d34377f Binary files /dev/null and b/images/Architecture/支付系统异常处理-支付平台.jpg differ diff --git a/images/Architecture/支付系统异常处理-支付异步通知.jpg b/images/Architecture/支付系统异常处理-支付异步通知.jpg new file mode 100644 index 0000000..229444a Binary files /dev/null and b/images/Architecture/支付系统异常处理-支付异步通知.jpg differ diff --git a/images/Architecture/支付系统异常处理-支付订单渠道11.jpg b/images/Architecture/支付系统异常处理-支付订单渠道11.jpg new file mode 100644 index 0000000..143edfe Binary files /dev/null and b/images/Architecture/支付系统异常处理-支付订单渠道11.jpg differ diff --git a/images/Architecture/支付系统异常处理-支付订单渠道1N.jpg b/images/Architecture/支付系统异常处理-支付订单渠道1N.jpg new file mode 100644 index 0000000..11262e7 Binary files /dev/null and b/images/Architecture/支付系统异常处理-支付订单渠道1N.jpg differ diff --git a/images/Architecture/数据替换.png b/images/Architecture/数据替换.png new file mode 100644 index 0000000..ba7db6f Binary files /dev/null and b/images/Architecture/数据替换.png differ diff --git a/images/Architecture/数据脱敏过程.png b/images/Architecture/数据脱敏过程.png new file mode 100644 index 0000000..b7453f1 Binary files /dev/null and b/images/Architecture/数据脱敏过程.png differ diff --git a/images/Architecture/文件下载漏洞.jpg b/images/Architecture/文件下载漏洞.jpg new file mode 100644 index 0000000..2fe360f Binary files /dev/null and b/images/Architecture/文件下载漏洞.jpg differ diff --git a/images/Architecture/服务注册于发现-动态扩容.png b/images/Architecture/服务注册于发现-动态扩容.png new file mode 100644 index 0000000..19756d1 Binary files /dev/null and b/images/Architecture/服务注册于发现-动态扩容.png differ diff --git a/images/Architecture/服务网格ServiceMesh.png b/images/Architecture/服务网格ServiceMesh.png new file mode 100644 index 0000000..e28e982 Binary files /dev/null and b/images/Architecture/服务网格ServiceMesh.png differ diff --git a/images/Architecture/模式一:传统集中式代理.png b/images/Architecture/模式一:传统集中式代理.png new file mode 100644 index 0000000..c898f04 Binary files /dev/null and b/images/Architecture/模式一:传统集中式代理.png differ diff --git a/images/Architecture/模式三:主机独立进程代理.png b/images/Architecture/模式三:主机独立进程代理.png new file mode 100644 index 0000000..ab3d267 Binary files /dev/null and b/images/Architecture/模式三:主机独立进程代理.png differ diff --git a/images/Architecture/模式二:客户端嵌入式代理.png b/images/Architecture/模式二:客户端嵌入式代理.png new file mode 100644 index 0000000..63b09f8 Binary files /dev/null and b/images/Architecture/模式二:客户端嵌入式代理.png differ diff --git a/images/Architecture/熔断.png b/images/Architecture/熔断.png new file mode 100644 index 0000000..afe0624 Binary files /dev/null and b/images/Architecture/熔断.png differ diff --git a/images/Architecture/用户-权限.png b/images/Architecture/用户-权限.png new file mode 100644 index 0000000..d16d68d Binary files /dev/null and b/images/Architecture/用户-权限.png differ diff --git a/images/Architecture/用户-权限Demo.png b/images/Architecture/用户-权限Demo.png new file mode 100644 index 0000000..bf1f1df Binary files /dev/null and b/images/Architecture/用户-权限Demo.png differ diff --git a/images/Architecture/用户-角色-权限.png b/images/Architecture/用户-角色-权限.png new file mode 100644 index 0000000..ef477b5 Binary files /dev/null and b/images/Architecture/用户-角色-权限.png differ diff --git a/images/Architecture/用户-角色-权限Demo用户管理.png b/images/Architecture/用户-角色-权限Demo用户管理.png new file mode 100644 index 0000000..a26f031 Binary files /dev/null and b/images/Architecture/用户-角色-权限Demo用户管理.png differ diff --git a/images/Architecture/用户-角色-权限Demo角色管理.png b/images/Architecture/用户-角色-权限Demo角色管理.png new file mode 100644 index 0000000..feb5487 Binary files /dev/null and b/images/Architecture/用户-角色-权限Demo角色管理.png differ diff --git a/images/Architecture/监控-发现故障的征兆.jpg b/images/Architecture/监控-发现故障的征兆.jpg new file mode 100644 index 0000000..6edc82f Binary files /dev/null and b/images/Architecture/监控-发现故障的征兆.jpg differ diff --git a/images/Architecture/秒杀系统.png b/images/Architecture/秒杀系统.png new file mode 100644 index 0000000..99f8a81 Binary files /dev/null and b/images/Architecture/秒杀系统.png differ diff --git a/images/Architecture/简化模式.png b/images/Architecture/简化模式.png new file mode 100644 index 0000000..9652f6d Binary files /dev/null and b/images/Architecture/简化模式.png differ diff --git a/images/Architecture/简化模式优缺点.png b/images/Architecture/简化模式优缺点.png new file mode 100644 index 0000000..4661453 Binary files /dev/null and b/images/Architecture/简化模式优缺点.png differ diff --git a/images/Architecture/组织-用户-角色-权限.png b/images/Architecture/组织-用户-角色-权限.png new file mode 100644 index 0000000..00b8485 Binary files /dev/null and b/images/Architecture/组织-用户-角色-权限.png differ diff --git a/images/Architecture/组织-用户-角色-权限数据范围.png b/images/Architecture/组织-用户-角色-权限数据范围.png new file mode 100644 index 0000000..fd599ae Binary files /dev/null and b/images/Architecture/组织-用户-角色-权限数据范围.png differ diff --git a/images/Architecture/网关-权限控制,服务治理.png b/images/Architecture/网关-权限控制,服务治理.png new file mode 100644 index 0000000..b6caf2d Binary files /dev/null and b/images/Architecture/网关-权限控制,服务治理.png differ diff --git a/images/Architecture/聚合服务.jpg b/images/Architecture/聚合服务.jpg new file mode 100644 index 0000000..555dac3 Binary files /dev/null and b/images/Architecture/聚合服务.jpg differ diff --git a/images/Architecture/订单上下游关系.jpg b/images/Architecture/订单上下游关系.jpg new file mode 100644 index 0000000..2280255 Binary files /dev/null and b/images/Architecture/订单上下游关系.jpg differ diff --git a/images/Architecture/订单业务架构.jpg b/images/Architecture/订单业务架构.jpg new file mode 100644 index 0000000..28447b8 Binary files /dev/null and b/images/Architecture/订单业务架构.jpg differ diff --git a/images/Architecture/订单服务-异步处理.png b/images/Architecture/订单服务-异步处理.png new file mode 100644 index 0000000..c83422a Binary files /dev/null and b/images/Architecture/订单服务-异步处理.png differ diff --git a/images/Architecture/订单服务-服务解耦.jpg b/images/Architecture/订单服务-服务解耦.jpg new file mode 100644 index 0000000..95e294f Binary files /dev/null and b/images/Architecture/订单服务-服务解耦.jpg differ diff --git a/images/Architecture/订单服务-流量控制.jpg b/images/Architecture/订单服务-流量控制.jpg new file mode 100644 index 0000000..fcc08b0 Binary files /dev/null and b/images/Architecture/订单服务-流量控制.jpg differ diff --git a/images/Architecture/订单流程.png b/images/Architecture/订单流程.png new file mode 100644 index 0000000..25b477b Binary files /dev/null and b/images/Architecture/订单流程.png differ diff --git a/images/Architecture/订单系统.jpg b/images/Architecture/订单系统.jpg new file mode 100644 index 0000000..262592f Binary files /dev/null and b/images/Architecture/订单系统.jpg differ diff --git a/images/Architecture/订单详细信息.png b/images/Architecture/订单详细信息.png new file mode 100644 index 0000000..a4c7d94 Binary files /dev/null and b/images/Architecture/订单详细信息.png differ diff --git a/images/Architecture/订单逆向流程.jpg b/images/Architecture/订单逆向流程.jpg new file mode 100644 index 0000000..2c97976 Binary files /dev/null and b/images/Architecture/订单逆向流程.jpg differ diff --git a/images/Architecture/调用行为记录.jpg b/images/Architecture/调用行为记录.jpg new file mode 100644 index 0000000..47cc4f7 Binary files /dev/null and b/images/Architecture/调用行为记录.jpg differ diff --git a/images/Architecture/调用行为还原.jpg b/images/Architecture/调用行为还原.jpg new file mode 100644 index 0000000..9a4aef1 Binary files /dev/null and b/images/Architecture/调用行为还原.jpg differ diff --git a/images/Architecture/账号注册流程.png b/images/Architecture/账号注册流程.png new file mode 100644 index 0000000..5e97b41 Binary files /dev/null and b/images/Architecture/账号注册流程.png differ diff --git a/images/Architecture/账号登录流程.png b/images/Architecture/账号登录流程.png new file mode 100644 index 0000000..6cf3f71 Binary files /dev/null and b/images/Architecture/账号登录流程.png differ diff --git a/images/Architecture/软件架构模式.png b/images/Architecture/软件架构模式.png new file mode 100644 index 0000000..90777bd Binary files /dev/null and b/images/Architecture/软件架构模式.png differ diff --git a/images/Architecture/进程内Lb模式.jpg b/images/Architecture/进程内Lb模式.jpg new file mode 100644 index 0000000..6f04d08 Binary files /dev/null and b/images/Architecture/进程内Lb模式.jpg differ diff --git a/images/Architecture/配置中心.jpg b/images/Architecture/配置中心.jpg new file mode 100644 index 0000000..16930d4 Binary files /dev/null and b/images/Architecture/配置中心.jpg differ diff --git a/images/Architecture/重复支付-下单流程.png b/images/Architecture/重复支付-下单流程.png new file mode 100644 index 0000000..d895dd5 Binary files /dev/null and b/images/Architecture/重复支付-下单流程.png differ diff --git a/images/Architecture/限流.png b/images/Architecture/限流.png new file mode 100644 index 0000000..fe8ed50 Binary files /dev/null and b/images/Architecture/限流.png differ diff --git a/images/Architecture/随机值.png b/images/Architecture/随机值.png new file mode 100644 index 0000000..27a0648 Binary files /dev/null and b/images/Architecture/随机值.png differ diff --git a/images/Architecture/隐藏方式.png b/images/Architecture/隐藏方式.png new file mode 100644 index 0000000..5370719 Binary files /dev/null and b/images/Architecture/隐藏方式.png differ diff --git a/images/BigData/Apache-Flink.png b/images/BigData/Apache-Flink.png new file mode 100644 index 0000000..b502d15 Binary files /dev/null and b/images/BigData/Apache-Flink.png differ diff --git a/images/BigData/Apache-Spark.png b/images/BigData/Apache-Spark.png new file mode 100644 index 0000000..2006684 Binary files /dev/null and b/images/BigData/Apache-Spark.png differ diff --git a/images/BigData/Apache-Storm.png b/images/BigData/Apache-Storm.png new file mode 100644 index 0000000..3edb0d6 Binary files /dev/null and b/images/BigData/Apache-Storm.png differ diff --git a/images/BigData/Data-Analytics-Applications.png b/images/BigData/Data-Analytics-Applications.png new file mode 100644 index 0000000..50882c2 Binary files /dev/null and b/images/BigData/Data-Analytics-Applications.png differ diff --git a/images/BigData/Data-Pipeline-Applications.png b/images/BigData/Data-Pipeline-Applications.png new file mode 100644 index 0000000..252df33 Binary files /dev/null and b/images/BigData/Data-Pipeline-Applications.png differ diff --git a/images/BigData/Event-driven-Applications.png b/images/BigData/Event-driven-Applications.png new file mode 100644 index 0000000..8b1d43a Binary files /dev/null and b/images/BigData/Event-driven-Applications.png differ diff --git a/images/BigData/Flink-分层API.png b/images/BigData/Flink-分层API.png new file mode 100644 index 0000000..4de2c94 Binary files /dev/null and b/images/BigData/Flink-分层API.png differ diff --git a/images/BigData/Flume-telnet.jpg b/images/BigData/Flume-telnet.jpg new file mode 100644 index 0000000..ff60c57 Binary files /dev/null and b/images/BigData/Flume-telnet.jpg differ diff --git a/images/BigData/Flume复杂应用.png b/images/BigData/Flume复杂应用.png new file mode 100644 index 0000000..85bdd95 Binary files /dev/null and b/images/BigData/Flume复杂应用.png differ diff --git a/images/BigData/HBase.jpg b/images/BigData/HBase.jpg new file mode 100644 index 0000000..a6dc493 Binary files /dev/null and b/images/BigData/HBase.jpg differ diff --git a/images/BigData/HBase基本架构.jpg b/images/BigData/HBase基本架构.jpg new file mode 100644 index 0000000..9f74d78 Binary files /dev/null and b/images/BigData/HBase基本架构.jpg differ diff --git a/images/BigData/HDFSDataNode工作机制.png b/images/BigData/HDFSDataNode工作机制.png new file mode 100644 index 0000000..bb05302 Binary files /dev/null and b/images/BigData/HDFSDataNode工作机制.png differ diff --git a/images/BigData/HDFS剖析文件写入.png b/images/BigData/HDFS剖析文件写入.png new file mode 100644 index 0000000..c17682e Binary files /dev/null and b/images/BigData/HDFS剖析文件写入.png differ diff --git a/images/BigData/HDFS小文件存档.png b/images/BigData/HDFS小文件存档.png new file mode 100644 index 0000000..539827e Binary files /dev/null and b/images/BigData/HDFS小文件存档.png differ diff --git a/images/BigData/HDFS掉线时限参数设置.png b/images/BigData/HDFS掉线时限参数设置.png new file mode 100644 index 0000000..c7ff2b3 Binary files /dev/null and b/images/BigData/HDFS掉线时限参数设置.png differ diff --git a/images/BigData/HDFS数据完整性.png b/images/BigData/HDFS数据完整性.png new file mode 100644 index 0000000..da5005d Binary files /dev/null and b/images/BigData/HDFS数据完整性.png differ diff --git a/images/BigData/HDFS机架感知.png b/images/BigData/HDFS机架感知.png new file mode 100644 index 0000000..e255002 Binary files /dev/null and b/images/BigData/HDFS机架感知.png differ diff --git a/images/BigData/HDFS组成架构1.png b/images/BigData/HDFS组成架构1.png new file mode 100644 index 0000000..61cc2d9 Binary files /dev/null and b/images/BigData/HDFS组成架构1.png differ diff --git a/images/BigData/HDFS组成架构2.png b/images/BigData/HDFS组成架构2.png new file mode 100644 index 0000000..105ff3b Binary files /dev/null and b/images/BigData/HDFS组成架构2.png differ diff --git a/images/BigData/HDFS网络拓扑-节点距离计算.png b/images/BigData/HDFS网络拓扑-节点距离计算.png new file mode 100644 index 0000000..89ae3e1 Binary files /dev/null and b/images/BigData/HDFS网络拓扑-节点距离计算.png differ diff --git a/images/BigData/HDFS读数据流程.png b/images/BigData/HDFS读数据流程.png new file mode 100644 index 0000000..1e44740 Binary files /dev/null and b/images/BigData/HDFS读数据流程.png differ diff --git a/images/BigData/Hadoop生态架构图.png b/images/BigData/Hadoop生态架构图.png new file mode 100644 index 0000000..b5b66f5 Binary files /dev/null and b/images/BigData/Hadoop生态架构图.png differ diff --git a/images/BigData/Kafka-PageCache.png b/images/BigData/Kafka-PageCache.png new file mode 100644 index 0000000..7de0534 Binary files /dev/null and b/images/BigData/Kafka-PageCache.png differ diff --git a/images/BigData/Kafka发布-订阅消息传递模式.png b/images/BigData/Kafka发布-订阅消息传递模式.png new file mode 100644 index 0000000..21108de Binary files /dev/null and b/images/BigData/Kafka发布-订阅消息传递模式.png differ diff --git a/images/BigData/Kafka服务治理-ISR-1.png b/images/BigData/Kafka服务治理-ISR-1.png new file mode 100644 index 0000000..bd3a194 Binary files /dev/null and b/images/BigData/Kafka服务治理-ISR-1.png differ diff --git a/images/BigData/Kafka服务治理-ISR-2.png b/images/BigData/Kafka服务治理-ISR-2.png new file mode 100644 index 0000000..3d8c3e9 Binary files /dev/null and b/images/BigData/Kafka服务治理-ISR-2.png differ diff --git a/images/BigData/Kafka服务治理-ISR-3.png b/images/BigData/Kafka服务治理-ISR-3.png new file mode 100644 index 0000000..7ecaa39 Binary files /dev/null and b/images/BigData/Kafka服务治理-ISR-3.png differ diff --git a/images/BigData/Kafka服务治理-ISR-4.png b/images/BigData/Kafka服务治理-ISR-4.png new file mode 100644 index 0000000..bc27079 Binary files /dev/null and b/images/BigData/Kafka服务治理-ISR-4.png differ diff --git a/images/BigData/Kafka服务治理-故障恢复.png b/images/BigData/Kafka服务治理-故障恢复.png new file mode 100644 index 0000000..a2b9354 Binary files /dev/null and b/images/BigData/Kafka服务治理-故障恢复.png differ diff --git a/images/BigData/Kafka架构.png b/images/BigData/Kafka架构.png new file mode 100644 index 0000000..8300f90 Binary files /dev/null and b/images/BigData/Kafka架构.png differ diff --git a/images/BigData/Kafka点对点消息传递模式.png b/images/BigData/Kafka点对点消息传递模式.png new file mode 100644 index 0000000..e6d1857 Binary files /dev/null and b/images/BigData/Kafka点对点消息传递模式.png differ diff --git a/images/BigData/Kafka的网络设计.jpg b/images/BigData/Kafka的网络设计.jpg new file mode 100644 index 0000000..1d1de84 Binary files /dev/null and b/images/BigData/Kafka的网络设计.jpg differ diff --git a/images/BigData/consumer_group示例.png b/images/BigData/consumer_group示例.png new file mode 100644 index 0000000..692c457 Binary files /dev/null and b/images/BigData/consumer_group示例.png differ diff --git a/images/BigData/consumer_group示例2.png b/images/BigData/consumer_group示例2.png new file mode 100644 index 0000000..fba9684 Binary files /dev/null and b/images/BigData/consumer_group示例2.png differ diff --git a/images/BigData/kafka-consume.jpg b/images/BigData/kafka-consume.jpg new file mode 100644 index 0000000..e8edfb3 Binary files /dev/null and b/images/BigData/kafka-consume.jpg differ diff --git a/images/BigData/kafka-offset.png b/images/BigData/kafka-offset.png new file mode 100644 index 0000000..1d48fd5 Binary files /dev/null and b/images/BigData/kafka-offset.png differ diff --git a/images/BigData/kafka-partition.jpg b/images/BigData/kafka-partition.jpg new file mode 100644 index 0000000..56983f2 Binary files /dev/null and b/images/BigData/kafka-partition.jpg differ diff --git a/images/BigData/kafka-partition.png b/images/BigData/kafka-partition.png new file mode 100644 index 0000000..411682d Binary files /dev/null and b/images/BigData/kafka-partition.png differ diff --git a/images/BigData/kafka-producer.jpg b/images/BigData/kafka-producer.jpg new file mode 100644 index 0000000..0a9fe79 Binary files /dev/null and b/images/BigData/kafka-producer.jpg differ diff --git a/images/BigData/kafka-topic.jpg b/images/BigData/kafka-topic.jpg new file mode 100644 index 0000000..918de2f Binary files /dev/null and b/images/BigData/kafka-topic.jpg differ diff --git a/images/BigData/kafka-zookeeper.png b/images/BigData/kafka-zookeeper.png new file mode 100644 index 0000000..6f5c093 Binary files /dev/null and b/images/BigData/kafka-zookeeper.png differ diff --git a/images/BigData/kafka-传统数据请求.png b/images/BigData/kafka-传统数据请求.png new file mode 100644 index 0000000..d46acbe Binary files /dev/null and b/images/BigData/kafka-传统数据请求.png differ diff --git a/images/BigData/kafka-零拷贝.jpg b/images/BigData/kafka-零拷贝.jpg new file mode 100644 index 0000000..a6f7b01 Binary files /dev/null and b/images/BigData/kafka-零拷贝.jpg differ diff --git a/images/BigData/kafka-零拷贝优化.jpg b/images/BigData/kafka-零拷贝优化.jpg new file mode 100644 index 0000000..c94d4fd Binary files /dev/null and b/images/BigData/kafka-零拷贝优化.jpg differ diff --git a/images/BigData/kafka-零拷贝的方式.png b/images/BigData/kafka-零拷贝的方式.png new file mode 100644 index 0000000..1319e8c Binary files /dev/null and b/images/BigData/kafka-零拷贝的方式.png differ diff --git a/images/BigData/kafka分区副本.png b/images/BigData/kafka分区副本.png new file mode 100644 index 0000000..d071b6a Binary files /dev/null and b/images/BigData/kafka分区副本.png differ diff --git a/images/BigData/message物理结构.png b/images/BigData/message物理结构.png new file mode 100644 index 0000000..f3db3a2 Binary files /dev/null and b/images/BigData/message物理结构.png differ diff --git a/images/BigData/前台推荐页面.jpg b/images/BigData/前台推荐页面.jpg new file mode 100644 index 0000000..0bbfe99 Binary files /dev/null and b/images/BigData/前台推荐页面.jpg differ diff --git a/images/BigData/启动Kafka日志.png b/images/BigData/启动Kafka日志.png new file mode 100644 index 0000000..6e12834 Binary files /dev/null and b/images/BigData/启动Kafka日志.png differ diff --git a/images/BigData/基于Flink商品实时推荐.jpg b/images/BigData/基于Flink商品实时推荐.jpg new file mode 100644 index 0000000..20b5cb0 Binary files /dev/null and b/images/BigData/基于Flink商品实时推荐.jpg differ diff --git a/images/BigData/基于产品画像的产品相似度计算方法.jpg b/images/BigData/基于产品画像的产品相似度计算方法.jpg new file mode 100644 index 0000000..28984b0 Binary files /dev/null and b/images/BigData/基于产品画像的产品相似度计算方法.jpg differ diff --git a/images/BigData/基于协同过滤的产品相似度计算方法.jpg b/images/BigData/基于协同过滤的产品相似度计算方法.jpg new file mode 100644 index 0000000..1f15c3a Binary files /dev/null and b/images/BigData/基于协同过滤的产品相似度计算方法.jpg differ diff --git a/images/BigData/基于热度的推荐逻辑.jpg b/images/BigData/基于热度的推荐逻辑.jpg new file mode 100644 index 0000000..6a54aaa Binary files /dev/null and b/images/BigData/基于热度的推荐逻辑.jpg differ diff --git a/images/BigData/处理无界和有界数据.png b/images/BigData/处理无界和有界数据.png new file mode 100644 index 0000000..29dfe8a Binary files /dev/null and b/images/BigData/处理无界和有界数据.png differ diff --git a/images/BigData/安装Flink1.png b/images/BigData/安装Flink1.png new file mode 100644 index 0000000..621624a Binary files /dev/null and b/images/BigData/安装Flink1.png differ diff --git a/images/BigData/安装Flink2.png b/images/BigData/安装Flink2.png new file mode 100644 index 0000000..c2ca0c7 Binary files /dev/null and b/images/BigData/安装Flink2.png differ diff --git a/images/BigData/安装Flink3.png b/images/BigData/安装Flink3.png new file mode 100644 index 0000000..914f4ab Binary files /dev/null and b/images/BigData/安装Flink3.png differ diff --git a/images/BigData/安装Flink4.png b/images/BigData/安装Flink4.png new file mode 100644 index 0000000..ec9822a Binary files /dev/null and b/images/BigData/安装Flink4.png differ diff --git a/images/BigData/安装Flink5.png b/images/BigData/安装Flink5.png new file mode 100644 index 0000000..f1ff523 Binary files /dev/null and b/images/BigData/安装Flink5.png differ diff --git a/images/BigData/实时计算TopN热榜.png b/images/BigData/实时计算TopN热榜.png new file mode 100644 index 0000000..ee393d7 Binary files /dev/null and b/images/BigData/实时计算TopN热榜.png differ diff --git a/images/Database/CROSS-JOIN.png b/images/Database/CROSS-JOIN.png new file mode 100644 index 0000000..be31bcf Binary files /dev/null and b/images/Database/CROSS-JOIN.png differ diff --git a/images/Database/DRBD磁盘复制.jpg b/images/Database/DRBD磁盘复制.jpg new file mode 100644 index 0000000..c27cc0d Binary files /dev/null and b/images/Database/DRBD磁盘复制.jpg differ diff --git a/images/Database/FULL-OUTER-JOIN-EXCLUDING-INNER-JOIN.png b/images/Database/FULL-OUTER-JOIN-EXCLUDING-INNER-JOIN.png new file mode 100644 index 0000000..2f21d38 Binary files /dev/null and b/images/Database/FULL-OUTER-JOIN-EXCLUDING-INNER-JOIN.png differ diff --git a/images/Database/InnoDB的锁机制兼容情况.jpg b/images/Database/InnoDB的锁机制兼容情况.jpg new file mode 100644 index 0000000..a844301 Binary files /dev/null and b/images/Database/InnoDB的锁机制兼容情况.jpg differ diff --git a/images/Database/LEFT-JOIN-EXCLUDING-INNER-JOIN.png b/images/Database/LEFT-JOIN-EXCLUDING-INNER-JOIN.png new file mode 100644 index 0000000..c1560cb Binary files /dev/null and b/images/Database/LEFT-JOIN-EXCLUDING-INNER-JOIN.png differ diff --git a/images/Database/MHA+多节点集群.jpg b/images/Database/MHA+多节点集群.jpg new file mode 100644 index 0000000..e4417e5 Binary files /dev/null and b/images/Database/MHA+多节点集群.jpg differ diff --git a/images/Database/MHA-Manager.jpg b/images/Database/MHA-Manager.jpg new file mode 100644 index 0000000..b0f9238 Binary files /dev/null and b/images/Database/MHA-Manager.jpg differ diff --git a/images/Database/MySQL-Cluster.jpg b/images/Database/MySQL-Cluster.jpg new file mode 100644 index 0000000..994f7cb Binary files /dev/null and b/images/Database/MySQL-Cluster.jpg differ diff --git a/images/Database/MySQL-Galera.jpg b/images/Database/MySQL-Galera.jpg new file mode 100644 index 0000000..8d816fa Binary files /dev/null and b/images/Database/MySQL-Galera.jpg differ diff --git a/images/Database/MySQL-Paxos.jpg b/images/Database/MySQL-Paxos.jpg new file mode 100644 index 0000000..09530fc Binary files /dev/null and b/images/Database/MySQL-Paxos.jpg differ diff --git a/images/Database/MySQL主从集群.jpg b/images/Database/MySQL主从集群.jpg new file mode 100644 index 0000000..e059d12 Binary files /dev/null and b/images/Database/MySQL主从集群.jpg differ diff --git a/images/Database/MySQL架构设计.jpg b/images/Database/MySQL架构设计.jpg new file mode 100644 index 0000000..01a70fe Binary files /dev/null and b/images/Database/MySQL架构设计.jpg differ diff --git a/images/Database/MySQL查询过程.png b/images/Database/MySQL查询过程.png new file mode 100644 index 0000000..ff21c49 Binary files /dev/null and b/images/Database/MySQL查询过程.png differ diff --git a/images/Database/RIGHT-JOIN-EXCLUDING-INNER-JOIN.png b/images/Database/RIGHT-JOIN-EXCLUDING-INNER-JOIN.png new file mode 100644 index 0000000..2ff8bfa Binary files /dev/null and b/images/Database/RIGHT-JOIN-EXCLUDING-INNER-JOIN.png differ diff --git a/images/Database/SAN共享储存.jpg b/images/Database/SAN共享储存.jpg new file mode 100644 index 0000000..5c06a45 Binary files /dev/null and b/images/Database/SAN共享储存.jpg differ diff --git a/images/Database/SQL常用JOIN.png b/images/Database/SQL常用JOIN.png new file mode 100644 index 0000000..a1ac639 Binary files /dev/null and b/images/Database/SQL常用JOIN.png differ diff --git a/images/Database/SQL所有JOIN.png b/images/Database/SQL所有JOIN.png new file mode 100644 index 0000000..b4b5a0e Binary files /dev/null and b/images/Database/SQL所有JOIN.png differ diff --git a/images/Database/ZooKeeper+Proxy.jpg b/images/Database/ZooKeeper+Proxy.jpg new file mode 100644 index 0000000..2289204 Binary files /dev/null and b/images/Database/ZooKeeper+Proxy.jpg differ diff --git a/images/Database/binlog写入流程.jpg b/images/Database/binlog写入流程.jpg new file mode 100644 index 0000000..0acffb0 Binary files /dev/null and b/images/Database/binlog写入流程.jpg differ diff --git a/images/Database/binlog和redolog区别.jpg b/images/Database/binlog和redolog区别.jpg new file mode 100644 index 0000000..775bccf Binary files /dev/null and b/images/Database/binlog和redolog区别.jpg differ diff --git a/images/Database/binlog文件服务器.jpg b/images/Database/binlog文件服务器.jpg new file mode 100644 index 0000000..984b87f Binary files /dev/null and b/images/Database/binlog文件服务器.jpg differ diff --git a/images/Database/coordinator线程.jpg b/images/Database/coordinator线程.jpg new file mode 100644 index 0000000..dd927be Binary files /dev/null and b/images/Database/coordinator线程.jpg differ diff --git a/images/Database/redolog位置指针.jpg b/images/Database/redolog位置指针.jpg new file mode 100644 index 0000000..3ecc755 Binary files /dev/null and b/images/Database/redolog位置指针.jpg differ diff --git a/images/Database/redolog写入流程.jpg b/images/Database/redolog写入流程.jpg new file mode 100644 index 0000000..3501c15 Binary files /dev/null and b/images/Database/redolog写入流程.jpg differ diff --git a/images/Database/update的binlog执行流程.jpg b/images/Database/update的binlog执行流程.jpg new file mode 100644 index 0000000..5de7eb6 Binary files /dev/null and b/images/Database/update的binlog执行流程.jpg differ diff --git a/images/Database/主从复制原理.jpg b/images/Database/主从复制原理.jpg new file mode 100644 index 0000000..d1a3770 Binary files /dev/null and b/images/Database/主从复制原理.jpg differ diff --git a/images/Database/主从或主主半同步复制.jpg b/images/Database/主从或主主半同步复制.jpg new file mode 100644 index 0000000..ee71630 Binary files /dev/null and b/images/Database/主从或主主半同步复制.jpg differ diff --git a/images/Database/全连接(FULL-OUTER-JOIN).png b/images/Database/全连接(FULL-OUTER-JOIN).png new file mode 100644 index 0000000..ceeed90 Binary files /dev/null and b/images/Database/全连接(FULL-OUTER-JOIN).png differ diff --git a/images/Database/内连接(INNER-JOIN).png b/images/Database/内连接(INNER-JOIN).png new file mode 100644 index 0000000..47d4423 Binary files /dev/null and b/images/Database/内连接(INNER-JOIN).png differ diff --git a/images/Database/双通道复制.jpg b/images/Database/双通道复制.jpg new file mode 100644 index 0000000..16562a1 Binary files /dev/null and b/images/Database/双通道复制.jpg differ diff --git a/images/Database/右连接(RIGHT-JOIN).png b/images/Database/右连接(RIGHT-JOIN).png new file mode 100644 index 0000000..d11170d Binary files /dev/null and b/images/Database/右连接(RIGHT-JOIN).png differ diff --git a/images/Database/左连接(LEFT-JOIN).png b/images/Database/左连接(LEFT-JOIN).png new file mode 100644 index 0000000..7b14683 Binary files /dev/null and b/images/Database/左连接(LEFT-JOIN).png differ diff --git a/images/Database/并行复制.jpg b/images/Database/并行复制.jpg new file mode 100644 index 0000000..c6394e6 Binary files /dev/null and b/images/Database/并行复制.jpg differ diff --git a/images/Database/按库并行.jpg b/images/Database/按库并行.jpg new file mode 100644 index 0000000..0dc5dcd Binary files /dev/null and b/images/Database/按库并行.jpg differ diff --git a/images/Database/索引结构-B+Tree.png b/images/Database/索引结构-B+Tree.png new file mode 100644 index 0000000..b6a5d98 Binary files /dev/null and b/images/Database/索引结构-B+Tree.png differ diff --git a/images/Database/索引结构-B+Tree指针.png b/images/Database/索引结构-B+Tree指针.png new file mode 100644 index 0000000..c6d59cf Binary files /dev/null and b/images/Database/索引结构-B+Tree指针.png differ diff --git a/images/Database/索引结构-B+Tree案例.png b/images/Database/索引结构-B+Tree案例.png new file mode 100644 index 0000000..dae81a7 Binary files /dev/null and b/images/Database/索引结构-B+Tree案例.png differ diff --git a/images/Database/索引结构-B-Tree.png b/images/Database/索引结构-B-Tree.png new file mode 100644 index 0000000..1bc2581 Binary files /dev/null and b/images/Database/索引结构-B-Tree.png differ diff --git a/images/Database/索引结构-B-Tree指针.png b/images/Database/索引结构-B-Tree指针.png new file mode 100644 index 0000000..025dbed Binary files /dev/null and b/images/Database/索引结构-B-Tree指针.png differ diff --git a/images/Database/索引结构-B-Tree问题.png b/images/Database/索引结构-B-Tree问题.png new file mode 100644 index 0000000..e7e8adf Binary files /dev/null and b/images/Database/索引结构-B-Tree问题.png differ diff --git a/images/Database/索引结构-二叉树.png b/images/Database/索引结构-二叉树.png new file mode 100644 index 0000000..1ea66e7 Binary files /dev/null and b/images/Database/索引结构-二叉树.png differ diff --git a/images/Database/索引结构-红黑树.png b/images/Database/索引结构-红黑树.png new file mode 100644 index 0000000..e4135ac Binary files /dev/null and b/images/Database/索引结构-红黑树.png differ diff --git a/images/DevOps/Git仿集中式.png b/images/DevOps/Git仿集中式.png new file mode 100644 index 0000000..d755f4a Binary files /dev/null and b/images/DevOps/Git仿集中式.png differ diff --git a/images/DevOps/Java火焰图.gif b/images/DevOps/Java火焰图.gif deleted file mode 100644 index 6e1e7e4..0000000 Binary files a/images/DevOps/Java火焰图.gif and /dev/null differ diff --git a/images/DevOps/SVN集中式.png b/images/DevOps/SVN集中式.png new file mode 100644 index 0000000..55db7e4 Binary files /dev/null and b/images/DevOps/SVN集中式.png differ diff --git a/images/DevOps/atop-mem.png b/images/DevOps/atop-mem.png new file mode 100644 index 0000000..c5004ce Binary files /dev/null and b/images/DevOps/atop-mem.png differ diff --git a/images/DevOps/feature分支.png b/images/DevOps/feature分支.png new file mode 100644 index 0000000..263b95e Binary files /dev/null and b/images/DevOps/feature分支.png differ diff --git a/images/DevOps/hotfix分支.png b/images/DevOps/hotfix分支.png new file mode 100644 index 0000000..7f3bdcc Binary files /dev/null and b/images/DevOps/hotfix分支.png differ diff --git a/images/DevOps/master与develop分支.png b/images/DevOps/master与develop分支.png new file mode 100644 index 0000000..2057388 Binary files /dev/null and b/images/DevOps/master与develop分支.png differ diff --git a/images/DevOps/raspberry_pi_2_Ubunut_nmon_busy.png b/images/DevOps/raspberry_pi_2_Ubunut_nmon_busy.png new file mode 100644 index 0000000..e173d05 Binary files /dev/null and b/images/DevOps/raspberry_pi_2_Ubunut_nmon_busy.png differ diff --git a/images/DevOps/release分支.png b/images/DevOps/release分支.png new file mode 100644 index 0000000..26a0aad Binary files /dev/null and b/images/DevOps/release分支.png differ diff --git a/images/DevOps/火焰图-瓶颈点1.jpg b/images/DevOps/火焰图-瓶颈点1.jpg new file mode 100644 index 0000000..d6cac39 Binary files /dev/null and b/images/DevOps/火焰图-瓶颈点1.jpg differ diff --git a/images/DevOps/火焰图-瓶颈点2-代码.jpg b/images/DevOps/火焰图-瓶颈点2-代码.jpg new file mode 100644 index 0000000..cbc4a58 Binary files /dev/null and b/images/DevOps/火焰图-瓶颈点2-代码.jpg differ diff --git a/images/DevOps/火焰图-瓶颈点2.jpg b/images/DevOps/火焰图-瓶颈点2.jpg new file mode 100644 index 0000000..b7a0dcd Binary files /dev/null and b/images/DevOps/火焰图-瓶颈点2.jpg differ diff --git a/images/DevOps/火焰图-瓶颈点3-代码.jpg b/images/DevOps/火焰图-瓶颈点3-代码.jpg new file mode 100644 index 0000000..34fe993 Binary files /dev/null and b/images/DevOps/火焰图-瓶颈点3-代码.jpg differ diff --git a/images/DevOps/火焰图-瓶颈点3.jpg b/images/DevOps/火焰图-瓶颈点3.jpg new file mode 100644 index 0000000..17346d3 Binary files /dev/null and b/images/DevOps/火焰图-瓶颈点3.jpg differ diff --git a/images/DevOps/火焰图案例.jpg b/images/DevOps/火焰图案例.jpg new file mode 100644 index 0000000..2379d2e Binary files /dev/null and b/images/DevOps/火焰图案例.jpg differ diff --git a/images/DevOps/火焰图案例.svg b/images/DevOps/火焰图案例.svg new file mode 100644 index 0000000..3459a2d --- /dev/null +++ b/images/DevOps/火焰图案例.svg @@ -0,0 +1,18917 @@ + + + + + + + + + + + + + + +Flame Graph + +Reset Zoom +Search + + + +java/lang/String:::toUpperCase (1 samples, 0.02%) + + + +java/lang/ThreadLocal:::setInitialValue (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +java/lang/ThreadLocal$ThreadLocalMap:::remove (1 samples, 0.02%) + + + +smp_apic_timer_interrupt (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/ArrayOf:::write (2 samples, 0.04%) + + + +net_rx_action (1 samples, 0.02%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +java/util/GregorianCalendar:::computeTime (2 samples, 0.04%) + + + +org/apache/kafka/common/metrics/stats/Meter:::record (3 samples, 0.06%) + + + +PhaseIdealLoop::build_and_optimize (12 samples, 0.23%) + + + +java/io/RandomAccessFile:::writeBytes (41 samples, 0.80%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +java/net/URI$Parser:::parseAuthority (1 samples, 0.02%) + + + +java/util/LinkedHashMap:::get (1 samples, 0.02%) + + + +__block_write_begin (1 samples, 0.02%) + + + +os::javaTimeMillis (1 samples, 0.02%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +java/util/regex/Pattern$BranchConn:::match (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +smp_apic_timer_interrupt (1 samples, 0.02%) + + + +Node::clone (1 samples, 0.02%) + + + +Unsafe_Park (25 samples, 0.49%) + + + +pipe_read (1 samples, 0.02%) + + + +sk_reset_timer (1 samples, 0.02%) + + + +__lll_unlock_wake (2 samples, 0.04%) + + + +file_remove_privs (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +generic_exec_single (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/condition/ProducesRequestCondition:::getMatchingCondition (16 samples, 0.31%) + + + +sun/security/provider/DigestBase:::engineDigest (6 samples, 0.12%) + + + +org/springframework/web/util/UrlPathHelper:::getRemainingPath (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +__audit_syscall_exit (2 samples, 0.04%) + + + +java/util/LinkedHashMap:::forEach (1 samples, 0.02%) + + + +com/coohua/caf/core/sentinel/SentinelHttpInterceptor:::preHandle (28 samples, 0.54%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/ser/std/BooleanSerializer:::serialize (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy49:::annotationType (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +__vdso_clock_gettime (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11Processor:::prepareResponse (14 samples, 0.27%) + + + +org/apache/catalina/connector/Request:::doGetSession (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +CodeBlob::is_zombie (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/Type$8:::write (1 samples, 0.02%) + + + +java/util/Calendar$Builder:::build (1 samples, 0.02%) + + + +CollectedHeap::common_mem_allocate_init (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +org/apache/coyote/AbstractProcessor:::action (6 samples, 0.12%) + + + +java/text/DigitList:::getLong (1 samples, 0.02%) + + + +com/google/gson/internal/$Gson$Types:::canonicalize (2 samples, 0.04%) + + + +generic_write_end (5 samples, 0.10%) + + + +process_backlog (1 samples, 0.02%) + + + +org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver:::resolveArgument (2 samples, 0.04%) + + + +java/util/ArrayList:::toArray (1 samples, 0.02%) + + + +try_to_wake_up (3 samples, 0.06%) + + + +updateBytesCRC32 (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/CtSph:::entryWithPriority (17 samples, 0.33%) + + + +com/sun/crypto/provider/CipherCore:::init (19 samples, 0.37%) + + + +org/springframework/boot/actuate/web/trace/servlet/TraceableHttpServletRequest:::extractHeaders (5 samples, 0.10%) + + + +com/google/gson/internal/bind/ArrayTypeAdapter:::write (1 samples, 0.02%) + + + +itable stub (3 samples, 0.06%) + + + +ip_rcv (1 samples, 0.02%) + + + +com/coohuadata/analytics/javasdk/util/KafkaProducer:::send (59 samples, 1.15%) + + + +java/lang/StringBuffer:::append (1 samples, 0.02%) + + + +schedule_hrtimeout_range (14 samples, 0.27%) + + + +jni_GetObjectField (5 samples, 0.10%) + + + +futex_wake_op (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +org/springframework/beans/TypeConverterSupport:::doConvert (2 samples, 0.04%) + + + +org/apache/catalina/connector/Request:::getAttribute (3 samples, 0.06%) + + + +java/util/LinkedHashMap:::removeEldestEntry (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +sun/nio/ch/FileDispatcherImpl:::write0 (48 samples, 0.93%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +ParseGenerator::generate (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +CollectedHeap::allocate_from_tlab_slow (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils:::isInJavaLangAnnotationPackage (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +schedule (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::setAttribute (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +java/util/HashMap:::put (2 samples, 0.04%) + + + +io/micrometer/shaded/org/pcollections/IntTree:::get (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +org/apache/kafka/common/requests/RequestHeader:::toStruct (2 samples, 0.04%) + + + +io/micrometer/shaded/org/pcollections/IntTree:::get (1 samples, 0.02%) + + + +java/util/concurrent/ConcurrentHashMap:::get (2 samples, 0.04%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +tcp_event_new_data_sent (2 samples, 0.04%) + + + +org/apache/catalina/connector/RequestFacade:::getRequestURL (1 samples, 0.02%) + + + +JVM_GetArrayElement (2 samples, 0.04%) + + + +CodeHeap::find_start (1 samples, 0.02%) + + + +grab_cache_page_write_begin (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy5:::annotationType (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils$AnnotationCollector:::process (1 samples, 0.02%) + + + +java/util/regex/Pattern$Curly:::match (1 samples, 0.02%) + + + +org/apache/logging/log4j/spi/AbstractLogger:::logMessage (7 samples, 0.14%) + + + +sun/nio/cs/UTF_8$Decoder:::decodeArrayLoop (6 samples, 0.12%) + + + +schedule (10 samples, 0.19%) + + + +SYSC_sendto (6 samples, 0.12%) + + + +ret_from_intr (1 samples, 0.02%) + + + +sys_write (31 samples, 0.60%) + + + +org/apache/logging/slf4j/Log4jLogger:::isTraceEnabled (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +start_xmit (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11OutputBuffer:::write (9 samples, 0.18%) + + + +kmem_cache_free (1 samples, 0.02%) + + + +sock_poll (1 samples, 0.02%) + + + +java/util/regex/Pattern$Begin:::match (3 samples, 0.06%) + + + +frame::sender_for_compiled_frame (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/apache/logging/log4j/spi/AbstractLogger:::logMessage (60 samples, 1.17%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::nextToken (8 samples, 0.16%) + + + +java/lang/String:::hashCode (1 samples, 0.02%) + + + +tcp_poll (1 samples, 0.02%) + + + +JVM_GetStackTraceElement (4 samples, 0.08%) + + + +java/io/FilterOutputStream:::close (1 samples, 0.02%) + + + +org/springframework/util/ReflectionUtils:::makeAccessible (2 samples, 0.04%) + + + +com/coohua/caf/core/util/HttpUtil:::getPatternUrl (5 samples, 0.10%) + + + +org/apache/kafka/common/protocol/types/Type$3:::validate (1 samples, 0.02%) + + + +_new_array_Java (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +java/util/regex/Pattern$BmpCharProperty:::match (1 samples, 0.02%) + + + +deflate_slow (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +Parse::Parse (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/ObjectMapper:::writeValueAsString (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (203 samples, 3.95%) +org/.. + + +do_softirq (1 samples, 0.02%) + + + +do_sync_readv_writev (16 samples, 0.31%) + + + +com/alibaba/csp/sentinel/slots/logger/LogSlot:::exit (15 samples, 0.29%) + + + +__do_softirq (1 samples, 0.02%) + + + +Node::in (1 samples, 0.02%) + + + +vfs_write (2 samples, 0.04%) + + + +sys_poll (1 samples, 0.02%) + + + +pthread_mutex_trylock (1 samples, 0.02%) + + + +kvm_clock_get_cycles (1 samples, 0.02%) + + + +java/util/ArrayList$Itr:::next (1 samples, 0.02%) + + + +org/jboss/netty/util/internal/LinkedTransferQueue:::xfer (1 samples, 0.02%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (2 samples, 0.04%) + + + +do_sync_write (1 samples, 0.02%) + + + +sys_futex (2 samples, 0.04%) + + + +kmem_cache_alloc (1 samples, 0.02%) + + + +java/lang/ref/Finalizer:::runFinalizer (1 samples, 0.02%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +org/springframework/web/servlet/handler/AbstractHandlerMethodMapping:::lookupHandlerMethod (126 samples, 2.45%) +or.. + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +redis/clients/jedis/JedisClusterCommand:::runWithRetries (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +java/lang/String:::hashCode (2 samples, 0.04%) + + + +JavaThread::thread_from_jni_environment (2 samples, 0.04%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::nextToken (1 samples, 0.02%) + + + +java/util/concurrent/ConcurrentHashMap:::putVal (2 samples, 0.04%) + + + +ip_queue_xmit (6 samples, 0.12%) + + + +mod_timer (1 samples, 0.02%) + + + +java/util/Collections$UnmodifiableCollection:::isEmpty (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +virtnet_poll (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +native_send_call_func_single_ipi (2 samples, 0.04%) + + + +java/lang/StringBuffer:::append (1 samples, 0.02%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (2 samples, 0.04%) + + + +JavaCallWrapper::JavaCallWrapper (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +sock_poll (1 samples, 0.02%) + + + +org/apache/catalina/core/StandardWrapper:::allocate (1 samples, 0.02%) + + + +YoungList::rs_length_sampling_next (3 samples, 0.06%) + + + +alloc_pages_current (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/deserializer/FieldDeserializer:::setValue (2 samples, 0.04%) + + + +java/lang/Throwable:::getOurStackTrace (31 samples, 0.60%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (2 samples, 0.04%) + + + +wake_up_state (29 samples, 0.56%) + + + +org/springframework/web/servlet/resource/ResourceUrlProviderExposingInterceptor:::preHandle (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +__schedule (1 samples, 0.02%) + + + +java/lang/StringBuilder:::append (2 samples, 0.04%) + + + +org/apache/coyote/Response:::getWriteListener (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::nextToken (1 samples, 0.02%) + + + +InstanceKlass::allocate_instance (1 samples, 0.02%) + + + +wake_up_state (15 samples, 0.29%) + + + +java/util/zip/Deflater:::deflateBytes (1 samples, 0.02%) + + + +org/springframework/boot/actuate/web/trace/servlet/TraceableHttpServletRequest:::getUri (1 samples, 0.02%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics:::addEvent (60 samples, 1.17%) + + + +kvm_sched_clock_read (1 samples, 0.02%) + + + +org/springframework/http/converter/AbstractHttpMessageConverter:::canWrite (1 samples, 0.02%) + + + +java/util/regex/Pattern$CharProperty:::match (3 samples, 0.06%) + + + +do_softirq (1 samples, 0.02%) + + + +ktime_get_real (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +com/coohua/caf/core/util/HttpUtil:::getPatternUrl (9 samples, 0.18%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +tcp_event_new_data_sent (1 samples, 0.02%) + + + +org/apache/kafka/common/utils/ByteUtils:::writeVarint (1 samples, 0.02%) + + + +com/fasterxml/jackson/core/json/WriterBasedJsonGenerator:::writeStartObject (1 samples, 0.02%) + + + +ClassLoaderDataGraph::roots_cld_do (1 samples, 0.02%) + + + +Matcher::match (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +java/util/AbstractList:::hashCode (1 samples, 0.02%) + + + +tcp_rcv_state_process (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor53:::invoke (1 samples, 0.02%) + + + +virtnet_select_queue (1 samples, 0.02%) + + + +java/net/URI:::<init> (3 samples, 0.06%) + + + +java/util/regex/Pattern:::compile (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils:::findAnnotation (2 samples, 0.04%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +tcp_recvmsg (8 samples, 0.16%) + + + +java/util/LinkedList:::unlink (1 samples, 0.02%) + + + +java/text/DateFormatSymbols:::initializeData (1 samples, 0.02%) + + + +virtnet_poll (1 samples, 0.02%) + + + +apic_timer_interrupt (1 samples, 0.02%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +sock_def_readable (1 samples, 0.02%) + + + +org/springframework/web/servlet/handler/AbstractHandlerMethodMapping:::addMatchingMappings (4 samples, 0.08%) + + + +dev_queue_xmit (19 samples, 0.37%) + + + +java/util/HashMap:::putMapEntries (1 samples, 0.02%) + + + +com/sun/crypto/provider/CipherCore:::doFinal (3 samples, 0.06%) + + + +org/apache/catalina/connector/RequestFacade:::getHeader (1 samples, 0.02%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +jni_fast_GetIntField (1 samples, 0.02%) + + + +java/util/HashSet:::iterator (16 samples, 0.31%) + + + +do_softirq (1 samples, 0.02%) + + + +tcp_stream_memory_free (1 samples, 0.02%) + + + +java/util/regex/Pattern$GroupTail:::match (3 samples, 0.06%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (2 samples, 0.04%) + + + +JvmtiVMObjectAllocEventCollector::JvmtiVMObjectAllocEventCollector (1 samples, 0.02%) + + + +jni_GetPrimitiveArrayCritical (1 samples, 0.02%) + + + +_new_array_Java (1 samples, 0.02%) + + + +deflateReset (3 samples, 0.06%) + + + +__do_softirq (1 samples, 0.02%) + + + +java/nio/charset/CharsetEncoder:::encode (5 samples, 0.10%) + + + +call_softirq (1 samples, 0.02%) + + + +java/util/Formatter$FormatSpecifier:::print (3 samples, 0.06%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +java/lang/StringCoding:::encode (1 samples, 0.02%) + + + +[libpthread-2.17.so] (3 samples, 0.06%) + + + +sun/nio/ch/SocketChannelImpl:::write (1 samples, 0.02%) + + + +sock_def_readable (1 samples, 0.02%) + + + +java/util/AbstractList$Itr:::<init> (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor94:::invoke (1 samples, 0.02%) + + + +hrtimer_cancel (4 samples, 0.08%) + + + +call_softirq (1 samples, 0.02%) + + + +do_softirq (2 samples, 0.04%) + + + +sun/security/jca/ProviderList$ServiceList:::tryGet (2 samples, 0.04%) + + + +org/springframework/web/method/HandlerMethod:::<init> (2 samples, 0.04%) + + + +irq_exit (1 samples, 0.02%) + + + +inflateStateCheck (4 samples, 0.08%) + + + +netif_receive_skb_internal (1 samples, 0.02%) + + + +java/util/AbstractList:::equals (2 samples, 0.04%) + + + +io/micrometer/core/instrument/MeterRegistry:::registerMeterIfNecessary (2 samples, 0.04%) + + + +org/springframework/web/filter/FormContentFilter:::doFilterInternal (205 samples, 3.99%) +org/.. + + +CompileBroker::invoke_compiler_on_method (48 samples, 0.93%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +Parse::do_field_access (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (5 samples, 0.10%) + + + +java/util/Formatter:::format (17 samples, 0.33%) + + + +jlong_disjoint_arraycopy (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/deserializer/FieldDeserializer:::setValue (2 samples, 0.04%) + + + +java/util/ArrayList:::iterator (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +org/jboss/netty/channel/socket/nio/SelectorUtil:::select (1 samples, 0.02%) + + + +java/lang/StringBuffer:::append (1 samples, 0.02%) + + + +StringTable::intern (8 samples, 0.16%) + + + +add_wait_queue (2 samples, 0.04%) + + + +org/springframework/core/annotation/AnnotatedElementUtils:::searchWithFindSemantics (26 samples, 0.51%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +ip_queue_xmit (3 samples, 0.06%) + + + +java/lang/reflect/Method:::hashCode (1 samples, 0.02%) + + + +CodeCache::find_blob (8 samples, 0.16%) + + + +path_put (1 samples, 0.02%) + + + +Parse::do_one_bytecode (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/context/ContextUtil:::trueEnter (1 samples, 0.02%) + + + +I2C/C2I adapters (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11Processor:::recycle (4 samples, 0.08%) + + + +org/apache/kafka/common/protocol/types/Type$7:::write (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::scanNumber (1 samples, 0.02%) + + + +__skb_clone (1 samples, 0.02%) + + + +__netdev_alloc_skb (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +__ext4_handle_dirty_metadata (1 samples, 0.02%) + + + +pipe_read (1 samples, 0.02%) + + + +__page_cache_alloc (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/RequestMappingInfo:::getMatchingCondition (3 samples, 0.06%) + + + +call_softirq (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +java/text/DecimalFormat:::parse (3 samples, 0.06%) + + + +java/util/regex/Pattern$3:::isSatisfiedBy (2 samples, 0.04%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +block_write_end (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slotchain/DefaultProcessorSlotChain$1:::exit (15 samples, 0.29%) + + + +G1CollectedHeap::mem_allocate (1 samples, 0.02%) + + + +ep_poll (24 samples, 0.47%) + + + +com/coohua/caf/core/metrics/LatencyStat$Timer:::observeDuration (2 samples, 0.04%) + + + +AddPNode::Ideal (1 samples, 0.02%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +pthread_getspecific (1 samples, 0.02%) + + + +OopMapCache::lookup (1 samples, 0.02%) + + + +java/util/stream/ReduceOps$3ReducingSink:::accept (4 samples, 0.08%) + + + +tcp_ack (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +deflate (166 samples, 3.23%) +def.. + + +jni_SetBooleanField (1 samples, 0.02%) + + + +java/text/SimpleDateFormat:::subFormat (3 samples, 0.06%) + + + +ip_output (1 samples, 0.02%) + + + +G1SATBCardTableLoggingModRefBS::write_ref_field_work (3 samples, 0.06%) + + + +fsnotify (3 samples, 0.06%) + + + +_new_array_nozero_Java (1 samples, 0.02%) + + + +JvmtiVMObjectAllocEventCollector::JvmtiVMObjectAllocEventCollector (1 samples, 0.02%) + + + +java/util/regex/Pattern$GroupHead:::match (1 samples, 0.02%) + + + +java/util/regex/Pattern$GroupHead:::match (1 samples, 0.02%) + + + +os::javaTimeMillis (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +com/coohua/ad/data/manager/AdDataManager:::setUserAdClickIntervalTimeStamp (2 samples, 0.04%) + + + +tcp_send_ack (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +org/springframework/boot/actuate/trace/http/HttpTrace$Request:::<init> (6 samples, 0.12%) + + + +ip_rcv (1 samples, 0.02%) + + + +JVM_IHashCode (2 samples, 0.04%) + + + +vtable stub (1 samples, 0.02%) + + + +java/lang/ThreadLocal:::setInitialValue (3 samples, 0.06%) + + + +_raw_spin_lock (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +_int_malloc (13 samples, 0.25%) + + + +call_softirq (1 samples, 0.02%) + + + +JavaThread::handle_special_suspend_equivalent_condition (1 samples, 0.02%) + + + +schedule (2 samples, 0.04%) + + + +jni_ReleasePrimitiveArrayCritical (6 samples, 0.12%) + + + +sys_futex (14 samples, 0.27%) + + + +java/util/regex/Pattern$Branch:::match (3 samples, 0.06%) + + + +rw_verify_area (2 samples, 0.04%) + + + +org/apache/kafka/clients/producer/internals/Sender:::sendProduceRequests (47 samples, 0.91%) + + + +security_file_permission (1 samples, 0.02%) + + + +__do_softirq (2 samples, 0.04%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +HeapRegion::block_size (1 samples, 0.02%) + + + +java/util/LinkedHashMap$LinkedKeyIterator:::next (2 samples, 0.04%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics:::getLibProperties (578 samples, 11.25%) +com/coohuadata/a.. + + +ret_from_intr (1 samples, 0.02%) + + + +JavaThread::last_frame (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils$AnnotationCollector:::<init> (4 samples, 0.08%) + + + +net_rx_action (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (14 samples, 0.27%) + + + +__do_softirq (1 samples, 0.02%) + + + +java/util/zip/Inflater:::init (2 samples, 0.04%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +__alloc_pages_nodemask (1 samples, 0.02%) + + + +java/util/zip/Deflater:::deflateBytes (171 samples, 3.33%) +jav.. + + +com/alibaba/fastjson/parser/JSONScanner:::next (1 samples, 0.02%) + + + +java/lang/Object:::toString (2 samples, 0.04%) + + + +org/springframework/web/servlet/mvc/method/RequestMappingInfo:::hashCode (2 samples, 0.04%) + + + +__fsnotify_parent (1 samples, 0.02%) + + + +org/apache/tomcat/util/threads/TaskQueue:::offer (26 samples, 0.51%) + + + +virtnet_poll (1 samples, 0.02%) + + + +java/lang/StringCoding:::encode (3 samples, 0.06%) + + + +Interpreter (4,903 samples, 95.39%) +Interpreter + + +irq_exit (1 samples, 0.02%) + + + +java/util/LinkedHashMap:::get (11 samples, 0.21%) + + + +sun/nio/ch/EPollArrayWrapper:::epollCtl (16 samples, 0.31%) + + + +org/springframework/http/converter/AbstractGenericHttpMessageConverter:::canWrite (1 samples, 0.02%) + + + +compress_block (4 samples, 0.08%) + + + +redis/clients/jedis/JedisCluster$25:::execute (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor62:::invoke (1 samples, 0.02%) + + + +__libc_writev (1 samples, 0.02%) + + + +org/apache/catalina/core/StandardContextValve:::invoke (216 samples, 4.20%) +org/a.. + + +ParseGenerator::generate (1 samples, 0.02%) + + + +java/lang/StringBuilder:::append (1 samples, 0.02%) + + + +org/springframework/util/NumberUtils:::parseNumber (3 samples, 0.06%) + + + +java/lang/StringBuffer:::append (1 samples, 0.02%) + + + +com/coohua/caf/core/metrics/LatencyStat:::setMax (1 samples, 0.02%) + + + +java/nio/charset/Charset:::atBugLevel (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +jlong_disjoint_arraycopy (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +ep_poll (19 samples, 0.37%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +org/apache/catalina/connector/CoyoteAdapter:::normalize (3 samples, 0.06%) + + + +sun/nio/cs/ISO_8859_1$Encoder:::encodeBufferLoop (3 samples, 0.06%) + + + +java/security/Provider:::getService (2 samples, 0.04%) + + + +copy_user_enhanced_fast_string (1 samples, 0.02%) + + + +kmem_cache_free (1 samples, 0.02%) + + + +org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter:::canWrite (3 samples, 0.06%) + + + +java/util/regex/Pattern$BmpCharProperty:::match (4 samples, 0.08%) + + + +do_sync_write (21 samples, 0.41%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +com/google/gson/stream/JsonWriter:::string (21 samples, 0.41%) + + + +__fsnotify_parent (1 samples, 0.02%) + + + +java/util/regex/Pattern:::matcher (2 samples, 0.04%) + + + +java/lang/StringBuffer:::append (1 samples, 0.02%) + + + +CollectedHeap::post_allocation_setup_array (4 samples, 0.08%) + + + +sun/nio/ch/EPollArrayWrapper:::poll (46 samples, 0.89%) + + + +tcp_current_mss (1 samples, 0.02%) + + + +sys_epoll_wait (1 samples, 0.02%) + + + +javax/crypto/Cipher:::chooseProvider (30 samples, 0.58%) + + + +SharedRuntime::complete_monitor_locking_C (2 samples, 0.04%) + + + +java/security/Provider:::getService (1 samples, 0.02%) + + + +sun/nio/ch/EPollArrayWrapper:::interrupt (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +_new_array_Java (2 samples, 0.04%) + + + +org/springframework/web/servlet/DispatcherServlet:::doDispatch (187 samples, 3.64%) +org/.. + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +com/coohua/ad/data/interceptor/UserInterceptor:::afterCompletion (143 samples, 2.78%) +co.. + + +PhaseIdealLoop::split_if_with_blocks (1 samples, 0.02%) + + + +java/lang/String:::toUpperCase (2 samples, 0.04%) + + + +org/apache/catalina/core/StandardContext:::getApplicationEventListeners (1 samples, 0.02%) + + + +java/lang/ThreadLocal$ThreadLocalMap:::set (1 samples, 0.02%) + + + +java_lang_Thread::is_alive (1 samples, 0.02%) + + + +com/coohua/ad/data/manager/AdLogTrackManager:::ecpTrack (9 samples, 0.18%) + + + +schedule_hrtimeout_range_clock (14 samples, 0.27%) + + + +redis/clients/jedis/JedisClusterCommand:::runWithRetries (1 samples, 0.02%) + + + +java/lang/String:::hashCode (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/B2CConverter:::convert (2 samples, 0.04%) + + + +do_softirq (2 samples, 0.04%) + + + +org/apache/kafka/common/metrics/stats/Meter:::record (2 samples, 0.04%) + + + +java/lang/Integer:::toString (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/apache/tomcat/util/net/AbstractEndpoint:::processSocket (27 samples, 0.53%) + + + +vfs_write (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +sun/misc/Unsafe:::unpark (34 samples, 0.66%) + + + +tcp_poll (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor:::writeWithMessageConverters (40 samples, 0.78%) + + + +do_softirq (1 samples, 0.02%) + + + +futex_wake_op (20 samples, 0.39%) + + + +pqdownheap (1 samples, 0.02%) + + + +org/springframework/web/servlet/FrameworkServlet:::service (192 samples, 3.74%) +org/.. + + +__call_rcu (1 samples, 0.02%) + + + +java/util/HashMap:::resize (1 samples, 0.02%) + + + +os::javaTimeMillis (2 samples, 0.04%) + + + +java/lang/Thread:::sleep (12 samples, 0.23%) + + + +ip_queue_xmit (1 samples, 0.02%) + + + +Symbol::as_klass_external_name (37 samples, 0.72%) + + + +sock_def_readable (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/apache/coyote/AbstractProcessor:::action (1 samples, 0.02%) + + + +ThreadStateTransition::trans_from_native (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +BarrierSet::static_write_ref_array_pre (2 samples, 0.04%) + + + +net_rx_action (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/StringCache:::toString (1 samples, 0.02%) + + + +G1CollectedHeap::can_elide_initializing_store_barrier (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +jbyte_disjoint_arraycopy (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy48:::annotationType (1 samples, 0.02%) + + + +Compile::Process_OopMap_Node (2 samples, 0.04%) + + + +kmem_cache_alloc (1 samples, 0.02%) + + + +__irqentry_text_start (2 samples, 0.04%) + + + +org/springframework/http/CacheControl:::getHeaderValue (1 samples, 0.02%) + + + +sys_futex (2 samples, 0.04%) + + + +com/coohuadata/analytics/javasdk/util/KafkaProducerHandler:::run (82 samples, 1.60%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +sock_def_readable (1 samples, 0.02%) + + + +sch_direct_xmit (1 samples, 0.02%) + + + +Klass::external_name (52 samples, 1.01%) + + + +JVM_GetStackTraceElement (463 samples, 9.01%) +JVM_GetStackT.. + + +generic_exec_single (2 samples, 0.04%) + + + +sysret_audit (1 samples, 0.02%) + + + +com/coohuadata/analytics/javasdk/util/KafkaProducer$1:::onCompletion (31 samples, 0.60%) + + + +Compile::Compile (48 samples, 0.93%) + + + +generic_file_aio_write (21 samples, 0.41%) + + + +get_futex_key (1 samples, 0.02%) + + + +release_sock (1 samples, 0.02%) + + + +__schedule (16 samples, 0.31%) + + + +org/apache/kafka/common/network/Selector$SelectorMetrics:::recordBytesReceived (5 samples, 0.10%) + + + +org/springframework/core/annotation/AnnotatedElementUtils:::searchWithFindSemantics (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +java/util/AbstractList:::equals (1 samples, 0.02%) + + + +PhaseCFG::build_dominator_tree (1 samples, 0.02%) + + + +java/lang/String:::toLowerCase (1 samples, 0.02%) + + + +OptoRuntime::new_array_C (1 samples, 0.02%) + + + +java/util/Formatter:::format (15 samples, 0.29%) + + + +HandleMarkCleaner::~HandleMarkCleaner (1 samples, 0.02%) + + + +RegMask::smear_to_sets (1 samples, 0.02%) + + + +virtnet_poll (1 samples, 0.02%) + + + +pqdownheap (2 samples, 0.04%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +mod_timer (1 samples, 0.02%) + + + +java/util/regex/Pattern$Neg:::match (106 samples, 2.06%) +j.. + + +__skb_checksum (1 samples, 0.02%) + + + +java/lang/Boolean:::equals (1 samples, 0.02%) + + + +inet_gro_receive (1 samples, 0.02%) + + + +ClassLoaderData::oops_do (1 samples, 0.02%) + + + +OptoRuntime::register_finalizer (8 samples, 0.16%) + + + +sun/nio/ch/FileDispatcherImpl:::writev0 (21 samples, 0.41%) + + + +jni_GetObjectField (4 samples, 0.08%) + + + +do_softirq (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +update_time (1 samples, 0.02%) + + + +_multianewarray2_Java (11 samples, 0.21%) + + + +IndexSetIterator::advance_and_next (2 samples, 0.04%) + + + +__ext4_journal_stop (1 samples, 0.02%) + + + +__pollwait (1 samples, 0.02%) + + + +org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter:::canWrite (1 samples, 0.02%) + + + +Parse::do_one_bytecode (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +tcp_sendmsg (5 samples, 0.10%) + + + +jni_GetObjectField (92 samples, 1.79%) + + + +PhaseAggressiveCoalesce::insert_copies (1 samples, 0.02%) + + + +Compile::Optimize (16 samples, 0.31%) + + + +sk_reset_timer (1 samples, 0.02%) + + + +java/net/URI$Parser:::parseServer (3 samples, 0.06%) + + + +wake_futex (2 samples, 0.04%) + + + +org/springframework/boot/actuate/trace/http/HttpTrace$Request:::<init> (73 samples, 1.42%) + + + +org/apache/kafka/common/network/Selector:::attemptRead (21 samples, 0.41%) + + + +java/util/Formatter$Conversion:::isValid (1 samples, 0.02%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +java/lang/Long:::getChars (1 samples, 0.02%) + + + +OptoRuntime::new_array_C (1 samples, 0.02%) + + + +pthread_mutex_trylock (1 samples, 0.02%) + + + +irq_exit (2 samples, 0.04%) + + + +net_rx_action (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +java/lang/ref/Reference$ReferenceHandler:::run (2 samples, 0.04%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics:::assertProperties (131 samples, 2.55%) +co.. + + +org/springframework/core/annotation/AnnotatedElementUtils:::searchWithFindSemantics (11 samples, 0.21%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +pthread_getspecific (2 samples, 0.04%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +sys_write (12 samples, 0.23%) + + + +generic_exec_single (1 samples, 0.02%) + + + +ep_poll (18 samples, 0.35%) + + + +__do_softirq (1 samples, 0.02%) + + + +napi_gro_receive (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::doFilter (211 samples, 4.11%) +org/.. + + +call_function_single_interrupt (1 samples, 0.02%) + + + +tcp_recvmsg (1 samples, 0.02%) + + + +java/util/regex/Pattern$Begin:::match (15 samples, 0.29%) + + + +system_call_fastpath (12 samples, 0.23%) + + + +org/apache/tomcat/util/buf/StringCache:::toString (2 samples, 0.04%) + + + +java/lang/String:::hashCode (1 samples, 0.02%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics$KafkaConsumer:::send (490 samples, 9.53%) +com/coohuadat.. + + +java/lang/ThreadLocal$ThreadLocalMap:::set (1 samples, 0.02%) + + + +sg_next (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping:::handleMatch (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +checkcast_arraycopy (1 samples, 0.02%) + + + +do_futex (3 samples, 0.06%) + + + +org/jboss/netty/channel/socket/nio/NioWorker:::write0 (1 samples, 0.02%) + + + +pipe_write (21 samples, 0.41%) + + + +pthread_getspecific (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +java/util/HashMap:::resize (1 samples, 0.02%) + + + +vfs_write (30 samples, 0.58%) + + + +system_call_fastpath (25 samples, 0.49%) + + + +Java_java_net_SocketInputStream_socketRead0 (1 samples, 0.02%) + + + +io/micrometer/core/instrument/Tags:::and (2 samples, 0.04%) + + + +com/coohua/ad/data/service/impl/AdDataServiceImpl:::checkAdValid (3 samples, 0.06%) + + + +call_softirq (1 samples, 0.02%) + + + +com/sun/crypto/provider/AESCrypt:::makeSessionKey (17 samples, 0.33%) + + + +sys_epoll_wait (20 samples, 0.39%) + + + +fget_light (1 samples, 0.02%) + + + +__fsnotify_parent (1 samples, 0.02%) + + + +finish_task_switch (16 samples, 0.31%) + + + +java/util/concurrent/locks/LockSupport:::parkNanos (5 samples, 0.10%) + + + +ret_from_intr (2 samples, 0.04%) + + + +ThreadInVMfromNative::~ThreadInVMfromNative (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/config/LoggerConfig:::log (3 samples, 0.06%) + + + +itable stub (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (18 samples, 0.35%) + + + +__srcu_read_unlock (2 samples, 0.04%) + + + +sys_poll (1 samples, 0.02%) + + + +java/util/Formatter$FixedString:::print (1 samples, 0.02%) + + + +java/net/URI$Parser:::parseServer (1 samples, 0.02%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +tcp_send_mss (2 samples, 0.04%) + + + +__ext4_journal_get_write_access (1 samples, 0.02%) + + + +detach_buf (1 samples, 0.02%) + + + +java/util/HashMap:::put (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +poll_schedule_timeout (1 samples, 0.02%) + + + +sys_futex (2 samples, 0.04%) + + + +ThreadBlockInVM::ThreadBlockInVM (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/ser/std/StdKeySerializers$StringKeySerializer:::serialize (7 samples, 0.14%) + + + +com/coohua/caf/core/rpc/MotanConfigPrintSpringListener:::onApplicationEvent (13 samples, 0.25%) + + + +com/fasterxml/jackson/databind/ser/std/MapSerializer:::serialize (37 samples, 0.72%) + + + +java/lang/Object:::hashCode (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +inet_csk_destroy_sock (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +tcp_rearm_rto (1 samples, 0.02%) + + + +com/google/gson/Gson:::getAdapter (3 samples, 0.06%) + + + +tcp_sendmsg (36 samples, 0.70%) + + + +net_rx_action (1 samples, 0.02%) + + + +sun/nio/cs/ISO_8859_1$Decoder:::decodeLoop (1 samples, 0.02%) + + + +org/springframework/web/multipart/support/MultipartResolutionDelegate:::resolveMultipartArgument (4 samples, 0.08%) + + + +java/lang/AbstractStringBuilder:::append (4 samples, 0.08%) + + + +org/springframework/core/convert/TypeDescriptor:::equals (17 samples, 0.33%) + + + +org/springframework/core/convert/support/GenericConversionService$ConverterCacheKey:::equals (19 samples, 0.37%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +sun/nio/ch/EPollSelectorImpl:::putEventOps (1 samples, 0.02%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +ConcurrentG1RefineThread::sample_young_list_rs_lengths (3 samples, 0.06%) + + + +itable stub (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/pattern/LoggerPatternConverter:::format (1 samples, 0.02%) + + + +napi_gro_receive (1 samples, 0.02%) + + + +org/apache/kafka/common/metrics/Sensor:::record (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +java/util/ArrayList:::add (1 samples, 0.02%) + + + +OptoRuntime::new_array_C (1 samples, 0.02%) + + + +java/text/SimpleDateFormat:::subFormat (3 samples, 0.06%) + + + +jshort_disjoint_arraycopy (6 samples, 0.12%) + + + +java/util/zip/DeflaterOutputStream:::write (89 samples, 1.73%) + + + +call_softirq (1 samples, 0.02%) + + + +netif_skb_features (1 samples, 0.02%) + + + +java/util/concurrent/locks/ReentrantReadWriteLock$Sync:::tryAcquireShared (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/springframework/beans/TypeConverterDelegate:::convertIfNecessary (27 samples, 0.53%) + + + +java/lang/String:::hashCode (1 samples, 0.02%) + + + +napi_gro_receive (1 samples, 0.02%) + + + +GangWorker::loop (19 samples, 0.37%) + + + +Monitor::ILock (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +java/util/regex/Pattern$Begin:::match (1 samples, 0.02%) + + + +java/text/DateFormatSymbols:::getProviderInstance (2 samples, 0.04%) + + + +java/util/HashMap:::resize (5 samples, 0.10%) + + + +ip_rcv (1 samples, 0.02%) + + + +__schedule (10 samples, 0.19%) + + + +java/lang/ThreadLocal:::getMap (1 samples, 0.02%) + + + +get_rps_cpu (1 samples, 0.02%) + + + +org/apache/logging/log4j/spi/AbstractLogger:::logMessageSafely (3 samples, 0.06%) + + + +TypeArrayKlass::allocate_common (1 samples, 0.02%) + + + +org/apache/kafka/common/metrics/Sensor:::record (5 samples, 0.10%) + + + +Unsafe_Unpark (2 samples, 0.04%) + + + +org/joda/time/format/FormatUtils:::appendPaddedInteger (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +_raw_spin_lock (1 samples, 0.02%) + + + +jbd2_journal_stop (1 samples, 0.02%) + + + +java/util/zip/Deflater:::deflateBytes (1 samples, 0.02%) + + + +futex_wait (19 samples, 0.37%) + + + +wake_up_state (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11Processor:::prepareRequest (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +fget_light (2 samples, 0.04%) + + + +Interpreter (1 samples, 0.02%) + + + +java/util/concurrent/FutureTask:::run (82 samples, 1.60%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +org/joda/time/field/PreciseDateTimeField:::get (1 samples, 0.02%) + + + +org/springframework/core/convert/TypeDescriptor:::valueOf (2 samples, 0.04%) + + + +do_softirq (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +java/util/HashMap$HashIterator:::hasNext (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +java/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject:::awaitNanos (5 samples, 0.10%) + + + +irq_exit (1 samples, 0.02%) + + + +JavaThread::run (5,080 samples, 98.83%) +JavaThread::run + + +call_function_single_interrupt (1 samples, 0.02%) + + + +__mark_inode_dirty (9 samples, 0.18%) + + + +irq_exit (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils:::isInJavaLangAnnotationPackage (1 samples, 0.02%) + + + +Interpreter (2 samples, 0.04%) + + + +G1SATBCardTableLoggingModRefBS::invalidate (1 samples, 0.02%) + + + +java/text/DateFormatSymbols:::getProviderInstance (1 samples, 0.02%) + + + +virtnet_poll (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer:::parseField (1 samples, 0.02%) + + + +[libpthread-2.17.so] (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +java/util/HashMap:::put (1 samples, 0.02%) + + + +deflate (9 samples, 0.18%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +PhaseIterGVN::optimize (4 samples, 0.08%) + + + +ret_from_intr (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor86:::invoke (1 samples, 0.02%) + + + +__GI___libc_poll (1 samples, 0.02%) + + + +io/micrometer/core/instrument/Timer$Builder:::register (6 samples, 0.12%) + + + +java/lang/ref/Finalizer:::register (1 samples, 0.02%) + + + +__lll_unlock_wake (1 samples, 0.02%) + + + +HandleMarkCleaner::~HandleMarkCleaner (2 samples, 0.04%) + + + +java/util/ArrayList:::size (1 samples, 0.02%) + + + +java/util/zip/Deflater:::deflateBytes (89 samples, 1.73%) + + + +java/util/HashMap:::put (1 samples, 0.02%) + + + +skb_checksum (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/pattern/LevelPatternConverter:::format (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot:::exit (1 samples, 0.02%) + + + +java/util/AbstractList:::equals (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer:::smartMatch (18 samples, 0.35%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +jbyte_disjoint_arraycopy (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +Java_java_util_zip_Deflater_deflateBytes (87 samples, 1.69%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (2 samples, 0.04%) + + + +lock_sock_nested (1 samples, 0.02%) + + + +frame::oops_interpreted_do (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +com/coohua/caf/core/metrics/LatencyStat:::setMax (6 samples, 0.12%) + + + +__do_softirq (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +java/lang/Class:::getDeclaringClass0 (1 samples, 0.02%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +java/util/Formatter$FormatSpecifier:::print (5 samples, 0.10%) + + + +com/coohua/caf/core/metrics/LatencyStat:::observe (17 samples, 0.33%) + + + +deflateStateCheck (1 samples, 0.02%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +ep_scan_ready_list.isra.7 (4 samples, 0.08%) + + + +apic_timer_interrupt (1 samples, 0.02%) + + + +skb_put (1 samples, 0.02%) + + + +java/io/ByteArrayOutputStream:::write (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +org/springframework/web/util/UrlPathHelper:::getRemainingPath (1 samples, 0.02%) + + + +__vdso_gettimeofday (2 samples, 0.04%) + + + +do_softirq (1 samples, 0.02%) + + + +java/lang/StringBuilder:::append (2 samples, 0.04%) + + + +java/util/Calendar:::<init> (1 samples, 0.02%) + + + +sock_def_readable (1 samples, 0.02%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics$KafkaConsumer:::send (2 samples, 0.04%) + + + +virtqueue_add_inbuf (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer:::parseField (68 samples, 1.32%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +org/springframework/web/method/annotation/ModelFactory:::findSessionAttributeArguments (2 samples, 0.04%) + + + +CompressedReadStream::read_signed_int (4 samples, 0.08%) + + + +call_softirq (1 samples, 0.02%) + + + +org/springframework/core/DefaultParameterNameDiscoverer:::<init> (3 samples, 0.06%) + + + +com/weibo/api/motan/proxy/AbstractRefererHandler:::invokeRequest (10 samples, 0.19%) + + + +ret_from_intr (2 samples, 0.04%) + + + +ret_from_intr (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +org/springframework/web/method/support/InvocableHandlerMethod:::invokeForRequest (141 samples, 2.74%) +or.. + + +CollectedHeap::new_store_pre_barrier (1 samples, 0.02%) + + + +Parker::park (4 samples, 0.08%) + + + +org/springframework/core/annotation/AnnotationUtils$AnnotationCollector:::<init> (5 samples, 0.10%) + + + +org/apache/kafka/common/metrics/stats/Meter:::record (1 samples, 0.02%) + + + +generic_exec_single (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +java/lang/reflect/Method:::invoke (138 samples, 2.68%) +ja.. + + +java/util/GregorianCalendar:::computeFields (2 samples, 0.04%) + + + +java_lang_Throwable::fill_in_stack_trace (4 samples, 0.08%) + + + +com/coohua/caf/core/sentinel/SentinelHttpInterceptor:::preHandle (2 samples, 0.04%) + + + +java/io/DataOutputStream:::write (1 samples, 0.02%) + + + +java/util/regex/Pattern$GroupHead:::match (4 samples, 0.08%) + + + +org/apache/coyote/Response:::recycle (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +java/net/SocketInputStream:::socketRead0 (1 samples, 0.02%) + + + +org/springframework/util/AntPathMatcher:::match (49 samples, 0.95%) + + + +__vdso_gettimeofday (2 samples, 0.04%) + + + +G1BlockOffsetArrayContigSpace::zero_bottom_entry_raw (1 samples, 0.02%) + + + +java_lang_StackTraceElement::create (420 samples, 8.17%) +java_lang_S.. + + +ip_rcv (1 samples, 0.02%) + + + +sysret_audit (1 samples, 0.02%) + + + +ObjArrayKlass::allocate (1 samples, 0.02%) + + + +G1AllocRegion::new_alloc_region_and_allocate (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils$AnnotationCollector:::process (9 samples, 0.18%) + + + +do_softirq (1 samples, 0.02%) + + + +_copy_from_user (1 samples, 0.02%) + + + +sys_write (43 samples, 0.84%) + + + +do_softirq (1 samples, 0.02%) + + + +PhaseRegAlloc::is_oop (1 samples, 0.02%) + + + +netif_receive_skb_internal (1 samples, 0.02%) + + + +java/lang/StringCoding:::decode (1 samples, 0.02%) + + + +java/util/HashMap$HashIterator:::hasNext (1 samples, 0.02%) + + + +java/lang/ThreadLocal:::getMap (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +java/util/regex/Pattern$Begin:::match (2 samples, 0.04%) + + + +org/springframework/web/filter/HiddenHttpMethodFilter:::doFilterInternal (211 samples, 4.11%) +org/.. + + +java/util/regex/Pattern$GroupHead:::match (1 samples, 0.02%) + + + +java/lang/Integer:::toString (2 samples, 0.04%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +_raw_spin_lock_irqsave (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/pattern/NameAbbreviator$PatternAbbreviator:::abbreviate (1 samples, 0.02%) + + + +java/util/HashMap:::put (1 samples, 0.02%) + + + +vfs_write (12 samples, 0.23%) + + + +com/coohua/caf/core/kv/JedisClusterClient$$FastClassBySpringCGLIB$$1bc9b45c:::invoke (2 samples, 0.04%) + + + +org/springframework/cglib/proxy/MethodProxy:::invoke (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +UTF8::convert_to_unicode (22 samples, 0.43%) + + + +G1UpdateRSOrPushRefOopClosure::do_oop (1 samples, 0.02%) + + + +org/springframework/web/util/UrlPathHelper:::decodeInternal (6 samples, 0.12%) + + + +org/apache/tomcat/websocket/server/WsFilter:::doFilter (3,448 samples, 67.08%) +org/apache/tomcat/websocket/server/WsFilter:::doFilter + + +__audit_syscall_entry (1 samples, 0.02%) + + + +java/util/regex/Pattern$Dollar:::match (1 samples, 0.02%) + + + +__skb_gro_checksum_complete (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +CodeCache::find_blob (1 samples, 0.02%) + + + +java/util/Formatter$FormatSpecifier:::print (6 samples, 0.12%) + + + +__libc_writev (20 samples, 0.39%) + + + +org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler:::invoke (12 samples, 0.23%) + + + +tcp_sendmsg (15 samples, 0.29%) + + + +org/apache/kafka/clients/producer/internals/BufferPool:::allocate (3 samples, 0.06%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/springframework/web/context/request/ServletWebRequest:::getNativeRequest (1 samples, 0.02%) + + + +com/alibaba/fastjson/serializer/StringCodec:::deserialze (7 samples, 0.14%) + + + +org/springframework/core/convert/support/GenericConversionService:::getConverter (22 samples, 0.43%) + + + +BarrierSet::static_write_ref_array_post (1 samples, 0.02%) + + + +ReferenceProcessor::process_discovered_references (3 samples, 0.06%) + + + +__skb_checksum (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils$AnnotationCollector:::process (22 samples, 0.43%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +netif_receive_skb_internal (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils:::isInJavaLangAnnotationPackage (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +TypeArrayKlass::allocate_common (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +JVM_IHashCode (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +__irqentry_text_start (2 samples, 0.04%) + + + +org/springframework/aop/aspectj/AbstractAspectJAdvice:::invokeAdviceMethod (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +_new_instance_Java (1 samples, 0.02%) + + + +java/util/regex/Pattern:::matcher (2 samples, 0.04%) + + + +sock_aio_read (9 samples, 0.18%) + + + +x2apic_send_IPI_mask (2 samples, 0.04%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +skb_release_all (1 samples, 0.02%) + + + +Java_java_util_zip_Deflater_deflateBytes (1 samples, 0.02%) + + + +redis/clients/jedis/JedisCluster$21:::execute (1 samples, 0.02%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics:::assertProperties (4 samples, 0.08%) + + + +call_softirq (1 samples, 0.02%) + + + +Monitor::ILock (1 samples, 0.02%) + + + +java/util/GregorianCalendar:::computeTime (3 samples, 0.06%) + + + +do_futex (6 samples, 0.12%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +try_to_wake_up (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +__lll_unlock_wake (3 samples, 0.06%) + + + +frame::is_interpreted_frame (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (206 samples, 4.01%) +org/.. + + +java_lang_Throwable::fill_in_stack_trace (74 samples, 1.44%) + + + +Monitor::unlock (1 samples, 0.02%) + + + +java/lang/String:::equals (2 samples, 0.04%) + + + +com/sun/proxy/$Proxy77:::annotationType (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +csum_partial_ext (1 samples, 0.02%) + + + +org/springframework/util/MimeType$SpecificityComparator:::compare (1 samples, 0.02%) + + + +itable stub (5 samples, 0.10%) + + + +PhaseIdealLoop::build_loop_late (4 samples, 0.08%) + + + +send_tree (9 samples, 0.18%) + + + +java/nio/Buffer:::limit (1 samples, 0.02%) + + + +java/util/concurrent/ThreadPoolExecutor:::execute (26 samples, 0.51%) + + + +sys_epoll_wait (25 samples, 0.49%) + + + +__memset_sse2 (1 samples, 0.02%) + + + +java/security/Provider$ServiceKey:::<init> (9 samples, 0.18%) + + + +java/lang/StringCoding:::decode (3 samples, 0.06%) + + + +javax/crypto/Cipher:::tokenizeTransformation (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/appender/AsyncAppender$AsyncThread:::run (99 samples, 1.93%) +o.. + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +sys_write (2 samples, 0.04%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +Java_java_util_zip_Deflater_init (37 samples, 0.72%) + + + +auditsys (1 samples, 0.02%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (1 samples, 0.02%) + + + +io/micrometer/core/instrument/config/MeterFilter:::map (1 samples, 0.02%) + + + +pthread_getspecific (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +retint_careful (1 samples, 0.02%) + + + +rcu_process_callbacks (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +pipe_poll (1 samples, 0.02%) + + + +sun/nio/cs/UTF_8$Decoder:::decodeLoop (1 samples, 0.02%) + + + +java/util/HashMap:::resize (2 samples, 0.04%) + + + +smp_apic_timer_interrupt (1 samples, 0.02%) + + + +java/net/SocketInputStream:::socketRead0 (1 samples, 0.02%) + + + +tcp_event_data_recv (1 samples, 0.02%) + + + +com/alibaba/fastjson/serializer/IntegerCodec:::deserialze (3 samples, 0.06%) + + + +DirtyCardQueueSet::apply_closure_to_completed_buffer (5 samples, 0.10%) + + + +irq_exit (1 samples, 0.02%) + + + +java/text/SimpleDateFormat:::initialize (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +ipv4_mtu (1 samples, 0.02%) + + + +java/util/Formatter:::format (23 samples, 0.45%) + + + +sun/misc/Unsafe:::unpark (25 samples, 0.49%) + + + +sun/nio/ch/SelectorImpl:::select (25 samples, 0.49%) + + + +Interpreter (4,346 samples, 84.55%) +Interpreter + + +org/apache/logging/log4j/core/impl/Log4jLogEvent:::<init> (5 samples, 0.10%) + + + +os::javaTimeMillis (2 samples, 0.04%) + + + +dev_hard_start_xmit (3 samples, 0.06%) + + + +pthread_mutex_unlock (1 samples, 0.02%) + + + +java/util/HashSet:::iterator (2 samples, 0.04%) + + + +do_sys_poll (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +java/util/regex/Pattern$CharProperty:::match (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +java/util/HashMap:::newNode (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (204 samples, 3.97%) +org/.. + + +com/alibaba/csp/sentinel/slots/statistic/base/LeapArray:::currentWindow (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +jbd2__journal_start (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor61:::invoke (1 samples, 0.02%) + + + +tcp_data_queue (1 samples, 0.02%) + + + +java/util/concurrent/LinkedBlockingQueue:::offer (22 samples, 0.43%) + + + +java/lang/Throwable:::<init> (78 samples, 1.52%) + + + +ip_queue_xmit (23 samples, 0.45%) + + + +org/springframework/context/support/AbstractApplicationContext:::publishEvent (3 samples, 0.06%) + + + +arrayOopDesc::base (1 samples, 0.02%) + + + +OptoRuntime::new_array_C (1 samples, 0.02%) + + + +org/springframework/context/event/SimpleApplicationEventMulticaster:::multicastEvent (22 samples, 0.43%) + + + +org/jboss/netty/channel/DefaultChannelPipeline$DefaultChannelHandlerContext:::sendDownstream (2 samples, 0.04%) + + + +pthread_cond_signal@@GLIBC_2.3.2 (1 samples, 0.02%) + + + +OptoRuntime::new_instance_C (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +java/util/LinkedHashMap:::get (4 samples, 0.08%) + + + +call_rcu_sched (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +ip_finish_output (5 samples, 0.10%) + + + +org/apache/tomcat/util/net/NioBlockingSelector:::write (85 samples, 1.65%) + + + +org/apache/kafka/common/protocol/types/ArrayOf:::validate (1 samples, 0.02%) + + + +org/joda/time/DateTimeFieldType$StandardDateTimeFieldType:::getField (1 samples, 0.02%) + + + +system_call_after_swapgs (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +os::javaTimeMillis (2 samples, 0.04%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +java/util/HashMap:::putMapEntries (5 samples, 0.10%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics$KafkaConsumer:::send (1 samples, 0.02%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (1 samples, 0.02%) + + + +dev_gro_receive (1 samples, 0.02%) + + + +MachNode::ideal_reg (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor63:::invoke (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +java/util/regex/Pattern:::matcher (1 samples, 0.02%) + + + +VM_Operation::evaluate (4 samples, 0.08%) + + + +itable stub (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +HandleMarkCleaner::~HandleMarkCleaner (2 samples, 0.04%) + + + +skb_copy_datagram_iovec (5 samples, 0.10%) + + + +org/springframework/core/annotation/AnnotationUtils:::findAnnotation (3 samples, 0.06%) + + + +java/util/concurrent/ConcurrentHashMap:::putVal (1 samples, 0.02%) + + + +java/util/regex/Pattern$Begin:::match (108 samples, 2.10%) +j.. + + +com/alibaba/fastjson/parser/JSONLexerBase:::scanSymbol (16 samples, 0.31%) + + + +tcp_v4_early_demux (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/SymbolTable:::addSymbol (2 samples, 0.04%) + + + +java/nio/charset/CharsetDecoder:::implReset (1 samples, 0.02%) + + + +com/coohua/caf/core/rpc/MotanTracingFilter:::filter (8 samples, 0.16%) + + + +inet_gro_receive (1 samples, 0.02%) + + + +org/springframework/web/method/annotation/ModelAttributeMethodProcessor:::supportsReturnType (2 samples, 0.04%) + + + +JavaThread::handle_special_suspend_equivalent_condition (3 samples, 0.06%) + + + +org/springframework/boot/actuate/trace/http/HttpTrace$Request:::<init> (6 samples, 0.12%) + + + +pthread_getspecific (1 samples, 0.02%) + + + +com/google/gson/internal/bind/ArrayTypeAdapter:::write (1 samples, 0.02%) + + + +try_fill_recv (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +java/util/ComparableTimSort:::binarySort (3 samples, 0.06%) + + + +java/lang/StringCoding:::encode (8 samples, 0.16%) + + + +run_timer_softirq (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor55:::invoke (2,635 samples, 51.26%) +sun/reflect/GeneratedMethodAccessor55:::invoke + + +OopMapSet::all_do (1 samples, 0.02%) + + + +java/util/regex/Pattern:::matcher (8 samples, 0.16%) + + + +bictcp_cong_avoid (1 samples, 0.02%) + + + +java/util/HashMap:::put (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +__fsnotify_parent (1 samples, 0.02%) + + + +org/apache/catalina/mapper/Mapper:::internalMap (9 samples, 0.18%) + + + +schedule (11 samples, 0.21%) + + + +jshort_disjoint_arraycopy (2 samples, 0.04%) + + + +jshort_disjoint_arraycopy (2 samples, 0.04%) + + + +sun/nio/ch/NativeThread:::current (1 samples, 0.02%) + + + +jlong_disjoint_arraycopy (5 samples, 0.10%) + + + +sun/reflect/GeneratedMethodAccessor94:::invoke (1 samples, 0.02%) + + + +PhaseIterGVN::remove_globally_dead_node (1 samples, 0.02%) + + + +generic_exec_single (1 samples, 0.02%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +java/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject:::doSignal (1 samples, 0.02%) + + + +JVM_GetStackTraceDepth (3 samples, 0.06%) + + + +java/lang/StringBuffer:::append (11 samples, 0.21%) + + + +InterpreterRuntime::ldc (1 samples, 0.02%) + + + +ipv4_mtu (1 samples, 0.02%) + + + +G1RemSet::oops_into_collection_set_do (5 samples, 0.10%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +org/springframework/boot/actuate/trace/http/HttpTrace$Request:::<init> (73 samples, 1.42%) + + + +java/lang/StringBuilder:::append (1 samples, 0.02%) + + + +org/apache/catalina/connector/InputBuffer:::read (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +jbyte_disjoint_arraycopy (1 samples, 0.02%) + + + +InterpreterRuntime::method (1 samples, 0.02%) + + + +Parse::do_all_blocks (1 samples, 0.02%) + + + +Klass::external_name (5 samples, 0.10%) + + + +com/fasterxml/jackson/databind/ObjectMapper:::_configAndWriteValue (60 samples, 1.17%) + + + +java/util/ArrayList:::toArray (1 samples, 0.02%) + + + +java/util/Arrays:::sort (3 samples, 0.06%) + + + +__schedule (12 samples, 0.23%) + + + +_new_instance_Java (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +system_call_fastpath (6 samples, 0.12%) + + + +org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping$PartialMatchHelper:::<init> (67 samples, 1.30%) + + + +tcp_data_queue (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/AbstractChunk:::indexOf (3 samples, 0.06%) + + + +call_softirq (1 samples, 0.02%) + + + +__audit_syscall_entry (1 samples, 0.02%) + + + +G1CollectedHeap::do_collection_pause_at_safepoint (4 samples, 0.08%) + + + +sys_futex (4 samples, 0.08%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +vfs_read (3 samples, 0.06%) + + + +org/springframework/beans/factory/support/AbstractBeanFactory:::getBeanExpressionResolver (1 samples, 0.02%) + + + +__dev_kfree_skb_any (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer:::deserialze (1 samples, 0.02%) + + + +org/apache/catalina/connector/RequestFacade:::getRequestURL (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +com/coohua/ad/data/utils/GzipUtil:::deCompress (1,033 samples, 20.10%) +com/coohua/ad/data/utils/GzipUt.. + + +ResourceMark::~ResourceMark (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping$PartialMatchHelper:::<init> (4 samples, 0.08%) + + + +sun/nio/ch/SocketChannelImpl:::read (16 samples, 0.31%) + + + +org/springframework/util/StringUtils:::tokenizeToStringArray (1 samples, 0.02%) + + + +ext4_dirty_inode (2 samples, 0.04%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +org/apache/kafka/common/metrics/stats/Count:::update (1 samples, 0.02%) + + + +java/util/concurrent/ThreadPoolExecutor:::runWorker (124 samples, 2.41%) +ja.. + + +jni_GetPrimitiveArrayCritical (148 samples, 2.88%) +jn.. + + +org/apache/coyote/AbstractProcessor:::parseHost (1 samples, 0.02%) + + + +system_call_fastpath (31 samples, 0.60%) + + + +OptoRuntime::is_deoptimized_caller_frame (1 samples, 0.02%) + + + +futex_wake_op (5 samples, 0.10%) + + + +tcp4_gro_receive (1 samples, 0.02%) + + + +__inet_lookup_established (1 samples, 0.02%) + + + +org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite:::selectHandler (2 samples, 0.04%) + + + +java/lang/Class:::getEnclosingMethod0 (1 samples, 0.02%) + + + +java/text/SimpleDateFormat:::initialize (4 samples, 0.08%) + + + +HandleMarkCleaner::~HandleMarkCleaner (32 samples, 0.62%) + + + +com/google/gson/internal/bind/MapTypeAdapterFactory$Adapter:::write (56 samples, 1.09%) + + + +com/alibaba/csp/sentinel/slots/statistic/base/LeapArray:::currentWindow (4 samples, 0.08%) + + + +__libc_calloc (4 samples, 0.08%) + + + +org/apache/kafka/common/requests/ProduceRequest:::toStruct (7 samples, 0.14%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +BarrierSet::static_write_ref_array_post (1 samples, 0.02%) + + + +ObjectSynchronizer::FastHashCode (1 samples, 0.02%) + + + +vfs_read (12 samples, 0.23%) + + + +java/io/ByteArrayInputStream:::read (1 samples, 0.02%) + + + +org/apache/logging/log4j/message/ParameterFormatter:::recursiveDeepToString (3 samples, 0.06%) + + + +java/util/regex/Pattern$Branch:::match (6 samples, 0.12%) + + + +com/google/gson/internal/bind/TypeAdapters$16:::write (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +hrtimer_try_to_cancel.part.25 (3 samples, 0.06%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +java/lang/reflect/Array:::get (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +do_sync_write (11 samples, 0.21%) + + + +Interpreter (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +java/lang/Throwable:::getOurStackTrace (473 samples, 9.20%) +java/lang/Thr.. + + +G1ParEvacuateFollowersClosure::do_void (11 samples, 0.21%) + + + +call_softirq (2 samples, 0.04%) + + + +inet_gro_receive (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/apache/tomcat/util/net/AbstractEndpoint:::processSocket (2 samples, 0.04%) + + + +org/springframework/http/MediaType:::isCompatibleWith (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +com/alibaba/fastjson/serializer/StringCodec:::getFastMatchToken (1 samples, 0.02%) + + + +java/util/regex/Pattern$Slice:::match (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +Method::bci_from (1 samples, 0.02%) + + + +JNIHandleBlock::allocate_handle (2 samples, 0.04%) + + + +jni_GetPrimitiveArrayCritical (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot:::exit (15 samples, 0.29%) + + + +com/coohua/caf/core/sentinel/SentinelHttpFilter:::doFilter (3,548 samples, 69.03%) +com/coohua/caf/core/sentinel/SentinelHttpFilter:::doFilter + + +tcp_rcv_established (1 samples, 0.02%) + + + +skb_checksum (1 samples, 0.02%) + + + +java/util/concurrent/locks/ReentrantLock$NonfairSync:::lock (1 samples, 0.02%) + + + +Type::meet_helper (2 samples, 0.04%) + + + +org/springframework/web/util/UrlPathHelper:::getPathWithinApplication (5 samples, 0.10%) + + + +org/apache/catalina/connector/RequestFacade:::getHeader (4 samples, 0.08%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (1 samples, 0.02%) + + + +epoll_ctl (2 samples, 0.04%) + + + +java/util/zip/GZIPOutputStream:::<init> (49 samples, 0.95%) + + + +hrtimer_try_to_cancel.part.25 (1 samples, 0.02%) + + + +os::stack_shadow_pages_available (1 samples, 0.02%) + + + +org/springframework/context/event/AbstractApplicationEventMulticaster$ListenerCacheKey:::hashCode (1 samples, 0.02%) + + + +org/springframework/web/servlet/handler/AbstractHandlerMethodMapping:::getHandlerInternal (10 samples, 0.19%) + + + +tcp_ack (1 samples, 0.02%) + + + +dput (1 samples, 0.02%) + + + +__wake_up_sync_key (16 samples, 0.31%) + + + +org/apache/kafka/common/protocol/types/ArrayOf:::validate (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +HandleArea::allocate_handle (1 samples, 0.02%) + + + +org/springframework/core/MethodParameter:::getNestedParameterType (1 samples, 0.02%) + + + +Java_java_util_zip_Inflater_end (1 samples, 0.02%) + + + +org/apache/catalina/connector/CoyoteAdapter:::service (224 samples, 4.36%) +org/a.. + + +G1CollectedHeap::evacuate_collection_set (3 samples, 0.06%) + + + +__sb_start_write (1 samples, 0.02%) + + + +irq_exit (2 samples, 0.04%) + + + +Parse::Parse (1 samples, 0.02%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics:::addEvent (1,243 samples, 24.18%) +com/coohuadata/analytics/javasdk/Coohu.. + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +HandleMarkCleaner::~HandleMarkCleaner (36 samples, 0.70%) + + + +java/util/GregorianCalendar:::computeFields (1 samples, 0.02%) + + + +sun/nio/ch/IOUtil:::drain (1 samples, 0.02%) + + + +java/util/HashSet:::iterator (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +org/apache/catalina/mapper/Mapper:::internalMap (1 samples, 0.02%) + + + +org/springframework/boot/actuate/metrics/web/servlet/LongTaskTimingHandlerInterceptor:::afterCompletion (2 samples, 0.04%) + + + +pthread_cond_signal@@GLIBC_2.3.2 (2 samples, 0.04%) + + + +org/apache/tomcat/util/http/NamesEnumerator:::findNext (1 samples, 0.02%) + + + +java/util/HashMap:::put (1 samples, 0.02%) + + + +java/util/ArrayList:::toArray (1 samples, 0.02%) + + + +do_futex (23 samples, 0.45%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11Processor:::endRequest (1 samples, 0.02%) + + + +org/apache/commons/lang3/StringUtils:::splitWorker (7 samples, 0.14%) + + + +java/util/zip/Inflater:::end (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +java/util/HashMap:::put (2 samples, 0.04%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +org/springframework/web/servlet/handler/AbstractHandlerMethodMapping:::lookupHandlerMethod (9 samples, 0.18%) + + + +org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler:::supportsReturnType (1 samples, 0.02%) + + + +fill_window (4 samples, 0.08%) + + + +java/util/GregorianCalendar:::computeFields (1 samples, 0.02%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics:::addEvent (6 samples, 0.12%) + + + +sun/reflect/GeneratedMethodAccessor76:::invoke (1 samples, 0.02%) + + + +sys_futex (24 samples, 0.47%) + + + +org/springframework/core/ResolvableType:::forMethodParameter (1 samples, 0.02%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +org/springframework/aop/aspectj/AbstractAspectJAdvice:::invokeAdviceMethod (1 samples, 0.02%) + + + +__generic_file_aio_write (20 samples, 0.39%) + + + +java/util/zip/Deflater:::end (1 samples, 0.02%) + + + +java/util/concurrent/ThreadPoolExecutor:::execute (23 samples, 0.45%) + + + +sun/reflect/GeneratedMethodAccessor68:::invoke (1 samples, 0.02%) + + + +tcp_schedule_loss_probe (1 samples, 0.02%) + + + +java/lang/String:::toLowerCase (1 samples, 0.02%) + + + +tcp_transmit_skb (1 samples, 0.02%) + + + +system_call_fastpath (24 samples, 0.47%) + + + +smp_apic_timer_interrupt (1 samples, 0.02%) + + + +pthread_cond_signal@@GLIBC_2.3.2 (21 samples, 0.41%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +alloc_pages_current (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +java/lang/Object:::toString (5 samples, 0.10%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +java/lang/StringCoding:::decode (1 samples, 0.02%) + + + +java/util/regex/Pattern$BitClass:::isSatisfiedBy (1 samples, 0.02%) + + + +_complete_monitor_locking_Java (1 samples, 0.02%) + + + +itable stub (1 samples, 0.02%) + + + +org/springframework/web/method/annotation/ModelFactory:::initModel (2 samples, 0.04%) + + + +java/text/SimpleDateFormat:::subParse (4 samples, 0.08%) + + + +sun/misc/Unsafe:::park (5 samples, 0.10%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy5:::annotationType (1 samples, 0.02%) + + + +org/springframework/util/ConcurrentReferenceHashMap$Segment:::restructureIfNecessary (1 samples, 0.02%) + + + +org/springframework/web/servlet/DispatcherServlet:::processDispatchResult (9 samples, 0.18%) + + + +__do_softirq (1 samples, 0.02%) + + + +deflateInit2_ (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition:::matchRequestMethod (5 samples, 0.10%) + + + +sun/nio/ch/EPollArrayWrapper:::epollWait (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::intValue (1 samples, 0.02%) + + + +java/text/DigitList:::getLong (1 samples, 0.02%) + + + +PhaseChaitin::interfere_with_live (3 samples, 0.06%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +OptoRuntime::register_finalizer (1 samples, 0.02%) + + + +Unsafe_Unpark (1 samples, 0.02%) + + + +mutex_unlock (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +futex_wait_queue_me (4 samples, 0.08%) + + + +org/apache/catalina/connector/Request:::setAttribute (4 samples, 0.08%) + + + +pthread_cond_signal@@GLIBC_2.3.2 (2 samples, 0.04%) + + + +BacktraceBuilder::expand (3 samples, 0.06%) + + + +do_softirq (1 samples, 0.02%) + + + +ObjectMonitor::enter (1 samples, 0.02%) + + + +java/util/zip/Deflater:::deflateBytes (9 samples, 0.18%) + + + +org/springframework/web/servlet/handler/AbstractHandlerMapping:::getHandler (10 samples, 0.19%) + + + +org/apache/catalina/connector/RequestFacade:::getAttribute (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +generic_exec_single (1 samples, 0.02%) + + + +deflateResetKeep (2 samples, 0.04%) + + + +tcp_rearm_rto (1 samples, 0.02%) + + + +__mark_inode_dirty (3 samples, 0.06%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +java/util/HashMap:::get (1 samples, 0.02%) + + + +Node::replace_edge (1 samples, 0.02%) + + + +copy_user_enhanced_fast_string (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +java/util/HashMap:::put (2 samples, 0.04%) + + + +do_softirq (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +arrayof_jint_fill (1 samples, 0.02%) + + + +sun/nio/ch/EPollArrayWrapper:::interrupt (26 samples, 0.51%) + + + +G1ParScanThreadState::copy_to_survivor_space (3 samples, 0.06%) + + + +InstanceRefKlass::oop_oop_iterate_backwards_nv (1 samples, 0.02%) + + + +jni_GetPrimitiveArrayCritical (4 samples, 0.08%) + + + +hrtimer_start_range_ns (1 samples, 0.02%) + + + +schedule (3 samples, 0.06%) + + + +sys_read (5 samples, 0.10%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +[libc-2.17.so] (20 samples, 0.39%) + + + +org/apache/kafka/common/requests/ProduceRequest:::<init> (3 samples, 0.06%) + + + +process_backlog (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +__libc_calloc (1 samples, 0.02%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +tcp_poll (1 samples, 0.02%) + + + +checkcast_arraycopy (1 samples, 0.02%) + + + +java/util/concurrent/ArrayBlockingQueue:::offer (2 samples, 0.04%) + + + +sun/reflect/GeneratedMethodAccessor92:::invoke (1 samples, 0.02%) + + + +org/apache/tomcat/util/net/NioEndpoint$Poller:::run (128 samples, 2.49%) +or.. + + +java/util/Vector:::addElement (1 samples, 0.02%) + + + +ip_queue_xmit (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +AddNode::Identity (2 samples, 0.04%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +rcu_process_callbacks (1 samples, 0.02%) + + + +call_stub (5,032 samples, 97.90%) +call_stub + + +java/util/regex/Pattern$5:::isSatisfiedBy (7 samples, 0.14%) + + + +JvmtiVMObjectAllocEventCollector::~JvmtiVMObjectAllocEventCollector (1 samples, 0.02%) + + + +ep_remove (3 samples, 0.06%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/logger/LogSlot:::entry (9 samples, 0.18%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +JavaCalls::call_helper (5 samples, 0.10%) + + + +clock_gettime (1 samples, 0.02%) + + + +sys_poll (1 samples, 0.02%) + + + +all (5,140 samples, 100%) + + + +irq_exit (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +tcp_push (11 samples, 0.21%) + + + +com/coohua/caf/core/kv/JedisClusterClient:::expire (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +java/util/ArrayList:::toArray (1 samples, 0.02%) + + + +org/apache/kafka/common/utils/AbstractIterator:::hasNext (1 samples, 0.02%) + + + +updatewindow (6 samples, 0.12%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +sun/nio/ch/EPollArrayWrapper:::poll (1 samples, 0.02%) + + + +java/util/GregorianCalendar:::computeFields (1 samples, 0.02%) + + + +java/util/LinkedHashMap:::afterNodeAccess (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::getParameterMap (11 samples, 0.21%) + + + +do_softirq (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +com/coohua/ad/data/controller/AdDataController:::process (138 samples, 2.68%) +co.. + + +try_to_wake_up (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (16 samples, 0.31%) + + + +org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver:::resolveArgument (56 samples, 1.09%) + + + +itable stub (4 samples, 0.08%) + + + +net_rx_action (1 samples, 0.02%) + + + +com/coohua/caf/core/metrics/LatencyStat:::observe (1 samples, 0.02%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +updatewindow (18 samples, 0.35%) + + + +do_softirq (2 samples, 0.04%) + + + +tcp_transmit_skb (1 samples, 0.02%) + + + +wake_futex (2 samples, 0.04%) + + + +auditsys (1 samples, 0.02%) + + + +tcp4_gro_receive (1 samples, 0.02%) + + + +__tcp_push_pending_frames (29 samples, 0.56%) + + + +org/springframework/boot/actuate/trace/http/HttpExchangeTracer$$Lambda$656/561830191:::test (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping:::handleMatch (7 samples, 0.14%) + + + +java/util/regex/Pattern$Curly:::match (1 samples, 0.02%) + + + +Parse::do_all_blocks (1 samples, 0.02%) + + + +JavaThread::last_frame (1 samples, 0.02%) + + + +java/lang/String:::toLowerCase (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot:::entry (10 samples, 0.19%) + + + +call_softirq (1 samples, 0.02%) + + + +LShiftLNode::Identity (1 samples, 0.02%) + + + +java/util/zip/Inflater:::inflateBytes (45 samples, 0.88%) + + + +net_rx_action (1 samples, 0.02%) + + + +org/apache/tomcat/util/net/NioEndpoint$Poller:::add (27 samples, 0.53%) + + + +org/springframework/web/servlet/HandlerInterceptor:::postHandle (1 samples, 0.02%) + + + +org/springframework/http/MediaType:::isCompatibleWith (1 samples, 0.02%) + + + +wake_futex (3 samples, 0.06%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +java/nio/ByteBuffer:::get (1 samples, 0.02%) + + + +java/util/LinkedHashMap$LinkedHashIterator:::hasNext (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +try_fill_recv (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11Processor:::prepareResponse (1 samples, 0.02%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter:::doFilterInternal (3,748 samples, 72.92%) +org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter:::doFilterInternal + + +_new_array_Java (1 samples, 0.02%) + + + +java/util/HashMap:::resize (2 samples, 0.04%) + + + +G1CollectedHeap::new_mutator_alloc_region (1 samples, 0.02%) + + + +org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler:::invoke (2 samples, 0.04%) + + + +jni_fast_GetIntField (1 samples, 0.02%) + + + +Type::hashcons (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +java/util/ArrayList:::iterator (1 samples, 0.02%) + + + +schedule (12 samples, 0.23%) + + + +com/coohua/ad/data/interceptor/UserInterceptor:::preHandle (9 samples, 0.18%) + + + +org/apache/catalina/connector/RequestFacade:::getAttribute (1 samples, 0.02%) + + + +java/security/Provider$ServiceKey:::<init> (1 samples, 0.02%) + + + +sock_def_readable (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (3,613 samples, 70.29%) +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter + + +com/alibaba/fastjson/serializer/IntegerCodec:::deserialze (1 samples, 0.02%) + + + +java/util/stream/ReferencePipeline:::collect (15 samples, 0.29%) + + + +__do_softirq (1 samples, 0.02%) + + + +sun/nio/ch/SelectorImpl:::select (31 samples, 0.60%) + + + +org/apache/catalina/authenticator/AuthenticatorBase:::invoke (3,778 samples, 73.50%) +org/apache/catalina/authenticator/AuthenticatorBase:::invoke + + +com/coohua/caf/core/kv/JedisClusterClient:::incrBy (1 samples, 0.02%) + + + +__strlen_sse2_pminub (4 samples, 0.08%) + + + +org/apache/tomcat/util/net/NioBlockingSelector$BlockPoller:::events (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +wake_futex (15 samples, 0.29%) + + + +Java_sun_nio_ch_EPollArrayWrapper_interrupt (1 samples, 0.02%) + + + +java/util/AbstractList$Itr:::hasNext (1 samples, 0.02%) + + + +smp_apic_timer_interrupt (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +org/springframework/util/StringUtils:::uriDecode (3 samples, 0.06%) + + + +java/io/ObjectOutputStream:::writeArray (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/logger/LogSlot:::entry (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +TypeArrayKlass::allocate_common (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (3,448 samples, 67.08%) +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter + + +pthread_getspecific (2 samples, 0.04%) + + + +UTF8::convert_to_unicode (2 samples, 0.04%) + + + +java/lang/String:::equals (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/impl/Log4jLogEvent:::getTimeMillis (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (192 samples, 3.74%) +org/.. + + +__do_softirq (1 samples, 0.02%) + + + +_new_array_nozero_Java (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (3,605 samples, 70.14%) +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter + + +pthread_getspecific (1 samples, 0.02%) + + + +deflate (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy0:::annotationType (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::nextToken (1 samples, 0.02%) + + + +java_lang_Thread::park_event (1 samples, 0.02%) + + + +kfree (1 samples, 0.02%) + + + +__vdso_gettimeofday (2 samples, 0.04%) + + + +generic_exec_single (1 samples, 0.02%) + + + +Interpreter (236 samples, 4.59%) +Inter.. + + +__do_softirq (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +writeBytes (40 samples, 0.78%) + + + +sys_futex (2 samples, 0.04%) + + + +ep_ptable_queue_proc (2 samples, 0.04%) + + + +Java_java_net_SocketOutputStream_socketWrite0 (3 samples, 0.06%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +com/sun/crypto/provider/AESCrypt:::init (17 samples, 0.33%) + + + +epoll_wait (1 samples, 0.02%) + + + +fsnotify (1 samples, 0.02%) + + + +java/lang/String:::charAt (3 samples, 0.06%) + + + +call_softirq (2 samples, 0.04%) + + + +tcp_transmit_skb (4 samples, 0.08%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +vfs_write (24 samples, 0.47%) + + + +call_softirq (1 samples, 0.02%) + + + +checkcast_arraycopy_uninit (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::setAttribute (1 samples, 0.02%) + + + +ThreadInVMfromNative::~ThreadInVMfromNative (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +futex_wait_queue_me (21 samples, 0.41%) + + + +javax/crypto/Cipher$Transform:::matches (2 samples, 0.04%) + + + +Parse::do_one_block (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/ser/std/NumberSerializers$LongSerializer:::serialize (1 samples, 0.02%) + + + +JavaThread::thread_main_inner (5,080 samples, 98.83%) +JavaThread::thread_main_inner + + +__do_softirq (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/StringCache:::toString (2 samples, 0.04%) + + + +java/util/regex/Pattern$Curly:::match (4 samples, 0.08%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/springframework/web/method/HandlerMethod:::<init> (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +resource_allocate_bytes (4 samples, 0.08%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +java/text/SimpleDateFormat:::initialize (15 samples, 0.29%) + + + +java/text/SimpleDateFormat:::compile (1 samples, 0.02%) + + + +Java_java_net_SocketInputStream_socketRead0 (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11Processor:::service (3,985 samples, 77.53%) +org/apache/coyote/http11/Http11Processor:::service + + +getnstimeofday64 (1 samples, 0.02%) + + + +itable stub (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +ConstantPool::klass_at_impl (1 samples, 0.02%) + + + +java/util/HashMap:::newNode (4 samples, 0.08%) + + + +sun/reflect/GeneratedMethodAccessor75:::invoke (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11InputBuffer:::parseRequestLine (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +org/springframework/web/bind/support/WebBindingInitializer:::initBinder (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (3,548 samples, 69.03%) +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/ArrayOf:::validate (1 samples, 0.02%) + + + +org/springframework/boot/actuate/trace/http/HttpExchangeTracer$$Lambda$699/710427695:::get (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +sysret_audit (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +tcp4_gro_receive (1 samples, 0.02%) + + + +__lll_unlock_wake (2 samples, 0.04%) + + + +do_softirq (1 samples, 0.02%) + + + +java/util/zip/GZIPOutputStream:::finish (9 samples, 0.18%) + + + +vtable stub (2 samples, 0.04%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +G1CollectedHeap::allocate_new_tlab (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::recycle (1 samples, 0.02%) + + + +jbd2_journal_get_write_access (1 samples, 0.02%) + + + +generic_exec_single (1 samples, 0.02%) + + + +do_softirq (2 samples, 0.04%) + + + +java/util/Formatter$FormatSpecifier:::print (7 samples, 0.14%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +org/apache/tomcat/util/net/NioEndpoint$SocketProcessor:::doRun (227 samples, 4.42%) +org/a.. + + +java/util/regex/Pattern$GroupHead:::match (3 samples, 0.06%) + + + +org/apache/logging/log4j/core/pattern/MessagePatternConverter:::format (3 samples, 0.06%) + + + +java/util/Formatter$FormatSpecifier:::print (4 samples, 0.08%) + + + +do_softirq (1 samples, 0.02%) + + + +oopDesc::obj_field_put (7 samples, 0.14%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +Parker::park (1 samples, 0.02%) + + + +org/springframework/util/AntPathMatcher:::doMatch (5 samples, 0.10%) + + + +tcp_done (1 samples, 0.02%) + + + +CollectedHeap::common_mem_allocate_init (1 samples, 0.02%) + + + +java/util/regex/Pattern$GroupHead:::match (106 samples, 2.06%) +j.. + + +org/apache/logging/log4j/core/filter/AbstractFilterable:::isFiltered (1 samples, 0.02%) + + + +_new_instance_Java (2 samples, 0.04%) + + + +org/apache/kafka/common/protocol/types/Type$7:::write (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +security_socket_recvmsg (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +finish_task_switch (1 samples, 0.02%) + + + +__schedule (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +JVM_FillInStackTrace (4 samples, 0.08%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +java/util/Comparator$$Lambda$213/1325144078:::compare (2 samples, 0.04%) + + + +do_futex (1 samples, 0.02%) + + + +java/lang/AbstractStringBuilder:::append (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/DefaultJSONParser:::parse (1 samples, 0.02%) + + + +java/util/regex/Pattern$GroupHead:::match (2 samples, 0.04%) + + + +org/apache/logging/log4j/message/ParameterizedMessage:::getFormattedMessage (6 samples, 0.12%) + + + +validate_xmit_skb.part.93 (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +virtnet_poll (1 samples, 0.02%) + + + +itable stub (3 samples, 0.06%) + + + +call_softirq (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +java/util/AbstractList$Itr:::hasNext (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +ext4_mark_inode_dirty (2 samples, 0.04%) + + + +java/util/concurrent/ConcurrentHashMap:::replaceNode (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +tcp_push (4 samples, 0.08%) + + + +org/springframework/beans/factory/support/AbstractBeanFactory:::getBean (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/springframework/web/filter/HiddenHttpMethodFilter:::doFilterInternal (3,679 samples, 71.58%) +org/springframework/web/filter/HiddenHttpMethodFilter:::doFilterInternal + + +JVM_GetStackTraceElement (29 samples, 0.56%) + + + +_raw_spin_lock_bh (1 samples, 0.02%) + + + +ParseGenerator::generate (1 samples, 0.02%) + + + +com/coohua/platform/security/AESCoder:::decrypt (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +java/net/SocketOutputStream:::socketWrite0 (3 samples, 0.06%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +HandleMarkCleaner::~HandleMarkCleaner (1 samples, 0.02%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (6 samples, 0.12%) + + + +os::javaTimeNanos (1 samples, 0.02%) + + + +netif_skb_features (1 samples, 0.02%) + + + +JNIHandleBlock::allocate_handle (1 samples, 0.02%) + + + +org/springframework/web/filter/OncePerRequestFilter:::doFilter (216 samples, 4.20%) +org/s.. + + +lock_hrtimer_base.isra.20 (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +napi_gro_receive (1 samples, 0.02%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +java/util/StringTokenizer:::nextToken (1 samples, 0.02%) + + + +virtnet_poll (1 samples, 0.02%) + + + +__vdso_gettimeofday (2 samples, 0.04%) + + + +inet_sendmsg (16 samples, 0.31%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +java_lang_Throwable::fill_in_stack_trace (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +org/springframework/web/context/request/AbstractRequestAttributes:::executeRequestDestructionCallbacks (1 samples, 0.02%) + + + +tcp_recvmsg (3 samples, 0.06%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/appender/AsyncAppender$AsyncThread:::callAppenders (60 samples, 1.17%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +org/joda/time/tz/CachedDateTimeZone:::getInfo (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +org/jboss/netty/channel/SimpleChannelHandler:::writeRequested (2 samples, 0.04%) + + + +java/util/concurrent/ConcurrentHashMap:::putVal (2 samples, 0.04%) + + + +com/alibaba/csp/sentinel/slots/statistic/StatisticSlot:::entry (1 samples, 0.02%) + + + +G1CollectedHeap::process_discovered_references (3 samples, 0.06%) + + + +_new_array_nozero_Java (1 samples, 0.02%) + + + +__alloc_pages_nodemask (1 samples, 0.02%) + + + +org/springframework/util/AntPathMatcher:::doMatch (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/Schema:::read (5 samples, 0.10%) + + + +netif_receive_skb_internal (1 samples, 0.02%) + + + +java/util/HashMap$HashIterator:::hasNext (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +tcp_check_req (1 samples, 0.02%) + + + +java_lang_StackTraceElement::create (3 samples, 0.06%) + + + +java/util/concurrent/ArrayBlockingQueue:::offer (3 samples, 0.06%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +org/apache/kafka/clients/producer/internals/RecordAccumulator:::expiredBatches (2 samples, 0.04%) + + + +__inet_lookup_established (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor51:::invoke (3 samples, 0.06%) + + + +org/apache/catalina/connector/CoyoteAdapter:::postParseRequest (1 samples, 0.02%) + + + +java/nio/charset/Charset:::atBugLevel (1 samples, 0.02%) + + + +sun/nio/ch/EPollArrayWrapper:::poll (3 samples, 0.06%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +frame::sender_for_compiled_frame (15 samples, 0.29%) + + + +org/springframework/boot/actuate/web/trace/servlet/TraceableHttpServletRequest:::extractHeaders (29 samples, 0.56%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (1 samples, 0.02%) + + + +com/coohua/ad/data/manager/AdLogTrackManager:::logTrack (6 samples, 0.12%) + + + +free_old_xmit_skbs.isra.32 (1 samples, 0.02%) + + + +sun/nio/cs/UTF_8$Decoder:::decode (1 samples, 0.02%) + + + +do_sync_read (9 samples, 0.18%) + + + +org/springframework/context/event/SimpleApplicationEventMulticaster:::multicastEvent (3 samples, 0.06%) + + + +org/apache/catalina/connector/CoyoteAdapter:::parseSessionCookiesId (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +irq_exit (2 samples, 0.04%) + + + +napi_complete_done (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +finish_task_switch (12 samples, 0.23%) + + + +os::javaTimeMillis (3 samples, 0.06%) + + + +java/util/Collections$UnmodifiableSet:::hashCode (1 samples, 0.02%) + + + +Parse::do_all_blocks (1 samples, 0.02%) + + + +run_timer_softirq (1 samples, 0.02%) + + + +futex_wake_op (2 samples, 0.04%) + + + +_new_array_Java (1 samples, 0.02%) + + + +tcp_v4_early_demux (1 samples, 0.02%) + + + +java/util/concurrent/ThreadPoolExecutor:::runWorker (4,105 samples, 79.86%) +java/util/concurrent/ThreadPoolExecutor:::runWorker + + +os::javaTimeMillis (1 samples, 0.02%) + + + +hash_futex (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/ser/impl/PropertySerializerMap$Multi:::serializerFor (1 samples, 0.02%) + + + +java/util/LinkedHashMap$LinkedHashIterator:::hasNext (1 samples, 0.02%) + + + +java/util/HashMap:::putMapEntries (18 samples, 0.35%) + + + +wake_futex (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +G1SATBCardTableLoggingModRefBS::write_ref_array_work (2 samples, 0.04%) + + + +G1SATBCardTableLoggingModRefBS::invalidate (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/condition/AbstractMediaTypeExpression:::compareTo (3 samples, 0.06%) + + + +checkcast_arraycopy (3 samples, 0.06%) + + + +java/lang/ThreadLocal:::set (4 samples, 0.08%) + + + +Compile::Code_Gen (30 samples, 0.58%) + + + +ret_from_intr (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +jbd2_journal_dirty_metadata (1 samples, 0.02%) + + + +com/coohua/ad/data/manager/AdDataManager:::incrMiniProgramClickLimit (1 samples, 0.02%) + + + +set_skb_frag (1 samples, 0.02%) + + + +mutex_unlock (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy40:::annotationType (1 samples, 0.02%) + + + +sun/nio/cs/UTF_8$Decoder:::decodeArrayLoop (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +OptoRuntime::new_array_C (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/ArrayOf:::sizeOf (1 samples, 0.02%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor60:::invoke (1 samples, 0.02%) + + + +_raw_spin_lock (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +UTF8::unicode_length (19 samples, 0.37%) + + + +org/apache/logging/log4j/message/ParameterizedMessage:::getFormattedMessage (1 samples, 0.02%) + + + +com/coohua/ad/data/interceptor/UserInterceptor$BaseKeyUglyUtil:::getBaseKey (97 samples, 1.89%) +c.. + + +com/alibaba/fastjson/parser/deserializer/FieldDeserializer:::setValue (1 samples, 0.02%) + + + +MemNode::Ideal_common (1 samples, 0.02%) + + + +__ext4_get_inode_loc (1 samples, 0.02%) + + + +com/alibaba/fastjson/JSON:::parseObject (13 samples, 0.25%) + + + +org/springframework/beans/TypeConverterSupport:::doConvert (33 samples, 0.64%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +org/apache/kafka/clients/NodeApiVersions:::latestUsableVersion (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/ser/std/NumberSerializers$LongSerializer:::serialize (1 samples, 0.02%) + + + +file_update_time (3 samples, 0.06%) + + + +__do_softirq (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +sch_direct_xmit (3 samples, 0.06%) + + + +run_timer_softirq (1 samples, 0.02%) + + + +sys_futex (1 samples, 0.02%) + + + +java/util/stream/ReduceOps$3:::getOpFlags (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +java/util/concurrent/ConcurrentHashMap:::get (7 samples, 0.14%) + + + +process_backlog (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11Processor:::prepareResponse (1 samples, 0.02%) + + + +futex_wait_queue_me (5 samples, 0.10%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +InstanceKlass::allocate_instance (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (192 samples, 3.74%) +org/.. + + +java/util/ArrayList:::isEmpty (1 samples, 0.02%) + + + +clock_gettime (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer:::smartMatch (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +ep_scan_ready_list.isra.7 (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +JVM_GetStackTraceDepth (1 samples, 0.02%) + + + +io/micrometer/core/instrument/Clock$1:::monotonicTime (1 samples, 0.02%) + + + +do_sync_read (2 samples, 0.04%) + + + +__skb_gro_checksum_complete (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +local_bh_disable (1 samples, 0.02%) + + + +com/coohua/platform/security/AESCoder:::decrypt (7 samples, 0.14%) + + + +ret_from_intr (1 samples, 0.02%) + + + +org/apache/tomcat/util/http/ValuesEnumerator:::findNext (7 samples, 0.14%) + + + +unlock_page (1 samples, 0.02%) + + + +java/lang/String:::toLowerCase (7 samples, 0.14%) + + + +org/apache/kafka/common/protocol/types/ArrayOf:::read (5 samples, 0.10%) + + + +CollectedHeap::common_mem_allocate_init (6 samples, 0.12%) + + + +smp_apic_timer_interrupt (1 samples, 0.02%) + + + +auditsys (1 samples, 0.02%) + + + +Java_java_util_zip_Deflater_deflateBytes (169 samples, 3.29%) +Jav.. + + +java/util/regex/Pattern:::matcher (2 samples, 0.04%) + + + +java/util/HashMap:::put (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +sk_reset_timer (1 samples, 0.02%) + + + +java/lang/StringBuffer:::append (3 samples, 0.06%) + + + +kvm_clock_get_cycles (1 samples, 0.02%) + + + +skb_checksum (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +org/apache/tomcat/util/http/MimeHeaders:::getUniqueValue (1 samples, 0.02%) + + + +tcp4_gro_receive (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +itable stub (1 samples, 0.02%) + + + +jni_SetIntField (5 samples, 0.10%) + + + +sun/nio/cs/ISO_8859_1$Decoder:::decodeLoop (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +set_skb_frag (1 samples, 0.02%) + + + +JVM_IHashCode (1 samples, 0.02%) + + + +itable stub (2 samples, 0.04%) + + + +org/springframework/web/util/UrlPathHelper:::getPathWithinApplication (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +org/apache/coyote/AbstractProtocol$ConnectionHandler:::process (225 samples, 4.38%) +org/a.. + + +com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot:::entry (2 samples, 0.04%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +java/util/zip/InflaterInputStream:::read (48 samples, 0.93%) + + + +do_softirq (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +__tcp_push_pending_frames (11 samples, 0.21%) + + + +do_futex (31 samples, 0.60%) + + + +org/springframework/core/annotation/AnnotationUtils:::getAnnotation (2 samples, 0.04%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +org/apache/kafka/common/utils/Checksums:::update (8 samples, 0.16%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +org/springframework/web/servlet/FrameworkServlet:::publishRequestHandledEvent (29 samples, 0.56%) + + + +do_softirq (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11InputBuffer:::parseRequestLine (32 samples, 0.62%) + + + +sun/nio/ch/EPollArrayWrapper:::interrupt (12 samples, 0.23%) + + + +ip_rcv (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +java/util/ArrayList:::toArray (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +ConstantPool::klass_at_impl (1 samples, 0.02%) + + + +org/apache/kafka/clients/InFlightRequests:::nodesWithTimedOutRequests (3 samples, 0.06%) + + + +org/apache/logging/log4j/core/appender/AsyncAppender:::append (47 samples, 0.91%) + + + +[libpthread-2.17.so] (6 samples, 0.12%) + + + +sun/misc/Unsafe:::unpark (2 samples, 0.04%) + + + +org/apache/kafka/clients/producer/internals/RecordAccumulator:::append (13 samples, 0.25%) + + + +frame::sender (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +java/util/Formatter$FormatSpecifier:::print (2 samples, 0.04%) + + + +org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping:::handleMatch (1 samples, 0.02%) + + + +os::is_interrupted (1 samples, 0.02%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +[libpthread-2.17.so] (1 samples, 0.02%) + + + +org/springframework/util/PropertyPlaceholderHelper:::parseStringValue (2 samples, 0.04%) + + + +Klass::external_name (2 samples, 0.04%) + + + +g1_post_barrier_slow Runtime1 stub (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/CtSph:::entryWithPriority (1 samples, 0.02%) + + + +tcp_transmit_skb (1 samples, 0.02%) + + + +G1CollectedHeap::allocate_new_tlab (1 samples, 0.02%) + + + +try_to_wake_up (15 samples, 0.29%) + + + +packet_rcv (3 samples, 0.06%) + + + +java/util/stream/ReferencePipeline:::collect (3 samples, 0.06%) + + + +java/util/zip/Deflater:::init (43 samples, 0.84%) + + + +process_backlog (1 samples, 0.02%) + + + +try_to_wake_up (5 samples, 0.10%) + + + +com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot:::entry (12 samples, 0.23%) + + + +com/coohua/caf/core/sentinel/SentinelHttpInterceptor:::afterCompletion (18 samples, 0.35%) + + + +org/springframework/boot/actuate/web/trace/servlet/TraceableHttpServletRequest:::getUri (10 samples, 0.19%) + + + +sysret_audit (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +ext4_mark_inode_dirty (7 samples, 0.14%) + + + +call_softirq (1 samples, 0.02%) + + + +sysret_audit (1 samples, 0.02%) + + + +org/springframework/web/servlet/FrameworkServlet:::publishRequestHandledEvent (3 samples, 0.06%) + + + +StringTable::intern (242 samples, 4.71%) +Strin.. + + +fsnotify (1 samples, 0.02%) + + + +vfs_write (41 samples, 0.80%) + + + +com/sun/proxy/$Proxy109:::annotationType (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter:::format (1 samples, 0.02%) + + + +java/util/ArrayList:::toArray (2 samples, 0.04%) + + + +__x2apic_send_IPI_mask (2 samples, 0.04%) + + + +java/util/GregorianCalendar:::getFixedDate (1 samples, 0.02%) + + + +inet_recvmsg (3 samples, 0.06%) + + + +org/springframework/web/servlet/mvc/method/RequestMappingInfo:::getMatchingCondition (1 samples, 0.02%) + + + +org/apache/tomcat/util/net/NioEndpoint$NioSocketWrapper:::doWrite (5 samples, 0.10%) + + + +com/sun/proxy/$Proxy77:::annotationType (2 samples, 0.04%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +tcp_v4_early_demux (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +sun/nio/ch/SocketChannelImpl:::read (1 samples, 0.02%) + + + +InstanceKlass::allocate_objArray (1 samples, 0.02%) + + + +java/lang/String:::hashCode (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (3 samples, 0.06%) + + + +java/net/URI$Parser:::parseAuthority (2 samples, 0.04%) + + + +org/apache/catalina/connector/CoyoteAdapter:::parsePathParameters (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor103:::invoke (1 samples, 0.02%) + + + +sys_futex (2 samples, 0.04%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +tcp_write_xmit (1 samples, 0.02%) + + + +java/lang/ThreadLocal$ThreadLocalMap:::set (3 samples, 0.06%) + + + +do_softirq (1 samples, 0.02%) + + + +__do_softirq (2 samples, 0.04%) + + + +java_lang_StackTraceElement::set_fileName (1 samples, 0.02%) + + + +java/util/regex/Pattern$Slice:::match (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy0:::annotationType (1 samples, 0.02%) + + + +sys_futex (5 samples, 0.10%) + + + +CodeHeap::find_start (1 samples, 0.02%) + + + +__audit_syscall_exit (2 samples, 0.04%) + + + +SYSC_recvfrom (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::recycle (1 samples, 0.02%) + + + +do_futex (19 samples, 0.37%) + + + +java/util/regex/Pattern:::matcher (2 samples, 0.04%) + + + +org/apache/tomcat/util/buf/StringCache:::toString (12 samples, 0.23%) + + + +G1SATBCardTableLoggingModRefBS::write_ref_array_work (1 samples, 0.02%) + + + +objArrayOopDesc::obj_at (1 samples, 0.02%) + + + +[libpthread-2.17.so] (47 samples, 0.91%) + + + +ret_from_intr (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +[libc-2.17.so] (18 samples, 0.35%) + + + +java_lang_StackTraceElement::create (2 samples, 0.04%) + + + +[libpthread-2.17.so] (12 samples, 0.23%) + + + +process_backlog (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +java/util/Arrays:::sort (1 samples, 0.02%) + + + +java/text/DecimalFormat:::subparse (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/springframework/http/converter/AbstractHttpMessageConverter:::canWrite (4 samples, 0.08%) + + + +irq_exit (1 samples, 0.02%) + + + +system_call_fastpath (3 samples, 0.06%) + + + +java/util/concurrent/ArrayBlockingQueue:::offer (35 samples, 0.68%) + + + +org/apache/kafka/common/protocol/types/ArrayOf:::validate (1 samples, 0.02%) + + + +com/alibaba/fastjson/serializer/StringCodec:::deserialze (1 samples, 0.02%) + + + +sun/nio/ch/EPollArrayWrapper:::interrupt (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +com/alibaba/fastjson/JSON:::parseObject (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +irq_exit (2 samples, 0.04%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +generic_exec_single (1 samples, 0.02%) + + + +Parse::do_one_block (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +page_to_skb (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +jni_GetByteArrayRegion (1 samples, 0.02%) + + + +LoadNode::Ideal (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/ArrayOf:::sizeOf (2 samples, 0.04%) + + + +java/util/concurrent/ConcurrentHashMap:::replaceNode (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +org/apache/kafka/common/metrics/Sensor:::record (6 samples, 0.12%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +java/util/Formatter$FormatSpecifier:::print (3 samples, 0.06%) + + + +Unsafe_Unpark (2 samples, 0.04%) + + + +dev_gro_receive (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +map_id_up (1 samples, 0.02%) + + + +__audit_syscall_exit (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy49:::annotationType (1 samples, 0.02%) + + + +java/util/concurrent/ThreadPoolExecutor:::getTask (5 samples, 0.10%) + + + +org/apache/kafka/common/protocol/types/ArrayOf:::sizeOf (4 samples, 0.08%) + + + +__do_softirq (1 samples, 0.02%) + + + +com/coohua/caf/core/kv/JedisClusterClient$$FastClassBySpringCGLIB$$1bc9b45c:::invoke (1 samples, 0.02%) + + + +Unsafe_Park (38 samples, 0.74%) + + + +org/apache/logging/log4j/core/pattern/LiteralPatternConverter:::format (1 samples, 0.02%) + + + +java/text/DateFormatSymbols:::copyMembers (2 samples, 0.04%) + + + +sun/nio/cs/UTF_8$Encoder:::encode (10 samples, 0.19%) + + + +java/util/regex/Pattern$Branch:::match (4 samples, 0.08%) + + + +jlong_disjoint_arraycopy (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +tcp_send_ack (1 samples, 0.02%) + + + +generic_exec_single (1 samples, 0.02%) + + + +sun/nio/cs/ISO_8859_1$Decoder:::decodeLoop (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +cap_file_permission (1 samples, 0.02%) + + + +tcp_rearm_rto (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONScanner:::scanFieldLong (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +apic_timer_interrupt (1 samples, 0.02%) + + + +java_lang_String::equals (37 samples, 0.72%) + + + +_raw_spin_unlock_irqrestore (2 samples, 0.04%) + + + +org/springframework/util/StringUtils:::uriDecode (2 samples, 0.04%) + + + +build_tree (3 samples, 0.06%) + + + +ktime_get_real (1 samples, 0.02%) + + + +do_futex (2 samples, 0.04%) + + + +ret_from_intr (1 samples, 0.02%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (1 samples, 0.02%) + + + +java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1:::next (2 samples, 0.04%) + + + +schedule_hrtimeout_range (16 samples, 0.31%) + + + +G1CollectedHeap::unsafe_max_tlab_alloc (1 samples, 0.02%) + + + +com/coohua/caf/core/kv/JedisClusterClientAspect:::around (4 samples, 0.08%) + + + +sk_stream_alloc_skb (2 samples, 0.04%) + + + +inet_recvmsg (9 samples, 0.18%) + + + +java/lang/StringBuffer:::append (1 samples, 0.02%) + + + +org/apache/kafka/common/metrics/stats/SampledStat:::record (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +__skb_to_sgvec (2 samples, 0.04%) + + + +org/apache/tomcat/util/http/NamesEnumerator:::findNext (1 samples, 0.02%) + + + +org/springframework/util/StringUtils:::tokenizeToStringArray (23 samples, 0.45%) + + + +sun/nio/ch/IOUtil:::drain (3 samples, 0.06%) + + + +sun/nio/cs/UTF_8$Decoder:::decode (1 samples, 0.02%) + + + +java/util/regex/Pattern$BmpCharProperty:::match (1 samples, 0.02%) + + + +vfs_read (5 samples, 0.10%) + + + +net_rx_action (1 samples, 0.02%) + + + +try_to_wake_up (2 samples, 0.04%) + + + +org/springframework/util/ConcurrentReferenceHashMap$Segment:::restructureIfNecessary (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::parseParameters (63 samples, 1.23%) + + + +sun/misc/Unsafe:::park (33 samples, 0.64%) + + + +Unsafe_Unpark (2 samples, 0.04%) + + + +java_lang_Throwable::get_stack_trace_element (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +CodeBlob::is_zombie (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +org/apache/kafka/common/metrics/Sensor:::record (8 samples, 0.16%) + + + +objArrayOopDesc::obj_at (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +CodeHeap::find_start (7 samples, 0.14%) + + + +org/apache/kafka/common/requests/ProduceResponse:::<init> (1 samples, 0.02%) + + + +java/util/regex/Pattern:::sequence (1 samples, 0.02%) + + + +_raw_spin_lock (1 samples, 0.02%) + + + +java/util/stream/ReferencePipeline$2$1:::accept (7 samples, 0.14%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +itable stub (1 samples, 0.02%) + + + +org/springframework/web/method/annotation/InitBinderDataBinderFactory:::initBinder (1 samples, 0.02%) + + + +java/util/HashMap:::newNode (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +javax/crypto/Cipher:::getInstance (5 samples, 0.10%) + + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +StringTable::intern (14 samples, 0.27%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +java/util/LinkedHashMap:::get (1 samples, 0.02%) + + + +org/springframework/boot/actuate/metrics/web/servlet/LongTaskTimingHandlerInterceptor:::afterCompletion (1 samples, 0.02%) + + + +sys_futex (19 samples, 0.37%) + + + +net_rx_action (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +java/util/Locale:::getLanguage (1 samples, 0.02%) + + + +JavaThread::thread_from_jni_environment (22 samples, 0.43%) + + + +do_futex (2 samples, 0.04%) + + + +HandleMarkCleaner::~HandleMarkCleaner (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +sun/nio/cs/UTF_8$Encoder:::encode (1 samples, 0.02%) + + + +futex_wake_op (3 samples, 0.06%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +pipe_write (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +pthread_self (1 samples, 0.02%) + + + +org/apache/kafka/common/network/NetworkReceive:::readFrom (18 samples, 0.35%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (1 samples, 0.02%) + + + +__schedule (1 samples, 0.02%) + + + +__schedule (1 samples, 0.02%) + + + +sys_write (25 samples, 0.49%) + + + +org/apache/logging/log4j/core/config/AwaitCompletionReliabilityStrategy:::afterLogEvent (1 samples, 0.02%) + + + +ext4_get_inode_loc (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +java/text/DateFormatSymbols:::initializeData (2 samples, 0.04%) + + + +java/util/LinkedHashMap:::clear (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (200 samples, 3.89%) +org/.. + + +org/apache/tomcat/util/http/MimeHeaders:::getValue (2 samples, 0.04%) + + + +org/apache/kafka/clients/producer/internals/Sender$SenderMetrics:::maybeRegisterTopicMetrics (1 samples, 0.02%) + + + +napi_gro_receive (1 samples, 0.02%) + + + +pthread_cond_timedwait@@GLIBC_2.3.2 (29 samples, 0.56%) + + + +java/util/Calendar:::<init> (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +frame::oops_do_internal (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor66:::invoke (1 samples, 0.02%) + + + +itable stub (1 samples, 0.02%) + + + +org/springframework/web/servlet/support/WebContentGenerator:::prepareResponse (1 samples, 0.02%) + + + +org/springframework/web/util/UrlPathHelper:::getLookupPathForRequest (9 samples, 0.18%) + + + +itable stub (1 samples, 0.02%) + + + +java/lang/Throwable:::getStackTraceDepth (4 samples, 0.08%) + + + +do_sync_write (1 samples, 0.02%) + + + +com/coohua/ad/data/interceptor/UserInterceptor$BaseKeyUglyUtil:::getBaseKey (9 samples, 0.18%) + + + +com/fasterxml/jackson/databind/ser/impl/PropertySerializerMap$Double:::serializerFor (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +java/util/Formatter:::format (23 samples, 0.45%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::matchField (3 samples, 0.06%) + + + +com/sun/crypto/provider/AESCrypt:::makeSessionKey (1 samples, 0.02%) + + + +virtnet_poll (1 samples, 0.02%) + + + +rw_verify_area (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +ext4_get_inode_loc (1 samples, 0.02%) + + + +java/net/URI$Parser:::parseHostname (1 samples, 0.02%) + + + +do_sync_write (38 samples, 0.74%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +tcp_data_queue (1 samples, 0.02%) + + + +os::javaTimeMillis (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (3,765 samples, 73.25%) +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter + + +ip_local_out_sk (1 samples, 0.02%) + + + +apic_timer_interrupt (1 samples, 0.02%) + + + +PhaseIdealLoop::split_if_with_blocks_post (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +start_thread (5,106 samples, 99.34%) +start_thread + + +do_futex (1 samples, 0.02%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +__libc_send (6 samples, 0.12%) + + + +com/coohua/caf/core/sentinel/SentinelHttpInterceptor:::afterCompletion (1 samples, 0.02%) + + + +_raw_spin_unlock (1 samples, 0.02%) + + + +wake_futex (1 samples, 0.02%) + + + +tcp_poll (4 samples, 0.08%) + + + +java/util/regex/Pattern$BranchConn:::match (3 samples, 0.06%) + + + +dev_gro_receive (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +org/springframework/boot/actuate/trace/http/HttpExchangeTracer:::getHeadersIfIncluded (4 samples, 0.08%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +Interpreter (12 samples, 0.23%) + + + +futex_wake_op (31 samples, 0.60%) + + + +com/fasterxml/jackson/databind/ser/std/MapSerializer:::serializeFields (34 samples, 0.66%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +com/coohua/caf/core/metrics/LatencyStat:::setMin (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +java/util/GregorianCalendar:::computeFields (2 samples, 0.04%) + + + +com/coohua/ad/data/manager/AdLogTrackManager:::logTrack (60 samples, 1.17%) + + + +java/util/regex/Pattern$BranchConn:::match (1 samples, 0.02%) + + + +_new_array_nozero_Java (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender:::append (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +call_stub (1 samples, 0.02%) + + + +java/util/HashMap:::newNode (1 samples, 0.02%) + + + +pvclock_clocksource_read (1 samples, 0.02%) + + + +page_waitqueue (1 samples, 0.02%) + + + +file_update_time (1 samples, 0.02%) + + + +java/lang/StringBuilder:::append (1 samples, 0.02%) + + + +java/util/HashMap:::put (3 samples, 0.06%) + + + +com/weibo/api/motan/filter/AccessLogFilter:::logAccess (5 samples, 0.10%) + + + +napi_gro_receive (1 samples, 0.02%) + + + +tcp_send_ack (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils$AnnotationCollector:::process (11 samples, 0.21%) + + + +call_stub (1 samples, 0.02%) + + + +sun/security/jca/ProviderList$ServiceList:::tryGet (23 samples, 0.45%) + + + +sys_epoll_ctl (13 samples, 0.25%) + + + +java/lang/ref/Reference:::tryHandlePending (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +Reflection::array_get (2 samples, 0.04%) + + + +net_rx_action (1 samples, 0.02%) + + + +get_page_from_freelist (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +com/coohua/ad/data/manager/AdDataManager:::incrUserDayClickLimit (1 samples, 0.02%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +org/springframework/web/filter/OncePerRequestFilter:::doFilter (200 samples, 3.89%) +org/.. + + +__do_softirq (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/ser/std/NumberSerializers$IntegerSerializer:::serialize (1 samples, 0.02%) + + + +JNIHandleBlock::allocate_handle (4 samples, 0.08%) + + + +UTF8::unicode_length (1 samples, 0.02%) + + + +io/micrometer/core/instrument/Tags$ArrayIterator:::<init> (1 samples, 0.02%) + + + +org/springframework/web/util/UrlPathHelper:::decodeInternal (3 samples, 0.06%) + + + +WeakPreserveExceptionMark::WeakPreserveExceptionMark (14 samples, 0.27%) + + + +java/util/GregorianCalendar:::computeTime (1 samples, 0.02%) + + + +Java_java_net_SocketInputStream_socketRead0 (1 samples, 0.02%) + + + +com/coohua/caf/core/kv/JedisClusterClient$$EnhancerBySpringCGLIB$$b8ee9ffc:::incrBy (1 samples, 0.02%) + + + +com/coohua/caf/core/sentinel/SentinelHttpFilter:::doFilter (200 samples, 3.89%) +com/.. + + +java/util/HashMap:::put (2 samples, 0.04%) + + + +hrtimer_start_range_ns (1 samples, 0.02%) + + + +JNIHandleBlock::allocate_handle (19 samples, 0.37%) + + + +org/springframework/aop/aspectj/AbstractAspectJAdvice:::invokeAdviceMethod (2 samples, 0.04%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +inet_gro_receive (1 samples, 0.02%) + + + +__ext4_handle_dirty_metadata (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +java/util/regex/Pattern$Curly:::match (47 samples, 0.91%) + + + +java/net/URI$Parser:::parseHostname (1 samples, 0.02%) + + + +org/apache/kafka/clients/NetworkClient:::canSendRequest (1 samples, 0.02%) + + + +hrtimer_cancel (2 samples, 0.04%) + + + +WeakPreserveExceptionMark::WeakPreserveExceptionMark (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +java/util/ArrayList:::toArray (1 samples, 0.02%) + + + +java/text/SimpleDateFormat:::subParse (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +G1ParScanThreadState::trim_queue (3 samples, 0.06%) + + + +java/lang/StringBuilder:::append (1 samples, 0.02%) + + + +PhaseChaitin::Split (4 samples, 0.08%) + + + +org/apache/kafka/clients/producer/internals/Sender$SenderMetrics:::updateProduceRequestMetrics (13 samples, 0.25%) + + + +IndexSetIterator::IndexSetIterator (1 samples, 0.02%) + + + +InstanceKlass::allocate_instance (2 samples, 0.04%) + + + +ret_from_intr (1 samples, 0.02%) + + + +pipe_write (11 samples, 0.21%) + + + +virtnet_poll (1 samples, 0.02%) + + + +ep_send_events_proc (3 samples, 0.06%) + + + +do_readv_writev (17 samples, 0.33%) + + + +org/springframework/core/annotation/AnnotatedElementUtils:::searchWithFindSemantics (4 samples, 0.08%) + + + +do_softirq (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::getAttribute (2 samples, 0.04%) + + + +java/util/LinkedHashMap:::afterNodeInsertion (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +org/apache/tomcat/util/http/ValuesEnumerator:::findNext (1 samples, 0.02%) + + + +sock_def_readable (1 samples, 0.02%) + + + +do_futex (2 samples, 0.04%) + + + +java/util/concurrent/ArrayBlockingQueue:::offer (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy5:::annotationType (2 samples, 0.04%) + + + +org/apache/kafka/clients/producer/internals/ProducerInterceptors:::onAcknowledgement (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +java/lang/String:::equals (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +sys_epoll_ctl (2 samples, 0.04%) + + + +irq_exit (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/Type$8:::sizeOf (2 samples, 0.04%) + + + +org/apache/kafka/common/protocol/types/Type$7:::read (2 samples, 0.04%) + + + +java/text/DateFormatSymbols:::getProviderInstance (1 samples, 0.02%) + + + +java/lang/Throwable:::getStackTraceDepth (1 samples, 0.02%) + + + +itable stub (1 samples, 0.02%) + + + +system_call_fastpath (5 samples, 0.10%) + + + +system_call_fastpath (5 samples, 0.10%) + + + +JavaThread::last_frame (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +apic_timer_interrupt (1 samples, 0.02%) + + + +java/util/HashMap$EntrySet:::iterator (1 samples, 0.02%) + + + +lock_timer_base.isra.33 (1 samples, 0.02%) + + + +pthread_cond_signal@@GLIBC_2.3.2 (2 samples, 0.04%) + + + +org/apache/tomcat/util/http/Parameters:::processParameters (5 samples, 0.10%) + + + +call_softirq (1 samples, 0.02%) + + + +deflate (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +Symbol::as_klass_external_name (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy47:::annotationType (1 samples, 0.02%) + + + +org/springframework/web/accept/ContentNegotiationManager:::resolveMediaTypes (1 samples, 0.02%) + + + +java/lang/StringCoding:::encode (1 samples, 0.02%) + + + +schedule_hrtimeout_range_clock (1 samples, 0.02%) + + + +java/util/Formatter$FormatSpecifier:::localizedMagnitude (2 samples, 0.04%) + + + +java/util/HashSet:::contains (2 samples, 0.04%) + + + +do_softirq (1 samples, 0.02%) + + + +ObjectMonitor::enter (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/statistic/StatisticSlot:::exit (14 samples, 0.27%) + + + +sun/reflect/GeneratedMethodAccessor50:::invoke (1 samples, 0.02%) + + + +org/apache/catalina/connector/RequestFacade:::getHeader (1 samples, 0.02%) + + + +sysret_audit (2 samples, 0.04%) + + + +napi_gro_receive (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +BarrierSet::static_write_ref_array_post (2 samples, 0.04%) + + + +java/util/Formatter$FormatSpecifier:::print (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +org/springframework/web/method/support/InvocableHandlerMethod:::<init> (5 samples, 0.10%) + + + +os::javaTimeMillis (2 samples, 0.04%) + + + +com/coohua/caf/core/metrics/ProfilerHttpFilter:::doFilter (203 samples, 3.95%) +com/.. + + +arrayof_jint_fill (1 samples, 0.02%) + + + +java/util/regex/Pattern$3:::isSatisfiedBy (1 samples, 0.02%) + + + +java/util/GregorianCalendar:::computeFields (1 samples, 0.02%) + + + +schedule (1 samples, 0.02%) + + + +sun/nio/ch/EPollSelectorImpl:::putEventOps (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/CtSph:::entryWithPriority (2 samples, 0.04%) + + + +lock_sock_nested (1 samples, 0.02%) + + + +java/lang/Class:::reflectionData (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +smp_apic_timer_interrupt (1 samples, 0.02%) + + + +java/util/regex/Pattern$GroupHead:::match (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandler:::supportsReturnType (1 samples, 0.02%) + + + +finish_task_switch (17 samples, 0.33%) + + + +org/springframework/context/event/AbstractApplicationEventMulticaster$ListenerCacheKey:::hashCode (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +itable stub (1 samples, 0.02%) + + + +org/apache/tomcat/util/net/NioBlockingSelector:::write (5 samples, 0.10%) + + + +frame::sender (16 samples, 0.31%) + + + +inet_sendmsg (2 samples, 0.04%) + + + +com/coohua/ad/data/interceptor/UserInterceptor:::buildLogMessage (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +org/apache/kafka/common/network/Selector$SelectorMetrics:::recordBytesSent (7 samples, 0.14%) + + + +HandleMarkCleaner::~HandleMarkCleaner (1 samples, 0.02%) + + + +com/coohua/ad/data/manager/AdLogTrackManager:::logTrack (1,261 samples, 24.53%) +com/coohua/ad/data/manager/AdLogTrackM.. + + +__do_softirq (1 samples, 0.02%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics:::assertKey (1 samples, 0.02%) + + + +system_call_fastpath (18 samples, 0.35%) + + + +kfree (1 samples, 0.02%) + + + +JNIHandleBlock::allocate_handle (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +checkcast_arraycopy_uninit (2 samples, 0.04%) + + + +java/net/SocketInputStream:::read (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +com/alibaba/fastjson/JSON:::parseObject (1 samples, 0.02%) + + + +os::javaTimeMillis (1 samples, 0.02%) + + + +BacktraceBuilder::push (1 samples, 0.02%) + + + +__lll_unlock_wake (1 samples, 0.02%) + + + +getnstimeofday64 (1 samples, 0.02%) + + + +ip_finish_output (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +java/util/Spliterators$IteratorSpliterator:::forEachRemaining (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +G1RootProcessor::scan_remembered_sets (5 samples, 0.10%) + + + +java/util/concurrent/ConcurrentHashMap:::putVal (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +sk_run_filter (2 samples, 0.04%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +checkcast_arraycopy (2 samples, 0.04%) + + + +deflateEnd (1 samples, 0.02%) + + + +org/apache/kafka/clients/NetworkClient:::handleInitiateApiVersionRequests (2 samples, 0.04%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +java/lang/String:::indexOf (1 samples, 0.02%) + + + +dev_hard_start_xmit (15 samples, 0.29%) + + + +java/util/Calendar:::setTimeInMillis (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +OptoRuntime::register_finalizer (4 samples, 0.08%) + + + +java/util/stream/ReduceOps$3:::getOpFlags (1 samples, 0.02%) + + + +_complete_monitor_locking_Java (2 samples, 0.04%) + + + +org/apache/catalina/connector/Request:::setAttribute (2 samples, 0.04%) + + + +com/alibaba/fastjson/serializer/IntegerCodec:::deserialze (2 samples, 0.04%) + + + +java/util/concurrent/ConcurrentHashMap:::replaceNode (1 samples, 0.02%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +sysret_audit (2 samples, 0.04%) + + + +CollectedHeap::allocate_from_tlab_slow (1 samples, 0.02%) + + + +Compile::get_alias_index (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (4 samples, 0.08%) + + + +tcp_current_mss (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::matchField (2 samples, 0.04%) + + + +LatestMethodCache::get_method (1 samples, 0.02%) + + + +HandleMarkCleaner::~HandleMarkCleaner (11 samples, 0.21%) + + + +java/security/Provider:::getService (22 samples, 0.43%) + + + +PhaseLive::add_liveout (3 samples, 0.06%) + + + +org/springframework/web/filter/OncePerRequestFilter:::doFilter (1 samples, 0.02%) + + + +PhaseIterGVN::optimize (3 samples, 0.06%) + + + +com/fasterxml/jackson/databind/ser/std/StdKeySerializers$StringKeySerializer:::serialize (6 samples, 0.12%) + + + +vtable stub (2 samples, 0.04%) + + + +run_timer_softirq (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +G1AllocRegion::retire (1 samples, 0.02%) + + + +com/coohua/platform/security/AESCoder:::decrypt (83 samples, 1.61%) + + + +org/apache/kafka/clients/producer/internals/RecordAccumulator:::tryAppend (2 samples, 0.04%) + + + +__vdso_gettimeofday (2 samples, 0.04%) + + + +do_softirq (1 samples, 0.02%) + + + +__tcp_push_pending_frames (4 samples, 0.08%) + + + +RefineRecordRefsIntoCSCardTableEntryClosure::do_card_ptr (3 samples, 0.06%) + + + +ret_from_intr (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +java/util/LinkedHashMap:::newNode (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +inet_sendmsg (6 samples, 0.12%) + + + +sys_futex (6 samples, 0.12%) + + + +java_lang_Throwable::fill_in_stack_trace (67 samples, 1.30%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/ObjectMapper:::writeValueAsString (1 samples, 0.02%) + + + +sun/misc/Unsafe:::unpark (2 samples, 0.04%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +jlong_disjoint_arraycopy (1 samples, 0.02%) + + + +run_timer_softirq (1 samples, 0.02%) + + + +__lll_unlock_wake (2 samples, 0.04%) + + + +CollectedHeap::common_mem_allocate_init (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +java/math/BigDecimal:::<init> (1 samples, 0.02%) + + + +objArrayOopDesc::obj_at (11 samples, 0.21%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +org/springframework/context/expression/StandardBeanExpressionResolver:::evaluate (2 samples, 0.04%) + + + +org/LatencyUtils/TimeCappedMovingAverageIntervalEstimator:::recordInterval (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +Parse::do_one_bytecode (1 samples, 0.02%) + + + +skb_to_sgvec (2 samples, 0.04%) + + + +__vdso_gettimeofday (2 samples, 0.04%) + + + +page_to_skb (1 samples, 0.02%) + + + +sun/nio/ch/SelectionKeyImpl:::nioInterestOps (1 samples, 0.02%) + + + +PhaseIdealLoop::build_loop_late_post (3 samples, 0.06%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +skb_copy_datagram_iovec (1 samples, 0.02%) + + + +java/util/regex/Pattern$Single:::isSatisfiedBy (1 samples, 0.02%) + + + +io/micrometer/core/instrument/Timer$Builder:::register (3 samples, 0.06%) + + + +io/micrometer/shaded/org/pcollections/HashPMap:::get (2 samples, 0.04%) + + + +ret_from_intr (1 samples, 0.02%) + + + +G1CollectorPolicy::predict_region_elapsed_time_ms (1 samples, 0.02%) + + + +java/lang/ThreadLocal$ThreadLocalMap:::remove (1 samples, 0.02%) + + + +do_sys_poll (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +apic_timer_interrupt (1 samples, 0.02%) + + + +__skb_gro_checksum_complete (1 samples, 0.02%) + + + +TaskQueueSetSuper::randomParkAndMiller (1 samples, 0.02%) + + + +Type::hashcons (1 samples, 0.02%) + + + +inet_gro_receive (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +sun/nio/ch/FileDispatcherImpl:::read0 (6 samples, 0.12%) + + + +sock_aio_read.part.7 (9 samples, 0.18%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +java/text/DateFormatSymbols:::getProviderInstance (2 samples, 0.04%) + + + +do_softirq (1 samples, 0.02%) + + + +org/apache/catalina/mapper/MappingData:::recycle (1 samples, 0.02%) + + + +system_call_fastpath (14 samples, 0.27%) + + + +org/apache/kafka/common/requests/ProduceRequest:::createPartitionSizes (2 samples, 0.04%) + + + +irq_exit (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/Type$8:::validate (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +java/util/concurrent/ThreadPoolExecutor:::getTask (55 samples, 1.07%) + + + +irq_exit (1 samples, 0.02%) + + + +jlong_disjoint_arraycopy (2 samples, 0.04%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +_int_free (2 samples, 0.04%) + + + +java/util/Formatter$FixedString:::print (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +org/apache/catalina/core/StandardHostValve:::invoke (3,781 samples, 73.56%) +org/apache/catalina/core/StandardHostValve:::invoke + + +CollectedHeap::post_allocation_setup_array (1 samples, 0.02%) + + + +javax/crypto/Cipher:::tokenizeTransformation (3 samples, 0.06%) + + + +java/text/DecimalFormat:::equals (1 samples, 0.02%) + + + +redis/clients/jedis/Protocol:::process (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +com/alibaba/fastjson/serializer/LongCodec:::deserialze (6 samples, 0.12%) + + + +java/util/regex/Pattern$Curly:::match (3 samples, 0.06%) + + + +__do_softirq (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +net_rps_action_and_irq_enable.isra.91 (2 samples, 0.04%) + + + +java/util/GregorianCalendar:::computeTime (2 samples, 0.04%) + + + +generic_exec_single (1 samples, 0.02%) + + + +try_to_wake_up (1 samples, 0.02%) + + + +start_this_handle (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +__memset_sse2 (32 samples, 0.62%) + + + +__libc_calloc (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::scanString (6 samples, 0.12%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (1 samples, 0.02%) + + + +sun/nio/ch/SelectorImpl:::select (55 samples, 1.07%) + + + +JNIHandleBlock::allocate_block (1 samples, 0.02%) + + + +CodeCache::find_blob (1 samples, 0.02%) + + + +java/util/LinkedHashMap$LinkedKeyIterator:::next (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/MessageBytes:::isNull (1 samples, 0.02%) + + + +system_call_fastpath (2 samples, 0.04%) + + + +org/springframework/aop/framework/ReflectiveMethodInvocation:::proceed (1 samples, 0.02%) + + + +jlong_disjoint_arraycopy (1 samples, 0.02%) + + + +java/util/zip/Inflater:::init (2 samples, 0.04%) + + + +java/util/stream/ForEachOps$ForEachOp$OfRef:::evaluateSequential (2 samples, 0.04%) + + + +sock_sendmsg (6 samples, 0.12%) + + + +call_softirq (1 samples, 0.02%) + + + +java/lang/Class:::reflectionData (1 samples, 0.02%) + + + +ext4_mark_iloc_dirty (3 samples, 0.06%) + + + +Parse::do_one_block (1 samples, 0.02%) + + + +org/springframework/aop/framework/ReflectiveMethodInvocation:::proceed (1 samples, 0.02%) + + + +jni_ReleasePrimitiveArrayCritical (105 samples, 2.04%) +j.. + + +[libc-2.17.so] (1 samples, 0.02%) + + + +OptoRuntime::is_deoptimized_caller_frame (1 samples, 0.02%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/impl/Log4jLogEvent:::serialize (3 samples, 0.06%) + + + +org/apache/kafka/common/protocol/types/Schema:::read (2 samples, 0.04%) + + + +TypeArrayKlass::allocate_common (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +java/util/concurrent/ConcurrentHashMap$MapEntry:::getValue (1 samples, 0.02%) + + + +java/util/Calendar:::createCalendar (1 samples, 0.02%) + + + +IndexSetIterator::advance_and_next (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy0:::annotationType (1 samples, 0.02%) + + + +org/apache/kafka/clients/NetworkClient:::pollDelayMs (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +virtqueue_kick_prepare (2 samples, 0.04%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (1 samples, 0.02%) + + + +CollectedHeap::allocate_from_tlab_slow (1 samples, 0.02%) + + + +sun/nio/cs/ThreadLocalCoders$Cache:::forName (2 samples, 0.04%) + + + +ip_output (6 samples, 0.12%) + + + +java/text/SimpleDateFormat:::format (5 samples, 0.10%) + + + +longest_match (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +org/apache/tomcat/websocket/server/WsFilter:::doFilter (192 samples, 3.74%) +org/.. + + +java/io/SequenceInputStream:::read (1 samples, 0.02%) + + + +system_call_fastpath (31 samples, 0.60%) + + + +__mnt_want_write (1 samples, 0.02%) + + + +java/text/SimpleDateFormat:::initialize (2 samples, 0.04%) + + + +java/lang/Throwable:::getStackTraceElement (466 samples, 9.07%) +java/lang/Thr.. + + +fget_light (1 samples, 0.02%) + + + +java/util/regex/Pattern$Curly:::match (3 samples, 0.06%) + + + +sun/nio/ch/IOVecWrapper:::get (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::intValue (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +sun/nio/ch/SocketChannelImpl:::write (54 samples, 1.05%) + + + +java/lang/System:::identityHashCode (1 samples, 0.02%) + + + +java/lang/Throwable:::fillInStackTrace (77 samples, 1.50%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +InstanceKlass::register_finalizer (4 samples, 0.08%) + + + +__pthread_enable_asynccancel (1 samples, 0.02%) + + + +futex_wait (4 samples, 0.08%) + + + +checkcast_arraycopy (1 samples, 0.02%) + + + +java/lang/Object:::toString (1 samples, 0.02%) + + + +java/util/HashMap:::resize (1 samples, 0.02%) + + + +java/lang/ThreadLocal$ThreadLocalMap:::set (3 samples, 0.06%) + + + +do_futex (5 samples, 0.10%) + + + +sun/misc/Unsafe:::unpark (2 samples, 0.04%) + + + +fget_light (3 samples, 0.06%) + + + +irq_exit (1 samples, 0.02%) + + + +org/springframework/aop/framework/CglibAopProxy$DynamicAdvisedInterceptor:::intercept (2 samples, 0.04%) + + + +ip_rcv (1 samples, 0.02%) + + + +pthread_mutex_unlock (1 samples, 0.02%) + + + +java/util/HashMap:::containsKey (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/config/LoggerConfig:::log (7 samples, 0.14%) + + + +memcpy_toiovec (1 samples, 0.02%) + + + +JVM_Sleep (11 samples, 0.21%) + + + +java/lang/AbstractStringBuilder:::append (1 samples, 0.02%) + + + +java/util/Formatter$FormatSpecifier:::print (2 samples, 0.04%) + + + +ip_output (3 samples, 0.06%) + + + +smp_call_function_single_async (2 samples, 0.04%) + + + +org/springframework/web/filter/OncePerRequestFilter:::doFilter (3,684 samples, 71.67%) +org/springframework/web/filter/OncePerRequestFilter:::doFilter + + +JavaThread::handle_special_suspend_equivalent_condition (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +java/io/ByteArrayInputStream:::read (1 samples, 0.02%) + + + +rw_verify_area (1 samples, 0.02%) + + + +sys_recvfrom (1 samples, 0.02%) + + + +__memset (1 samples, 0.02%) + + + +org/springframework/aop/framework/ReflectiveMethodInvocation:::proceed (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +pthread_cond_timedwait@@GLIBC_2.3.2 (6 samples, 0.12%) + + + +sun/security/provider/SHA:::implCompress (4 samples, 0.08%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender:::directEncodeEvent (52 samples, 1.01%) + + + +inflate (13 samples, 0.25%) + + + +Parker::unpark (1 samples, 0.02%) + + + +jni_SetBooleanField (2 samples, 0.04%) + + + +JVM_FillInStackTrace (74 samples, 1.44%) + + + +java/util/HashMap$EntrySet:::iterator (2 samples, 0.04%) + + + +java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1:::next (1 samples, 0.02%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +org/springframework/web/util/UrlPathHelper:::getRemainingPath (1 samples, 0.02%) + + + +java/util/concurrent/ConcurrentHashMap:::get (2 samples, 0.04%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +tcp_v4_syn_recv_sock (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/SerializerProvider:::<init> (1 samples, 0.02%) + + + +org/apache/tomcat/util/http/ValuesEnumerator:::findNext (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +sys_futex (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +system_call_fastpath (15 samples, 0.29%) + + + +org/apache/kafka/clients/NetworkClient:::handleCompletedReceives (1 samples, 0.02%) + + + +sys_futex (1 samples, 0.02%) + + + +PhaseIFG::remove_node (2 samples, 0.04%) + + + +system_call_fastpath (2 samples, 0.04%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/springframework/web/filter/OncePerRequestFilter:::doFilter (206 samples, 4.01%) +org/.. + + +JNIHandles::make_local (1 samples, 0.02%) + + + +org/apache/kafka/clients/producer/internals/Sender:::handleProduceResponse (34 samples, 0.66%) + + + +sk_run_filter (1 samples, 0.02%) + + + +com/alibaba/fastjson/JSON:::parseObject (185 samples, 3.60%) +com.. + + +do_softirq (1 samples, 0.02%) + + + +__skb_gro_checksum_complete (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor65:::invoke (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11InputBuffer:::doRead (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +java/util/Arrays:::sort (4 samples, 0.08%) + + + +do_softirq (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +__getnstimeofday64 (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +HandleMarkCleaner::~HandleMarkCleaner (1 samples, 0.02%) + + + +java/util/regex/Pattern$GroupHead:::match (2 samples, 0.04%) + + + +org/joda/time/format/DateTimeFormatter:::printTo (2 samples, 0.04%) + + + +com/coohua/caf/core/rpc/MotanProfilerFilter:::filter (10 samples, 0.19%) + + + +ip_output (23 samples, 0.45%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +net_rx_action (2 samples, 0.04%) + + + +org/apache/kafka/common/network/Selector$SelectorMetrics:::maybeRegisterConnectionMetrics (1 samples, 0.02%) + + + +javax/crypto/Cipher:::getInstance (46 samples, 0.89%) + + + +java/text/DateFormatSymbols:::initializeData (2 samples, 0.04%) + + + +skb_clone (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy50:::annotationType (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::nextToken (1 samples, 0.02%) + + + +java/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject:::await (33 samples, 0.64%) + + + +do_softirq (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor90:::invoke (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +local_clock (1 samples, 0.02%) + + + +org/springframework/boot/actuate/trace/http/HttpExchangeTracer$FilteredTraceableResponse:::<init> (1 samples, 0.02%) + + + +org/jboss/netty/channel/socket/nio/NioWorker:::run (2 samples, 0.04%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +tcp_schedule_loss_probe (2 samples, 0.04%) + + + +os::javaTimeMillis (1 samples, 0.02%) + + + +CollectedHeap::common_mem_allocate_init (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +tcp_md5_do_lookup (1 samples, 0.02%) + + + +pthread_getspecific (1 samples, 0.02%) + + + +ReferenceProcessor::process_discovered_reflist (3 samples, 0.06%) + + + +system_call_fastpath (4 samples, 0.08%) + + + +CollectedHeap::new_store_pre_barrier (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy50:::annotationType (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +Parse::do_one_bytecode (1 samples, 0.02%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (2 samples, 0.04%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +java/util/concurrent/ConcurrentHashMap$BaseIterator:::hasNext (3 samples, 0.06%) + + + +VM_G1IncCollectionPause::doit (4 samples, 0.08%) + + + +org/apache/catalina/connector/Request:::setAttribute (3 samples, 0.06%) + + + +java/util/regex/Pattern$Branch:::match (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/springframework/context/event/SimpleApplicationEventMulticaster:::multicastEvent (1 samples, 0.02%) + + + +Compile::fill_buffer (2 samples, 0.04%) + + + +org/springframework/core/annotation/AnnotatedElementUtils:::searchWithFindSemantics (9 samples, 0.18%) + + + +java/lang/Long:::getChars (1 samples, 0.02%) + + + +InstanceKlass::register_finalizer (2 samples, 0.04%) + + + +updateBytesCRC32 (8 samples, 0.16%) + + + +org/apache/tomcat/util/buf/B2CConverter:::convert (1 samples, 0.02%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (4 samples, 0.08%) + + + +net_rx_action (2 samples, 0.04%) + + + +sun/nio/ch/EPollArrayWrapper:::epollWait (23 samples, 0.45%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +java/util/regex/Pattern$Dollar:::match (1 samples, 0.02%) + + + +sun/nio/ch/SelectionKeyImpl:::nioInterestOps (1 samples, 0.02%) + + + +java/util/HashMap:::put (2 samples, 0.04%) + + + +Java_java_io_RandomAccessFile_writeBytes (41 samples, 0.80%) + + + +com/coohua/ad/data/service/impl/AdDataServiceImpl:::ecpProcess (54 samples, 1.05%) + + + +get_rps_cpu (1 samples, 0.02%) + + + +java/util/HashMap:::afterNodeInsertion (1 samples, 0.02%) + + + +org/apache/kafka/common/network/Selector:::pollSelectionKeys (60 samples, 1.17%) + + + +java/util/Calendar$Builder:::build (1 samples, 0.02%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +org/apache/catalina/connector/RequestFacade:::setAttribute (2 samples, 0.04%) + + + +do_sync_read (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +tcp4_gro_receive (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor103:::invoke (4 samples, 0.08%) + + + +Java_java_util_zip_Deflater_deflateBytes (9 samples, 0.18%) + + + +com/google/gson/internal/bind/TypeAdapters$16:::write (22 samples, 0.43%) + + + +org/springframework/web/servlet/handler/AbstractHandlerMethodMapping:::addMatchingMappings (46 samples, 0.89%) + + + +__block_write_begin (1 samples, 0.02%) + + + +tcp_push (30 samples, 0.58%) + + + +java/util/HashMap:::get (1 samples, 0.02%) + + + +[libpthread-2.17.so] (16 samples, 0.31%) + + + +checkcast_arraycopy (1 samples, 0.02%) + + + +PhaseIFG::SquareUp (1 samples, 0.02%) + + + +java/util/regex/Pattern$GroupHead:::match (1 samples, 0.02%) + + + +do_sync_readv_writev (1 samples, 0.02%) + + + +rw_verify_area (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +org/apache/coyote/Request:::recycle (3 samples, 0.06%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +org/springframework/web/filter/OncePerRequestFilter:::doFilter (3,765 samples, 73.25%) +org/springframework/web/filter/OncePerRequestFilter:::doFilter + + +futex_wake (1 samples, 0.02%) + + + +java/util/zip/InflaterInputStream:::read (917 samples, 17.84%) +java/util/zip/InflaterInput.. + + +java/security/Provider$ServiceKey:::equals (1 samples, 0.02%) + + + +compress_block (30 samples, 0.58%) + + + +call_softirq (1 samples, 0.02%) + + + +java/util/regex/Pattern$Node:::match (2 samples, 0.04%) + + + +java/util/regex/Pattern$GroupHead:::match (1 samples, 0.02%) + + + +virtnet_poll (1 samples, 0.02%) + + + +tcp_write_xmit (11 samples, 0.21%) + + + +HeapRegion::oops_on_card_seq_iterate_careful (3 samples, 0.06%) + + + +org/springframework/aop/framework/CglibAopProxy$DynamicAdvisedInterceptor:::intercept (1 samples, 0.02%) + + + +VMThread::loop (4 samples, 0.08%) + + + +PhaseChaitin::gather_lrg_masks (3 samples, 0.06%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +com/coohua/platform/security/Base64$Decoder:::process (3 samples, 0.06%) + + + +redis/clients/jedis/Pipeline:::sync (4 samples, 0.08%) + + + +java/text/SimpleDateFormat:::compile (1 samples, 0.02%) + + + +com/coohua/ad/data/utils/GzipUtil:::deCompress (56 samples, 1.09%) + + + +java_lang_Throwable::get_stack_trace_element (3 samples, 0.06%) + + + +process_backlog (1 samples, 0.02%) + + + +apic_timer_interrupt (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (2 samples, 0.04%) + + + +java/util/regex/Pattern$GroupHead:::match (5 samples, 0.10%) + + + +ext4_da_write_begin (4 samples, 0.08%) + + + +org/springframework/http/converter/xml/SourceHttpMessageConverter:::supports (1 samples, 0.02%) + + + +futex_wait (23 samples, 0.45%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (211 samples, 4.11%) +org/.. + + +PhaseIterGVN::add_users_to_worklist (1 samples, 0.02%) + + + +JfrBackend::is_event_enabled (1 samples, 0.02%) + + + +org/springframework/core/ResolvableType:::forMethodParameter (5 samples, 0.10%) + + + +java/util/AbstractList:::hashCode (1 samples, 0.02%) + + + +os::javaTimeMillis (1 samples, 0.02%) + + + +local_bh_enable_ip (1 samples, 0.02%) + + + +java_lang_StackTraceElement::create (26 samples, 0.51%) + + + +virtnet_poll (1 samples, 0.02%) + + + +java/util/AbstractList:::hashCode (3 samples, 0.06%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +ipv4_mtu (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender:::append (56 samples, 1.09%) + + + +org/springframework/web/util/UrlPathHelper:::decodeInternal (3 samples, 0.06%) + + + +org/springframework/web/context/request/async/StandardServletAsyncWebRequest:::<init> (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +org/apache/logging/log4j/spi/AbstractLogger:::logMessage (3 samples, 0.06%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (1 samples, 0.02%) + + + +org/springframework/util/AntPathMatcher:::doMatch (11 samples, 0.21%) + + + +try_to_wake_up (2 samples, 0.04%) + + + +Java_java_util_zip_Inflater_inflateBytes (4 samples, 0.08%) + + + +java/lang/reflect/Array:::get (3 samples, 0.06%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +ext4_mark_iloc_dirty (1 samples, 0.02%) + + + +sun/security/provider/SecureRandom:::engineNextBytes (7 samples, 0.14%) + + + +system_call_fastpath (2 samples, 0.04%) + + + +java/util/regex/Pattern$Single:::isSatisfiedBy (1 samples, 0.02%) + + + +InterpreterRuntime::ldc (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor:::supportsReturnType (1 samples, 0.02%) + + + +try_to_wake_up (29 samples, 0.56%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +Java_java_util_zip_Inflater_inflateBytes (37 samples, 0.72%) + + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +futex_wait (5 samples, 0.10%) + + + +do_softirq (1 samples, 0.02%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +ThreadBlockInVM::ThreadBlockInVM (1 samples, 0.02%) + + + +java/lang/AbstractStringBuilder:::append (10 samples, 0.19%) + + + +java/util/HashSet:::iterator (1 samples, 0.02%) + + + +com/alibaba/fastjson/serializer/IntegerCodec:::getFastMatchToken (1 samples, 0.02%) + + + +tcp_gro_receive (1 samples, 0.02%) + + + +java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1:::next (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +nmethod::is_nmethod (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +org/apache/kafka/clients/producer/internals/RecordAccumulator:::expiredBatches (9 samples, 0.18%) + + + +java/security/Provider$Service:::newInstance (1 samples, 0.02%) + + + +fsnotify (1 samples, 0.02%) + + + +_raw_spin_lock_irqsave (1 samples, 0.02%) + + + +org/apache/tomcat/util/net/NioEndpoint$Poller:::timeout (2 samples, 0.04%) + + + +java/lang/Object:::hashCode (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +_raw_spin_unlock_bh (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +java/util/regex/Pattern$GroupTail:::match (2 samples, 0.04%) + + + +java/lang/String:::toLowerCase (3 samples, 0.06%) + + + +virtnet_poll (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +sun/nio/cs/UTF_8$Decoder:::decodeLoop (6 samples, 0.12%) + + + +OptoRuntime::new_instance_C (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/ser/std/StringSerializer:::serialize (9 samples, 0.18%) + + + +org/springframework/web/servlet/handler/HandlerInterceptorAdapter:::postHandle (1 samples, 0.02%) + + + +__memmove_ssse3_back (3 samples, 0.06%) + + + +java/util/zip/InflaterInputStream:::read (6 samples, 0.12%) + + + +java/net/URI$Parser:::parseHostname (3 samples, 0.06%) + + + +__schedule (3 samples, 0.06%) + + + +[libpthread-2.17.so] (3 samples, 0.06%) + + + +java/util/HashMap$HashIterator:::hasNext (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/appender/AsyncAppender:::append (3 samples, 0.06%) + + + +G1SATBCardTableLoggingModRefBS::write_ref_array_work (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +os::javaTimeMillis (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +ParseGenerator::generate (1 samples, 0.02%) + + + +jlong_disjoint_arraycopy (1 samples, 0.02%) + + + +pipe_poll (1 samples, 0.02%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (17 samples, 0.33%) + + + +__do_softirq (1 samples, 0.02%) + + + +JNIHandleBlock::allocate_handle (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (3,753 samples, 73.02%) +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter + + +__dev_queue_xmit (5 samples, 0.10%) + + + +com/coohua/caf/core/kv/JedisClusterClient:::setex (1 samples, 0.02%) + + + +do_futex (1 samples, 0.02%) + + + +OptoRuntime::is_deoptimized_caller_frame (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotatedElementUtils:::searchWithFindSemantics (16 samples, 0.31%) + + + +vtable stub (1 samples, 0.02%) + + + +com/coohua/platform/security/Base64:::decode (1 samples, 0.02%) + + + +tcp_write_xmit (28 samples, 0.54%) + + + +__build_skb (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +java/util/Spliterator:::getExactSizeIfKnown (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +jlong_disjoint_arraycopy (1 samples, 0.02%) + + + +finish_task_switch (9 samples, 0.18%) + + + +Dict::Insert (1 samples, 0.02%) + + + +ep_poll (1 samples, 0.02%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +__audit_syscall_exit (1 samples, 0.02%) + + + +pthread_cond_signal@@GLIBC_2.3.2 (7 samples, 0.14%) + + + +wake_futex (5 samples, 0.10%) + + + +org/apache/catalina/connector/Request:::setAttribute (2 samples, 0.04%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +futex_wake_op (2 samples, 0.04%) + + + +ip_local_out_sk (1 samples, 0.02%) + + + +finish_task_switch (3 samples, 0.06%) + + + +sock_aio_write (16 samples, 0.31%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +org/springframework/web/util/UrlPathHelper:::decodeInternal (1 samples, 0.02%) + + + +org/apache/catalina/core/StandardContextValve:::invoke (3,777 samples, 73.48%) +org/apache/catalina/core/StandardContextValve:::invoke + + +itable stub (3 samples, 0.06%) + + + +ObjArrayKlass::multi_allocate (9 samples, 0.18%) + + + +InterpreterRuntime::frequency_counter_overflow_inner (1 samples, 0.02%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +tcp_stream_memory_free (1 samples, 0.02%) + + + +java/util/ArrayList:::add (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +sun/misc/Unsafe:::park (27 samples, 0.53%) + + + +CollectedHeap::allocate_from_tlab_slow (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +_new_array_Java (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +java/lang/ref/Finalizer$FinalizerThread:::run (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +java/text/DateFormatSymbols:::copyMembers (2 samples, 0.04%) + + + +do_futex (14 samples, 0.27%) + + + +org/apache/coyote/http11/Http11OutputBuffer:::end (5 samples, 0.10%) + + + +net_rx_action (1 samples, 0.02%) + + + +org/jboss/netty/channel/DefaultChannelPipeline:::sendDownstream (2 samples, 0.04%) + + + +JVM_GetDeclaringClass (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/condition/PatternsRequestCondition:::getMatchingPatterns (1 samples, 0.02%) + + + +OptoRuntime::new_array_nozero_C (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/Utf8Decoder:::decodeHasArray (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +org/springframework/context/i18n/LocaleContextHolder:::setLocale (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy0:::annotationType (1 samples, 0.02%) + + + +ObjArrayKlass::allocate (1 samples, 0.02%) + + + +[libpthread-2.17.so] (1 samples, 0.02%) + + + +sun/nio/ch/IOUtil:::write (23 samples, 0.45%) + + + +jshort_arraycopy (1 samples, 0.02%) + + + +java/util/HashSet:::iterator (1 samples, 0.02%) + + + +do_futex (21 samples, 0.41%) + + + +irq_exit (2 samples, 0.04%) + + + +sun/nio/ch/FileDispatcherImpl:::write0 (3 samples, 0.06%) + + + +ret_from_intr (1 samples, 0.02%) + + + +sys_epoll_wait (18 samples, 0.35%) + + + +java/lang/String:::charAt (2 samples, 0.04%) + + + +OptoRuntime::new_array_C (1 samples, 0.02%) + + + +__libc_read (1 samples, 0.02%) + + + +jni_GetByteArrayRegion (3 samples, 0.06%) + + + +TypeArrayKlass::allocate_common (6 samples, 0.12%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::nextToken (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/MessageBytes:::toString (2 samples, 0.04%) + + + +OptoRuntime::new_array_nozero_C (1 samples, 0.02%) + + + +sys_writev (18 samples, 0.35%) + + + +com/weibo/api/motan/transport/netty/NettyClient:::request (2 samples, 0.04%) + + + +[libpthread-2.17.so] (31 samples, 0.60%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +futex_wake (2 samples, 0.04%) + + + +InstanceKlass::register_finalizer (5 samples, 0.10%) + + + +ip_finish_output (21 samples, 0.41%) + + + +com/coohua/platform/security/Base64:::decode (3 samples, 0.06%) + + + +generic_file_buffered_write (10 samples, 0.19%) + + + +do_softirq (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +sys_futex (18 samples, 0.35%) + + + +hrtimer_start_range_ns (2 samples, 0.04%) + + + +org/springframework/boot/actuate/trace/http/HttpExchangeTracer:::getHeadersIfIncluded (47 samples, 0.91%) + + + +java/util/regex/Pattern$Node:::match (3 samples, 0.06%) + + + +irq_exit (1 samples, 0.02%) + + + +InstanceKlass::allocate_instance (1 samples, 0.02%) + + + +com/coohua/ad/data/interceptor/UserInterceptor:::buildLogMessage (136 samples, 2.65%) +co.. + + +java/lang/String:::hashCode (2 samples, 0.04%) + + + +org/springframework/beans/TypeConverterDelegate:::convertIfNecessary (1 samples, 0.02%) + + + +ipv4_dst_check (1 samples, 0.02%) + + + +java/util/HashMap:::putMapEntries (1 samples, 0.02%) + + + +JavaThread::oops_do (2 samples, 0.04%) + + + +org/joda/time/chrono/BasicYearDateTimeField:::get (2 samples, 0.04%) + + + +Interpreter (176 samples, 3.42%) +Int.. + + +__netif_receive_skb (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot:::entry (1 samples, 0.02%) + + + +PhaseIterGVN::transform_old (4 samples, 0.08%) + + + +java/util/LinkedHashMap:::newNode (1 samples, 0.02%) + + + +org/springframework/web/servlet/FrameworkServlet:::service (3,446 samples, 67.04%) +org/springframework/web/servlet/FrameworkServlet:::service + + +smp_call_function_single_async (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/springframework/web/util/UrlPathHelper:::getLookupPathForRequest (8 samples, 0.16%) + + + +generic_exec_single (1 samples, 0.02%) + + + +system_call_fastpath (19 samples, 0.37%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +sun/nio/ch/IOUtil:::drain (4 samples, 0.08%) + + + +generic_pipe_buf_unmap (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/appender/AsyncAppender:::append (6 samples, 0.12%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +sun/nio/ch/EPollArrayWrapper:::epollWait (18 samples, 0.35%) + + + +kmem_cache_free (1 samples, 0.02%) + + + +java/util/Formatter$FormatSpecifier:::localizedMagnitude (2 samples, 0.04%) + + + +sun/nio/cs/ISO_8859_1$Decoder:::decodeLoop (5 samples, 0.10%) + + + +com/coohua/caf/core/kv/JedisClusterClient$$FastClassBySpringCGLIB$$1bc9b45c:::invoke (1 samples, 0.02%) + + + +TypeArrayKlass::multi_allocate (6 samples, 0.12%) + + + +org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandler:::supportsReturnType (2 samples, 0.04%) + + + +com/coohua/ad/data/interceptor/UserInterceptor:::preHandle (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils:::getAnnotation (2 samples, 0.04%) + + + +java/util/regex/Pattern$Neg:::match (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy8:::annotationType (1 samples, 0.02%) + + + +pthread_cond_wait@@GLIBC_2.3.2 (20 samples, 0.39%) + + + +vtable stub (11 samples, 0.21%) + + + +process_backlog (1 samples, 0.02%) + + + +sun/nio/ch/EPollArrayWrapper:::poll (19 samples, 0.37%) + + + +java/util/Formatter$Conversion:::isValid (2 samples, 0.04%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +PhaseIFG::init (1 samples, 0.02%) + + + +org/springframework/boot/actuate/trace/http/HttpExchangeTracer:::sendingResponse (10 samples, 0.19%) + + + +java/text/SimpleDateFormat:::subFormat (1 samples, 0.02%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +itable stub (5 samples, 0.10%) + + + +org/apache/kafka/common/record/ByteBufferLogInputStream:::nextBatch (1 samples, 0.02%) + + + +sock_aio_write (37 samples, 0.72%) + + + +pthread_self@plt (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +org/springframework/web/context/request/ServletWebRequest:::getParameterValues (8 samples, 0.16%) + + + +jshort_disjoint_arraycopy (5 samples, 0.10%) + + + +java/util/HashMap:::resize (1 samples, 0.02%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot:::exit (1 samples, 0.02%) + + + +ip_local_out_sk (3 samples, 0.06%) + + + +OptoRuntime::new_array_nozero_C (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils$AnnotationCollector:::process (24 samples, 0.47%) + + + +G1CollectedHeap::attempt_allocation_slow (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (200 samples, 3.89%) +org/.. + + +__irqentry_text_start (1 samples, 0.02%) + + + +tcp_cleanup_rbuf (1 samples, 0.02%) + + + +CodeHeap::find_start (1 samples, 0.02%) + + + +InterpreterRuntime::ldc (3 samples, 0.06%) + + + +java/util/LinkedHashSet:::spliterator (1 samples, 0.02%) + + + +do_futex (17 samples, 0.33%) + + + +Java_java_lang_Throwable_fillInStackTrace (75 samples, 1.46%) + + + +irq_exit (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +do_sync_read (1 samples, 0.02%) + + + +dev_queue_xmit (5 samples, 0.10%) + + + +java/lang/Throwable:::fillInStackTrace (4 samples, 0.08%) + + + +org/springframework/web/filter/FormContentFilter:::doFilterInternal (3,609 samples, 70.21%) +org/springframework/web/filter/FormContentFilter:::doFilterInternal + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (3,548 samples, 69.03%) +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter + + +ret_from_intr (1 samples, 0.02%) + + + +org/apache/kafka/clients/producer/internals/Sender:::sendProducerData (108 samples, 2.10%) +o.. + + +do_futex (2 samples, 0.04%) + + + +java/util/zip/GZIPInputStream:::skipBytes (1 samples, 0.02%) + + + +__memmove_ssse3_back (1 samples, 0.02%) + + + +org/apache/catalina/mapper/Mapper:::internalMapWrapper (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +InstanceKlass::method_with_orig_idnum (1 samples, 0.02%) + + + +OptoRuntime::is_deoptimized_caller_frame (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor:::writeWithMessageConverters (5 samples, 0.10%) + + + +java/lang/ThreadLocal:::set (1 samples, 0.02%) + + + +com/coohua/ad/data/interceptor/UserInterceptor:::buildLogMessage (7 samples, 0.14%) + + + +java/util/stream/Sink:::end (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +system_call_fastpath (20 samples, 0.39%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/condition/ProducesRequestCondition:::getMatchingCondition (2 samples, 0.04%) + + + +InstanceKlass::oop_oop_iterate_nv (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +sock_poll (1 samples, 0.02%) + + + +Handle::Handle (1 samples, 0.02%) + + + +org/springframework/util/AntPathMatcher:::doMatch (3 samples, 0.06%) + + + +sun/misc/Unsafe:::unpark (3 samples, 0.06%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +sun/nio/ch/EPollArrayWrapper:::epollCtl (2 samples, 0.04%) + + + +Unsafe_Unpark (24 samples, 0.47%) + + + +org/apache/kafka/clients/NetworkClient:::handleCompletedReceives (12 samples, 0.23%) + + + +java/util/regex/Pattern:::closure (1 samples, 0.02%) + + + +pthread_cond_signal@@GLIBC_2.3.2 (21 samples, 0.41%) + + + +java_lang_StackTraceElement::set_methodName (1 samples, 0.02%) + + + +system_call_fastpath (6 samples, 0.12%) + + + +InstanceKlass::method_with_orig_idnum (11 samples, 0.21%) + + + +com/coohua/caf/core/base/SpringApplicationEventListenerAutoConfiguration:::onApplicationEvent (5 samples, 0.10%) + + + +java/util/concurrent/locks/ReentrantLock$NonfairSync:::lock (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +com/coohua/caf/core/kv/JedisClusterClientAspect:::around (1 samples, 0.02%) + + + +PhaseGVN::transform_no_reclaim (1 samples, 0.02%) + + + +java/util/regex/Pattern$Slice:::match (2 samples, 0.04%) + + + +oopDesc::size_given_klass (1 samples, 0.02%) + + + +org/apache/juli/logging/DirectJDKLog:::isDebugEnabled (1 samples, 0.02%) + + + +futex_wait_queue_me (16 samples, 0.31%) + + + +com/coohua/ad/data/interceptor/UserInterceptor:::afterCompletion (2 samples, 0.04%) + + + +validate_xmit_skb.part.93 (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +__vdso_clock_gettime (2 samples, 0.04%) + + + +_raw_spin_unlock_irqrestore (2 samples, 0.04%) + + + +org/apache/logging/log4j/core/AbstractLifeCycle:::isStarted (1 samples, 0.02%) + + + +tcp_ack (1 samples, 0.02%) + + + +os::javaTimeMillis (1 samples, 0.02%) + + + +Parse::do_call (1 samples, 0.02%) + + + +org/joda/time/format/DateTimeFormatter:::printTo (6 samples, 0.12%) + + + +org/apache/tomcat/util/buf/StringCache:::toString (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::setAttribute (2 samples, 0.04%) + + + +__libc_write (1 samples, 0.02%) + + + +deflate (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +Monitor::lock_without_safepoint_check (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +org/springframework/web/filter/HiddenHttpMethodFilter:::doFilterInternal (1 samples, 0.02%) + + + +java/lang/Thread:::isAlive (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +CodeBlob::is_nmethod (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (1 samples, 0.02%) + + + +JavaCallWrapper::JavaCallWrapper (1 samples, 0.02%) + + + +com/coohua/caf/core/web/HttpTracingInterceptor:::preHandle (2 samples, 0.04%) + + + +net_rx_action (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +org/apache/kafka/clients/producer/KafkaProducer:::doSend (45 samples, 0.88%) + + + +org/springframework/web/filter/OncePerRequestFilter:::doFilter (3,753 samples, 73.02%) +org/springframework/web/filter/OncePerRequestFilter:::doFilter + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +java/security/Provider:::getService (2 samples, 0.04%) + + + +os::javaTimeMillis (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +com/alibaba/fastjson/serializer/IntegerCodec:::getFastMatchToken (2 samples, 0.04%) + + + +org/springframework/aop/framework/ReflectiveMethodInvocation:::proceed (1 samples, 0.02%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +JavaThread::last_frame (1 samples, 0.02%) + + + +com/coohua/caf/core/kv/JedisClusterClientAspect:::around (2 samples, 0.04%) + + + +Unsafe_Unpark (3 samples, 0.06%) + + + +org/apache/kafka/clients/producer/internals/Sender:::run (286 samples, 5.56%) +org/apa.. + + +java/util/GregorianCalendar:::computeFields (2 samples, 0.04%) + + + +org/springframework/util/StringUtils:::uriDecode (1 samples, 0.02%) + + + +tcp_transmit_skb (7 samples, 0.14%) + + + +ip_rcv (1 samples, 0.02%) + + + +pthread_cond_signal@@GLIBC_2.3.2 (32 samples, 0.62%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +sk_run_filter (1 samples, 0.02%) + + + +__schedule (11 samples, 0.21%) + + + +org/apache/tomcat/websocket/server/WsFilter:::doFilter (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (2 samples, 0.04%) + + + +org/apache/catalina/connector/Request:::parseParameters (5 samples, 0.10%) + + + +ConvI2LNode::Value (1 samples, 0.02%) + + + +JavaCalls::call_virtual (5,032 samples, 97.90%) +JavaCalls::call_virtual + + +java/util/LinkedHashMap:::get (5 samples, 0.10%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +org/springframework/aop/aspectj/AbstractAspectJAdvice:::getJoinPointMatch (1 samples, 0.02%) + + + +schedule (18 samples, 0.35%) + + + +futex_wake (1 samples, 0.02%) + + + +sock_aio_read (4 samples, 0.08%) + + + +com/coohua/caf/core/kv/JedisClusterClient:::incrBy (1 samples, 0.02%) + + + +clock_gettime (2 samples, 0.04%) + + + +vtable stub (3 samples, 0.06%) + + + +__x2apic_send_IPI_mask (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (3 samples, 0.06%) + + + +java/util/AbstractList:::equals (1 samples, 0.02%) + + + +HandleMarkCleaner::~HandleMarkCleaner (11 samples, 0.21%) + + + +java/lang/String:::hashCode (1 samples, 0.02%) + + + +deflate_slow (9 samples, 0.18%) + + + +sock_def_readable (1 samples, 0.02%) + + + +sch_direct_xmit (15 samples, 0.29%) + + + +InstanceKlass::allocate_instance (1 samples, 0.02%) + + + +sun/nio/ch/FileDispatcherImpl:::read0 (18 samples, 0.35%) + + + +java/util/HashMap:::put (2 samples, 0.04%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +dev_gro_receive (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::decimalValue (1 samples, 0.02%) + + + +virtnet_poll (1 samples, 0.02%) + + + +com/fasterxml/jackson/core/io/NumberOutput:::_outputFullBillion (1 samples, 0.02%) + + + +wake_up_state (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +BarrierSet::static_write_ref_array_pre (1 samples, 0.02%) + + + +org/joda/time/tz/CachedDateTimeZone:::getInfo (1 samples, 0.02%) + + + +Parse::Parse (1 samples, 0.02%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +CollectedHeap::post_allocation_setup_array (1 samples, 0.02%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +org/apache/kafka/clients/producer/internals/RecordAccumulator:::drain (11 samples, 0.21%) + + + +checkcast_arraycopy_uninit (2 samples, 0.04%) + + + +do_softirq (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor105:::invoke (1 samples, 0.02%) + + + +itable stub (4 samples, 0.08%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +java/lang/ThreadLocal$ThreadLocalMap:::set (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy47:::annotationType (1 samples, 0.02%) + + + +java/util/LinkedList:::unlink (1 samples, 0.02%) + + + +org/springframework/web/servlet/support/WebContentGenerator:::checkRequest (1 samples, 0.02%) + + + +ConcurrentG1RefineThread::run (3 samples, 0.06%) + + + +deflate (85 samples, 1.65%) + + + +org/apache/catalina/connector/RequestFacade:::getHeader (3 samples, 0.06%) + + + +java/util/regex/Pattern$GroupTail:::match (1 samples, 0.02%) + + + +__inet_lookup_established (1 samples, 0.02%) + + + +javax/crypto/Cipher$Transform:::matches (2 samples, 0.04%) + + + +itable stub (2 samples, 0.04%) + + + +_new_array_nozero_Java (1 samples, 0.02%) + + + +PhaseChaitin::build_ifg_physical (6 samples, 0.12%) + + + +java/lang/StringCoding:::decode (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +java/util/regex/Pattern$GroupTail:::match (4 samples, 0.08%) + + + +system_call_fastpath (25 samples, 0.49%) + + + +java/util/HashMap:::newNode (1 samples, 0.02%) + + + +org/apache/coyote/Request:::getContentType (5 samples, 0.10%) + + + +CompressedReadStream::read_int_mb (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (3 samples, 0.06%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/StringCache:::toString (2 samples, 0.04%) + + + +sun/reflect/GeneratedMethodAccessor55:::invoke (138 samples, 2.68%) +su.. + + +virtnet_poll (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +try_to_wake_up (19 samples, 0.37%) + + + +net_rx_action (1 samples, 0.02%) + + + +skb_release_all (1 samples, 0.02%) + + + +com/coohua/caf/core/metrics/ProfilerHttpFilter:::doFilter (3,588 samples, 69.81%) +com/coohua/caf/core/metrics/ProfilerHttpFilter:::doFilter + + +Method::line_number_from_bci (1 samples, 0.02%) + + + +napi_gro_receive (1 samples, 0.02%) + + + +java/util/regex/Pattern$Neg:::match (3 samples, 0.06%) + + + +com/alibaba/fastjson/parser/JSONScanner:::scanFieldString (5 samples, 0.10%) + + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +longest_match (27 samples, 0.53%) + + + +org/springframework/aop/framework/CglibAopProxy$DynamicAdvisedInterceptor:::intercept (5 samples, 0.10%) + + + +irq_exit (1 samples, 0.02%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +java/util/Collections$UnmodifiableCollection:::isEmpty (1 samples, 0.02%) + + + +TypeInt::xmeet (1 samples, 0.02%) + + + +java/util/Calendar:::createCalendar (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor:::supportsReturnType (12 samples, 0.23%) + + + +org/apache/kafka/clients/producer/internals/Sender$SenderMetrics:::updateProduceRequestMetrics (1 samples, 0.02%) + + + +org/springframework/web/servlet/DispatcherServlet:::doService (189 samples, 3.68%) +org/.. + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +pthread_mutex_unlock (1 samples, 0.02%) + + + +csum_partial (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +java/lang/ThreadLocal:::set (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +org/apache/commons/pool2/impl/GenericObjectPool:::borrowObject (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +call_softirq (2 samples, 0.04%) + + + +system_call_fastpath (18 samples, 0.35%) + + + +generic_exec_single (1 samples, 0.02%) + + + +org/springframework/web/cors/CorsUtils:::isPreFlightRequest (4 samples, 0.08%) + + + +do_softirq (1 samples, 0.02%) + + + +BarrierSet::static_write_ref_array_pre (1 samples, 0.02%) + + + +java/util/ArrayList:::toArray (1 samples, 0.02%) + + + +java/util/concurrent/ArrayBlockingQueue:::offer (3 samples, 0.06%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy8:::annotationType (1 samples, 0.02%) + + + +java/util/HashMap:::afterNodeInsertion (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +org/springframework/web/accept/ContentNegotiationManager:::resolveMediaTypes (1 samples, 0.02%) + + + +current_kernel_time (1 samples, 0.02%) + + + +java/net/URI:::<init> (8 samples, 0.16%) + + + +inflate (3 samples, 0.06%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/LoggerContext:::getLogger (1 samples, 0.02%) + + + +java/util/HashMap:::put (3 samples, 0.06%) + + + +java/lang/Enum:::hashCode (1 samples, 0.02%) + + + +__pthread_enable_asynccancel (1 samples, 0.02%) + + + +generic_exec_single (1 samples, 0.02%) + + + +Java_java_lang_reflect_Array_get (1 samples, 0.02%) + + + +BarrierSet::static_write_ref_array_pre (2 samples, 0.04%) + + + +wake_futex (30 samples, 0.58%) + + + +java/util/HashMap:::put (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/springframework/web/filter/RequestContextFilter:::doFilterInternal (3,600 samples, 70.04%) +org/springframework/web/filter/RequestContextFilter:::doFilterInternal + + +free (1 samples, 0.02%) + + + +do_sync_write (25 samples, 0.49%) + + + +sun/misc/Unsafe:::unpark (8 samples, 0.16%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +java/util/HashMap:::put (3 samples, 0.06%) + + + +java/text/DateFormatSymbols:::getProviderInstance (1 samples, 0.02%) + + + +java/util/zip/Inflater:::inflateBytes (2 samples, 0.04%) + + + +ep_scan_ready_list.isra.7 (3 samples, 0.06%) + + + +G1CollectedHeap::can_elide_initializing_store_barrier (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +Unsafe_Unpark (1 samples, 0.02%) + + + +__sb_start_write (1 samples, 0.02%) + + + +sched_clock (1 samples, 0.02%) + + + +smp_apic_timer_interrupt (1 samples, 0.02%) + + + +inflate (219 samples, 4.26%) +inflate + + +com/coohua/caf/core/sentinel/SentinelHttpFilter:::doFilter (1 samples, 0.02%) + + + +java/lang/reflect/Method:::invoke (2,644 samples, 51.44%) +java/lang/reflect/Method:::invoke + + +do_futex (4 samples, 0.08%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +CompressedReadStream::read_signed_int (2 samples, 0.04%) + + + +java/util/HashMap:::newNode (1 samples, 0.02%) + + + +java/text/SimpleDateFormat:::subParse (1 samples, 0.02%) + + + +java/security/Provider$ServiceKey:::equals (2 samples, 0.04%) + + + +[libc-2.17.so] (1 samples, 0.02%) + + + +wake_futex (1 samples, 0.02%) + + + +java_lang_Throwable::get_stack_trace_element (27 samples, 0.53%) + + + +__schedule (10 samples, 0.19%) + + + +org/apache/kafka/common/requests/ProduceRequest:::<init> (2 samples, 0.04%) + + + +com/alibaba/csp/sentinel/slots/statistic/base/LeapArray:::currentWindow (2 samples, 0.04%) + + + +do_softirq (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +java/util/regex/Pattern$Begin:::match (3 samples, 0.06%) + + + +ConcurrentG1RefineThread::run_young_rs_sampling (3 samples, 0.06%) + + + +org/apache/coyote/http11/Http11OutputBuffer:::end (88 samples, 1.71%) + + + +do_sync_read (4 samples, 0.08%) + + + +JavaThread::thread_from_jni_environment (2 samples, 0.04%) + + + +java/text/SimpleDateFormat:::parse (2 samples, 0.04%) + + + +InstanceKlass::mask_for (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy49:::annotationType (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/annotation/HttpHeadersReturnValueHandler:::supportsReturnType (1 samples, 0.02%) + + + +inflate_table (2 samples, 0.04%) + + + +net_rx_action (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +io/micrometer/core/instrument/MeterRegistry:::registerMeterIfNecessary (6 samples, 0.12%) + + + +schedule_hrtimeout_range (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +G1ParScanThreadState::copy_to_survivor_space (1 samples, 0.02%) + + + +OptoRuntime::multianewarray2_C (10 samples, 0.19%) + + + +org/apache/kafka/common/Cluster:::leaderFor (5 samples, 0.10%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/condition/PatternsRequestCondition:::getMatchingPatterns (55 samples, 1.07%) + + + +_register_finalizer_Java (4 samples, 0.08%) + + + +java/text/SimpleDateFormat:::parse (5 samples, 0.10%) + + + +OptoRuntime::new_array_nozero_C (1 samples, 0.02%) + + + +call_softirq (2 samples, 0.04%) + + + +vfs_write (24 samples, 0.47%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +BarrierSet::static_write_ref_array_post (2 samples, 0.04%) + + + +irq_exit (1 samples, 0.02%) + + + +com/google/gson/internal/bind/MapTypeAdapterFactory$Adapter:::write (1 samples, 0.02%) + + + +__do_softirq (2 samples, 0.04%) + + + +org/springframework/web/method/annotation/ModelFactory:::initModel (1 samples, 0.02%) + + + +redis/clients/jedis/Protocol:::process (1 samples, 0.02%) + + + +org/apache/kafka/common/network/Selector:::addToCompletedReceives (5 samples, 0.10%) + + + +fill_window (3 samples, 0.06%) + + + +java/text/DateFormatSymbols:::getProviderInstance (2 samples, 0.04%) + + + +java/util/Collections$UnmodifiableMap:::size (1 samples, 0.02%) + + + +frame::sender (1 samples, 0.02%) + + + +TypeArrayKlass::allocate_common (1 samples, 0.02%) + + + +com/coohua/ad/data/service/impl/AdDataServiceImpl:::ecpProcess (4 samples, 0.08%) + + + +UTF8::unicode_length (3 samples, 0.06%) + + + +_new_array_nozero_Java (1 samples, 0.02%) + + + +pipe_read (1 samples, 0.02%) + + + +java/net/URI:::<init> (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +java/util/regex/Pattern$GroupTail:::match (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/StringCache:::toString (4 samples, 0.08%) + + + +[libpthread-2.17.so] (1 samples, 0.02%) + + + +com/coohua/ad/data/domain/AdDataDomain:::getAdId (1 samples, 0.02%) + + + +jlong_disjoint_arraycopy (1 samples, 0.02%) + + + +security_file_permission (2 samples, 0.04%) + + + +org/springframework/web/util/UrlPathHelper:::getPathWithinServletMapping (2 samples, 0.04%) + + + +java/util/regex/Pattern$CharProperty:::match (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +java/lang/StringBuilder:::append (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/condition/AbstractMediaTypeExpression:::hashCode (3 samples, 0.06%) + + + +com/sun/proxy/$Proxy109:::annotationType (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor51:::invoke (1 samples, 0.02%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics:::addEvent (1 samples, 0.02%) + + + +JavaCallWrapper::~JavaCallWrapper (1 samples, 0.02%) + + + +rb_erase (1 samples, 0.02%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (2 samples, 0.04%) + + + +process_backlog (1 samples, 0.02%) + + + +G1SATBCardTableModRefBS::write_ref_array_pre (1 samples, 0.02%) + + + +I2C/C2I adapters (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +[libc-2.17.so] (27 samples, 0.53%) + + + +[libpthread-2.17.so] (25 samples, 0.49%) + + + +Interpreter (4,470 samples, 86.96%) +Interpreter + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +sun/nio/ch/SelectorImpl:::lockAndDoSelect (6 samples, 0.12%) + + + +__irqentry_text_start (2 samples, 0.04%) + + + +jni_fast_GetBooleanField (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +java/lang/ThreadLocal$ThreadLocalMap:::getEntryAfterMiss (1 samples, 0.02%) + + + +StringTable::intern (128 samples, 2.49%) +St.. + + +JVM_GetArrayElement (2 samples, 0.04%) + + + +Java_java_util_zip_Inflater_end (1 samples, 0.02%) + + + +java/util/regex/Pattern$BmpCharProperty:::match (1 samples, 0.02%) + + + +org/apache/kafka/clients/producer/KafkaProducer:::waitOnMetadata (2 samples, 0.04%) + + + +__skb_to_sgvec (1 samples, 0.02%) + + + +sock_def_readable (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/impl/Log4jLogEvent:::<init> (1 samples, 0.02%) + + + +fsnotify (2 samples, 0.04%) + + + +java/util/regex/Pattern$CharProperty:::match (1 samples, 0.02%) + + + +com/coohua/ad/data/manager/AdDataManager:::incrUserDayClickLimit (6 samples, 0.12%) + + + +__skb_checksum (1 samples, 0.02%) + + + +G1CollectedHeap::attempt_allocation_slow (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +Java_java_util_zip_Deflater_deflateBytes (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +hrtimer_try_to_cancel.part.25 (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter:::invokeHandlerMethod (153 samples, 2.98%) +or.. + + +org/springframework/web/method/support/InvocableHandlerMethod:::invokeForRequest (2,706 samples, 52.65%) +org/springframework/web/method/support/InvocableHandlerMethod:::invokeForRequest + + +skb_release_head_state (1 samples, 0.02%) + + + +java/util/regex/Pattern$Neg:::match (2 samples, 0.04%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (2 samples, 0.04%) + + + +com/fasterxml/jackson/databind/ObjectMapper:::_configAndWriteValue (1 samples, 0.02%) + + + +auditsys (1 samples, 0.02%) + + + +java/util/concurrent/ConcurrentHashMap:::putVal (7 samples, 0.14%) + + + +__do_softirq (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +finish_task_switch (1 samples, 0.02%) + + + +inet_sendmsg (1 samples, 0.02%) + + + +java/lang/Throwable:::<init> (1 samples, 0.02%) + + + +system_call_fastpath (24 samples, 0.47%) + + + +com/sun/proxy/$Proxy8:::annotationType (2 samples, 0.04%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics:::getLibProperties (4 samples, 0.08%) + + + +redis/clients/jedis/Connection:::getStatusCodeReply (1 samples, 0.02%) + + + +jlong_disjoint_arraycopy (1 samples, 0.02%) + + + +java/util/HashMap:::resize (1 samples, 0.02%) + + + +com/coohua/ad/data/controller/AdDataController:::process (2,634 samples, 51.25%) +com/coohua/ad/data/controller/AdDataController:::process + + +call_function_single_interrupt (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/DefaultJSONParser:::getResolveStatus (1 samples, 0.02%) + + + +__mnt_want_write_file (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/util/TimeUtil$1:::run (13 samples, 0.25%) + + + +frame::sender (1 samples, 0.02%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/springframework/web/context/request/ServletWebRequest:::getHeaderValues (1 samples, 0.02%) + + + +run_timer_softirq (1 samples, 0.02%) + + + +tcp_v4_early_demux (1 samples, 0.02%) + + + +wake_up_state (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::getLocale (3 samples, 0.06%) + + + +system_call_fastpath (19 samples, 0.37%) + + + +redis/clients/jedis/JedisSlotBasedConnectionHandler:::getConnectionFromSlot (1 samples, 0.02%) + + + +skb_release_data (1 samples, 0.02%) + + + +java/util/HashMap:::put (5 samples, 0.10%) + + + +vtable stub (1 samples, 0.02%) + + + +org/apache/kafka/clients/InFlightRequests:::nodesWithTimedOutRequests (1 samples, 0.02%) + + + +Method::line_number_from_bci (49 samples, 0.95%) + + + +java/lang/ThreadLocal:::set (1 samples, 0.02%) + + + +java/text/SimpleDateFormat:::initialize (1 samples, 0.02%) + + + +copy_user_enhanced_fast_string (3 samples, 0.06%) + + + +pthread_cond_signal@@GLIBC_2.3.2 (2 samples, 0.04%) + + + +java/util/HashMap:::resize (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +CompressedReadStream::read_signed_int (2 samples, 0.04%) + + + +org/apache/kafka/clients/NetworkClient:::canSendRequest (1 samples, 0.02%) + + + +sys_read (1 samples, 0.02%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +JVM_IsThreadAlive (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +CompileBroker::compiler_thread_loop (48 samples, 0.93%) + + + +java/lang/String:::toLowerCase (1 samples, 0.02%) + + + +java/util/HashMap:::put (2 samples, 0.04%) + + + +java_lang_Throwable::get_stack_trace_element (444 samples, 8.64%) +java_lang_Th.. + + +__dev_queue_xmit (1 samples, 0.02%) + + + +G1CollectorPolicy::add_region_to_incremental_cset_lhs (1 samples, 0.02%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (3 samples, 0.06%) + + + +call_softirq (1 samples, 0.02%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +I2C/C2I adapters (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +DebugInformationRecorder::dump_object_pool (1 samples, 0.02%) + + + +G1CollectedHeap::allocate_new_tlab (1 samples, 0.02%) + + + +__wake_up_sync_key (18 samples, 0.35%) + + + +OptoRuntime::new_array_nozero_C (1 samples, 0.02%) + + + +BarrierSet::static_write_ref_array_post (2 samples, 0.04%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +__memmove_ssse3_back (8 samples, 0.16%) + + + +jni_ExceptionOccurred (1 samples, 0.02%) + + + +PhaseIdealLoop::build_loop_early (3 samples, 0.06%) + + + +do_softirq (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +ep_scan_ready_list.isra.7 (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +Parse::do_one_block (1 samples, 0.02%) + + + +tcp_fin (1 samples, 0.02%) + + + +org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite:::selectHandler (50 samples, 0.97%) + + + +java/util/HashMap:::resize (8 samples, 0.16%) + + + +org/apache/catalina/connector/Request:::setAttribute (1 samples, 0.02%) + + + +java/util/regex/Matcher:::replaceAll (1 samples, 0.02%) + + + +jbyte_disjoint_arraycopy (1 samples, 0.02%) + + + +__pthread_mutex_cond_lock (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/LoggerContext:::getLogger (1 samples, 0.02%) + + + +__block_commit_write.isra.19 (1 samples, 0.02%) + + + +os::javaTimeMillis (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +Dict::Insert (1 samples, 0.02%) + + + +G1SATBCardTableModRefBS::enqueue (1 samples, 0.02%) + + + +com/coohua/ad/data/domain/AdDataDomain:::setAnonymous (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +org/apache/tomcat/util/threads/ThreadPoolExecutor:::currentThreadShouldBeStopped (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +java/util/ArrayList:::toArray (3 samples, 0.06%) + + + +org/springframework/util/LinkedCaseInsensitiveMap:::hashCode (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (1 samples, 0.02%) + + + +fdval (1 samples, 0.02%) + + + +IndexSet::IndexSet (1 samples, 0.02%) + + + +com/fasterxml/jackson/core/json/WriterBasedJsonGenerator:::writeEndObject (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (9 samples, 0.18%) + + + +Java_java_lang_Throwable_fillInStackTrace (4 samples, 0.08%) + + + +java/lang/Class:::getConstructor0 (1 samples, 0.02%) + + + +org/springframework/core/convert/support/GenericConversionService$ConverterCacheKey:::equals (1 samples, 0.02%) + + + +sys_sendto (6 samples, 0.12%) + + + +java/util/GregorianCalendar:::computeTime (1 samples, 0.02%) + + + +rw_verify_area (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/context/ContextUtil:::trueEnter (2 samples, 0.04%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +TypeArrayKlass::allocate_common (1 samples, 0.02%) + + + +org/apache/catalina/connector/CoyoteAdapter:::postParseRequest (1 samples, 0.02%) + + + +java/util/ArrayList:::add (1 samples, 0.02%) + + + +__GI___libc_poll (1 samples, 0.02%) + + + +CodeBlob::is_zombie (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +deflate_slow (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +system_call_fastpath (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +org/springframework/context/i18n/LocaleContextHolder:::setLocale (4 samples, 0.08%) + + + +PhaseIdealLoop::get_early_ctrl (1 samples, 0.02%) + + + +sun/nio/ch/SocketChannelImpl:::write (4 samples, 0.08%) + + + +PhaseCFG::do_global_code_motion (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/DefaultJSONParser:::parse (1 samples, 0.02%) + + + +virtnet_poll (1 samples, 0.02%) + + + +oopDesc::obj_field_put (1 samples, 0.02%) + + + +java/util/Calendar:::createCalendar (5 samples, 0.10%) + + + +do_softirq (1 samples, 0.02%) + + + +java_start (5,106 samples, 99.34%) +java_start + + +InstanceRefKlass::oop_oop_iterate_nv (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/ObjectMapper:::_configAndWriteValue (1 samples, 0.02%) + + + +sun/misc/Unsafe:::unpark (21 samples, 0.41%) + + + +sun/reflect/GeneratedMethodAccessor95:::invoke (1 samples, 0.02%) + + + +org/apache/tomcat/util/net/NioEndpoint$NioSocketWrapper:::doWrite (86 samples, 1.67%) + + + +net_rx_action (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy77:::annotationType (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +AbsSeq::davg (1 samples, 0.02%) + + + +JVM_GetStackTraceElement (1 samples, 0.02%) + + + +org/springframework/web/servlet/FrameworkServlet:::processRequest (192 samples, 3.74%) +org/.. + + +org/springframework/boot/actuate/trace/http/HttpExchangeTracer$$Lambda$656/561830191:::test (1 samples, 0.02%) + + + +java/lang/ref/Finalizer:::register (1 samples, 0.02%) + + + +java/util/HashSet:::isEmpty (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +java/util/LinkedHashMap:::get (2 samples, 0.04%) + + + +schedule_hrtimeout_range (11 samples, 0.21%) + + + +call_softirq (1 samples, 0.02%) + + + +mod_timer (1 samples, 0.02%) + + + +os::javaTimeMillis (1 samples, 0.02%) + + + +com/coohua/caf/core/metrics/LatencyStat:::setMin (2 samples, 0.04%) + + + +org/apache/catalina/connector/CoyoteAdapter:::parseSessionSslId (1 samples, 0.02%) + + + +thread_entry (5,032 samples, 97.90%) +thread_entry + + +com/google/gson/stream/JsonWriter:::close (1 samples, 0.02%) + + + +tcp4_gro_receive (1 samples, 0.02%) + + + +Unsafe_Unpark (8 samples, 0.16%) + + + +netdev_pick_tx (1 samples, 0.02%) + + + +__audit_syscall_entry (1 samples, 0.02%) + + + +java/util/regex/Pattern$Ctype:::isSatisfiedBy (1 samples, 0.02%) + + + +Java_sun_nio_ch_EPollArrayWrapper_epollWait (1 samples, 0.02%) + + + +ep_send_events_proc (1 samples, 0.02%) + + + +getnstimeofday64 (1 samples, 0.02%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +pthread_getspecific (4 samples, 0.08%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +_tr_flush_block (7 samples, 0.14%) + + + +call_softirq (1 samples, 0.02%) + + + +org/springframework/util/AntPathMatcher:::doMatch (10 samples, 0.19%) + + + +org/springframework/web/util/UrlPathHelper:::getPathWithinApplication (4 samples, 0.08%) + + + +org/springframework/web/filter/CharacterEncodingFilter:::doFilterInternal (3,756 samples, 73.07%) +org/springframework/web/filter/CharacterEncodingFilter:::doFilterInternal + + +org/springframework/cglib/proxy/MethodProxy:::invoke (2 samples, 0.04%) + + + +java/util/HashMap:::newNode (1 samples, 0.02%) + + + +build_tree (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +java/util/HashMap:::put (2 samples, 0.04%) + + + +java/security/Provider$ServiceKey:::hashCode (1 samples, 0.02%) + + + +build_tree (81 samples, 1.58%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +malloc (1 samples, 0.02%) + + + +java/text/DateFormatSymbols:::getInstance (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +pvclock_clocksource_read (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/ObjectMapper:::writeValueAsString (68 samples, 1.32%) + + + +org/springframework/context/event/AbstractApplicationEventMulticaster$ListenerRetriever:::getApplicationListeners (2 samples, 0.04%) + + + +InterpreterRuntime::frequency_counter_overflow (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +jni_fast_GetIntField (2 samples, 0.04%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +epoll_ctl (15 samples, 0.29%) + + + +com/sun/proxy/$Proxy48:::annotationType (1 samples, 0.02%) + + + +TypeArrayKlass::allocate_common (1 samples, 0.02%) + + + +org/apache/kafka/common/metrics/Sensor:::record (5 samples, 0.10%) + + + +org/springframework/web/servlet/FrameworkServlet:::logResult (1 samples, 0.02%) + + + +org/apache/coyote/AbstractProcessor:::action (103 samples, 2.00%) +o.. + + +oopDesc::obj_field_put (2 samples, 0.04%) + + + +virtnet_poll (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/config/LoggerConfig:::log (3 samples, 0.06%) + + + +ret_from_intr (1 samples, 0.02%) + + + +org/springframework/util/StringUtils:::trimAllWhitespace (1 samples, 0.02%) + + + +G1SATBCardTableModRefBS::write_ref_field_pre_work (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +org/springframework/web/servlet/DispatcherServlet:::processDispatchResult (165 samples, 3.21%) +org.. + + +__do_softirq (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/Type$11:::sizeOf (2 samples, 0.04%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter:::getSessionAttributesHandler (1 samples, 0.02%) + + + +pthread_self (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/impl/Log4jLogEvent:::<init> (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +get_futex_key (1 samples, 0.02%) + + + +java/util/regex/Pattern$Curly:::match (1 samples, 0.02%) + + + +x2apic_send_IPI_mask (2 samples, 0.04%) + + + +consume_skb (1 samples, 0.02%) + + + +java/lang/Throwable:::<init> (4 samples, 0.08%) + + + +free_old_xmit_skbs.isra.32 (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +dev_gro_receive (1 samples, 0.02%) + + + +OptoRuntime::new_instance_C (2 samples, 0.04%) + + + +org/springframework/context/support/AbstractApplicationContext:::publishEvent (22 samples, 0.43%) + + + +do_softirq (1 samples, 0.02%) + + + +_raw_spin_lock (1 samples, 0.02%) + + + +java/net/URI$Parser:::parseServer (1 samples, 0.02%) + + + +Symbol::as_unicode (2 samples, 0.04%) + + + +hrtimer_cancel (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::scanSymbol (1 samples, 0.02%) + + + +org/apache/kafka/clients/producer/internals/RecordAccumulator:::ready (21 samples, 0.41%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +sun/misc/Unsafe:::park (39 samples, 0.76%) + + + +java/util/regex/Pattern$Node:::match (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils$AnnotationCollector:::process (1 samples, 0.02%) + + + +system_call_fastpath (2 samples, 0.04%) + + + +apic_timer_interrupt (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/config/LoggerConfig:::log (5 samples, 0.10%) + + + +file_update_time (9 samples, 0.18%) + + + +Unsafe_Unpark (21 samples, 0.41%) + + + +java/util/LinkedHashMap:::newNode (1 samples, 0.02%) + + + +javax/crypto/Cipher$Transform:::matches (2 samples, 0.04%) + + + +apic_timer_interrupt (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver:::resolveArgument (1 samples, 0.02%) + + + +JavaCalls::call_virtual (5,032 samples, 97.90%) +JavaCalls::call_virtual + + +org/apache/catalina/connector/RequestFacade:::removeAttribute (3 samples, 0.06%) + + + +JfrBackend::is_event_enabled (1 samples, 0.02%) + + + +sun/nio/ch/EPollArrayWrapper:::epollCtl (2 samples, 0.04%) + + + +org/apache/catalina/webresources/Cache:::getResource (4 samples, 0.08%) + + + +java/lang/String:::hashCode (1 samples, 0.02%) + + + +__pthread_disable_asynccancel (1 samples, 0.02%) + + + +do_sync_write (2 samples, 0.04%) + + + +auditsys (1 samples, 0.02%) + + + +_new_array_Java (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +StringTable::intern (1 samples, 0.02%) + + + +java/util/AbstractList:::hashCode (1 samples, 0.02%) + + + +_raw_spin_unlock (1 samples, 0.02%) + + + +unroll_tree_refs (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +java/util/HashMap$HashIterator:::hasNext (2 samples, 0.04%) + + + +org/springframework/util/StringUtils:::uriDecode (6 samples, 0.12%) + + + +arrayOopDesc::base (7 samples, 0.14%) + + + +__sb_end_write (1 samples, 0.02%) + + + +com/coohua/caf/core/rpc/MotanConfigPrintSpringListener:::onApplicationEvent (2 samples, 0.04%) + + + +jni_ReleasePrimitiveArrayCritical (7 samples, 0.14%) + + + +irq_exit (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::intValue (2 samples, 0.04%) + + + +sys_read (15 samples, 0.29%) + + + +java/io/StringWriter:::write (1 samples, 0.02%) + + + +virtnet_poll (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11Processor:::prepareRequest (1 samples, 0.02%) + + + +java/util/concurrent/locks/ReentrantLock$NonfairSync:::lock (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer:::parseRest (1 samples, 0.02%) + + + +org/springframework/beans/TypeConverterSupport:::doConvert (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils:::findAnnotation (1 samples, 0.02%) + + + +hrtimer_start_range_ns (2 samples, 0.04%) + + + +process_backlog (1 samples, 0.02%) + + + +jbd2_journal_put_journal_head (1 samples, 0.02%) + + + +sys_write (1 samples, 0.02%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +__schedule (18 samples, 0.35%) + + + +Type::hashcons (1 samples, 0.02%) + + + +java/util/HashMap:::merge (3 samples, 0.06%) + + + +net_rx_action (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils:::isInJavaLangAnnotationPackage (2 samples, 0.04%) + + + +ret_from_intr (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::setAttribute (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::getAttribute (2 samples, 0.04%) + + + +java_lang_String::equals (3 samples, 0.06%) + + + +net_rx_action (1 samples, 0.02%) + + + +ext4_da_write_end (5 samples, 0.10%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +java/util/zip/Inflater:::ensureOpen (1 samples, 0.02%) + + + +BarrierSet::static_write_ref_array_pre (1 samples, 0.02%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/Schema:::read (1 samples, 0.02%) + + + +skb_clone (1 samples, 0.02%) + + + +org/springframework/web/servlet/support/WebContentGenerator:::prepareResponse (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/Type$7:::sizeOf (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +com/coohua/caf/core/web/HttpTracingInterceptor:::preHandle (9 samples, 0.18%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +java_lang_StackTraceElement::set_declaringClass (1 samples, 0.02%) + + + +__dev_queue_xmit (2 samples, 0.04%) + + + +org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler:::getAttributeValue (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +com/weibo/api/motan/filter/AccessLogFilter:::filter (8 samples, 0.16%) + + + +org/apache/kafka/common/protocol/types/ArrayOf:::read (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +sys_write (24 samples, 0.47%) + + + +org/apache/catalina/connector/Request:::setAttribute (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler:::getAttributeValue (1 samples, 0.02%) + + + +BacktraceBuilder::push (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/system/SystemSlot:::entry (2 samples, 0.04%) + + + +irq_exit (1 samples, 0.02%) + + + +java/net/SocketInputStream:::read (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/MessageBytes:::toString (1 samples, 0.02%) + + + +ObjectSynchronizer::FastHashCode (1 samples, 0.02%) + + + +java/util/zip/GZIPOutputStream:::finish (174 samples, 3.39%) +jav.. + + +java/lang/ThreadLocal:::set (3 samples, 0.06%) + + + +do_softirq (1 samples, 0.02%) + + + +__do_softirq (2 samples, 0.04%) + + + +org/springframework/core/MethodParameter:::hashCode (1 samples, 0.02%) + + + +java/text/DateFormatSymbols:::copyMembers (1 samples, 0.02%) + + + +java/net/SocketInputStream:::socketRead0 (1 samples, 0.02%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +java/util/LinkedHashMap:::get (3 samples, 0.06%) + + + +java/util/GregorianCalendar:::computeFields (1 samples, 0.02%) + + + +PhaseChaitin::fixup_spills (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +org/apache/catalina/connector/ResponseFacade:::containsHeader (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::setAttribute (1 samples, 0.02%) + + + +tcp_md5_do_lookup (1 samples, 0.02%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +org/apache/kafka/clients/producer/internals/Sender:::sendProduceRequest (46 samples, 0.89%) + + + +javax/crypto/Cipher$Transform:::matches (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +__wake_up_sync_key (9 samples, 0.18%) + + + +irq_exit (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotatedElementUtils:::searchWithFindSemantics (2 samples, 0.04%) + + + +jni_SetIntField (77 samples, 1.50%) + + + +ip_rcv (1 samples, 0.02%) + + + +nmethod::is_zombie (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (2 samples, 0.04%) + + + +IndexSet::initialize (1 samples, 0.02%) + + + +java/io/ByteArrayInputStream:::close (2 samples, 0.04%) + + + +__getnstimeofday64 (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/condition/ProducesRequestCondition$ProduceMediaTypeExpression:::matchMediaType (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterFactory:::createFilterChain (6 samples, 0.12%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +G1ParScanThreadState::copy_to_survivor_space (2 samples, 0.04%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (216 samples, 4.20%) +org/a.. + + +java/util/concurrent/ConcurrentHashMap:::putVal (1 samples, 0.02%) + + + +G1CollectedHeap::retire_mutator_alloc_region (1 samples, 0.02%) + + + +sys_read (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy5:::annotationType (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy5:::annotationType (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/springframework/web/filter/OncePerRequestFilter:::doFilter (3,548 samples, 69.03%) +org/springframework/web/filter/OncePerRequestFilter:::doFilter + + +org/apache/tomcat/util/net/NioEndpoint$NioSocketWrapper:::fillReadBuffer (27 samples, 0.53%) + + + +do_futex (1 samples, 0.02%) + + + +OtherRegionsTable::occupied (1 samples, 0.02%) + + + +com/coohua/caf/core/kv/JedisClient:::incrBy (1 samples, 0.02%) + + + +sun/nio/ch/SocketChannelImpl:::write (26 samples, 0.51%) + + + +sun/nio/cs/ISO_8859_1$Decoder:::decodeLoop (1 samples, 0.02%) + + + +__kfree_skb (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +sys_futex (21 samples, 0.41%) + + + +CollectedHeap::allocate_from_tlab_slow (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +java/lang/StringCoding:::decode (5 samples, 0.10%) + + + +org/springframework/util/CollectionUtils$MultiValueMapAdapter:::getFirst (3 samples, 0.06%) + + + +org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor:::getProducibleMediaTypes (3 samples, 0.06%) + + + +sock_def_readable (1 samples, 0.02%) + + + +ObjArrayKlass::allocate (1 samples, 0.02%) + + + +tcp_poll (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor103:::invoke (2 samples, 0.04%) + + + +com/weibo/api/motan/transport/netty/NettyChannel:::request (2 samples, 0.04%) + + + +__percpu_counter_add (1 samples, 0.02%) + + + +java/util/Formatter$FormatSpecifier:::checkBadFlags (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::recycle (7 samples, 0.14%) + + + +net_rx_action (1 samples, 0.02%) + + + +org/springframework/boot/actuate/web/trace/servlet/HttpTraceFilter:::doFilterInternal (200 samples, 3.89%) +org/.. + + +sun/nio/cs/UTF_8$Encoder:::encode (6 samples, 0.12%) + + + +net_rx_action (1 samples, 0.02%) + + + +longest_match (1 samples, 0.02%) + + + +itable stub (1 samples, 0.02%) + + + +futex_wake_op (1 samples, 0.02%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +Java_java_util_zip_Inflater_inflateBytes (760 samples, 14.79%) +Java_java_util_zip_Inf.. + + +do_softirq (1 samples, 0.02%) + + + +org/springframework/aop/aspectj/AbstractAspectJAdvice:::invokeAdviceMethod (4 samples, 0.08%) + + + +java/util/zip/GZIPInputStream:::readUByte (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +ip_output (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer:::parseRest (120 samples, 2.33%) +c.. + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +org/springframework/web/servlet/FrameworkServlet:::processRequest (3,445 samples, 67.02%) +org/springframework/web/servlet/FrameworkServlet:::processRequest + + +com/alibaba/fastjson/parser/DefaultJSONParser:::parse (2 samples, 0.04%) + + + +G1SATBCardTableLoggingModRefBS::invalidate (1 samples, 0.02%) + + + +CollectedHeap::new_store_pre_barrier (1 samples, 0.02%) + + + +java/util/zip/DeflaterOutputStream:::write (1 samples, 0.02%) + + + +Unsafe_Park (5 samples, 0.10%) + + + +call_softirq (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +inflateInit2_ (1 samples, 0.02%) + + + +checkcast_arraycopy (1 samples, 0.02%) + + + +org/springframework/web/util/UrlPathHelper:::getPathWithinApplication (1 samples, 0.02%) + + + +JVM_GetEnclosingMethodInfo (1 samples, 0.02%) + + + +update_time (9 samples, 0.18%) + + + +JavaThread::thread_from_jni_environment (4 samples, 0.08%) + + + +org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler:::getAttributeValue (3 samples, 0.06%) + + + +irq_exit (1 samples, 0.02%) + + + +_tr_flush_block (127 samples, 2.47%) +_t.. + + +java/util/LinkedHashMap:::get (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +generic_exec_single (2 samples, 0.04%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +__GI___libc_poll (1 samples, 0.02%) + + + +itable stub (1 samples, 0.02%) + + + +os::sleep (8 samples, 0.16%) + + + +__do_softirq (1 samples, 0.02%) + + + +checkcast_arraycopy_uninit (1 samples, 0.02%) + + + +HandleMarkCleaner::~HandleMarkCleaner (1 samples, 0.02%) + + + +java/lang/String:::charAt (3 samples, 0.06%) + + + +ciEnv::register_method (1 samples, 0.02%) + + + +org/apache/catalina/connector/CoyoteAdapter:::postParseRequest (21 samples, 0.41%) + + + +java/util/regex/Pattern$GroupTail:::match (1 samples, 0.02%) + + + +system_call_after_swapgs (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +org/springframework/web/filter/RequestContextFilter:::doFilterInternal (204 samples, 3.97%) +org/.. + + +sys_read (3 samples, 0.06%) + + + +longest_match (2 samples, 0.04%) + + + +_raw_spin_unlock_irqrestore (2 samples, 0.04%) + + + +org/springframework/boot/actuate/metrics/web/servlet/LongTaskTimingHandlerInterceptor:::getLongTaskTimerSamples (5 samples, 0.10%) + + + +org/apache/catalina/core/ApplicationFilterChain:::doFilter (3,685 samples, 71.69%) +org/apache/catalina/core/ApplicationFilterChain:::doFilter + + +sun/misc/Unsafe:::unpark (3 samples, 0.06%) + + + +org/apache/logging/log4j/message/ParameterizedMessage:::getFormattedMessage (5 samples, 0.10%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +javax/crypto/Cipher:::chooseProvider (2 samples, 0.04%) + + + +Matcher::xform (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +java/lang/StringBuilder:::append (4 samples, 0.08%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +pqdownheap (60 samples, 1.17%) + + + +org/springframework/util/MimeType$SpecificityComparator:::compare (3 samples, 0.06%) + + + +inflateStateCheck (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +java/util/HashMap:::put (1 samples, 0.02%) + + + +org/springframework/web/filter/OncePerRequestFilter:::doFilter (211 samples, 4.11%) +org/.. + + +sun/nio/ch/SelectionKeyImpl:::nioInterestOps (1 samples, 0.02%) + + + +com/coohua/ad/data/interceptor/UserInterceptor:::preHandle (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/SymbolTable:::addSymbol (5 samples, 0.10%) + + + +arrayof_jint_fill (4 samples, 0.08%) + + + +vtable stub (1 samples, 0.02%) + + + +netif_receive_skb_internal (1 samples, 0.02%) + + + +sysret_audit (1 samples, 0.02%) + + + +inet_recvmsg (1 samples, 0.02%) + + + +C2Compiler::compile_method (48 samples, 0.93%) + + + +java/util/regex/Pattern$Curly:::match (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +vfs_writev (18 samples, 0.35%) + + + +irq_exit (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/Struct:::set (1 samples, 0.02%) + + + +jlong_disjoint_arraycopy (4 samples, 0.08%) + + + +call_softirq (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +org/apache/coyote/AbstractProtocol$ConnectionHandler:::process (4,041 samples, 78.62%) +org/apache/coyote/AbstractProtocol$ConnectionHandler:::process + + +com/coohua/caf/core/web/HttpTracingInterceptor:::postHandle (1 samples, 0.02%) + + + +InstanceRefKlass::oop_oop_iterate_backwards_nv (3 samples, 0.06%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +org/springframework/web/util/UrlPathHelper:::getPathWithinApplication (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer:::parseField (4 samples, 0.08%) + + + +java/net/SocketInputStream:::read (1 samples, 0.02%) + + + +java/util/LinkedHashMap:::newNode (1 samples, 0.02%) + + + +java/util/concurrent/ConcurrentHashMap$EntryIterator:::next (1 samples, 0.02%) + + + +__audit_syscall_exit (1 samples, 0.02%) + + + +java/util/Formatter$FormatSpecifier:::print (2 samples, 0.04%) + + + +ip_rcv (1 samples, 0.02%) + + + +org/apache/kafka/common/requests/ProduceRequest$Builder:::build (3 samples, 0.06%) + + + +pthread_cond_wait@@GLIBC_2.3.2 (18 samples, 0.35%) + + + +CodeBuffer::free_blob (1 samples, 0.02%) + + + +org/springframework/web/filter/OncePerRequestFilter:::doFilter (204 samples, 3.97%) +org/.. + + +org/apache/kafka/common/requests/AbstractResponse:::parseResponse (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +org/springframework/web/servlet/handler/ConversionServiceExposingInterceptor:::preHandle (2 samples, 0.04%) + + + +tcp_time_wait (1 samples, 0.02%) + + + +com/coohua/caf/core/metrics/LatencyStat:::setMax (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +java_lang_Thread::set_thread_status (1 samples, 0.02%) + + + +org/apache/kafka/common/metrics/stats/SampledStat:::record (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +MultiNode::is_CFG (1 samples, 0.02%) + + + +apic_timer_interrupt (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +org/apache/tomcat/util/http/NamesEnumerator:::findNext (12 samples, 0.23%) + + + +com/sun/proxy/$Proxy0:::annotationType (1 samples, 0.02%) + + + +org/springframework/web/servlet/handler/AbstractHandlerMethodMapping:::getHandlerInternal (138 samples, 2.68%) +or.. + + +org/apache/tomcat/util/buf/MessageBytes:::toString (1 samples, 0.02%) + + + +sysret_check (1 samples, 0.02%) + + + +_complete_monitor_locking_Java (1 samples, 0.02%) + + + +vfs_read (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer:::deserialze (9 samples, 0.18%) + + + +virtnet_poll (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +inet_gro_receive (1 samples, 0.02%) + + + +org/springframework/cglib/proxy/MethodProxy:::invoke (1 samples, 0.02%) + + + +com/weibo/api/motan/proxy/RefererInvocationHandler:::invoke (10 samples, 0.19%) + + + +mark_buffer_dirty (1 samples, 0.02%) + + + +futex_wait_setup (1 samples, 0.02%) + + + +ThreadLocalAllocBuffer::fill (1 samples, 0.02%) + + + +__audit_syscall_entry (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/StringCache:::toString (4 samples, 0.08%) + + + +ret_from_intr (1 samples, 0.02%) + + + +org/apache/coyote/Request:::recycle (8 samples, 0.16%) + + + +java/text/DecimalFormat:::parse (1 samples, 0.02%) + + + +napi_gro_receive (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +sys_write (1 samples, 0.02%) + + + +futex_wake_op (1 samples, 0.02%) + + + +BacktraceBuilder::expand (2 samples, 0.04%) + + + +tcp_ack (1 samples, 0.02%) + + + +PhaseChaitin::Register_Allocate (25 samples, 0.49%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +updateBytesCRC32 (3 samples, 0.06%) + + + +java/util/regex/Pattern:::RemoveQEQuoting (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy43:::annotationType (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +sys_futex (1 samples, 0.02%) + + + +G1RootProcessor::process_java_roots (3 samples, 0.06%) + + + +malloc (4 samples, 0.08%) + + + +com/google/gson/stream/JsonWriter:::close (1 samples, 0.02%) + + + +tcp_send_mss (1 samples, 0.02%) + + + +jlong_disjoint_arraycopy (1 samples, 0.02%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +com/coohua/ad/data/service/impl/AdDataServiceImpl:::dataProcess (8 samples, 0.16%) + + + +org/apache/kafka/clients/producer/internals/ProducerBatch:::completeFutureAndFireCallbacks (32 samples, 0.62%) + + + +__audit_syscall_exit (1 samples, 0.02%) + + + +sun/nio/cs/UTF_8$Decoder:::decode (1 samples, 0.02%) + + + +sun/nio/ch/SocketChannelImpl:::translateReadyOps (1 samples, 0.02%) + + + +IndexSetIterator::advance_and_next (1 samples, 0.02%) + + + +java/util/HashMap$EntrySet:::iterator (2 samples, 0.04%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (3,685 samples, 71.69%) +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter + + +org/springframework/web/servlet/handler/AbstractUrlHandlerMapping:::lookupHandler (12 samples, 0.23%) + + + +vtable stub (2 samples, 0.04%) + + + +process_backlog (1 samples, 0.02%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot:::exit (15 samples, 0.29%) + + + +org/apache/kafka/clients/NetworkClient:::handleInitiateApiVersionRequests (2 samples, 0.04%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/apache/catalina/core/StandardContext:::getApplicationEventListeners (1 samples, 0.02%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +[unknown] (15 samples, 0.29%) + + + +wake_up_state (2 samples, 0.04%) + + + +__fsnotify_parent (1 samples, 0.02%) + + + +__srcu_read_unlock (1 samples, 0.02%) + + + +java/util/concurrent/ConcurrentHashMap:::putVal (1 samples, 0.02%) + + + +get_page_from_freelist (1 samples, 0.02%) + + + +org/apache/kafka/common/record/ByteBufferLogInputStream:::nextBatch (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +JNIHandleBlock::allocate_block (1 samples, 0.02%) + + + +java/util/HashMap:::put (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/node/StatisticNode:::addRtAndSuccess (10 samples, 0.19%) + + + +sun/nio/ch/EPollArrayWrapper:::interrupt (24 samples, 0.47%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +java/util/regex/Pattern$Slice:::match (1 samples, 0.02%) + + + +java/util/regex/Pattern$BmpCharProperty:::match (6 samples, 0.12%) + + + +hrtimer_start_range_ns (1 samples, 0.02%) + + + +java/lang/Throwable:::getStackTraceElement (1 samples, 0.02%) + + + +sun/security/jca/ProviderList$ServiceList:::tryGet (1 samples, 0.02%) + + + +mutex_lock (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +__vdso_clock_gettime (1 samples, 0.02%) + + + +CollectedHeap::allocate_from_tlab_slow (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/annotation/CallableMethodReturnValueHandler:::supportsReturnType (1 samples, 0.02%) + + + +java/lang/ThreadLocal$ThreadLocalMap:::getEntryAfterMiss (1 samples, 0.02%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (14 samples, 0.27%) + + + +smp_apic_timer_interrupt (1 samples, 0.02%) + + + +java/util/HashMap:::get (1 samples, 0.02%) + + + +java/util/concurrent/ConcurrentHashMap:::get (1 samples, 0.02%) + + + +jlong_disjoint_arraycopy (1 samples, 0.02%) + + + +consume_skb (1 samples, 0.02%) + + + +java/text/SimpleDateFormat:::compile (7 samples, 0.14%) + + + +org/apache/kafka/clients/NetworkClient:::doSend (33 samples, 0.64%) + + + +com/coohua/caf/core/kv/JedisClient$$EnhancerBySpringCGLIB$$712d2b04:::incrBy (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +sock_poll (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +java/util/HashMap:::newNode (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +org/springframework/web/servlet/handler/AbstractHandlerMapping:::getHandlerExecutionChain (15 samples, 0.29%) + + + +java/lang/AbstractStringBuilder:::append (14 samples, 0.27%) + + + +java/util/regex/Pattern$BmpCharProperty:::match (1 samples, 0.02%) + + + +CodeCache::find_blob (1 samples, 0.02%) + + + +sk_stream_alloc_skb (1 samples, 0.02%) + + + +generic_exec_single (1 samples, 0.02%) + + + +os::javaTimeMillis (1 samples, 0.02%) + + + +nmethod::is_zombie (1 samples, 0.02%) + + + +tcp_data_queue (1 samples, 0.02%) + + + +org/springframework/aop/aspectj/AbstractAspectJAdvice:::invokeAdviceMethod (1 samples, 0.02%) + + + +__srcu_read_lock (3 samples, 0.06%) + + + +java/util/IdentityHashMap:::get (1 samples, 0.02%) + + + +CollectedHeap::common_mem_allocate_init (2 samples, 0.04%) + + + +net_rx_action (1 samples, 0.02%) + + + +tcp_cleanup_rbuf (2 samples, 0.04%) + + + +org/apache/catalina/core/StandardWrapperValve:::invoke (216 samples, 4.20%) +org/a.. + + +GenericTaskQueueSet<OverflowTaskQueue<StarTask, (4 samples, 0.08%) + + + +jni_fast_GetIntField (25 samples, 0.49%) + + + +org/apache/catalina/core/StandardWrapperValve:::invoke (3,775 samples, 73.44%) +org/apache/catalina/core/StandardWrapperValve:::invoke + + +call_function_single_interrupt (1 samples, 0.02%) + + + +ObjArrayKlass::multi_allocate (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (3 samples, 0.06%) + + + +org/springframework/web/servlet/handler/AbstractUrlHandlerMapping:::getHandlerInternal (1 samples, 0.02%) + + + +Parse::do_call (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/ser/std/MapSerializer:::serializeFields (2 samples, 0.04%) + + + +com/google/gson/stream/JsonWriter:::string (5 samples, 0.10%) + + + +java/util/concurrent/ConcurrentMap:::computeIfAbsent (1 samples, 0.02%) + + + +java/lang/String:::equals (2 samples, 0.04%) + + + +java/text/SimpleDateFormat:::format (4 samples, 0.08%) + + + +java/util/concurrent/locks/ReentrantLock$NonfairSync:::lock (1 samples, 0.02%) + + + +dev_gro_receive (1 samples, 0.02%) + + + +__alloc_skb (1 samples, 0.02%) + + + +pthread_cond_signal@@GLIBC_2.3.2 (3 samples, 0.06%) + + + +G1CollectorPolicy::update_incremental_cset_info (1 samples, 0.02%) + + + +__skb_checksum (1 samples, 0.02%) + + + +org/apache/catalina/core/StandardHostValve:::invoke (216 samples, 4.20%) +org/a.. + + +wake_up_state (5 samples, 0.10%) + + + +getnstimeofday64 (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +__vdso_gettimeofday (2 samples, 0.04%) + + + +_new_array_nozero_Java (2 samples, 0.04%) + + + +Symbol::as_klass_external_name (4 samples, 0.08%) + + + +sock_def_readable (1 samples, 0.02%) + + + +generic_exec_single (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/StringCache:::toString (8 samples, 0.16%) + + + +com/alibaba/csp/sentinel/slots/logger/LogSlot:::entry (1 samples, 0.02%) + + + +wake_up_state (3 samples, 0.06%) + + + +G1ParTask::work (19 samples, 0.37%) + + + +system_call_fastpath (43 samples, 0.84%) + + + +org/apache/catalina/core/StandardContext:::getApplicationEventListeners (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +org/apache/kafka/clients/producer/internals/Sender:::completeBatch (34 samples, 0.66%) + + + +sun/nio/cs/UTF_8$Encoder:::encode (1 samples, 0.02%) + + + +org/apache/logging/log4j/message/ParameterFormatter:::recursiveDeepToString (1 samples, 0.02%) + + + +finish_task_switch (11 samples, 0.21%) + + + +pthread_getspecific (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/springframework/web/method/annotation/ModelFactory:::findSessionAttributeArguments (1 samples, 0.02%) + + + +org/jboss/netty/channel/socket/nio/NioWorker:::writeFromUserCode (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler:::invoke (1 samples, 0.02%) + + + +_new_array_nozero_Java (1 samples, 0.02%) + + + +__audit_syscall_exit (1 samples, 0.02%) + + + +sun/nio/ch/SelectorImpl:::select (1 samples, 0.02%) + + + +longest_match (12 samples, 0.23%) + + + +schedule_hrtimeout_range_clock (16 samples, 0.31%) + + + +select_estimate_accuracy (1 samples, 0.02%) + + + +org/springframework/beans/factory/support/DefaultSingletonBeanRegistry:::getSingleton (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor:::supportsReturnType (1 samples, 0.02%) + + + +java/util/HashSet:::iterator (1 samples, 0.02%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +OptoRuntime::new_array_nozero_C (1 samples, 0.02%) + + + +sun/nio/ch/NativeThread:::current (1 samples, 0.02%) + + + +OptoRuntime::new_instance_C (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/StringCache:::toString (1 samples, 0.02%) + + + +org/springframework/util/AntPathMatcher:::match (3 samples, 0.06%) + + + +inet_sendmsg (37 samples, 0.72%) + + + +org/springframework/web/accept/ContentNegotiationManager:::resolveMediaTypes (1 samples, 0.02%) + + + +org/springframework/web/method/annotation/ModelAttributeMethodProcessor:::supportsReturnType (31 samples, 0.60%) + + + +anon_pipe_buf_release (1 samples, 0.02%) + + + +jni_SetIntField (6 samples, 0.12%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +sun/nio/ch/IOUtil:::write (3 samples, 0.06%) + + + +sun/security/provider/ByteArrayAccess:::b2iBig64 (1 samples, 0.02%) + + + +jshort_arraycopy (1 samples, 0.02%) + + + +post_allocation_notify (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +G1RootProcessor::evacuate_roots (3 samples, 0.06%) + + + +ret_from_intr (1 samples, 0.02%) + + + +inflateInit2_ (1 samples, 0.02%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +ksize (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/pattern/MdcPatternConverter:::format (1 samples, 0.02%) + + + +java/lang/Thread:::sleep (12 samples, 0.23%) + + + +org/springframework/boot/actuate/trace/http/HttpExchangeTracer:::getHeadersIfIncluded (5 samples, 0.10%) + + + +hrtimer_cancel (1 samples, 0.02%) + + + +__fsnotify_parent (1 samples, 0.02%) + + + +net_rps_action_and_irq_enable.isra.91 (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/RequestMappingInfo:::getMatchingCondition (36 samples, 0.70%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +org/springframework/http/MediaType:::isCompatibleWith (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +vfs_read (1 samples, 0.02%) + + + +JVM_IsThreadAlive (1 samples, 0.02%) + + + +system_call_fastpath (14 samples, 0.27%) + + + +java/util/regex/Pattern$GroupHead:::match (3 samples, 0.06%) + + + +org/springframework/core/ResolvableType:::forType (2 samples, 0.04%) + + + +process_backlog (1 samples, 0.02%) + + + +Java_java_util_zip_Deflater_deflateBytes (1 samples, 0.02%) + + + +java/util/ComparableTimSort:::sort (3 samples, 0.06%) + + + +pthread_mutex_unlock@plt (1 samples, 0.02%) + + + +itable stub (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor108:::invoke (9 samples, 0.18%) + + + +org/apache/tomcat/util/http/NamesEnumerator:::findNext (3 samples, 0.06%) + + + +__alloc_skb (1 samples, 0.02%) + + + +java/lang/String:::regionMatches (1 samples, 0.02%) + + + +epoll_ctl (2 samples, 0.04%) + + + +org/jboss/netty/channel/socket/nio/NioSocketChannel$WriteTask:::run (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/system/SystemSlot:::exit (1 samples, 0.02%) + + + +org/joda/time/chrono/BasicYearDateTimeField:::get (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +java/util/AbstractList:::equals (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +java/lang/String:::hashCode (1 samples, 0.02%) + + + +java/util/regex/Pattern$BmpCharProperty:::match (7 samples, 0.14%) + + + +__do_softirq (1 samples, 0.02%) + + + +Unsafe_Unpark (33 samples, 0.64%) + + + +JavaThread::handle_special_suspend_equivalent_condition (1 samples, 0.02%) + + + +java/lang/AbstractStringBuilder:::append (2 samples, 0.04%) + + + +java/util/zip/Inflater:::inflateBytes (835 samples, 16.25%) +java/util/zip/Inflater:::.. + + +java/util/stream/ReduceOps$ReduceOp:::evaluateSequential (12 samples, 0.23%) + + + +net_rx_action (1 samples, 0.02%) + + + +PhaseChaitin::Simplify (3 samples, 0.06%) + + + +sock_aio_read.part.7 (3 samples, 0.06%) + + + +_raw_spin_unlock_irqrestore (5 samples, 0.10%) + + + +org/springframework/web/util/UrlPathHelper:::getLookupPathForRequest (10 samples, 0.19%) + + + +deflateStateCheck (1 samples, 0.02%) + + + +org/springframework/beans/factory/support/AbstractBeanFactory:::resolveEmbeddedValue (2 samples, 0.04%) + + + +__memmove_ssse3_back (3 samples, 0.06%) + + + +java/util/regex/Pattern$Begin:::match (8 samples, 0.16%) + + + +G1SATBCardTableLoggingModRefBS::write_ref_array_work (1 samples, 0.02%) + + + +java/net/URI$Parser:::parseAuthority (4 samples, 0.08%) + + + +_raw_spin_unlock_irqrestore (4 samples, 0.08%) + + + +com/sun/proxy/$Proxy109:::annotationType (1 samples, 0.02%) + + + +__mnt_want_write_file (1 samples, 0.02%) + + + +com/alibaba/fastjson/serializer/LongCodec:::deserialze (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (31 samples, 0.60%) + + + +com/fasterxml/jackson/databind/ser/std/MapSerializer:::serialize (2 samples, 0.04%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +__x2apic_send_IPI_mask (2 samples, 0.04%) + + + +org/apache/tomcat/util/buf/MessageBytes:::recycle (1 samples, 0.02%) + + + +finish_task_switch (10 samples, 0.19%) + + + +__do_softirq (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +add_transaction_credits (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +arrayof_jint_fill (2 samples, 0.04%) + + + +CollectedHeap::common_mem_allocate_init (1 samples, 0.02%) + + + +org/apache/catalina/connector/CoyoteAdapter:::service (3,927 samples, 76.40%) +org/apache/catalina/connector/CoyoteAdapter:::service + + +enqueue_to_backlog (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +java/lang/StringBuilder:::append (1 samples, 0.02%) + + + +_tr_init (2 samples, 0.04%) + + + +jshort_arraycopy (2 samples, 0.04%) + + + +_raw_spin_unlock_irqrestore (28 samples, 0.54%) + + + +java/util/ComparableTimSort:::sort (5 samples, 0.10%) + + + +BarrierSet::static_write_ref_array_post (1 samples, 0.02%) + + + +system_call_fastpath (3 samples, 0.06%) + + + +TypeLong::xmeet (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +G1CollectedHeap::unsafe_max_tlab_alloc (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONScanner:::scanFieldInt (3 samples, 0.06%) + + + +resource_allocate_bytes (1 samples, 0.02%) + + + +Symbol::as_unicode (48 samples, 0.93%) + + + +do_softirq (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +start_xmit (8 samples, 0.16%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +ip_queue_xmit (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +org/springframework/http/converter/ByteArrayHttpMessageConverter:::supports (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/block/flow/FlowSlot:::entry (1 samples, 0.02%) + + + +ret_from_intr (2 samples, 0.04%) + + + +copy_user_enhanced_fast_string (1 samples, 0.02%) + + + +org/apache/logging/log4j/message/ParameterFormatter:::appendMap (5 samples, 0.10%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy8:::annotationType (1 samples, 0.02%) + + + +__libc_recv (1 samples, 0.02%) + + + +page_to_skb (1 samples, 0.02%) + + + +ep_unregister_pollwait.isra.6 (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11InputBuffer:::parseHeader (8 samples, 0.16%) + + + +smp_apic_timer_interrupt (2 samples, 0.04%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (1 samples, 0.02%) + + + +java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet:::iterator (1 samples, 0.02%) + + + +java/lang/AbstractStringBuilder:::append (2 samples, 0.04%) + + + +irq_exit (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +java/lang/reflect/Method:::hashCode (1 samples, 0.02%) + + + +ext4_reserve_inode_write (3 samples, 0.06%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +org/springframework/util/StringUtils:::tokenizeToStringArray (8 samples, 0.16%) + + + +CollectedHeap::allocate_from_tlab_slow (1 samples, 0.02%) + + + +org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter:::doFilterInternal (215 samples, 4.18%) +org/.. + + +org/springframework/util/StringUtils:::tokenizeToStringArray (5 samples, 0.10%) + + + +sys_futex (1 samples, 0.02%) + + + +virtnet_poll (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/pattern/ThreadNamePatternConverter:::format (1 samples, 0.02%) + + + +org/springframework/util/MimeType$SpecificityComparator:::compare (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/statistic/StatisticSlot:::entry (1 samples, 0.02%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +_int_malloc (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +java/util/Comparator$$Lambda$213/1325144078:::compare (1 samples, 0.02%) + + + +jbd2_journal_put_journal_head (1 samples, 0.02%) + + + +inflateStateCheck (15 samples, 0.29%) + + + +start_xmit (1 samples, 0.02%) + + + +com/coohua/ad/data/service/impl/AdDataServiceImpl:::clickProcess (20 samples, 0.39%) + + + +com/coohua/ad/data/manager/AdDataManager:::adChargeForCreative (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +sun/reflect/GeneratedMethodAccessor73:::invoke (3 samples, 0.06%) + + + +generic_exec_single (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter:::invokeHandlerMethod (2,834 samples, 55.14%) +org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter:::invo.. + + +org/springframework/web/servlet/handler/AbstractHandlerMapping:::getHandler (177 samples, 3.44%) +org.. + + +org/joda/time/chrono/BasicMonthOfYearDateTimeField:::get (1 samples, 0.02%) + + + +call_softirq (2 samples, 0.04%) + + + +UTF8::convert_to_unicode (15 samples, 0.29%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (2 samples, 0.04%) + + + +com/sun/proxy/$Proxy50:::annotationType (1 samples, 0.02%) + + + +sock_recvmsg (1 samples, 0.02%) + + + +apic_timer_interrupt (2 samples, 0.04%) + + + +java/util/regex/Pattern$Branch:::match (3 samples, 0.06%) + + + +__audit_syscall_exit (1 samples, 0.02%) + + + +do_csum (1 samples, 0.02%) + + + +org/apache/coyote/AbstractProtocol$RecycledProcessors:::push (1 samples, 0.02%) + + + +system_call_fastpath (2 samples, 0.04%) + + + +java (5,140 samples, 100.00%) +java + + +org/apache/catalina/mapper/Mapper:::internalMapWrapper (8 samples, 0.16%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics:::getLibProperties (35 samples, 0.68%) + + + +sun/nio/ch/IOUtil:::write (51 samples, 0.99%) + + + +java/util/ArrayList:::toArray (2 samples, 0.04%) + + + +OptoRuntime::multianewarray2_C (1 samples, 0.02%) + + + +packet_rcv (1 samples, 0.02%) + + + +com/alibaba/fastjson/parser/JSONLexerBase:::scanSymbol (1 samples, 0.02%) + + + +java/lang/StringBuffer:::append (1 samples, 0.02%) + + + +java/util/zip/DeflaterOutputStream:::write (1 samples, 0.02%) + + + +com/coohua/caf/core/kv/JedisClient$$FastClassBySpringCGLIB$$11ce05b4:::invoke (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +java/util/HashMap:::put (1 samples, 0.02%) + + + +java/lang/String:::equals (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +epoll_ctl (1 samples, 0.02%) + + + +java/util/zip/GZIPOutputStream:::finish (1 samples, 0.02%) + + + +os::Linux::safe_cond_timedwait (1 samples, 0.02%) + + + +java/lang/String:::indexOf (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +sun/nio/ch/NativeThread:::current (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +__dev_queue_xmit (19 samples, 0.37%) + + + +org/springframework/core/annotation/AnnotationUtils:::isInJavaLangAnnotationPackage (3 samples, 0.06%) + + + +call_softirq (1 samples, 0.02%) + + + +__ext4_journal_start_sb (1 samples, 0.02%) + + + +org/springframework/web/servlet/DispatcherServlet:::doService (3,406 samples, 66.26%) +org/springframework/web/servlet/DispatcherServlet:::doService + + +net_rx_action (1 samples, 0.02%) + + + +virtqueue_get_buf (1 samples, 0.02%) + + + +__alloc_skb (2 samples, 0.04%) + + + +vfs_write (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +Parse::do_get_xxx (1 samples, 0.02%) + + + +java/util/zip/InflaterInputStream:::read (1 samples, 0.02%) + + + +com/coohua/caf/core/metrics/LatencyStat$Timer:::<init> (1 samples, 0.02%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +futex_wait (14 samples, 0.27%) + + + +java/util/regex/Pattern$CharProperty:::match (8 samples, 0.16%) + + + +com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer:::deserialze (118 samples, 2.30%) +c.. + + +Threads::possibly_parallel_oops_do (2 samples, 0.04%) + + + +org/apache/kafka/common/utils/AbstractIterator:::next (1 samples, 0.02%) + + + +CodeHeap::deallocate (1 samples, 0.02%) + + + +G1ParScanThreadState::trim_queue (4 samples, 0.08%) + + + +__do_softirq (2 samples, 0.04%) + + + +org/springframework/web/servlet/FrameworkServlet:::service (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter:::record (57 samples, 1.11%) + + + +OptoRuntime::new_array_nozero_C (1 samples, 0.02%) + + + +java/util/concurrent/ThreadPoolExecutor:::getTask (41 samples, 0.80%) + + + +java/util/concurrent/ConcurrentHashMap:::putVal (2 samples, 0.04%) + + + +org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter:::record (4 samples, 0.08%) + + + +java/util/HashMap:::put (1 samples, 0.02%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (1 samples, 0.02%) + + + +pthread_getspecific@plt (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +java/lang/String:::split (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (2 samples, 0.04%) + + + +clock_gettime (1 samples, 0.02%) + + + +itable stub (1 samples, 0.02%) + + + +sun/reflect/GeneratedConstructorAccessor83:::newInstance (1 samples, 0.02%) + + + +tcp_data_queue (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (3 samples, 0.06%) + + + +com/coohua/caf/core/metrics/LatencyStat:::setMin (9 samples, 0.18%) + + + +schedule_hrtimeout_range_clock (11 samples, 0.21%) + + + +_register_finalizer_Java (10 samples, 0.19%) + + + +org/springframework/aop/framework/CglibAopProxy$DynamicAdvisedInterceptor:::intercept (1 samples, 0.02%) + + + +redis/clients/jedis/Jedis:::close (1 samples, 0.02%) + + + +ext4_dirty_inode (9 samples, 0.18%) + + + +VMThread::evaluate_operation (4 samples, 0.08%) + + + +system_call_fastpath (2 samples, 0.04%) + + + +org/apache/logging/log4j/core/LoggerContext:::getLogger (1 samples, 0.02%) + + + +Monitor::lock_without_safepoint_check (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +VMThread::run (4 samples, 0.08%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy0:::annotationType (1 samples, 0.02%) + + + +JavaCalls::call_helper (3 samples, 0.06%) + + + +redis/clients/jedis/Protocol:::process (1 samples, 0.02%) + + + +sun/nio/ch/EPollArrayWrapper:::epollWait (30 samples, 0.58%) + + + +rcu_process_gp_end (1 samples, 0.02%) + + + +java/util/Formatter:::format (3 samples, 0.06%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +java/util/GregorianCalendar:::computeTime (1 samples, 0.02%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +org/apache/kafka/common/record/MemoryRecordsBuilder:::close (8 samples, 0.16%) + + + +org/apache/coyote/http11/Http11OutputBuffer:::write (1 samples, 0.02%) + + + +java/util/TimSort:::sort (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (216 samples, 4.20%) +org/a.. + + +sock_poll (1 samples, 0.02%) + + + +tcp_sendmsg (2 samples, 0.04%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +java/lang/StringCoding:::encode (14 samples, 0.27%) + + + +org/apache/tomcat/util/net/NioEndpoint$Poller:::events (6 samples, 0.12%) + + + +schedule (10 samples, 0.19%) + + + +jshort_disjoint_arraycopy (8 samples, 0.16%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +tcp_transmit_skb (23 samples, 0.45%) + + + +org/apache/logging/log4j/message/ParameterFormatter:::recursiveDeepToString (5 samples, 0.10%) + + + +java/util/HashMap:::get (1 samples, 0.02%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +java/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject:::await (37 samples, 0.72%) + + + +call_softirq (1 samples, 0.02%) + + + +java/lang/AbstractStringBuilder:::append (1 samples, 0.02%) + + + +JavaCalls::call_helper (5,032 samples, 97.90%) +JavaCalls::call_helper + + +com/alibaba/csp/sentinel/slots/statistic/StatisticSlot:::entry (9 samples, 0.18%) + + + +ThreadStateTransition::trans_from_native (1 samples, 0.02%) + + + +org/springframework/web/context/request/ServletWebRequest:::getHeaderValues (1 samples, 0.02%) + + + +dev_hard_start_xmit (1 samples, 0.02%) + + + +java/util/LinkedHashMap$LinkedHashIterator:::hasNext (1 samples, 0.02%) + + + +tcp_rcv_established (1 samples, 0.02%) + + + +io/micrometer/core/instrument/Tags$ArrayIterator:::<init> (1 samples, 0.02%) + + + +org/apache/kafka/common/metrics/stats/Meter:::record (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +org/apache/catalina/connector/RequestFacade:::removeAttribute (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +ip_rcv (1 samples, 0.02%) + + + +org/apache/tomcat/util/net/SocketProcessorBase:::run (4,047 samples, 78.74%) +org/apache/tomcat/util/net/SocketProcessorBase:::run + + +ret_from_intr (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +itable stub (1 samples, 0.02%) + + + +com/fasterxml/jackson/databind/ser/std/MapSerializer:::serializeFields (46 samples, 0.89%) + + + +java/util/Collection:::stream (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +copy_user_enhanced_fast_string (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +ReferenceProcessor::process_phase3 (3 samples, 0.06%) + + + +sysret_audit (1 samples, 0.02%) + + + +sock_def_readable (1 samples, 0.02%) + + + +virtnet_poll (1 samples, 0.02%) + + + +java/text/SimpleDateFormat:::parse (1 samples, 0.02%) + + + +[libpthread-2.17.so] (24 samples, 0.47%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +Parse::Parse (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +java/util/LinkedHashMap$LinkedKeyIterator:::next (1 samples, 0.02%) + + + +futex_wait_setup (1 samples, 0.02%) + + + +sys_epoll_ctl (2 samples, 0.04%) + + + +smp_apic_timer_interrupt (1 samples, 0.02%) + + + +Parse::do_all_blocks (1 samples, 0.02%) + + + +org/springframework/web/filter/OncePerRequestFilter:::doFilter (3,612 samples, 70.27%) +org/springframework/web/filter/OncePerRequestFilter:::doFilter + + +call_function_single_interrupt (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +org/apache/kafka/clients/NetworkClient:::handleCompletedSends (4 samples, 0.08%) + + + +java/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject:::awaitNanos (52 samples, 1.01%) + + + +pipe_write (1 samples, 0.02%) + + + +call_function_single_interrupt (2 samples, 0.04%) + + + +org/springframework/web/servlet/mvc/condition/PatternsRequestCondition:::getMatchingPatterns (1 samples, 0.02%) + + + +Symbol::as_klass_external_name (5 samples, 0.10%) + + + +org/apache/tomcat/util/http/ValuesEnumerator:::findNext (1 samples, 0.02%) + + + +virtqueue_add_outbuf (1 samples, 0.02%) + + + +com/coohua/ad/data/service/impl/AdDataServiceImpl:::clickProcess (1 samples, 0.02%) + + + +org/springframework/boot/actuate/web/trace/servlet/HttpTraceFilter:::doFilterInternal (3,545 samples, 68.97%) +org/springframework/boot/actuate/web/trace/servlet/HttpTraceFilter:::doFilterInternal + + +java/lang/reflect/Method:::equals (1 samples, 0.02%) + + + +Parker::park (1 samples, 0.02%) + + + +java/math/BigDecimal:::layoutChars (1 samples, 0.02%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +org/apache/catalina/authenticator/AuthenticatorBase:::invoke (216 samples, 4.20%) +org/a.. + + +skb_release_all (1 samples, 0.02%) + + + +sun/nio/ch/SocketChannelImpl:::read (27 samples, 0.53%) + + + +com/google/gson/Gson:::toJson (60 samples, 1.17%) + + + +BacktraceBuilder::push (25 samples, 0.49%) + + + +Java_java_lang_Throwable_fillInStackTrace (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +tcp_v4_early_demux (1 samples, 0.02%) + + + +com/weibo/api/motan/rpc/AbstractReferer:::call (3 samples, 0.06%) + + + +java/util/Calendar:::createCalendar (4 samples, 0.08%) + + + +JVM_FillInStackTrace (1 samples, 0.02%) + + + +mod_timer (1 samples, 0.02%) + + + +ep_poll (1 samples, 0.02%) + + + +UTF8::unicode_length (28 samples, 0.54%) + + + +itable stub (2 samples, 0.04%) + + + +com/alibaba/csp/sentinel/slots/system/SystemSlot:::exit (1 samples, 0.02%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics:::assertKeyWithRegex (3 samples, 0.06%) + + + +skb_defer_rx_timestamp (1 samples, 0.02%) + + + +com/google/gson/internal/bind/ArrayTypeAdapter:::write (40 samples, 0.78%) + + + +Unsafe_Unpark (2 samples, 0.04%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +org/springframework/web/filter/RequestContextFilter:::doFilterInternal (1 samples, 0.02%) + + + +Unsafe_Park (30 samples, 0.58%) + + + +com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer:::smartMatch (3 samples, 0.06%) + + + +arrayof_jint_fill (1 samples, 0.02%) + + + +__memmove_ssse3_back (1 samples, 0.02%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +G1RemSet::refine_card (3 samples, 0.06%) + + + +net_rx_action (1 samples, 0.02%) + + + +java/util/LinkedHashMap:::get (2 samples, 0.04%) + + + +tcp_v4_destroy_sock (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (3 samples, 0.06%) + + + +do_sync_write (21 samples, 0.41%) + + + +process_backlog (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +JavaThread::check_and_handle_async_exceptions (1 samples, 0.02%) + + + +CollectedHeap::allocate_from_tlab_slow (1 samples, 0.02%) + + + +_new_instance_Java (1 samples, 0.02%) + + + +native_send_call_func_single_ipi (2 samples, 0.04%) + + + +itable stub (3 samples, 0.06%) + + + +call_softirq (1 samples, 0.02%) + + + +net_rx_action (2 samples, 0.04%) + + + +kfree (1 samples, 0.02%) + + + +HandleArea::allocate_handle (3 samples, 0.06%) + + + +PhaseIterGVN::subsume_node (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotationUtils:::getAnnotation (1 samples, 0.02%) + + + +org/apache/kafka/clients/producer/internals/Sender:::run (290 samples, 5.64%) +org/apa.. + + +java_lang_Throwable::get_stack_trace_depth (1 samples, 0.02%) + + + +deflate_slow (1 samples, 0.02%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +updateBytesCRC32 (4 samples, 0.08%) + + + +local_clock (2 samples, 0.04%) + + + +java/util/concurrent/locks/ReentrantLock$NonfairSync:::lock (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +plist_add (1 samples, 0.02%) + + + +org/springframework/web/filter/OncePerRequestFilter:::doFilter (3,603 samples, 70.10%) +org/springframework/web/filter/OncePerRequestFilter:::doFilter + + +java/util/HashMap:::entrySet (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/Type$8:::write (1 samples, 0.02%) + + + +org/apache/kafka/common/utils/PureJavaCrc32C:::update (8 samples, 0.16%) + + + +com/alibaba/csp/sentinel/slots/statistic/StatisticSlot:::entry (1 samples, 0.02%) + + + +sun/nio/ch/EPollArrayWrapper:::poll (27 samples, 0.53%) + + + +release_sock (1 samples, 0.02%) + + + +java/util/Formatter:::checkText (1 samples, 0.02%) + + + +org/springframework/web/filter/CharacterEncodingFilter:::doFilterInternal (216 samples, 4.20%) +org/s.. + + +ConstMethod::compressed_linenumber_table (1 samples, 0.02%) + + + +org/apache/tomcat/util/net/NioBlockingSelector$BlockPoller:::run (27 samples, 0.53%) + + + +pthread_mutex_unlock (1 samples, 0.02%) + + + +org/apache/tomcat/util/http/Parameters:::processParameters (55 samples, 1.07%) + + + +ip_rcv (1 samples, 0.02%) + + + +napi_gro_receive (1 samples, 0.02%) + + + +java/util/regex/Matcher:::end (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +vtable stub (2 samples, 0.04%) + + + +java/util/regex/Pattern$3:::isSatisfiedBy (4 samples, 0.08%) + + + +irq_exit (1 samples, 0.02%) + + + +java_lang_Throwable::fill_in_stack_trace (3 samples, 0.06%) + + + +inflate_table (30 samples, 0.58%) + + + +tcp_cleanup_rbuf (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +java/util/Calendar$Builder:::build (3 samples, 0.06%) + + + +call_softirq (1 samples, 0.02%) + + + +com/coohua/ad/data/service/impl/AdDataServiceImpl:::ecpProcess (1 samples, 0.02%) + + + +ret_from_intr (1 samples, 0.02%) + + + +java/net/URI$Parser:::parseAuthority (3 samples, 0.06%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +org/apache/coyote/http11/Http11Processor:::service (225 samples, 4.38%) +org/a.. + + +futex_wake_op (16 samples, 0.31%) + + + +org/apache/logging/log4j/core/appender/AsyncAppender:::append (4 samples, 0.08%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +hrtimer_try_to_cancel.part.25 (2 samples, 0.04%) + + + +java/util/HashMap:::get (1 samples, 0.02%) + + + +sys_futex (31 samples, 0.60%) + + + +java/util/HashMap:::put (1 samples, 0.02%) + + + +wake_up_state (2 samples, 0.04%) + + + +Method::line_number_from_bci (5 samples, 0.10%) + + + +java/lang/Class:::getEnclosingMethod0 (1 samples, 0.02%) + + + +system_call_fastpath (2 samples, 0.04%) + + + +deflate_slow (161 samples, 3.13%) +def.. + + +com/alibaba/fastjson/serializer/LongCodec:::deserialze (1 samples, 0.02%) + + + +adler32 (1 samples, 0.02%) + + + +java/lang/StringBuffer:::append (4 samples, 0.08%) + + + +do_futex (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +java/lang/reflect/Method:::hashCode (1 samples, 0.02%) + + + +org/apache/kafka/common/network/Selector:::poll (117 samples, 2.28%) +o.. + + +G1CollectedHeap::free_collection_set (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy109:::annotationType (1 samples, 0.02%) + + + +apic_timer_interrupt (1 samples, 0.02%) + + + +tcp_ack (1 samples, 0.02%) + + + +org/springframework/web/servlet/handler/AbstractUrlHandlerMapping:::getHandlerInternal (22 samples, 0.43%) + + + +irq_exit (1 samples, 0.02%) + + + +rcu_process_callbacks (1 samples, 0.02%) + + + +org/apache/kafka/common/utils/ByteBufferOutputStream:::write (1 samples, 0.02%) + + + +java/lang/AbstractStringBuilder:::append (1 samples, 0.02%) + + + +do_sys_poll (1 samples, 0.02%) + + + +java/lang/Throwable:::getOurStackTrace (4 samples, 0.08%) + + + +do_softirq (1 samples, 0.02%) + + + +java/util/zip/Inflater:::end (1 samples, 0.02%) + + + +com/coohua/ad/data/interceptor/UserInterceptor:::afterCompletion (9 samples, 0.18%) + + + +wake_up_state (19 samples, 0.37%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/springframework/util/ObjectUtils:::unwrapOptional (1 samples, 0.02%) + + + +java/util/regex/Pattern$CharProperty:::match (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +sys_futex (3 samples, 0.06%) + + + +HandleMarkCleaner::~HandleMarkCleaner (2 samples, 0.04%) + + + +ip_finish_output (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (19 samples, 0.37%) + + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +OptoRuntime::new_instance_C (1 samples, 0.02%) + + + +system_call_fastpath (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +org/springframework/cglib/proxy/MethodProxy:::invoke (1 samples, 0.02%) + + + +org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler:::invoke (3 samples, 0.06%) + + + +skb_checksum (1 samples, 0.02%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +java/util/LinkedHashMap:::get (1 samples, 0.02%) + + + +org/springframework/boot/actuate/trace/http/HttpExchangeTracer$$Lambda$701/1778763541:::accept (1 samples, 0.02%) + + + +org/springframework/beans/factory/support/AbstractBeanFactory:::doGetBean (2 samples, 0.04%) + + + +org/springframework/aop/framework/CglibAopProxy$DynamicAdvisedInterceptor:::intercept (1 samples, 0.02%) + + + +java/util/TimSort:::sort (1 samples, 0.02%) + + + +ext4_file_write (22 samples, 0.43%) + + + +oop_disjoint_arraycopy (2 samples, 0.04%) + + + +jlong_disjoint_arraycopy (1 samples, 0.02%) + + + +__mnt_want_write (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +vtable stub (1 samples, 0.02%) + + + +java/util/Spliterators$IteratorSpliterator:::forEachRemaining (11 samples, 0.21%) + + + +Matcher::Matcher (1 samples, 0.02%) + + + +com/alibaba/csp/sentinel/slots/statistic/StatisticSlot:::entry (9 samples, 0.18%) + + + +WeakPreserveExceptionMark::WeakPreserveExceptionMark (1 samples, 0.02%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (2 samples, 0.04%) + + + +irq_exit (1 samples, 0.02%) + + + +ip_local_deliver_finish (1 samples, 0.02%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +org/springframework/core/annotation/AnnotatedElementUtils:::searchWithFindSemantics (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +java/util/zip/Deflater:::deflateBytes (1 samples, 0.02%) + + + +jni_GetPrimitiveArrayCritical (3 samples, 0.06%) + + + +org/apache/catalina/connector/Request:::setAttribute (3 samples, 0.06%) + + + +ep_send_events_proc (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +call_function_single_interrupt (1 samples, 0.02%) + + + +java/nio/Buffer:::limit (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +PhaseIterGVN::transform_old (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/config/LoggerConfig:::log (56 samples, 1.09%) + + + +process_backlog (1 samples, 0.02%) + + + +java/lang/Long:::getChars (1 samples, 0.02%) + + + +JNIHandles::make_local (5 samples, 0.10%) + + + +_new_instance_Java (1 samples, 0.02%) + + + +finish_task_switch (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/condition/PatternsRequestCondition:::getMatchingPatterns (3 samples, 0.06%) + + + +java/util/regex/Pattern$5:::isSatisfiedBy (3 samples, 0.06%) + + + +tcp_child_process (1 samples, 0.02%) + + + +netif_receive_skb_internal (1 samples, 0.02%) + + + +java/util/stream/ReduceOps$ReduceOp:::evaluateSequential (1 samples, 0.02%) + + + +itable stub (2 samples, 0.04%) + + + +__do_softirq (1 samples, 0.02%) + + + +java/lang/ThreadLocal$ThreadLocalMap:::remove (2 samples, 0.04%) + + + +org/apache/tomcat/util/net/NioEndpoint$SocketProcessor:::doRun (4,046 samples, 78.72%) +org/apache/tomcat/util/net/NioEndpoint$SocketProcessor:::doRun + + +java/lang/String:::charAt (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +com/sun/proxy/$Proxy8:::annotationType (1 samples, 0.02%) + + + +pipe_write (21 samples, 0.41%) + + + +jshort_disjoint_arraycopy (2 samples, 0.04%) + + + +irq_exit (1 samples, 0.02%) + + + +org/apache/tomcat/util/net/SocketProcessorBase:::run (2 samples, 0.04%) + + + +native_send_call_func_single_ipi (1 samples, 0.02%) + + + +smp_apic_timer_interrupt (1 samples, 0.02%) + + + +PhaseChaitin::post_allocate_copy_removal (1 samples, 0.02%) + + + +java/util/TimSort:::sort (1 samples, 0.02%) + + + +G1UpdateRSOrPushRefOopClosure::do_oop (1 samples, 0.02%) + + + +java/lang/Object:::hashCode (1 samples, 0.02%) + + + +LShiftLNode::Ideal (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +__wake_up_sync_key (1 samples, 0.02%) + + + +sk_stream_alloc_skb (1 samples, 0.02%) + + + +schedule_hrtimeout_range_clock (1 samples, 0.02%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (3,447 samples, 67.06%) +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter + + +org/apache/tomcat/util/buf/StringCache:::toString (2 samples, 0.04%) + + + +org/apache/catalina/connector/Connector:::isParseBodyMethod (1 samples, 0.02%) + + + +skb_release_data (1 samples, 0.02%) + + + +InstanceKlass::allocate_instance (12 samples, 0.23%) + + + +llist_add_batch (1 samples, 0.02%) + + + +org/springframework/http/converter/AbstractGenericHttpMessageConverter:::canWrite (1 samples, 0.02%) + + + +org/apache/tomcat/util/buf/MessageBytes:::toString (6 samples, 0.12%) + + + +tcp_v4_do_rcv (1 samples, 0.02%) + + + +java/util/regex/Pattern$3:::isSatisfiedBy (2 samples, 0.04%) + + + +do_softirq (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::getAttribute (1 samples, 0.02%) + + + +G1CollectedHeap::iterate_dirty_card_closure (5 samples, 0.10%) + + + +com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot:::entry (1 samples, 0.02%) + + + +__vdso_gettimeofday (3 samples, 0.06%) + + + +__do_softirq (1 samples, 0.02%) + + + +org/springframework/web/servlet/DispatcherServlet:::doDispatch (3,386 samples, 65.88%) +org/springframework/web/servlet/DispatcherServlet:::doDispatch + + +java/util/regex/Pattern$GroupHead:::match (2 samples, 0.04%) + + + +Parse::do_call (1 samples, 0.02%) + + + +schedule_hrtimeout_range (1 samples, 0.02%) + + + +java_lang_Thread::set_thread_status (1 samples, 0.02%) + + + +com/alibaba/fastjson/util/TypeUtils:::castToLong (4 samples, 0.08%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +CollectedHeap::new_store_pre_barrier (1 samples, 0.02%) + + + +sun/nio/ch/EPollArrayWrapper:::epollWait (1 samples, 0.02%) + + + +java/util/HashMap:::put (16 samples, 0.31%) + + + +org/springframework/core/MethodParameter:::getNestedParameterType (1 samples, 0.02%) + + + +sys_futex (1 samples, 0.02%) + + + +ip_local_out_sk (6 samples, 0.12%) + + + +net_rps_action_and_irq_enable.isra.91 (1 samples, 0.02%) + + + +wake_futex (20 samples, 0.39%) + + + +org/apache/kafka/common/network/Selector:::send (2 samples, 0.04%) + + + +PhaseCCP::analyze (1 samples, 0.02%) + + + +org/springframework/util/LinkedCaseInsensitiveMap:::hashCode (1 samples, 0.02%) + + + +skb_to_sgvec (1 samples, 0.02%) + + + +__list_del_entry (1 samples, 0.02%) + + + +ip_finish_output (3 samples, 0.06%) + + + +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter (3,589 samples, 69.82%) +org/apache/catalina/core/ApplicationFilterChain:::internalDoFilter + + +ext4_inode_table (1 samples, 0.02%) + + + +napi_gro_receive (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +com/fasterxml/jackson/core/json/WriterBasedJsonGenerator:::writeStartObject (1 samples, 0.02%) + + + +java/util/regex/Pattern$Branch:::match (5 samples, 0.10%) + + + +fill_window (1 samples, 0.02%) + + + +java/util/HashMap:::putMapEntries (2 samples, 0.04%) + + + +org/springframework/util/AntPathMatcher:::doMatch (49 samples, 0.95%) + + + +com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer:::parseRest (9 samples, 0.18%) + + + +deflate_slow (81 samples, 1.58%) + + + +JavaThread::thread_from_jni_environment (1 samples, 0.02%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +Parker::unpark (1 samples, 0.02%) + + + +tcp_write_xmit (4 samples, 0.08%) + + + +pthread_mutex_lock (1 samples, 0.02%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +[libpthread-2.17.so] (2 samples, 0.04%) + + + +ip_rcv_finish (1 samples, 0.02%) + + + +java/util/GregorianCalendar:::getFixedDate (1 samples, 0.02%) + + + +sock_aio_write (2 samples, 0.04%) + + + +sun/reflect/GeneratedMethodAccessor70:::invoke (2 samples, 0.04%) + + + +java/util/Calendar$Builder:::build (3 samples, 0.06%) + + + +irq_exit (1 samples, 0.02%) + + + +java/lang/ThreadLocal$ThreadLocalMap:::set (2 samples, 0.04%) + + + +ret_from_intr (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +org/apache/catalina/connector/ResponseFacade:::containsHeader (1 samples, 0.02%) + + + +resource_allocate_bytes (4 samples, 0.08%) + + + +do_softirq (1 samples, 0.02%) + + + +java/lang/String:::toUpperCase (8 samples, 0.16%) + + + +jshort_disjoint_arraycopy (2 samples, 0.04%) + + + +ip_rcv (1 samples, 0.02%) + + + +org/apache/tomcat/util/net/SocketProcessorBase:::run (227 samples, 4.42%) +org/a.. + + +__do_softirq (1 samples, 0.02%) + + + +com/coohua/ad/data/manager/AdDataManager:::buildRedisAdClickCounter (2 samples, 0.04%) + + + +java/util/HashMap:::put (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +OptoRuntime::new_array_C (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (2 samples, 0.04%) + + + +__audit_syscall_entry (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +java/util/Formatter$FixedString:::print (1 samples, 0.02%) + + + +org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor:::getProducibleMediaTypes (14 samples, 0.27%) + + + +ip_local_out_sk (23 samples, 0.45%) + + + +java/lang/Object:::toString (3 samples, 0.06%) + + + +os::javaTimeMillis (3 samples, 0.06%) + + + +memcpy@plt (1 samples, 0.02%) + + + +org/springframework/web/servlet/support/WebContentGenerator:::checkRequest (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +org/springframework/boot/actuate/metrics/web/servlet/LongTaskTimingHandlerInterceptor:::preHandle (47 samples, 0.91%) + + + +sock_def_readable (1 samples, 0.02%) + + + +java/lang/Class:::getSimpleName (3 samples, 0.06%) + + + +com/google/gson/Gson:::toJson (1 samples, 0.02%) + + + +org/apache/commons/lang3/StringUtils:::splitWorker (5 samples, 0.10%) + + + +ip_rcv (1 samples, 0.02%) + + + +G1SATBCardTableModRefBS::g1_mark_as_young (1 samples, 0.02%) + + + +Monitor::unlock (1 samples, 0.02%) + + + +dput (1 samples, 0.02%) + + + +org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver:::resolveArgument (1 samples, 0.02%) + + + +Method::mask_for (1 samples, 0.02%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (1 samples, 0.02%) + + + +java/util/regex/Pattern$BranchConn:::match (4 samples, 0.08%) + + + +net_rx_action (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (3 samples, 0.06%) + + + +__netif_receive_skb (1 samples, 0.02%) + + + +sk_reset_timer (1 samples, 0.02%) + + + +sys_epoll_wait (1 samples, 0.02%) + + + +org/springframework/web/servlet/handler/AbstractHandlerMapping:::getHandler (1 samples, 0.02%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +sock_def_readable (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (2 samples, 0.04%) + + + +com/coohuadata/analytics/javasdk/CoohuaAnalytics$KafkaConsumer:::send (19 samples, 0.37%) + + + +kmem_cache_alloc_node (1 samples, 0.02%) + + + +vtable stub (2 samples, 0.04%) + + + +arrayOopDesc::base (5 samples, 0.10%) + + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +futex_wait_queue_me (11 samples, 0.21%) + + + +itable stub (1 samples, 0.02%) + + + +sun/nio/cs/UTF_8$Encoder:::encode (2 samples, 0.04%) + + + +pthread_mutex_lock (1 samples, 0.02%) + + + +scan_tree (5 samples, 0.10%) + + + +tcp_v4_rcv (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/appender/AsyncAppender:::append (3 samples, 0.06%) + + + +call_softirq (1 samples, 0.02%) + + + +napi_gro_receive (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +jshort_disjoint_arraycopy (1 samples, 0.02%) + + + +ip_local_deliver (1 samples, 0.02%) + + + +napi_gro_receive (1 samples, 0.02%) + + + +java/net/URLEncoder:::encode (2 samples, 0.04%) + + + +org/apache/kafka/common/requests/AbstractRequestResponse:::serialize (16 samples, 0.31%) + + + +java_lang_Throwable::get_stack_trace_depth (1 samples, 0.02%) + + + +java/util/stream/Collectors$$Lambda$421/1176968662:::accept (4 samples, 0.08%) + + + +org/springframework/util/ConcurrentReferenceHashMap$Segment:::restructureIfNecessary (1 samples, 0.02%) + + + +do_softirq (1 samples, 0.02%) + + + +org/apache/kafka/common/protocol/types/ArrayOf:::write (1 samples, 0.02%) + + + +PhaseLive::compute (4 samples, 0.08%) + + + +org/apache/catalina/connector/RequestFacade:::getRequestURL (2 samples, 0.04%) + + + +sk_reset_timer (1 samples, 0.02%) + + + +java/lang/String:::toString (1 samples, 0.02%) + + + +net_rx_action (1 samples, 0.02%) + + + +pthread_cond_timedwait@@GLIBC_2.3.2 (5 samples, 0.10%) + + + +do_softirq (2 samples, 0.04%) + + + +call_softirq (1 samples, 0.02%) + + + +org/springframework/web/filter/OncePerRequestFilter:::doFilter (216 samples, 4.20%) +org/s.. + + +process_backlog (1 samples, 0.02%) + + + +org/springframework/web/util/UrlPathHelper:::getPathWithinApplication (7 samples, 0.14%) + + + +org/springframework/aop/framework/ReflectiveMethodInvocation:::proceed (4 samples, 0.08%) + + + +sun/reflect/annotation/AnnotationInvocationHandler:::invoke (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +java/util/zip/InflaterInputStream:::fill (1 samples, 0.02%) + + + +java/lang/Throwable:::getStackTraceElement (4 samples, 0.08%) + + + +org/apache/logging/log4j/core/appender/OutputStreamManager:::flushBuffer (42 samples, 0.82%) + + + +java/util/concurrent/locks/LockSupport:::parkNanos (40 samples, 0.78%) + + + +__kfree_skb (1 samples, 0.02%) + + + +os::javaTimeMillis (1 samples, 0.02%) + + + +__mark_inode_dirty (1 samples, 0.02%) + + + +org/apache/catalina/connector/Request:::parseLocales (3 samples, 0.06%) + + + +do_softirq (1 samples, 0.02%) + + + +JVM_GetStackTraceDepth (1 samples, 0.02%) + + + +__netif_receive_skb_core (1 samples, 0.02%) + + + +dev_queue_xmit (2 samples, 0.04%) + + + +skb_clone (1 samples, 0.02%) + + + +call_softirq (1 samples, 0.02%) + + + +irq_exit (1 samples, 0.02%) + + + +java/util/Comparator$$Lambda$213/1325144078:::compare (1 samples, 0.02%) + + + +JNIHandles::make_local (1 samples, 0.02%) + + + +_new_array_nozero_Java (1 samples, 0.02%) + + + +__vdso_gettimeofday (1 samples, 0.02%) + + + +java/text/SimpleDateFormat:::initialize (2 samples, 0.04%) + + + +sock_poll (4 samples, 0.08%) + + + +com/coohua/caf/core/rpc/MotanSentinelFilter:::filter (8 samples, 0.16%) + + + +java/lang/Long:::toString (1 samples, 0.02%) + + + +com/google/gson/reflect/TypeToken:::<init> (3 samples, 0.06%) + + + +java_lang_Throwable::set_backtrace (2 samples, 0.04%) + + + +smp_call_function_single_async (1 samples, 0.02%) + + + +com/coohua/ad/data/interceptor/UserInterceptor:::preHandle (105 samples, 2.04%) +c.. + + +smp_call_function_single_interrupt (1 samples, 0.02%) + + + +Type::cmp (1 samples, 0.02%) + + + +_raw_spin_unlock_irqrestore (2 samples, 0.04%) + + + +irq_exit (1 samples, 0.02%) + + + +java/util/HashMap:::put (2 samples, 0.04%) + + + +x2apic_send_IPI_mask (1 samples, 0.02%) + + + +__do_softirq (1 samples, 0.02%) + + + +java/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject:::doSignal (1 samples, 0.02%) + + + +BufferBlob::free (1 samples, 0.02%) + + + +org/apache/logging/log4j/core/pattern/MdcPatternConverter:::format (1 samples, 0.02%) + + + +sun/misc/Unsafe:::unpark (1 samples, 0.02%) + + + +PhaseIterGVN::transform_old (3 samples, 0.06%) + + + +_raw_spin_unlock_irqrestore (3 samples, 0.06%) + + + +vtable stub (1 samples, 0.02%) + + + +_raw_spin_unlock (1 samples, 0.02%) + + + +system_call_fastpath (21 samples, 0.41%) + + + +org/apache/catalina/connector/RequestFacade:::removeAttribute (2 samples, 0.04%) + + + +redis/clients/jedis/JedisClusterCommand:::runWithRetries (1 samples, 0.02%) + + + +sys_read (1 samples, 0.02%) + + + +__irqentry_text_start (1 samples, 0.02%) + + + +java/lang/String:::toUpperCase (2 samples, 0.04%) + + + +java/util/regex/Pattern$Curly:::match (1 samples, 0.02%) + + + +process_backlog (1 samples, 0.02%) + + + +G1CollectedHeap::can_elide_tlab_store_barriers (1 samples, 0.02%) + + + +java/lang/Throwable:::getStackTraceElement (30 samples, 0.58%) + + + +ret_from_intr (1 samples, 0.02%) + + + +schedule (16 samples, 0.31%) + + + +org/springframework/web/context/support/ServletRequestHandledEvent:::<init> (2 samples, 0.04%) + + + + diff --git a/images/JAVA/AQS-Lock-Condition.png b/images/JAVA/AQS-Lock-Condition.png new file mode 100644 index 0000000..7f82150 Binary files /dev/null and b/images/JAVA/AQS-Lock-Condition.png differ diff --git a/images/JAVA/AQS.png b/images/JAVA/AQS.png new file mode 100644 index 0000000..08ceae5 Binary files /dev/null and b/images/JAVA/AQS.png differ diff --git a/images/JAVA/AQS同步队列与条件队列.png b/images/JAVA/AQS同步队列与条件队列.png new file mode 100644 index 0000000..6788eef Binary files /dev/null and b/images/JAVA/AQS同步队列与条件队列.png differ diff --git a/images/JAVA/AQS框架架构图.png b/images/JAVA/AQS框架架构图.png new file mode 100644 index 0000000..611b188 Binary files /dev/null and b/images/JAVA/AQS框架架构图.png differ diff --git a/images/JAVA/Annotation.png b/images/JAVA/Annotation.png new file mode 100644 index 0000000..70ba9a5 Binary files /dev/null and b/images/JAVA/Annotation.png differ diff --git a/images/JAVA/CMSGC单次STW长.png b/images/JAVA/CMSGC单次STW长.png new file mode 100644 index 0000000..adc9d88 Binary files /dev/null and b/images/JAVA/CMSGC单次STW长.png differ diff --git a/images/JAVA/CMSOldGC频繁.png b/images/JAVA/CMSOldGC频繁.png new file mode 100644 index 0000000..b4ca2f7 Binary files /dev/null and b/images/JAVA/CMSOldGC频繁.png differ diff --git a/images/JAVA/CMS收集器.png b/images/JAVA/CMS收集器.png new file mode 100644 index 0000000..667910a Binary files /dev/null and b/images/JAVA/CMS收集器.png differ diff --git a/images/JAVA/Classloader.png b/images/JAVA/Classloader.png new file mode 100644 index 0000000..138e0bd Binary files /dev/null and b/images/JAVA/Classloader.png differ diff --git a/images/JAVA/CompletableFuture.jpg b/images/JAVA/CompletableFuture.jpg new file mode 100644 index 0000000..29c34c2 Binary files /dev/null and b/images/JAVA/CompletableFuture.jpg differ diff --git a/images/JAVA/ConcurrentSkipListMap数据结构.jpg b/images/JAVA/ConcurrentSkipListMap数据结构.jpg new file mode 100644 index 0000000..d024b0b Binary files /dev/null and b/images/JAVA/ConcurrentSkipListMap数据结构.jpg differ diff --git a/images/JAVA/ConcurrentSkipListSet数据结构.jpg b/images/JAVA/ConcurrentSkipListSet数据结构.jpg new file mode 100644 index 0000000..014c7bf Binary files /dev/null and b/images/JAVA/ConcurrentSkipListSet数据结构.jpg differ diff --git a/images/JAVA/Condition等待队列结构.png b/images/JAVA/Condition等待队列结构.png new file mode 100644 index 0000000..3a216de Binary files /dev/null and b/images/JAVA/Condition等待队列结构.png differ diff --git a/images/JAVA/CopyOnWriteArrayList数据结构.jpg b/images/JAVA/CopyOnWriteArrayList数据结构.jpg new file mode 100644 index 0000000..978585b Binary files /dev/null and b/images/JAVA/CopyOnWriteArrayList数据结构.jpg differ diff --git a/images/JAVA/CopyOnWriteArraySet数据结构.jpg b/images/JAVA/CopyOnWriteArraySet数据结构.jpg new file mode 100644 index 0000000..cdb71d4 Binary files /dev/null and b/images/JAVA/CopyOnWriteArraySet数据结构.jpg differ diff --git a/images/JAVA/CountDownLatch数据结构.jpg b/images/JAVA/CountDownLatch数据结构.jpg new file mode 100644 index 0000000..7e287cf Binary files /dev/null and b/images/JAVA/CountDownLatch数据结构.jpg differ diff --git a/images/JAVA/CyclicBarrier数据结构.jpg b/images/JAVA/CyclicBarrier数据结构.jpg new file mode 100644 index 0000000..8109949 Binary files /dev/null and b/images/JAVA/CyclicBarrier数据结构.jpg differ diff --git a/images/JAVA/FullGC变化比例大.png b/images/JAVA/FullGC变化比例大.png new file mode 100644 index 0000000..3aefbd8 Binary files /dev/null and b/images/JAVA/FullGC变化比例大.png differ diff --git a/images/JAVA/G1收集器.jpg b/images/JAVA/G1收集器.jpg new file mode 100644 index 0000000..c1f08cf Binary files /dev/null and b/images/JAVA/G1收集器.jpg differ diff --git a/images/JAVA/IO多路复用.png b/images/JAVA/IO多路复用.png new file mode 100644 index 0000000..e6c6b5e Binary files /dev/null and b/images/JAVA/IO多路复用.png differ diff --git a/images/JAVA/JAVA_Monitor.jpg b/images/JAVA/JAVA_Monitor.jpg new file mode 100644 index 0000000..a6ef2b3 Binary files /dev/null and b/images/JAVA/JAVA_Monitor.jpg differ diff --git a/images/JAVA/JVM内存伸缩模型.png b/images/JAVA/JVM内存伸缩模型.png new file mode 100644 index 0000000..dfff727 Binary files /dev/null and b/images/JAVA/JVM内存伸缩模型.png differ diff --git a/images/JAVA/JVM内存结构(JDK1.6).png b/images/JAVA/JVM内存结构(JDK1.6).png new file mode 100644 index 0000000..7459e45 Binary files /dev/null and b/images/JAVA/JVM内存结构(JDK1.6).png differ diff --git a/images/JAVA/JVM内存结构(JDK1.7).png b/images/JAVA/JVM内存结构(JDK1.7).png new file mode 100644 index 0000000..c953132 Binary files /dev/null and b/images/JAVA/JVM内存结构(JDK1.7).png differ diff --git a/images/JAVA/JVM内存结构(JDK1.8).png b/images/JAVA/JVM内存结构(JDK1.8).png new file mode 100644 index 0000000..f5abeae Binary files /dev/null and b/images/JAVA/JVM内存结构(JDK1.8).png differ diff --git a/images/JAVA/JVM堆内存划分.png b/images/JAVA/JVM堆内存划分.png new file mode 100644 index 0000000..7dbbdb9 Binary files /dev/null and b/images/JAVA/JVM堆内存划分.png differ diff --git a/images/JAVA/JVM对象申请空间流程.png b/images/JAVA/JVM对象申请空间流程.png new file mode 100644 index 0000000..2e1a568 Binary files /dev/null and b/images/JAVA/JVM对象申请空间流程.png differ diff --git a/images/JAVA/JVM架构.png b/images/JAVA/JVM架构.png new file mode 100644 index 0000000..c0a7c8b Binary files /dev/null and b/images/JAVA/JVM架构.png differ diff --git a/images/JAVA/JVM类加载机制.png b/images/JAVA/JVM类加载机制.png new file mode 100644 index 0000000..6312a67 Binary files /dev/null and b/images/JAVA/JVM类加载机制.png differ diff --git a/images/JAVA/Java7ConcurrentHashMap结构.png b/images/JAVA/Java7ConcurrentHashMap结构.png new file mode 100644 index 0000000..74dd923 Binary files /dev/null and b/images/JAVA/Java7ConcurrentHashMap结构.png differ diff --git a/images/JAVA/Java7HashMap结构.png b/images/JAVA/Java7HashMap结构.png new file mode 100644 index 0000000..20c3064 Binary files /dev/null and b/images/JAVA/Java7HashMap结构.png differ diff --git a/images/JAVA/Java8ConcurrentHashMap结构.png b/images/JAVA/Java8ConcurrentHashMap结构.png new file mode 100644 index 0000000..8648e60 Binary files /dev/null and b/images/JAVA/Java8ConcurrentHashMap结构.png differ diff --git a/images/JAVA/Java8HashMap结构.png b/images/JAVA/Java8HashMap结构.png new file mode 100644 index 0000000..2a977e2 Binary files /dev/null and b/images/JAVA/Java8HashMap结构.png differ diff --git a/images/JAVA/LongAdder原理.png b/images/JAVA/LongAdder原理.png new file mode 100644 index 0000000..374361e Binary files /dev/null and b/images/JAVA/LongAdder原理.png differ diff --git a/images/JAVA/LongAdder和AtomicLong性能对比.png b/images/JAVA/LongAdder和AtomicLong性能对比.png new file mode 100644 index 0000000..948252d Binary files /dev/null and b/images/JAVA/LongAdder和AtomicLong性能对比.png differ diff --git a/images/JAVA/ParNew收集器.jpg b/images/JAVA/ParNew收集器.jpg new file mode 100644 index 0000000..57aeefc Binary files /dev/null and b/images/JAVA/ParNew收集器.jpg differ diff --git a/images/JAVA/ParallelGCFullGC日志.jpg b/images/JAVA/ParallelGCFullGC日志.jpg new file mode 100644 index 0000000..60d4f98 Binary files /dev/null and b/images/JAVA/ParallelGCFullGC日志.jpg differ diff --git a/images/JAVA/ParallelGCYoungGC日志.jpg b/images/JAVA/ParallelGCYoungGC日志.jpg new file mode 100644 index 0000000..077e855 Binary files /dev/null and b/images/JAVA/ParallelGCYoungGC日志.jpg differ diff --git a/images/JAVA/ParallelOld收集器.jpg b/images/JAVA/ParallelOld收集器.jpg new file mode 100644 index 0000000..70fce8c Binary files /dev/null and b/images/JAVA/ParallelOld收集器.jpg differ diff --git a/images/JAVA/Proactor模式.png b/images/JAVA/Proactor模式.png new file mode 100644 index 0000000..af7b1fc Binary files /dev/null and b/images/JAVA/Proactor模式.png differ diff --git a/images/JAVA/ReentrantReadWriteLock数据结构.jpg b/images/JAVA/ReentrantReadWriteLock数据结构.jpg new file mode 100644 index 0000000..44c8787 Binary files /dev/null and b/images/JAVA/ReentrantReadWriteLock数据结构.jpg differ diff --git a/images/JAVA/RuntimeDataArea.png b/images/JAVA/RuntimeDataArea.png new file mode 100644 index 0000000..8c35e73 Binary files /dev/null and b/images/JAVA/RuntimeDataArea.png differ diff --git a/images/JAVA/Semaphore数据结构.jpg b/images/JAVA/Semaphore数据结构.jpg new file mode 100644 index 0000000..4e4b9ab Binary files /dev/null and b/images/JAVA/Semaphore数据结构.jpg differ diff --git a/images/JAVA/SerialOld收集器.jpg b/images/JAVA/SerialOld收集器.jpg new file mode 100644 index 0000000..ee11c7b Binary files /dev/null and b/images/JAVA/SerialOld收集器.jpg differ diff --git a/images/JAVA/Serial收集器.jpg b/images/JAVA/Serial收集器.jpg new file mode 100644 index 0000000..ee11c7b Binary files /dev/null and b/images/JAVA/Serial收集器.jpg differ diff --git a/images/JAVA/Shenandoah收集器运行流程.jpg b/images/JAVA/Shenandoah收集器运行流程.jpg new file mode 100644 index 0000000..c6b089b Binary files /dev/null and b/images/JAVA/Shenandoah收集器运行流程.jpg differ diff --git a/images/JAVA/Thread-BLOCKED.png b/images/JAVA/Thread-BLOCKED.png new file mode 100644 index 0000000..8437d10 Binary files /dev/null and b/images/JAVA/Thread-BLOCKED.png differ diff --git a/images/JAVA/Thread-NEW.png b/images/JAVA/Thread-NEW.png new file mode 100644 index 0000000..9acf6c4 Binary files /dev/null and b/images/JAVA/Thread-NEW.png differ diff --git a/images/JAVA/Thread-RUNNABLE.png b/images/JAVA/Thread-RUNNABLE.png new file mode 100644 index 0000000..167816a Binary files /dev/null and b/images/JAVA/Thread-RUNNABLE.png differ diff --git a/images/JAVA/Thread-TERMINATED.png b/images/JAVA/Thread-TERMINATED.png new file mode 100644 index 0000000..3da4442 Binary files /dev/null and b/images/JAVA/Thread-TERMINATED.png differ diff --git a/images/JAVA/Thread-TIMED_WAITING.png b/images/JAVA/Thread-TIMED_WAITING.png new file mode 100644 index 0000000..aae3671 Binary files /dev/null and b/images/JAVA/Thread-TIMED_WAITING.png differ diff --git a/images/JAVA/Thread-WAITING.png b/images/JAVA/Thread-WAITING.png new file mode 100644 index 0000000..5e6a614 Binary files /dev/null and b/images/JAVA/Thread-WAITING.png differ diff --git a/images/JAVA/ThreadPoolExecutor任务调度流程.png b/images/JAVA/ThreadPoolExecutor任务调度流程.png new file mode 100644 index 0000000..fbe43d0 Binary files /dev/null and b/images/JAVA/ThreadPoolExecutor任务调度流程.png differ diff --git a/images/JAVA/ThreadPoolExecutor状态转换.png b/images/JAVA/ThreadPoolExecutor状态转换.png new file mode 100644 index 0000000..9496379 Binary files /dev/null and b/images/JAVA/ThreadPoolExecutor状态转换.png differ diff --git a/images/JAVA/ThreadPoolExecutor线程销毁流程.png b/images/JAVA/ThreadPoolExecutor线程销毁流程.png new file mode 100644 index 0000000..32b1c8e Binary files /dev/null and b/images/JAVA/ThreadPoolExecutor线程销毁流程.png differ diff --git a/images/JAVA/ThreadPoolExecutor运行流程.png b/images/JAVA/ThreadPoolExecutor运行流程.png new file mode 100644 index 0000000..8486a20 Binary files /dev/null and b/images/JAVA/ThreadPoolExecutor运行流程.png differ diff --git a/images/JAVA/Throwable.png b/images/JAVA/Throwable.png new file mode 100644 index 0000000..17569b8 Binary files /dev/null and b/images/JAVA/Throwable.png differ diff --git a/images/JAVA/ZGC内存布局.jpg b/images/JAVA/ZGC内存布局.jpg new file mode 100644 index 0000000..c005f18 Binary files /dev/null and b/images/JAVA/ZGC内存布局.jpg differ diff --git a/images/JAVA/ZGC收集器.jpg b/images/JAVA/ZGC收集器.jpg new file mode 100644 index 0000000..749c025 Binary files /dev/null and b/images/JAVA/ZGC收集器.jpg differ diff --git a/images/JAVA/epoll工作流程.jpg b/images/JAVA/epoll工作流程.jpg new file mode 100644 index 0000000..ed28026 Binary files /dev/null and b/images/JAVA/epoll工作流程.jpg differ diff --git a/images/JAVA/lambda-filter.jpg b/images/JAVA/lambda-filter.jpg new file mode 100644 index 0000000..c44c90c Binary files /dev/null and b/images/JAVA/lambda-filter.jpg differ diff --git a/images/JAVA/lambda-flatMap.jpg b/images/JAVA/lambda-flatMap.jpg new file mode 100644 index 0000000..6efc0ce Binary files /dev/null and b/images/JAVA/lambda-flatMap.jpg differ diff --git a/images/JAVA/lambda-groupingBy.jpg b/images/JAVA/lambda-groupingBy.jpg new file mode 100644 index 0000000..0d00ef4 Binary files /dev/null and b/images/JAVA/lambda-groupingBy.jpg differ diff --git a/images/JAVA/lambda-map.jpg b/images/JAVA/lambda-map.jpg new file mode 100644 index 0000000..b63833a Binary files /dev/null and b/images/JAVA/lambda-map.jpg differ diff --git a/images/JAVA/lambda-partitioningBy.jpg b/images/JAVA/lambda-partitioningBy.jpg new file mode 100644 index 0000000..010e7d4 Binary files /dev/null and b/images/JAVA/lambda-partitioningBy.jpg differ diff --git a/images/JAVA/lambda-reduce.jpg b/images/JAVA/lambda-reduce.jpg new file mode 100644 index 0000000..98e69b8 Binary files /dev/null and b/images/JAVA/lambda-reduce.jpg differ diff --git a/images/JAVA/poll工作流程.jpg b/images/JAVA/poll工作流程.jpg new file mode 100644 index 0000000..4b07886 Binary files /dev/null and b/images/JAVA/poll工作流程.jpg differ diff --git a/images/JAVA/select、poll、epoll对比.png b/images/JAVA/select、poll、epoll对比.png new file mode 100644 index 0000000..586e2e9 Binary files /dev/null and b/images/JAVA/select、poll、epoll对比.png differ diff --git a/images/JAVA/select工作流程.jpg b/images/JAVA/select工作流程.jpg new file mode 100644 index 0000000..30b45d8 Binary files /dev/null and b/images/JAVA/select工作流程.jpg differ diff --git a/images/JAVA/synchronized-修饰代码块.png b/images/JAVA/synchronized-修饰代码块.png new file mode 100644 index 0000000..2cdb3bd Binary files /dev/null and b/images/JAVA/synchronized-修饰代码块.png differ diff --git a/images/JAVA/synchronized-修饰普通函数.png b/images/JAVA/synchronized-修饰普通函数.png new file mode 100644 index 0000000..3f897a8 Binary files /dev/null and b/images/JAVA/synchronized-修饰普通函数.png differ diff --git a/images/JAVA/synchronized-修饰静态函数.png b/images/JAVA/synchronized-修饰静态函数.png new file mode 100644 index 0000000..37fe026 Binary files /dev/null and b/images/JAVA/synchronized-修饰静态函数.png differ diff --git a/images/JAVA/synchronized-偏向锁.png b/images/JAVA/synchronized-偏向锁.png new file mode 100644 index 0000000..b8994f0 Binary files /dev/null and b/images/JAVA/synchronized-偏向锁.png differ diff --git a/images/JAVA/synchronized-轻量级锁.png b/images/JAVA/synchronized-轻量级锁.png new file mode 100644 index 0000000..8ebce65 Binary files /dev/null and b/images/JAVA/synchronized-轻量级锁.png differ diff --git a/images/JAVA/synchronized-重量级锁.png b/images/JAVA/synchronized-重量级锁.png new file mode 100644 index 0000000..e6fd0b7 Binary files /dev/null and b/images/JAVA/synchronized-重量级锁.png differ diff --git a/images/JAVA/synchronized.jpg b/images/JAVA/synchronized.jpg new file mode 100644 index 0000000..7ee938d Binary files /dev/null and b/images/JAVA/synchronized.jpg differ diff --git a/images/JAVA/synchronized.png b/images/JAVA/synchronized.png new file mode 100644 index 0000000..04bf1c4 Binary files /dev/null and b/images/JAVA/synchronized.png differ diff --git a/images/JAVA/synchronized原理.png b/images/JAVA/synchronized原理.png new file mode 100644 index 0000000..deec6b7 Binary files /dev/null and b/images/JAVA/synchronized原理.png differ diff --git a/images/JAVA/任务拒绝.png b/images/JAVA/任务拒绝.png new file mode 100644 index 0000000..5e8629a Binary files /dev/null and b/images/JAVA/任务拒绝.png differ diff --git a/images/JAVA/任务缓冲策略.png b/images/JAVA/任务缓冲策略.png new file mode 100644 index 0000000..adf9b23 Binary files /dev/null and b/images/JAVA/任务缓冲策略.png differ diff --git a/images/JAVA/信号驱动式IO.png b/images/JAVA/信号驱动式IO.png new file mode 100644 index 0000000..284a00b Binary files /dev/null and b/images/JAVA/信号驱动式IO.png differ diff --git a/images/JAVA/共享锁(Share)模式.png b/images/JAVA/共享锁(Share)模式.png new file mode 100644 index 0000000..0e91d09 Binary files /dev/null and b/images/JAVA/共享锁(Share)模式.png differ diff --git a/images/JAVA/分析Unreachable.png b/images/JAVA/分析Unreachable.png new file mode 100644 index 0000000..62e66c9 Binary files /dev/null and b/images/JAVA/分析Unreachable.png differ diff --git a/images/JAVA/动态扩容引起的空间震荡.png b/images/JAVA/动态扩容引起的空间震荡.png new file mode 100644 index 0000000..46a7e59 Binary files /dev/null and b/images/JAVA/动态扩容引起的空间震荡.png differ diff --git a/images/JAVA/单Reactor单进程线程.png b/images/JAVA/单Reactor单进程线程.png new file mode 100644 index 0000000..6616416 Binary files /dev/null and b/images/JAVA/单Reactor单进程线程.png differ diff --git a/images/JAVA/单Reactor多线程多进程.png b/images/JAVA/单Reactor多线程多进程.png new file mode 100644 index 0000000..312f50d Binary files /dev/null and b/images/JAVA/单Reactor多线程多进程.png differ diff --git a/images/JAVA/双亲委派.png b/images/JAVA/双亲委派.png new file mode 100644 index 0000000..262210d Binary files /dev/null and b/images/JAVA/双亲委派.png differ diff --git a/images/JAVA/同步队列(syncQueue)结构.png b/images/JAVA/同步队列(syncQueue)结构.png new file mode 100644 index 0000000..ef3b86c Binary files /dev/null and b/images/JAVA/同步队列(syncQueue)结构.png differ diff --git a/images/JAVA/同步阻塞IO.png b/images/JAVA/同步阻塞IO.png new file mode 100644 index 0000000..1ca8e73 Binary files /dev/null and b/images/JAVA/同步阻塞IO.png differ diff --git a/images/JAVA/同步非阻塞IO.png b/images/JAVA/同步非阻塞IO.png new file mode 100644 index 0000000..cfab65b Binary files /dev/null and b/images/JAVA/同步非阻塞IO.png differ diff --git a/images/JAVA/复制(Copying)算法.png b/images/JAVA/复制(Copying)算法.png new file mode 100644 index 0000000..789416b Binary files /dev/null and b/images/JAVA/复制(Copying)算法.png differ diff --git a/images/JAVA/多Reactor多进程线程.png b/images/JAVA/多Reactor多进程线程.png new file mode 100644 index 0000000..f2e1ca9 Binary files /dev/null and b/images/JAVA/多Reactor多进程线程.png differ diff --git a/images/JAVA/寻找垃圾算法.png b/images/JAVA/寻找垃圾算法.png new file mode 100644 index 0000000..969cdf7 Binary files /dev/null and b/images/JAVA/寻找垃圾算法.png differ diff --git a/images/JAVA/年轻代.jpg b/images/JAVA/年轻代.jpg new file mode 100644 index 0000000..4d803b4 Binary files /dev/null and b/images/JAVA/年轻代.jpg differ diff --git a/images/JAVA/异步IO.png b/images/JAVA/异步IO.png new file mode 100644 index 0000000..62ad175 Binary files /dev/null and b/images/JAVA/异步IO.png differ diff --git a/images/JAVA/异步非阻塞IO.png b/images/JAVA/异步非阻塞IO.png new file mode 100644 index 0000000..be7de9c Binary files /dev/null and b/images/JAVA/异步非阻塞IO.png differ diff --git a/images/JAVA/引用级别.png b/images/JAVA/引用级别.png new file mode 100644 index 0000000..cc5ebb1 Binary files /dev/null and b/images/JAVA/引用级别.png differ diff --git a/images/JAVA/引用计数法-问题.png b/images/JAVA/引用计数法-问题.png new file mode 100644 index 0000000..e2a15b3 Binary files /dev/null and b/images/JAVA/引用计数法-问题.png differ diff --git a/images/JAVA/引用计数法.png b/images/JAVA/引用计数法.png new file mode 100644 index 0000000..ec2f0f9 Binary files /dev/null and b/images/JAVA/引用计数法.png differ diff --git a/images/JAVA/收集器.jpg b/images/JAVA/收集器.jpg new file mode 100644 index 0000000..1b3aa37 Binary files /dev/null and b/images/JAVA/收集器.jpg differ diff --git a/images/JAVA/收集器停顿时间和系统开销对比.png b/images/JAVA/收集器停顿时间和系统开销对比.png new file mode 100644 index 0000000..ceee9d0 Binary files /dev/null and b/images/JAVA/收集器停顿时间和系统开销对比.png differ diff --git a/images/JAVA/收集算法.png b/images/JAVA/收集算法.png new file mode 100644 index 0000000..023e6b2 Binary files /dev/null and b/images/JAVA/收集算法.png differ diff --git a/images/JAVA/数据结构—数组.png b/images/JAVA/数据结构—数组.png new file mode 100644 index 0000000..be1f546 Binary files /dev/null and b/images/JAVA/数据结构—数组.png differ diff --git a/images/JAVA/数据结构—环型结构.png b/images/JAVA/数据结构—环型结构.png new file mode 100644 index 0000000..3aaaeda Binary files /dev/null and b/images/JAVA/数据结构—环型结构.png differ diff --git a/images/JAVA/整理(Compact).png b/images/JAVA/整理(Compact).png new file mode 100644 index 0000000..c046017 Binary files /dev/null and b/images/JAVA/整理(Compact).png differ diff --git a/images/JAVA/标记整理(Mark-Compact)算法.png b/images/JAVA/标记整理(Mark-Compact)算法.png new file mode 100644 index 0000000..8f7ffa9 Binary files /dev/null and b/images/JAVA/标记整理(Mark-Compact)算法.png differ diff --git a/images/JAVA/标记清除(Mark-Sweep)算法.png b/images/JAVA/标记清除(Mark-Sweep)算法.png new file mode 100644 index 0000000..d052dcf Binary files /dev/null and b/images/JAVA/标记清除(Mark-Sweep)算法.png differ diff --git a/images/JAVA/标记(Mark).png b/images/JAVA/标记(Mark).png new file mode 100644 index 0000000..723bba4 Binary files /dev/null and b/images/JAVA/标记(Mark).png differ diff --git a/images/JAVA/根可达算法.png b/images/JAVA/根可达算法.png new file mode 100644 index 0000000..f71be42 Binary files /dev/null and b/images/JAVA/根可达算法.png differ diff --git a/images/JAVA/清除(Sweep)-内存.jpg b/images/JAVA/清除(Sweep)-内存.jpg new file mode 100644 index 0000000..73ce7bf Binary files /dev/null and b/images/JAVA/清除(Sweep)-内存.jpg differ diff --git a/images/JAVA/清除(Sweep)-回收.jpg b/images/JAVA/清除(Sweep)-回收.jpg new file mode 100644 index 0000000..84c30a2 Binary files /dev/null and b/images/JAVA/清除(Sweep)-回收.jpg differ diff --git a/images/JAVA/清除(Sweep).png b/images/JAVA/清除(Sweep).png new file mode 100644 index 0000000..a8a940c Binary files /dev/null and b/images/JAVA/清除(Sweep).png differ diff --git a/images/JAVA/源码指令.png b/images/JAVA/源码指令.png new file mode 100644 index 0000000..62e90c0 Binary files /dev/null and b/images/JAVA/源码指令.png differ diff --git a/images/JAVA/独占锁(Exclusive)模式.png b/images/JAVA/独占锁(Exclusive)模式.png new file mode 100644 index 0000000..65bb447 Binary files /dev/null and b/images/JAVA/独占锁(Exclusive)模式.png differ diff --git a/images/JAVA/生命周期状态.png b/images/JAVA/生命周期状态.png new file mode 100644 index 0000000..f73a519 Binary files /dev/null and b/images/JAVA/生命周期状态.png differ diff --git a/images/JAVA/线程池生命周期.png b/images/JAVA/线程池生命周期.png new file mode 100644 index 0000000..b4f5f6e Binary files /dev/null and b/images/JAVA/线程池生命周期.png differ diff --git a/images/JAVA/线程状态切换.jpg b/images/JAVA/线程状态切换.jpg new file mode 100644 index 0000000..06f1372 Binary files /dev/null and b/images/JAVA/线程状态切换.jpg differ diff --git a/images/JAVA/自旋锁(SpinLock).png b/images/JAVA/自旋锁(SpinLock).png new file mode 100644 index 0000000..f7077eb Binary files /dev/null and b/images/JAVA/自旋锁(SpinLock).png differ diff --git a/images/JAVA/获取任务流程图.png b/images/JAVA/获取任务流程图.png new file mode 100644 index 0000000..012f981 Binary files /dev/null and b/images/JAVA/获取任务流程图.png differ diff --git a/images/JAVA/过早晋升优化GC.png b/images/JAVA/过早晋升优化GC.png new file mode 100644 index 0000000..e02c12e Binary files /dev/null and b/images/JAVA/过早晋升优化GC.png differ diff --git a/images/JAVA/过早晋升优化Oldgen.png b/images/JAVA/过早晋升优化Oldgen.png new file mode 100644 index 0000000..662e1d9 Binary files /dev/null and b/images/JAVA/过早晋升优化Oldgen.png differ diff --git a/images/JAVA/锁状态升级流程.png b/images/JAVA/锁状态升级流程.png new file mode 100644 index 0000000..773ad58 Binary files /dev/null and b/images/JAVA/锁状态升级流程.png differ diff --git a/images/JAVA/队列类图.png b/images/JAVA/队列类图.png new file mode 100644 index 0000000..27f0062 Binary files /dev/null and b/images/JAVA/队列类图.png differ diff --git a/images/JAVA/阻塞IO.png b/images/JAVA/阻塞IO.png new file mode 100644 index 0000000..acd9ab1 Binary files /dev/null and b/images/JAVA/阻塞IO.png differ diff --git a/images/JAVA/非阻塞IO.png b/images/JAVA/非阻塞IO.png new file mode 100644 index 0000000..02949df Binary files /dev/null and b/images/JAVA/非阻塞IO.png differ diff --git a/images/Middleware/AOF.jpg b/images/Middleware/AOF.jpg new file mode 100644 index 0000000..468d7ac Binary files /dev/null and b/images/Middleware/AOF.jpg differ diff --git a/images/Middleware/ApacheDubbo.jpg b/images/Middleware/ApacheDubbo.jpg new file mode 100644 index 0000000..6892500 Binary files /dev/null and b/images/Middleware/ApacheDubbo.jpg differ diff --git a/images/Middleware/Broker数据结构.jpg b/images/Middleware/Broker数据结构.jpg new file mode 100644 index 0000000..c6cc3f9 Binary files /dev/null and b/images/Middleware/Broker数据结构.jpg differ diff --git a/images/Middleware/Bus介绍.png b/images/Middleware/Bus介绍.png new file mode 100644 index 0000000..f915c00 Binary files /dev/null and b/images/Middleware/Bus介绍.png differ diff --git a/images/Middleware/ByteBuf-get.png b/images/Middleware/ByteBuf-get.png new file mode 100644 index 0000000..429830d Binary files /dev/null and b/images/Middleware/ByteBuf-get.png differ diff --git a/images/Middleware/ByteBuf-更多操作.png b/images/Middleware/ByteBuf-更多操作.png new file mode 100644 index 0000000..081fff0 Binary files /dev/null and b/images/Middleware/ByteBuf-更多操作.png differ diff --git a/images/Middleware/ByteBufAllocator.png b/images/Middleware/ByteBufAllocator.png new file mode 100644 index 0000000..3314673 Binary files /dev/null and b/images/Middleware/ByteBufAllocator.png differ diff --git a/images/Middleware/ByteBuf工作流程.png b/images/Middleware/ByteBuf工作流程.png new file mode 100644 index 0000000..ca25e48 Binary files /dev/null and b/images/Middleware/ByteBuf工作流程.png differ diff --git a/images/Middleware/ByteBuf顺序访问索引.png b/images/Middleware/ByteBuf顺序访问索引.png new file mode 100644 index 0000000..a690be2 Binary files /dev/null and b/images/Middleware/ByteBuf顺序访问索引.png differ diff --git a/images/Middleware/CP粘包拆包图解.png b/images/Middleware/CP粘包拆包图解.png new file mode 100644 index 0000000..393c903 Binary files /dev/null and b/images/Middleware/CP粘包拆包图解.png differ diff --git a/images/Middleware/Client端NioEventLoop处理的事件.png b/images/Middleware/Client端NioEventLoop处理的事件.png new file mode 100644 index 0000000..5482b6f Binary files /dev/null and b/images/Middleware/Client端NioEventLoop处理的事件.png differ diff --git a/images/Middleware/CompositeBuffer.png b/images/Middleware/CompositeBuffer.png new file mode 100644 index 0000000..e3c83e5 Binary files /dev/null and b/images/Middleware/CompositeBuffer.png differ diff --git a/images/Middleware/CompositeByteBuf实现零拷贝.png b/images/Middleware/CompositeByteBuf实现零拷贝.png new file mode 100644 index 0000000..5cb8d9f Binary files /dev/null and b/images/Middleware/CompositeByteBuf实现零拷贝.png differ diff --git a/images/Middleware/Config介绍.png b/images/Middleware/Config介绍.png new file mode 100644 index 0000000..a9cf94b Binary files /dev/null and b/images/Middleware/Config介绍.png differ diff --git a/images/Middleware/DubboArchitecture.png b/images/Middleware/DubboArchitecture.png new file mode 100644 index 0000000..530fd53 Binary files /dev/null and b/images/Middleware/DubboArchitecture.png differ diff --git a/images/Middleware/Eurake.png b/images/Middleware/Eurake.png new file mode 100644 index 0000000..164da6e Binary files /dev/null and b/images/Middleware/Eurake.png differ diff --git a/images/Middleware/Eurake介绍.png b/images/Middleware/Eurake介绍.png new file mode 100644 index 0000000..14d31f0 Binary files /dev/null and b/images/Middleware/Eurake介绍.png differ diff --git a/images/Middleware/Feign.png b/images/Middleware/Feign.png new file mode 100644 index 0000000..385fc5d Binary files /dev/null and b/images/Middleware/Feign.png differ diff --git a/images/Middleware/Feign介绍.png b/images/Middleware/Feign介绍.png new file mode 100644 index 0000000..e6472aa Binary files /dev/null and b/images/Middleware/Feign介绍.png differ diff --git a/images/Middleware/Feign远程调用流程.png b/images/Middleware/Feign远程调用流程.png new file mode 100644 index 0000000..bb3c87d Binary files /dev/null and b/images/Middleware/Feign远程调用流程.png differ diff --git a/images/Middleware/Gateway介绍.png b/images/Middleware/Gateway介绍.png new file mode 100644 index 0000000..3e2105e Binary files /dev/null and b/images/Middleware/Gateway介绍.png differ diff --git a/images/Middleware/HashedWheelTimer.png b/images/Middleware/HashedWheelTimer.png new file mode 100644 index 0000000..9e7416a Binary files /dev/null and b/images/Middleware/HashedWheelTimer.png differ diff --git a/images/Middleware/Hystrix.png b/images/Middleware/Hystrix.png new file mode 100644 index 0000000..b57baa2 Binary files /dev/null and b/images/Middleware/Hystrix.png differ diff --git a/images/Middleware/Hystrix介绍.png b/images/Middleware/Hystrix介绍.png new file mode 100644 index 0000000..bc724ee Binary files /dev/null and b/images/Middleware/Hystrix介绍.png differ diff --git a/images/Middleware/Hystrix熔断.png b/images/Middleware/Hystrix熔断.png new file mode 100644 index 0000000..91a721d Binary files /dev/null and b/images/Middleware/Hystrix熔断.png differ diff --git a/images/Middleware/InfluxDB系统架构.jpg b/images/Middleware/InfluxDB系统架构.jpg new file mode 100644 index 0000000..b11593b Binary files /dev/null and b/images/Middleware/InfluxDB系统架构.jpg differ diff --git a/images/Middleware/Keepalived体系结构.jpg b/images/Middleware/Keepalived体系结构.jpg new file mode 100644 index 0000000..ce09c6f Binary files /dev/null and b/images/Middleware/Keepalived体系结构.jpg differ diff --git a/images/Middleware/LVS-DR-IP.png b/images/Middleware/LVS-DR-IP.png new file mode 100644 index 0000000..cbe868f Binary files /dev/null and b/images/Middleware/LVS-DR-IP.png differ diff --git a/images/Middleware/LVS-DR-STR.png b/images/Middleware/LVS-DR-STR.png new file mode 100644 index 0000000..2ef3331 Binary files /dev/null and b/images/Middleware/LVS-DR-STR.png differ diff --git a/images/Middleware/LVS-DR.png b/images/Middleware/LVS-DR.png new file mode 100644 index 0000000..5ab7808 Binary files /dev/null and b/images/Middleware/LVS-DR.png differ diff --git a/images/Middleware/LVS-ENAT-STR.png b/images/Middleware/LVS-ENAT-STR.png new file mode 100644 index 0000000..a21ab23 Binary files /dev/null and b/images/Middleware/LVS-ENAT-STR.png differ diff --git a/images/Middleware/LVS-NAT-IP.png b/images/Middleware/LVS-NAT-IP.png new file mode 100644 index 0000000..0c39234 Binary files /dev/null and b/images/Middleware/LVS-NAT-IP.png differ diff --git a/images/Middleware/LVS-NAT-STR.png b/images/Middleware/LVS-NAT-STR.png new file mode 100644 index 0000000..50f733a Binary files /dev/null and b/images/Middleware/LVS-NAT-STR.png differ diff --git a/images/Middleware/LVS-NAT.png b/images/Middleware/LVS-NAT.png new file mode 100644 index 0000000..51d948c Binary files /dev/null and b/images/Middleware/LVS-NAT.png differ diff --git a/images/Middleware/LVS-TP-TUN-STR.png b/images/Middleware/LVS-TP-TUN-STR.png new file mode 100644 index 0000000..31a1863 Binary files /dev/null and b/images/Middleware/LVS-TP-TUN-STR.png differ diff --git a/images/Middleware/LVS-full-NAT-STR.png b/images/Middleware/LVS-full-NAT-STR.png new file mode 100644 index 0000000..381819f Binary files /dev/null and b/images/Middleware/LVS-full-NAT-STR.png differ diff --git a/images/Middleware/LengthFieldBasedFrameDecoder.png b/images/Middleware/LengthFieldBasedFrameDecoder.png new file mode 100644 index 0000000..0f5c06b Binary files /dev/null and b/images/Middleware/LengthFieldBasedFrameDecoder.png differ diff --git a/images/Middleware/LengthFieldPrepender.png b/images/Middleware/LengthFieldPrepender.png new file mode 100644 index 0000000..554be87 Binary files /dev/null and b/images/Middleware/LengthFieldPrepender.png differ diff --git a/images/Middleware/MQ与DB一致性原理.png b/images/Middleware/MQ与DB一致性原理.png new file mode 100644 index 0000000..5099b17 Binary files /dev/null and b/images/Middleware/MQ与DB一致性原理.png differ diff --git a/images/Middleware/MyBatis-Executor.png b/images/Middleware/MyBatis-Executor.png new file mode 100644 index 0000000..c0dcde3 Binary files /dev/null and b/images/Middleware/MyBatis-Executor.png differ diff --git a/images/Middleware/MyBatis执行sql流程.png b/images/Middleware/MyBatis执行sql流程.png new file mode 100644 index 0000000..20dcd77 Binary files /dev/null and b/images/Middleware/MyBatis执行sql流程.png differ diff --git a/images/Middleware/MyBatis缓存机制.png b/images/Middleware/MyBatis缓存机制.png new file mode 100644 index 0000000..ce0a368 Binary files /dev/null and b/images/Middleware/MyBatis缓存机制.png differ diff --git a/images/Middleware/Mybatis架构.jpg b/images/Middleware/Mybatis架构.jpg new file mode 100644 index 0000000..8ada389 Binary files /dev/null and b/images/Middleware/Mybatis架构.jpg differ diff --git a/images/Middleware/Mybatis流程.png b/images/Middleware/Mybatis流程.png new file mode 100644 index 0000000..cf6a289 Binary files /dev/null and b/images/Middleware/Mybatis流程.png differ diff --git a/images/Middleware/Nacos架构图.jpeg b/images/Middleware/Nacos架构图.jpeg new file mode 100644 index 0000000..16832c4 Binary files /dev/null and b/images/Middleware/Nacos架构图.jpeg differ diff --git a/images/Middleware/Nacos逻辑架构及其组件介绍.png b/images/Middleware/Nacos逻辑架构及其组件介绍.png new file mode 100644 index 0000000..3de0f56 Binary files /dev/null and b/images/Middleware/Nacos逻辑架构及其组件介绍.png differ diff --git a/images/Middleware/Netty-Reactor.png b/images/Middleware/Netty-Reactor.png new file mode 100644 index 0000000..bf1317a Binary files /dev/null and b/images/Middleware/Netty-Reactor.png differ diff --git a/images/Middleware/Netty整体流程.png b/images/Middleware/Netty整体流程.png new file mode 100644 index 0000000..5484937 Binary files /dev/null and b/images/Middleware/Netty整体流程.png differ diff --git a/images/Middleware/Netty流程-关闭服务.jpg b/images/Middleware/Netty流程-关闭服务.jpg new file mode 100644 index 0000000..3e4feb0 Binary files /dev/null and b/images/Middleware/Netty流程-关闭服务.jpg differ diff --git a/images/Middleware/Netty流程-关闭连接.jpg b/images/Middleware/Netty流程-关闭连接.jpg new file mode 100644 index 0000000..0228484 Binary files /dev/null and b/images/Middleware/Netty流程-关闭连接.jpg differ diff --git a/images/Middleware/Netty流程-建立连接.jpg b/images/Middleware/Netty流程-建立连接.jpg new file mode 100644 index 0000000..7d815b0 Binary files /dev/null and b/images/Middleware/Netty流程-建立连接.jpg differ diff --git a/images/Middleware/Netty流程-服务启动.jpg b/images/Middleware/Netty流程-服务启动.jpg new file mode 100644 index 0000000..786a401 Binary files /dev/null and b/images/Middleware/Netty流程-服务启动.jpg differ diff --git a/images/Middleware/Netty流程-读写与业务处理.jpg b/images/Middleware/Netty流程-读写与业务处理.jpg new file mode 100644 index 0000000..3a67299 Binary files /dev/null and b/images/Middleware/Netty流程-读写与业务处理.jpg differ diff --git a/images/Middleware/Netty线程模型.png b/images/Middleware/Netty线程模型.png new file mode 100644 index 0000000..dbeaf30 Binary files /dev/null and b/images/Middleware/Netty线程模型.png differ diff --git a/images/Middleware/Nginx层级结构.png b/images/Middleware/Nginx层级结构.png new file mode 100644 index 0000000..e2919bb Binary files /dev/null and b/images/Middleware/Nginx层级结构.png differ diff --git a/images/Middleware/OAuth2介绍.png b/images/Middleware/OAuth2介绍.png new file mode 100644 index 0000000..027e525 Binary files /dev/null and b/images/Middleware/OAuth2介绍.png differ diff --git a/images/Middleware/RDB-gbsave.jpg b/images/Middleware/RDB-gbsave.jpg new file mode 100644 index 0000000..3a9faf7 Binary files /dev/null and b/images/Middleware/RDB-gbsave.jpg differ diff --git a/images/Middleware/RDB-save.jpg b/images/Middleware/RDB-save.jpg new file mode 100644 index 0000000..d1b65c6 Binary files /dev/null and b/images/Middleware/RDB-save.jpg differ diff --git a/images/Middleware/Redis主从复制-完整重同步.jpg b/images/Middleware/Redis主从复制-完整重同步.jpg new file mode 100644 index 0000000..79ec563 Binary files /dev/null and b/images/Middleware/Redis主从复制-完整重同步.jpg differ diff --git a/images/Middleware/Redis主从复制-部分重同步.jpg b/images/Middleware/Redis主从复制-部分重同步.jpg new file mode 100644 index 0000000..ade3b6f Binary files /dev/null and b/images/Middleware/Redis主从复制-部分重同步.jpg differ diff --git a/images/Middleware/Redis主从复制模式(Replication).png b/images/Middleware/Redis主从复制模式(Replication).png new file mode 100644 index 0000000..6b7fc20 Binary files /dev/null and b/images/Middleware/Redis主从复制模式(Replication).png differ diff --git a/images/Middleware/Redis主从复制模式(Replication)优缺点.png b/images/Middleware/Redis主从复制模式(Replication)优缺点.png new file mode 100644 index 0000000..4fd716b Binary files /dev/null and b/images/Middleware/Redis主从复制模式(Replication)优缺点.png differ diff --git a/images/Middleware/Redis全局hash字典.png b/images/Middleware/Redis全局hash字典.png new file mode 100644 index 0000000..068f484 Binary files /dev/null and b/images/Middleware/Redis全局hash字典.png differ diff --git a/images/Middleware/Redis哨兵模式(Sentinel).png b/images/Middleware/Redis哨兵模式(Sentinel).png new file mode 100644 index 0000000..e0ecca8 Binary files /dev/null and b/images/Middleware/Redis哨兵模式(Sentinel).png differ diff --git a/images/Middleware/Redis哨兵模式(Sentinel)优缺点.png b/images/Middleware/Redis哨兵模式(Sentinel)优缺点.png new file mode 100644 index 0000000..d85d3b6 Binary files /dev/null and b/images/Middleware/Redis哨兵模式(Sentinel)优缺点.png differ diff --git a/images/Middleware/Redis数据类型与底层数据结构关系.png b/images/Middleware/Redis数据类型与底层数据结构关系.png new file mode 100644 index 0000000..2d2de41 Binary files /dev/null and b/images/Middleware/Redis数据类型与底层数据结构关系.png differ diff --git a/images/Middleware/Redis集群模式(Cluster).png b/images/Middleware/Redis集群模式(Cluster).png new file mode 100644 index 0000000..9f00fb2 Binary files /dev/null and b/images/Middleware/Redis集群模式(Cluster).png differ diff --git a/images/Middleware/Redis集群模式(Cluster)优缺点.png b/images/Middleware/Redis集群模式(Cluster)优缺点.png new file mode 100644 index 0000000..3402326 Binary files /dev/null and b/images/Middleware/Redis集群模式(Cluster)优缺点.png differ diff --git a/images/Middleware/Ribbon.png b/images/Middleware/Ribbon.png new file mode 100644 index 0000000..2955386 Binary files /dev/null and b/images/Middleware/Ribbon.png differ diff --git a/images/Middleware/Ribbon介绍.png b/images/Middleware/Ribbon介绍.png new file mode 100644 index 0000000..3b7c6e7 Binary files /dev/null and b/images/Middleware/Ribbon介绍.png differ diff --git a/images/Middleware/Ribbon规则.png b/images/Middleware/Ribbon规则.png new file mode 100644 index 0000000..3c5ad1c Binary files /dev/null and b/images/Middleware/Ribbon规则.png differ diff --git a/images/Middleware/RocketMQ事务消息.jpg b/images/Middleware/RocketMQ事务消息.jpg new file mode 100644 index 0000000..cba58d7 Binary files /dev/null and b/images/Middleware/RocketMQ事务消息.jpg differ diff --git a/images/Middleware/RocketMQ实现原理.jpg b/images/Middleware/RocketMQ实现原理.jpg new file mode 100644 index 0000000..fd98817 Binary files /dev/null and b/images/Middleware/RocketMQ实现原理.jpg differ diff --git a/images/Middleware/RocketMQ消息正确订阅关系.png b/images/Middleware/RocketMQ消息正确订阅关系.png new file mode 100644 index 0000000..6bfa51d Binary files /dev/null and b/images/Middleware/RocketMQ消息正确订阅关系.png differ diff --git a/images/Middleware/RocketMQ消费失败消息积压.jpg b/images/Middleware/RocketMQ消费失败消息积压.jpg new file mode 100644 index 0000000..777091a Binary files /dev/null and b/images/Middleware/RocketMQ消费失败消息积压.jpg differ diff --git a/images/Middleware/Rocket消息丢失.jpg b/images/Middleware/Rocket消息丢失.jpg new file mode 100644 index 0000000..3c0aeaa Binary files /dev/null and b/images/Middleware/Rocket消息丢失.jpg differ diff --git a/images/Middleware/SDS简单动态字符.png b/images/Middleware/SDS简单动态字符.png new file mode 100644 index 0000000..c0937ab Binary files /dev/null and b/images/Middleware/SDS简单动态字符.png differ diff --git a/images/Middleware/Sentinel-features-overview.png b/images/Middleware/Sentinel-features-overview.png new file mode 100644 index 0000000..974649b Binary files /dev/null and b/images/Middleware/Sentinel-features-overview.png differ diff --git a/images/Middleware/Sentinel-opensource-eco.png b/images/Middleware/Sentinel-opensource-eco.png new file mode 100644 index 0000000..2d5f6a5 Binary files /dev/null and b/images/Middleware/Sentinel-opensource-eco.png differ diff --git a/images/Middleware/Server端NioEventLoop处理的事件.png b/images/Middleware/Server端NioEventLoop处理的事件.png new file mode 100644 index 0000000..82aefb5 Binary files /dev/null and b/images/Middleware/Server端NioEventLoop处理的事件.png differ diff --git a/images/Middleware/Sleuth介绍.png b/images/Middleware/Sleuth介绍.png new file mode 100644 index 0000000..f344846 Binary files /dev/null and b/images/Middleware/Sleuth介绍.png differ diff --git a/images/Middleware/SpringCloud.png b/images/Middleware/SpringCloud.png new file mode 100644 index 0000000..d9dfa61 Binary files /dev/null and b/images/Middleware/SpringCloud.png differ diff --git a/images/Middleware/SpringMVC工作原理.png b/images/Middleware/SpringMVC工作原理.png new file mode 100644 index 0000000..ab52726 Binary files /dev/null and b/images/Middleware/SpringMVC工作原理.png differ diff --git a/images/Middleware/SpringMVC流程.jpg b/images/Middleware/SpringMVC流程.jpg new file mode 100644 index 0000000..7e96021 Binary files /dev/null and b/images/Middleware/SpringMVC流程.jpg differ diff --git a/images/Middleware/Spring主要包.png b/images/Middleware/Spring主要包.png new file mode 100644 index 0000000..10c99ea Binary files /dev/null and b/images/Middleware/Spring主要包.png differ diff --git a/images/Middleware/Spring关系.jpg b/images/Middleware/Spring关系.jpg new file mode 100644 index 0000000..d6afc2b Binary files /dev/null and b/images/Middleware/Spring关系.jpg differ diff --git a/images/Middleware/Spring常用模块.png b/images/Middleware/Spring常用模块.png new file mode 100644 index 0000000..ec60efe Binary files /dev/null and b/images/Middleware/Spring常用模块.png differ diff --git a/images/Middleware/Spring常用注解.png b/images/Middleware/Spring常用注解.png new file mode 100644 index 0000000..a22b43a Binary files /dev/null and b/images/Middleware/Spring常用注解.png differ diff --git a/images/Middleware/Spring核心组件.png b/images/Middleware/Spring核心组件.png new file mode 100644 index 0000000..451f163 Binary files /dev/null and b/images/Middleware/Spring核心组件.png differ diff --git a/images/Middleware/Unpooled缓冲区.png b/images/Middleware/Unpooled缓冲区.png new file mode 100644 index 0000000..08ba702 Binary files /dev/null and b/images/Middleware/Unpooled缓冲区.png differ diff --git a/images/Middleware/Watcher监听机制的工作原理.png b/images/Middleware/Watcher监听机制的工作原理.png new file mode 100644 index 0000000..e0cc1cd Binary files /dev/null and b/images/Middleware/Watcher监听机制的工作原理.png differ diff --git a/images/Middleware/ZipList压缩列表.png b/images/Middleware/ZipList压缩列表.png new file mode 100644 index 0000000..7084537 Binary files /dev/null and b/images/Middleware/ZipList压缩列表.png differ diff --git a/images/Middleware/Zookeeper-ZXID.png b/images/Middleware/Zookeeper-ZXID.png new file mode 100644 index 0000000..b5f61ab Binary files /dev/null and b/images/Middleware/Zookeeper-ZXID.png differ diff --git a/images/Middleware/Zuul.png b/images/Middleware/Zuul.png new file mode 100644 index 0000000..15b2c35 Binary files /dev/null and b/images/Middleware/Zuul.png differ diff --git a/images/Middleware/Zuul介绍.png b/images/Middleware/Zuul介绍.png new file mode 100644 index 0000000..b1c4cea Binary files /dev/null and b/images/Middleware/Zuul介绍.png differ diff --git a/images/Middleware/dashboard-monitoring.png b/images/Middleware/dashboard-monitoring.png new file mode 100644 index 0000000..f1ab9b9 Binary files /dev/null and b/images/Middleware/dashboard-monitoring.png differ diff --git a/images/Middleware/echo_service.png b/images/Middleware/echo_service.png new file mode 100644 index 0000000..dc43903 Binary files /dev/null and b/images/Middleware/echo_service.png differ diff --git a/images/Middleware/nacos_config_er.jpeg b/images/Middleware/nacos_config_er.jpeg new file mode 100644 index 0000000..aaadc8e Binary files /dev/null and b/images/Middleware/nacos_config_er.jpeg differ diff --git a/images/Middleware/nacos_data_model.jpeg b/images/Middleware/nacos_data_model.jpeg new file mode 100644 index 0000000..c8e3510 Binary files /dev/null and b/images/Middleware/nacos_data_model.jpeg differ diff --git a/images/Middleware/nacos_sdk_class_relation.jpeg b/images/Middleware/nacos_sdk_class_relation.jpeg new file mode 100644 index 0000000..3040717 Binary files /dev/null and b/images/Middleware/nacos_sdk_class_relation.jpeg differ diff --git a/images/Middleware/quicklist.png b/images/Middleware/quicklist.png new file mode 100644 index 0000000..f19735d Binary files /dev/null and b/images/Middleware/quicklist.png differ diff --git a/images/Middleware/rabbitmq-work-01.png b/images/Middleware/rabbitmq-work-01.png new file mode 100644 index 0000000..6bd9597 Binary files /dev/null and b/images/Middleware/rabbitmq-work-01.png differ diff --git a/images/Middleware/rabbitmq-work-02.png b/images/Middleware/rabbitmq-work-02.png new file mode 100644 index 0000000..d19e86d Binary files /dev/null and b/images/Middleware/rabbitmq-work-02.png differ diff --git a/images/Middleware/rabbitmq-work-03-1.png b/images/Middleware/rabbitmq-work-03-1.png new file mode 100644 index 0000000..5a0c1cc Binary files /dev/null and b/images/Middleware/rabbitmq-work-03-1.png differ diff --git a/images/Middleware/rabbitmq-work-04-1.png b/images/Middleware/rabbitmq-work-04-1.png new file mode 100644 index 0000000..650084c Binary files /dev/null and b/images/Middleware/rabbitmq-work-04-1.png differ diff --git a/images/Middleware/rabbitmq-work-05-1.png b/images/Middleware/rabbitmq-work-05-1.png new file mode 100644 index 0000000..983233d Binary files /dev/null and b/images/Middleware/rabbitmq-work-05-1.png differ diff --git a/images/Middleware/skipList跳跃表.png b/images/Middleware/skipList跳跃表.png new file mode 100644 index 0000000..0add25e Binary files /dev/null and b/images/Middleware/skipList跳跃表.png differ diff --git a/images/Middleware/slice操作实现零拷贝.png b/images/Middleware/slice操作实现零拷贝.png new file mode 100644 index 0000000..5945f88 Binary files /dev/null and b/images/Middleware/slice操作实现零拷贝.png differ diff --git a/images/Middleware/zuul过滤器的生命周期.png b/images/Middleware/zuul过滤器的生命周期.png new file mode 100644 index 0000000..1502b7a Binary files /dev/null and b/images/Middleware/zuul过滤器的生命周期.png differ diff --git a/images/Middleware/zuul限流参数.png b/images/Middleware/zuul限流参数.png new file mode 100644 index 0000000..78c2df3 Binary files /dev/null and b/images/Middleware/zuul限流参数.png differ diff --git a/images/Middleware/七层负载均衡.png b/images/Middleware/七层负载均衡.png new file mode 100644 index 0000000..ee6fbdb Binary files /dev/null and b/images/Middleware/七层负载均衡.png differ diff --git a/images/Middleware/传统IO的流程.png b/images/Middleware/传统IO的流程.png new file mode 100644 index 0000000..bb61af0 Binary files /dev/null and b/images/Middleware/传统IO的流程.png differ diff --git a/images/Middleware/传统IO的流程Copy.png b/images/Middleware/传统IO的流程Copy.png new file mode 100644 index 0000000..8615b92 Binary files /dev/null and b/images/Middleware/传统IO的流程Copy.png differ diff --git a/images/Middleware/单线程reactor线程模型.png b/images/Middleware/单线程reactor线程模型.png new file mode 100644 index 0000000..f77beed Binary files /dev/null and b/images/Middleware/单线程reactor线程模型.png differ diff --git a/images/Middleware/四层负载均衡.png b/images/Middleware/四层负载均衡.png new file mode 100644 index 0000000..294023d Binary files /dev/null and b/images/Middleware/四层负载均衡.png differ diff --git a/images/Middleware/多线程reactor线程模型.png b/images/Middleware/多线程reactor线程模型.png new file mode 100644 index 0000000..37fed0c Binary files /dev/null and b/images/Middleware/多线程reactor线程模型.png differ diff --git a/images/Middleware/广播模式.jpg b/images/Middleware/广播模式.jpg new file mode 100644 index 0000000..8f12fe9 Binary files /dev/null and b/images/Middleware/广播模式.jpg differ diff --git a/images/Middleware/服务器启动的Leader选举.png b/images/Middleware/服务器启动的Leader选举.png new file mode 100644 index 0000000..8b340ac Binary files /dev/null and b/images/Middleware/服务器启动的Leader选举.png differ diff --git a/images/Middleware/服务器运行期间的Leader选举.png b/images/Middleware/服务器运行期间的Leader选举.png new file mode 100644 index 0000000..54f4772 Binary files /dev/null and b/images/Middleware/服务器运行期间的Leader选举.png differ diff --git a/images/Middleware/混合型reactor线程模型.png b/images/Middleware/混合型reactor线程模型.png new file mode 100644 index 0000000..db4c675 Binary files /dev/null and b/images/Middleware/混合型reactor线程模型.png differ diff --git a/images/Middleware/用户的普通Http请求执行顺序.jpg b/images/Middleware/用户的普通Http请求执行顺序.jpg new file mode 100644 index 0000000..31359b8 Binary files /dev/null and b/images/Middleware/用户的普通Http请求执行顺序.jpg differ diff --git a/images/Middleware/过滤器、拦截器添加后的执行顺序.jpg b/images/Middleware/过滤器、拦截器添加后的执行顺序.jpg new file mode 100644 index 0000000..228a93e Binary files /dev/null and b/images/Middleware/过滤器、拦截器添加后的执行顺序.jpg differ diff --git a/images/Middleware/集群模式.jpg b/images/Middleware/集群模式.jpg new file mode 100644 index 0000000..7c1ca3e Binary files /dev/null and b/images/Middleware/集群模式.jpg differ diff --git a/images/Middleware/零拷贝CPU.png b/images/Middleware/零拷贝CPU.png new file mode 100644 index 0000000..8c6a883 Binary files /dev/null and b/images/Middleware/零拷贝CPU.png differ diff --git a/images/Middleware/零拷贝整体流程图.png b/images/Middleware/零拷贝整体流程图.png new file mode 100644 index 0000000..7bf623c Binary files /dev/null and b/images/Middleware/零拷贝整体流程图.png differ diff --git a/images/OS/32位操作系统中的用户空间分布.png b/images/OS/32位操作系统中的用户空间分布.png new file mode 100644 index 0000000..fd26c30 Binary files /dev/null and b/images/OS/32位操作系统中的用户空间分布.png differ diff --git a/images/OS/32位的系统创建线程.png b/images/OS/32位的系统创建线程.png new file mode 100644 index 0000000..79ddec6 Binary files /dev/null and b/images/OS/32位的系统创建线程.png differ diff --git a/images/OS/64位的系统创建线程-不限制.png b/images/OS/64位的系统创建线程-不限制.png new file mode 100644 index 0000000..44e59b4 Binary files /dev/null and b/images/OS/64位的系统创建线程-不限制.png differ diff --git a/images/OS/64位的系统创建线程-不限制thread-max.png b/images/OS/64位的系统创建线程-不限制thread-max.png new file mode 100644 index 0000000..8606ff9 Binary files /dev/null and b/images/OS/64位的系统创建线程-不限制thread-max.png differ diff --git a/images/OS/64位的系统创建线程-大栈空间.png b/images/OS/64位的系统创建线程-大栈空间.png new file mode 100644 index 0000000..5dcc33d Binary files /dev/null and b/images/OS/64位的系统创建线程-大栈空间.png differ diff --git a/images/OS/64位的系统创建线程.png b/images/OS/64位的系统创建线程.png new file mode 100644 index 0000000..e5d582e Binary files /dev/null and b/images/OS/64位的系统创建线程.png differ diff --git a/images/OS/64位页表四级目录.png b/images/OS/64位页表四级目录.png new file mode 100644 index 0000000..85b2826 Binary files /dev/null and b/images/OS/64位页表四级目录.png differ diff --git a/images/OS/ACK报文.jpg b/images/OS/ACK报文.jpg new file mode 100644 index 0000000..8816ac1 Binary files /dev/null and b/images/OS/ACK报文.jpg differ diff --git a/images/OS/ETag与If-None-Match.jpg b/images/OS/ETag与If-None-Match.jpg new file mode 100644 index 0000000..41fcb4e Binary files /dev/null and b/images/OS/ETag与If-None-Match.jpg differ diff --git a/images/OS/Etag.png b/images/OS/Etag.png new file mode 100644 index 0000000..7d73160 Binary files /dev/null and b/images/OS/Etag.png differ diff --git a/images/OS/HTTP缓存-Cache-Control-第一步.png b/images/OS/HTTP缓存-Cache-Control-第一步.png new file mode 100644 index 0000000..ac18dca Binary files /dev/null and b/images/OS/HTTP缓存-Cache-Control-第一步.png differ diff --git a/images/OS/HTTP缓存-Cache-Control-第三步.png b/images/OS/HTTP缓存-Cache-Control-第三步.png new file mode 100644 index 0000000..79c09d9 Binary files /dev/null and b/images/OS/HTTP缓存-Cache-Control-第三步.png differ diff --git a/images/OS/HTTP缓存-Cache-Control-第二步.png b/images/OS/HTTP缓存-Cache-Control-第二步.png new file mode 100644 index 0000000..066a115 Binary files /dev/null and b/images/OS/HTTP缓存-Cache-Control-第二步.png differ diff --git a/images/OS/HTTP缓存-Cache-Control.png b/images/OS/HTTP缓存-Cache-Control.png new file mode 100644 index 0000000..d9bba6f Binary files /dev/null and b/images/OS/HTTP缓存-Cache-Control.png differ diff --git a/images/OS/HTTP缓存.jpg b/images/OS/HTTP缓存.jpg new file mode 100644 index 0000000..ded3148 Binary files /dev/null and b/images/OS/HTTP缓存.jpg differ diff --git a/images/OS/HTTP请求流程.jpg b/images/OS/HTTP请求流程.jpg new file mode 100644 index 0000000..6cded64 Binary files /dev/null and b/images/OS/HTTP请求流程.jpg differ diff --git a/images/OS/If-Modified-Since.png b/images/OS/If-Modified-Since.png new file mode 100644 index 0000000..7ed9a1a Binary files /dev/null and b/images/OS/If-Modified-Since.png differ diff --git a/images/OS/If-None-Match.png b/images/OS/If-None-Match.png new file mode 100644 index 0000000..fde5e65 Binary files /dev/null and b/images/OS/If-None-Match.png differ diff --git a/images/OS/IntelX86逻辑地址解析过程.png b/images/OS/IntelX86逻辑地址解析过程.png new file mode 100644 index 0000000..b3cdbee Binary files /dev/null and b/images/OS/IntelX86逻辑地址解析过程.png differ diff --git a/images/OS/Last-Modified.png b/images/OS/Last-Modified.png new file mode 100644 index 0000000..c2ae11d Binary files /dev/null and b/images/OS/Last-Modified.png differ diff --git a/images/OS/Last-Modified与If-Modified-Since.jpg b/images/OS/Last-Modified与If-Modified-Since.jpg new file mode 100644 index 0000000..09030c0 Binary files /dev/null and b/images/OS/Last-Modified与If-Modified-Since.jpg differ diff --git a/images/OS/Linux虚拟地址空间分布.png b/images/OS/Linux虚拟地址空间分布.png new file mode 100644 index 0000000..8c2714b Binary files /dev/null and b/images/OS/Linux虚拟地址空间分布.png differ diff --git a/images/OS/OSI参考模型.png b/images/OS/OSI参考模型.png new file mode 100644 index 0000000..e98e64e Binary files /dev/null and b/images/OS/OSI参考模型.png differ diff --git a/images/OS/SYN+ACK报文.jpg b/images/OS/SYN+ACK报文.jpg new file mode 100644 index 0000000..5bbd094 Binary files /dev/null and b/images/OS/SYN+ACK报文.jpg differ diff --git a/images/OS/SYN报文.jpg b/images/OS/SYN报文.jpg new file mode 100644 index 0000000..0ef0b24 Binary files /dev/null and b/images/OS/SYN报文.jpg differ diff --git a/images/OS/SYN队列与Accpet队列.jpg b/images/OS/SYN队列与Accpet队列.jpg new file mode 100644 index 0000000..e4ee7a4 Binary files /dev/null and b/images/OS/SYN队列与Accpet队列.jpg differ diff --git a/images/OS/TCPIP五层模型.png b/images/OS/TCPIP五层模型.png new file mode 100644 index 0000000..14497c8 Binary files /dev/null and b/images/OS/TCPIP五层模型.png differ diff --git a/images/OS/TCP协议.jpg b/images/OS/TCP协议.jpg new file mode 100644 index 0000000..cdc6e6d Binary files /dev/null and b/images/OS/TCP协议.jpg differ diff --git a/images/OS/TCP头部格式.jpg b/images/OS/TCP头部格式.jpg new file mode 100644 index 0000000..d6e2155 Binary files /dev/null and b/images/OS/TCP头部格式.jpg differ diff --git a/images/OS/TCP客户端-SYN_SEND流程.png b/images/OS/TCP客户端-SYN_SEND流程.png new file mode 100644 index 0000000..3f93fa9 Binary files /dev/null and b/images/OS/TCP客户端-SYN_SEND流程.png differ diff --git a/images/OS/TCP数据传输的优化策略.jpg b/images/OS/TCP数据传输的优化策略.jpg new file mode 100644 index 0000000..88a3b2a Binary files /dev/null and b/images/OS/TCP数据传输的优化策略.jpg differ diff --git a/images/OS/TCP服务端-SYN_RECV流程.png b/images/OS/TCP服务端-SYN_RECV流程.png new file mode 100644 index 0000000..646465a Binary files /dev/null and b/images/OS/TCP服务端-SYN_RECV流程.png differ diff --git a/images/OS/TCP状态.png b/images/OS/TCP状态.png new file mode 100644 index 0000000..969e52b Binary files /dev/null and b/images/OS/TCP状态.png differ diff --git a/images/OS/TCP连接状态查看.jpg b/images/OS/TCP连接状态查看.jpg new file mode 100644 index 0000000..e73cd9e Binary files /dev/null and b/images/OS/TCP连接状态查看.jpg differ diff --git a/images/OS/TCP连接的过程和状态变化.jpg b/images/OS/TCP连接的过程和状态变化.jpg new file mode 100644 index 0000000..9cd8390 Binary files /dev/null and b/images/OS/TCP连接的过程和状态变化.jpg differ diff --git a/images/OS/top-H线程数.png b/images/OS/top-H线程数.png new file mode 100644 index 0000000..ddda0f4 Binary files /dev/null and b/images/OS/top-H线程数.png differ diff --git a/images/OS/三次握手.jpg b/images/OS/三次握手.jpg new file mode 100644 index 0000000..d753258 Binary files /dev/null and b/images/OS/三次握手.jpg differ diff --git a/images/OS/三次握手避免历史连接.jpg b/images/OS/三次握手避免历史连接.jpg new file mode 100644 index 0000000..ab6d5ca Binary files /dev/null and b/images/OS/三次握手避免历史连接.jpg differ diff --git a/images/OS/二级分页.png b/images/OS/二级分页.png new file mode 100644 index 0000000..5a97507 Binary files /dev/null and b/images/OS/二级分页.png differ diff --git a/images/OS/优化TCP三次握手的策略.jpg b/images/OS/优化TCP三次握手的策略.jpg new file mode 100644 index 0000000..d03f31e Binary files /dev/null and b/images/OS/优化TCP三次握手的策略.jpg differ diff --git a/images/OS/优化TCP四次挥手的策略.jpg b/images/OS/优化TCP四次挥手的策略.jpg new file mode 100644 index 0000000..8827170 Binary files /dev/null and b/images/OS/优化TCP四次挥手的策略.jpg differ diff --git a/images/OS/内存分段-寻址的方式.png b/images/OS/内存分段-寻址的方式.png new file mode 100644 index 0000000..47833ad Binary files /dev/null and b/images/OS/内存分段-寻址的方式.png differ diff --git a/images/OS/内存分段-虚拟地址与物理地址.png b/images/OS/内存分段-虚拟地址与物理地址.png new file mode 100644 index 0000000..a2e7fc0 Binary files /dev/null and b/images/OS/内存分段-虚拟地址与物理地址.png differ diff --git a/images/OS/内存分页寻址.png b/images/OS/内存分页寻址.png new file mode 100644 index 0000000..aa231af Binary files /dev/null and b/images/OS/内存分页寻址.png differ diff --git a/images/OS/内存映射.png b/images/OS/内存映射.png new file mode 100644 index 0000000..b4deea9 Binary files /dev/null and b/images/OS/内存映射.png differ diff --git a/images/OS/创建线程时默认分配的栈空间大小.jpg b/images/OS/创建线程时默认分配的栈空间大小.jpg new file mode 100644 index 0000000..694ecfb Binary files /dev/null and b/images/OS/创建线程时默认分配的栈空间大小.jpg differ diff --git a/images/OS/单片机内存.png b/images/OS/单片机内存.png new file mode 100644 index 0000000..b72ac9c Binary files /dev/null and b/images/OS/单片机内存.png differ diff --git a/images/OS/同步双方初始序列号.jpg b/images/OS/同步双方初始序列号.jpg new file mode 100644 index 0000000..e1dbd01 Binary files /dev/null and b/images/OS/同步双方初始序列号.jpg differ diff --git a/images/OS/四次挥手.jpg b/images/OS/四次挥手.jpg new file mode 100644 index 0000000..f0bcb36 Binary files /dev/null and b/images/OS/四次挥手.jpg differ diff --git a/images/OS/基于TCP协议的客户端和服务器工作.jpg b/images/OS/基于TCP协议的客户端和服务器工作.jpg new file mode 100644 index 0000000..6d173e6 Binary files /dev/null and b/images/OS/基于TCP协议的客户端和服务器工作.jpg differ diff --git a/images/OS/客户端调用close过程.jpg b/images/OS/客户端调用close过程.jpg new file mode 100644 index 0000000..c954101 Binary files /dev/null and b/images/OS/客户端调用close过程.jpg differ diff --git a/images/OS/客户端连接服务端.jpg b/images/OS/客户端连接服务端.jpg new file mode 100644 index 0000000..f5fc291 Binary files /dev/null and b/images/OS/客户端连接服务端.jpg differ diff --git a/images/OS/换入换出.png b/images/OS/换入换出.png new file mode 100644 index 0000000..a5f0552 Binary files /dev/null and b/images/OS/换入换出.png differ diff --git a/images/OS/服务端接收到SYN段后_发送SYN_ACK处理流程.jpg b/images/OS/服务端接收到SYN段后_发送SYN_ACK处理流程.jpg new file mode 100644 index 0000000..1ea909a Binary files /dev/null and b/images/OS/服务端接收到SYN段后_发送SYN_ACK处理流程.jpg differ diff --git a/images/OS/段页式地址空间.png b/images/OS/段页式地址空间.png new file mode 100644 index 0000000..5db4ada Binary files /dev/null and b/images/OS/段页式地址空间.png differ diff --git a/images/OS/段页式管理中的段表、页表与内存的关系.png b/images/OS/段页式管理中的段表、页表与内存的关系.png new file mode 100644 index 0000000..f78d09f Binary files /dev/null and b/images/OS/段页式管理中的段表、页表与内存的关系.png differ diff --git a/images/OS/每个进程的内核空间都是一致的.png b/images/OS/每个进程的内核空间都是一致的.png new file mode 100644 index 0000000..53c0c79 Binary files /dev/null and b/images/OS/每个进程的内核空间都是一致的.png differ diff --git a/images/OS/虚拟地址寻址.png b/images/OS/虚拟地址寻址.png new file mode 100644 index 0000000..8e1c724 Binary files /dev/null and b/images/OS/虚拟地址寻址.png differ diff --git a/images/OS/进程的中间层.png b/images/OS/进程的中间层.png new file mode 100644 index 0000000..98f7644 Binary files /dev/null and b/images/OS/进程的中间层.png differ diff --git a/images/OS/避免资源浪费.jpg b/images/OS/避免资源浪费.jpg new file mode 100644 index 0000000..107041a Binary files /dev/null and b/images/OS/避免资源浪费.jpg differ diff --git a/images/OS/页表项地址转换.png b/images/OS/页表项地址转换.png new file mode 100644 index 0000000..ff750c4 Binary files /dev/null and b/images/OS/页表项地址转换.png differ diff --git a/images/Others/0BA8D167.gif b/images/Others/0BA8D167.gif new file mode 100644 index 0000000..53da6ed Binary files /dev/null and b/images/Others/0BA8D167.gif differ diff --git a/images/Others/201705221495449669133450.jpg b/images/Others/201705221495449669133450.jpg new file mode 100644 index 0000000..567d4b6 Binary files /dev/null and b/images/Others/201705221495449669133450.jpg differ diff --git a/images/Others/201705221495449833127277.png b/images/Others/201705221495449833127277.png new file mode 100644 index 0000000..2bc05c4 Binary files /dev/null and b/images/Others/201705221495449833127277.png differ diff --git a/images/Others/20181030154227362.png b/images/Others/20181030154227362.png new file mode 100644 index 0000000..f3eecf0 Binary files /dev/null and b/images/Others/20181030154227362.png differ diff --git a/images/Others/20181030154728569.png b/images/Others/20181030154728569.png new file mode 100644 index 0000000..a7cf876 Binary files /dev/null and b/images/Others/20181030154728569.png differ diff --git a/images/Others/2018103015481187.png b/images/Others/2018103015481187.png new file mode 100644 index 0000000..96bd7dd Binary files /dev/null and b/images/Others/2018103015481187.png differ diff --git a/images/Others/20181030155133360.png b/images/Others/20181030155133360.png new file mode 100644 index 0000000..de48c6a Binary files /dev/null and b/images/Others/20181030155133360.png differ diff --git a/images/Others/20181030155413727.png b/images/Others/20181030155413727.png new file mode 100644 index 0000000..b8c764b Binary files /dev/null and b/images/Others/20181030155413727.png differ diff --git a/images/Others/20181030155545483.png b/images/Others/20181030155545483.png new file mode 100644 index 0000000..061d60c Binary files /dev/null and b/images/Others/20181030155545483.png differ diff --git a/images/Others/20181030155612301.png b/images/Others/20181030155612301.png new file mode 100644 index 0000000..568bb99 Binary files /dev/null and b/images/Others/20181030155612301.png differ diff --git a/images/Others/2018103015572874.png b/images/Others/2018103015572874.png new file mode 100644 index 0000000..f0e7101 Binary files /dev/null and b/images/Others/2018103015572874.png differ diff --git a/images/Others/2018103015593523.png b/images/Others/2018103015593523.png new file mode 100644 index 0000000..1cc155b Binary files /dev/null and b/images/Others/2018103015593523.png differ diff --git a/images/Others/20181030160351154.png b/images/Others/20181030160351154.png new file mode 100644 index 0000000..faf0349 Binary files /dev/null and b/images/Others/20181030160351154.png differ diff --git a/images/Others/20181030160533499.png b/images/Others/20181030160533499.png new file mode 100644 index 0000000..b361fe8 Binary files /dev/null and b/images/Others/20181030160533499.png differ diff --git a/images/Others/20181030160604417.png b/images/Others/20181030160604417.png new file mode 100644 index 0000000..bbac35e Binary files /dev/null and b/images/Others/20181030160604417.png differ diff --git a/images/Others/20181030161017921.png b/images/Others/20181030161017921.png new file mode 100644 index 0000000..0ac9b40 Binary files /dev/null and b/images/Others/20181030161017921.png differ diff --git a/images/Others/20181030161142910.png b/images/Others/20181030161142910.png new file mode 100644 index 0000000..216e013 Binary files /dev/null and b/images/Others/20181030161142910.png differ diff --git a/images/Others/20181030161254216.png b/images/Others/20181030161254216.png new file mode 100644 index 0000000..7978134 Binary files /dev/null and b/images/Others/20181030161254216.png differ diff --git a/images/Others/20181030161922248.png b/images/Others/20181030161922248.png new file mode 100644 index 0000000..ebe3a70 Binary files /dev/null and b/images/Others/20181030161922248.png differ diff --git a/images/Others/20181030162041400.png b/images/Others/20181030162041400.png new file mode 100644 index 0000000..67ba17c Binary files /dev/null and b/images/Others/20181030162041400.png differ diff --git a/images/Others/20181030165223996.png b/images/Others/20181030165223996.png new file mode 100644 index 0000000..e2bba81 Binary files /dev/null and b/images/Others/20181030165223996.png differ diff --git a/images/Others/20181030165549295.png b/images/Others/20181030165549295.png new file mode 100644 index 0000000..4c5ab2d Binary files /dev/null and b/images/Others/20181030165549295.png differ diff --git a/images/Others/20181030165842703.png b/images/Others/20181030165842703.png new file mode 100644 index 0000000..6bd00f4 Binary files /dev/null and b/images/Others/20181030165842703.png differ diff --git a/images/Others/20181030170008544.png b/images/Others/20181030170008544.png new file mode 100644 index 0000000..9f3acaa Binary files /dev/null and b/images/Others/20181030170008544.png differ diff --git a/images/Others/20181031135416445.png b/images/Others/20181031135416445.png new file mode 100644 index 0000000..0ec6bb7 Binary files /dev/null and b/images/Others/20181031135416445.png differ diff --git a/images/Others/20181031135509461.png b/images/Others/20181031135509461.png new file mode 100644 index 0000000..5bce79b Binary files /dev/null and b/images/Others/20181031135509461.png differ diff --git a/images/Others/20181031135540101.png b/images/Others/20181031135540101.png new file mode 100644 index 0000000..9119576 Binary files /dev/null and b/images/Others/20181031135540101.png differ diff --git a/images/Others/20190110100508868.png b/images/Others/20190110100508868.png new file mode 100644 index 0000000..11648a8 Binary files /dev/null and b/images/Others/20190110100508868.png differ diff --git a/images/Others/20190110100622631.png b/images/Others/20190110100622631.png new file mode 100644 index 0000000..43b34d6 Binary files /dev/null and b/images/Others/20190110100622631.png differ diff --git a/images/Others/20190110100734530.png b/images/Others/20190110100734530.png new file mode 100644 index 0000000..467aff4 Binary files /dev/null and b/images/Others/20190110100734530.png differ diff --git a/images/Others/2019011010115881.png b/images/Others/2019011010115881.png new file mode 100644 index 0000000..572deea Binary files /dev/null and b/images/Others/2019011010115881.png differ diff --git a/images/Others/2019011010123767.png b/images/Others/2019011010123767.png new file mode 100644 index 0000000..4b3f60d Binary files /dev/null and b/images/Others/2019011010123767.png differ diff --git a/images/Others/20190110101907801.png b/images/Others/20190110101907801.png new file mode 100644 index 0000000..89a089f Binary files /dev/null and b/images/Others/20190110101907801.png differ diff --git a/images/Others/20190316140152621.png b/images/Others/20190316140152621.png new file mode 100644 index 0000000..07c1a80 Binary files /dev/null and b/images/Others/20190316140152621.png differ diff --git a/images/Others/20190316140238852.png b/images/Others/20190316140238852.png new file mode 100644 index 0000000..bbbbd09 Binary files /dev/null and b/images/Others/20190316140238852.png differ diff --git a/images/Others/20190316140505814.png b/images/Others/20190316140505814.png new file mode 100644 index 0000000..c2dc5f7 Binary files /dev/null and b/images/Others/20190316140505814.png differ diff --git a/images/Others/20190316140617590.png b/images/Others/20190316140617590.png new file mode 100644 index 0000000..f2eaa04 Binary files /dev/null and b/images/Others/20190316140617590.png differ diff --git a/images/Others/417876-20171119103804546-819180423.png b/images/Others/417876-20171119103804546-819180423.png new file mode 100644 index 0000000..c153c36 Binary files /dev/null and b/images/Others/417876-20171119103804546-819180423.png differ diff --git a/images/Others/417876-20171119103943062-564729605.png b/images/Others/417876-20171119103943062-564729605.png new file mode 100644 index 0000000..444548b Binary files /dev/null and b/images/Others/417876-20171119103943062-564729605.png differ diff --git a/images/Others/417876-20171119150102656-1550889934.png b/images/Others/417876-20171119150102656-1550889934.png new file mode 100644 index 0000000..c6da80b Binary files /dev/null and b/images/Others/417876-20171119150102656-1550889934.png differ diff --git a/images/Others/417876-20171128105146972-1080966086.png b/images/Others/417876-20171128105146972-1080966086.png new file mode 100644 index 0000000..390ec0c Binary files /dev/null and b/images/Others/417876-20171128105146972-1080966086.png differ diff --git a/images/Others/417876-20171129081632175-614878351.png b/images/Others/417876-20171129081632175-614878351.png new file mode 100644 index 0000000..5629346 Binary files /dev/null and b/images/Others/417876-20171129081632175-614878351.png differ diff --git a/images/Others/772743-20181027200424736-854569575.png b/images/Others/772743-20181027200424736-854569575.png new file mode 100644 index 0000000..ee4d236 Binary files /dev/null and b/images/Others/772743-20181027200424736-854569575.png differ diff --git a/images/Others/856154-20170905111655647-1134637623.png b/images/Others/856154-20170905111655647-1134637623.png new file mode 100644 index 0000000..3efbb6d Binary files /dev/null and b/images/Others/856154-20170905111655647-1134637623.png differ diff --git a/images/Others/856154-20170905112617351-1554043487.png b/images/Others/856154-20170905112617351-1554043487.png new file mode 100644 index 0000000..e795842 Binary files /dev/null and b/images/Others/856154-20170905112617351-1554043487.png differ diff --git a/images/Others/856154-20170905124338444-556465721.png b/images/Others/856154-20170905124338444-556465721.png new file mode 100644 index 0000000..0e39e8b Binary files /dev/null and b/images/Others/856154-20170905124338444-556465721.png differ diff --git a/images/Others/856154-20170905134011101-1824595229.png b/images/Others/856154-20170905134011101-1824595229.png new file mode 100644 index 0000000..60e3d09 Binary files /dev/null and b/images/Others/856154-20170905134011101-1824595229.png differ diff --git a/images/Others/856154-20170905134837851-1615718043.png b/images/Others/856154-20170905134837851-1615718043.png new file mode 100644 index 0000000..b12d940 Binary files /dev/null and b/images/Others/856154-20170905134837851-1615718043.png differ diff --git a/images/Others/856154-20170905152523304-803289488.png b/images/Others/856154-20170905152523304-803289488.png new file mode 100644 index 0000000..0c5f1ef Binary files /dev/null and b/images/Others/856154-20170905152523304-803289488.png differ diff --git a/images/Others/856154-20170905154209179-9123997.png b/images/Others/856154-20170905154209179-9123997.png new file mode 100644 index 0000000..6e157de Binary files /dev/null and b/images/Others/856154-20170905154209179-9123997.png differ diff --git a/images/Others/856154-20170905154425772-770303651.png b/images/Others/856154-20170905154425772-770303651.png new file mode 100644 index 0000000..7f0c255 Binary files /dev/null and b/images/Others/856154-20170905154425772-770303651.png differ diff --git a/images/Others/856154-20170905154724866-160919363.png b/images/Others/856154-20170905154724866-160919363.png new file mode 100644 index 0000000..94ca8f3 Binary files /dev/null and b/images/Others/856154-20170905154724866-160919363.png differ diff --git a/images/Others/856154-20170905155339491-1166069157.png b/images/Others/856154-20170905155339491-1166069157.png new file mode 100644 index 0000000..59a2d6d Binary files /dev/null and b/images/Others/856154-20170905155339491-1166069157.png differ diff --git a/images/Others/856154-20170905160057038-750351531.png b/images/Others/856154-20170905160057038-750351531.png new file mode 100644 index 0000000..22da02e Binary files /dev/null and b/images/Others/856154-20170905160057038-750351531.png differ diff --git a/images/Others/856154-20170905160433710-2004658473.png b/images/Others/856154-20170905160433710-2004658473.png new file mode 100644 index 0000000..975dade Binary files /dev/null and b/images/Others/856154-20170905160433710-2004658473.png differ diff --git a/images/Others/856154-20170905160515538-1647769062.png b/images/Others/856154-20170905160515538-1647769062.png new file mode 100644 index 0000000..45970a4 Binary files /dev/null and b/images/Others/856154-20170905160515538-1647769062.png differ diff --git a/images/Others/856154-20170905160826444-1625048711.png b/images/Others/856154-20170905160826444-1625048711.png new file mode 100644 index 0000000..0eaffd1 Binary files /dev/null and b/images/Others/856154-20170905160826444-1625048711.png differ diff --git a/images/Others/856154-20170905161614694-93470669.png b/images/Others/856154-20170905161614694-93470669.png new file mode 100644 index 0000000..7640ff0 Binary files /dev/null and b/images/Others/856154-20170905161614694-93470669.png differ diff --git a/images/Others/856154-20170905162404288-824548249.png b/images/Others/856154-20170905162404288-824548249.png new file mode 100644 index 0000000..a1746b7 Binary files /dev/null and b/images/Others/856154-20170905162404288-824548249.png differ diff --git a/images/Others/856154-20170905163730929-1374653206.png b/images/Others/856154-20170905163730929-1374653206.png new file mode 100644 index 0000000..cd02027 Binary files /dev/null and b/images/Others/856154-20170905163730929-1374653206.png differ diff --git a/images/Others/856154-20170905165253944-1162138475.png b/images/Others/856154-20170905165253944-1162138475.png new file mode 100644 index 0000000..e51d6e3 Binary files /dev/null and b/images/Others/856154-20170905165253944-1162138475.png differ diff --git a/images/Others/856154-20170905170655163-1805982960.png b/images/Others/856154-20170905170655163-1805982960.png new file mode 100644 index 0000000..68e422d Binary files /dev/null and b/images/Others/856154-20170905170655163-1805982960.png differ diff --git a/images/Others/856154-20170905170947257-1667065155.png b/images/Others/856154-20170905170947257-1667065155.png new file mode 100644 index 0000000..e8cd5df Binary files /dev/null and b/images/Others/856154-20170905170947257-1667065155.png differ diff --git a/images/Others/856154-20170905200131851-150143203.png b/images/Others/856154-20170905200131851-150143203.png new file mode 100644 index 0000000..06a7f31 Binary files /dev/null and b/images/Others/856154-20170905200131851-150143203.png differ diff --git a/images/Others/856154-20170905200305147-527881101.png b/images/Others/856154-20170905200305147-527881101.png new file mode 100644 index 0000000..e9feae4 Binary files /dev/null and b/images/Others/856154-20170905200305147-527881101.png differ diff --git a/images/Others/856154-20170905200726069-688175303.png b/images/Others/856154-20170905200726069-688175303.png new file mode 100644 index 0000000..91de736 Binary files /dev/null and b/images/Others/856154-20170905200726069-688175303.png differ diff --git a/images/Others/856154-20170905204329757-1196950664.png b/images/Others/856154-20170905204329757-1196950664.png new file mode 100644 index 0000000..f726c48 Binary files /dev/null and b/images/Others/856154-20170905204329757-1196950664.png differ diff --git a/images/Others/856154-20170905205012663-56609868.png b/images/Others/856154-20170905205012663-56609868.png new file mode 100644 index 0000000..812dedd Binary files /dev/null and b/images/Others/856154-20170905205012663-56609868.png differ diff --git a/images/Others/856154-20170905211428554-1617570377.png b/images/Others/856154-20170905211428554-1617570377.png new file mode 100644 index 0000000..9fd93d0 Binary files /dev/null and b/images/Others/856154-20170905211428554-1617570377.png differ diff --git a/images/Others/856154-20170905212138101-113776159.png b/images/Others/856154-20170905212138101-113776159.png new file mode 100644 index 0000000..9c09367 Binary files /dev/null and b/images/Others/856154-20170905212138101-113776159.png differ diff --git a/images/Others/856154-20170905213656241-1998475384.png b/images/Others/856154-20170905213656241-1998475384.png new file mode 100644 index 0000000..635e7a3 Binary files /dev/null and b/images/Others/856154-20170905213656241-1998475384.png differ diff --git a/images/Others/AltovaDiffDog.png b/images/Others/AltovaDiffDog.png new file mode 100644 index 0000000..ac4b6da Binary files /dev/null and b/images/Others/AltovaDiffDog.png differ diff --git a/images/Others/AptDiff.png b/images/Others/AptDiff.png new file mode 100644 index 0000000..f44e866 Binary files /dev/null and b/images/Others/AptDiff.png differ diff --git a/images/Others/BeyondCompare.png b/images/Others/BeyondCompare.png new file mode 100644 index 0000000..f0e2841 Binary files /dev/null and b/images/Others/BeyondCompare.png differ diff --git a/images/Others/CamelCase.gif b/images/Others/CamelCase.gif new file mode 100644 index 0000000..a4d5c5c Binary files /dev/null and b/images/Others/CamelCase.gif differ diff --git a/images/Others/Carbon.png b/images/Others/Carbon.png new file mode 100644 index 0000000..9bef86e Binary files /dev/null and b/images/Others/Carbon.png differ diff --git a/images/Others/CodeCompare.png b/images/Others/CodeCompare.png new file mode 100644 index 0000000..f92d960 Binary files /dev/null and b/images/Others/CodeCompare.png differ diff --git a/images/Others/CodeGlance.png b/images/Others/CodeGlance.png new file mode 100644 index 0000000..9ae1640 Binary files /dev/null and b/images/Others/CodeGlance.png differ diff --git a/images/Others/Diffuse.png b/images/Others/Diffuse.png new file mode 100644 index 0000000..edb2756 Binary files /dev/null and b/images/Others/Diffuse.png differ diff --git a/images/Others/Excalidraw.png b/images/Others/Excalidraw.png new file mode 100644 index 0000000..53636dc Binary files /dev/null and b/images/Others/Excalidraw.png differ diff --git a/images/Others/Extra-Icons.jpg b/images/Others/Extra-Icons.jpg new file mode 100644 index 0000000..ff2cb5a Binary files /dev/null and b/images/Others/Extra-Icons.jpg differ diff --git a/images/Others/Fiddler-AutoResponder-1.png b/images/Others/Fiddler-AutoResponder-1.png new file mode 100644 index 0000000..39136f9 Binary files /dev/null and b/images/Others/Fiddler-AutoResponder-1.png differ diff --git a/images/Others/Fiddler-AutoResponder-2.png b/images/Others/Fiddler-AutoResponder-2.png new file mode 100644 index 0000000..2cb51fc Binary files /dev/null and b/images/Others/Fiddler-AutoResponder-2.png differ diff --git a/images/Others/Fiddler-CaptureTraffic-Debugger.png b/images/Others/Fiddler-CaptureTraffic-Debugger.png new file mode 100644 index 0000000..c20bcc3 Binary files /dev/null and b/images/Others/Fiddler-CaptureTraffic-Debugger.png differ diff --git a/images/Others/Fiddler-CaptureTraffic.png b/images/Others/Fiddler-CaptureTraffic.png new file mode 100644 index 0000000..f50521f Binary files /dev/null and b/images/Others/Fiddler-CaptureTraffic.png differ diff --git a/images/Others/Fiddler-Composer.png b/images/Others/Fiddler-Composer.png new file mode 100644 index 0000000..3aaa44d Binary files /dev/null and b/images/Others/Fiddler-Composer.png differ diff --git a/images/Others/Fiddler-Filters-Host.png b/images/Others/Fiddler-Filters-Host.png new file mode 100644 index 0000000..41e3d52 Binary files /dev/null and b/images/Others/Fiddler-Filters-Host.png differ diff --git a/images/Others/Fiddler-Filters-Zone.png b/images/Others/Fiddler-Filters-Zone.png new file mode 100644 index 0000000..1b736bf Binary files /dev/null and b/images/Others/Fiddler-Filters-Zone.png differ diff --git a/images/Others/Fiddler-Filters.png b/images/Others/Fiddler-Filters.png new file mode 100644 index 0000000..0d0c2a6 Binary files /dev/null and b/images/Others/Fiddler-Filters.png differ diff --git a/images/Others/Fiddler-HTTPS-1.png b/images/Others/Fiddler-HTTPS-1.png new file mode 100644 index 0000000..414db96 Binary files /dev/null and b/images/Others/Fiddler-HTTPS-1.png differ diff --git a/images/Others/Fiddler-HTTPS-2.png b/images/Others/Fiddler-HTTPS-2.png new file mode 100644 index 0000000..b064615 Binary files /dev/null and b/images/Others/Fiddler-HTTPS-2.png differ diff --git a/images/Others/Fiddler-HTTPS-3.png b/images/Others/Fiddler-HTTPS-3.png new file mode 100644 index 0000000..3801049 Binary files /dev/null and b/images/Others/Fiddler-HTTPS-3.png differ diff --git a/images/Others/Fiddler-Inspectors.png b/images/Others/Fiddler-Inspectors.png new file mode 100644 index 0000000..09d25e1 Binary files /dev/null and b/images/Others/Fiddler-Inspectors.png differ diff --git a/images/Others/Fiddler-Statistics.png b/images/Others/Fiddler-Statistics.png new file mode 100644 index 0000000..114d764 Binary files /dev/null and b/images/Others/Fiddler-Statistics.png differ diff --git a/images/Others/Fiddler-Timeline.png b/images/Others/Fiddler-Timeline.png new file mode 100644 index 0000000..8b149a2 Binary files /dev/null and b/images/Others/Fiddler-Timeline.png differ diff --git a/images/Others/Fiddler-断点命令.png b/images/Others/Fiddler-断点命令.png new file mode 100644 index 0000000..2e277ec Binary files /dev/null and b/images/Others/Fiddler-断点命令.png differ diff --git a/images/Others/Fiddler内置命令与断点.png b/images/Others/Fiddler内置命令与断点.png new file mode 100644 index 0000000..411843e Binary files /dev/null and b/images/Others/Fiddler内置命令与断点.png differ diff --git a/images/Others/Fiddler抓包简介.png b/images/Others/Fiddler抓包简介.png new file mode 100644 index 0000000..76869c8 Binary files /dev/null and b/images/Others/Fiddler抓包简介.png differ diff --git a/images/Others/Fiddler抓取移动端-1.png b/images/Others/Fiddler抓取移动端-1.png new file mode 100644 index 0000000..0023602 Binary files /dev/null and b/images/Others/Fiddler抓取移动端-1.png differ diff --git a/images/Others/Fiddler抓取移动端-10.png b/images/Others/Fiddler抓取移动端-10.png new file mode 100644 index 0000000..5135190 Binary files /dev/null and b/images/Others/Fiddler抓取移动端-10.png differ diff --git a/images/Others/Fiddler抓取移动端-11.png b/images/Others/Fiddler抓取移动端-11.png new file mode 100644 index 0000000..afcec37 Binary files /dev/null and b/images/Others/Fiddler抓取移动端-11.png differ diff --git a/images/Others/Fiddler抓取移动端-12.png b/images/Others/Fiddler抓取移动端-12.png new file mode 100644 index 0000000..e45bfa8 Binary files /dev/null and b/images/Others/Fiddler抓取移动端-12.png differ diff --git a/images/Others/Fiddler抓取移动端-13.png b/images/Others/Fiddler抓取移动端-13.png new file mode 100644 index 0000000..41c978c Binary files /dev/null and b/images/Others/Fiddler抓取移动端-13.png differ diff --git a/images/Others/Fiddler抓取移动端-14.png b/images/Others/Fiddler抓取移动端-14.png new file mode 100644 index 0000000..b032722 Binary files /dev/null and b/images/Others/Fiddler抓取移动端-14.png differ diff --git a/images/Others/Fiddler抓取移动端-2.png b/images/Others/Fiddler抓取移动端-2.png new file mode 100644 index 0000000..809b710 Binary files /dev/null and b/images/Others/Fiddler抓取移动端-2.png differ diff --git a/images/Others/Fiddler抓取移动端-3.png b/images/Others/Fiddler抓取移动端-3.png new file mode 100644 index 0000000..1a78b20 Binary files /dev/null and b/images/Others/Fiddler抓取移动端-3.png differ diff --git a/images/Others/Fiddler抓取移动端-4.png b/images/Others/Fiddler抓取移动端-4.png new file mode 100644 index 0000000..ca61cd1 Binary files /dev/null and b/images/Others/Fiddler抓取移动端-4.png differ diff --git a/images/Others/Fiddler抓取移动端-5.png b/images/Others/Fiddler抓取移动端-5.png new file mode 100644 index 0000000..bffae92 Binary files /dev/null and b/images/Others/Fiddler抓取移动端-5.png differ diff --git a/images/Others/Fiddler抓取移动端-6.png b/images/Others/Fiddler抓取移动端-6.png new file mode 100644 index 0000000..86a46f9 Binary files /dev/null and b/images/Others/Fiddler抓取移动端-6.png differ diff --git a/images/Others/Fiddler抓取移动端-7.png b/images/Others/Fiddler抓取移动端-7.png new file mode 100644 index 0000000..78582e4 Binary files /dev/null and b/images/Others/Fiddler抓取移动端-7.png differ diff --git a/images/Others/Fiddler抓取移动端-8.png b/images/Others/Fiddler抓取移动端-8.png new file mode 100644 index 0000000..1497524 Binary files /dev/null and b/images/Others/Fiddler抓取移动端-8.png differ diff --git a/images/Others/Fiddler抓取移动端-9.png b/images/Others/Fiddler抓取移动端-9.png new file mode 100644 index 0000000..2d21fcf Binary files /dev/null and b/images/Others/Fiddler抓取移动端-9.png differ diff --git a/images/Others/GenerateAllSetter.gif b/images/Others/GenerateAllSetter.gif new file mode 100644 index 0000000..f01c5ac Binary files /dev/null and b/images/Others/GenerateAllSetter.gif differ diff --git a/images/Others/GenerateSerialVersionUID.gif b/images/Others/GenerateSerialVersionUID.gif new file mode 100644 index 0000000..e03598f Binary files /dev/null and b/images/Others/GenerateSerialVersionUID.gif differ diff --git a/images/Others/Git-Commit-Template.jpg b/images/Others/Git-Commit-Template.jpg new file mode 100644 index 0000000..fe25f4f Binary files /dev/null and b/images/Others/Git-Commit-Template.jpg differ diff --git a/images/Others/GsonFormat.gif b/images/Others/GsonFormat.gif new file mode 100644 index 0000000..7f01979 Binary files /dev/null and b/images/Others/GsonFormat.gif differ diff --git a/images/Others/HighlightBracketPair-set.jpg b/images/Others/HighlightBracketPair-set.jpg new file mode 100644 index 0000000..edb37a1 Binary files /dev/null and b/images/Others/HighlightBracketPair-set.jpg differ diff --git a/images/Others/HighlightBracketPair.gif b/images/Others/HighlightBracketPair.gif new file mode 100644 index 0000000..ab7b055 Binary files /dev/null and b/images/Others/HighlightBracketPair.gif differ diff --git a/images/Others/IDEA-Database-1.png b/images/Others/IDEA-Database-1.png new file mode 100644 index 0000000..315fe90 Binary files /dev/null and b/images/Others/IDEA-Database-1.png differ diff --git a/images/Others/IDEA-Database-2.png b/images/Others/IDEA-Database-2.png new file mode 100644 index 0000000..8b8f0b2 Binary files /dev/null and b/images/Others/IDEA-Database-2.png differ diff --git a/images/Others/IDEA-FTP-1.png b/images/Others/IDEA-FTP-1.png new file mode 100644 index 0000000..f0a4763 Binary files /dev/null and b/images/Others/IDEA-FTP-1.png differ diff --git a/images/Others/IDEA-FTP-2.png b/images/Others/IDEA-FTP-2.png new file mode 100644 index 0000000..b8ae990 Binary files /dev/null and b/images/Others/IDEA-FTP-2.png differ diff --git a/images/Others/IDEA-FTP-3.png b/images/Others/IDEA-FTP-3.png new file mode 100644 index 0000000..4740e91 Binary files /dev/null and b/images/Others/IDEA-FTP-3.png differ diff --git a/images/Others/IDEA-FTP-4.png b/images/Others/IDEA-FTP-4.png new file mode 100644 index 0000000..4bd39e5 Binary files /dev/null and b/images/Others/IDEA-FTP-4.png differ diff --git a/images/Others/IDEA-JIRA-1.png b/images/Others/IDEA-JIRA-1.png new file mode 100644 index 0000000..8bca6d3 Binary files /dev/null and b/images/Others/IDEA-JIRA-1.png differ diff --git a/images/Others/IDEA-JIRA-2.png b/images/Others/IDEA-JIRA-2.png new file mode 100644 index 0000000..769320f Binary files /dev/null and b/images/Others/IDEA-JIRA-2.png differ diff --git a/images/Others/IDEA-JIRA-3.png b/images/Others/IDEA-JIRA-3.png new file mode 100644 index 0000000..45b683b Binary files /dev/null and b/images/Others/IDEA-JIRA-3.png differ diff --git a/images/Others/IDEA-JIRA-4.png b/images/Others/IDEA-JIRA-4.png new file mode 100644 index 0000000..94a2684 Binary files /dev/null and b/images/Others/IDEA-JIRA-4.png differ diff --git a/images/Others/IDEA-JIRA-5.png b/images/Others/IDEA-JIRA-5.png new file mode 100644 index 0000000..6a6b3f7 Binary files /dev/null and b/images/Others/IDEA-JIRA-5.png differ diff --git a/images/Others/IDEA-SSH-1.png b/images/Others/IDEA-SSH-1.png new file mode 100644 index 0000000..721cb13 Binary files /dev/null and b/images/Others/IDEA-SSH-1.png differ diff --git a/images/Others/IDEA-SSH-2.png b/images/Others/IDEA-SSH-2.png new file mode 100644 index 0000000..e79f5f7 Binary files /dev/null and b/images/Others/IDEA-SSH-2.png differ diff --git a/images/Others/IDEA-SSH-3.png b/images/Others/IDEA-SSH-3.png new file mode 100644 index 0000000..ee4d844 Binary files /dev/null and b/images/Others/IDEA-SSH-3.png differ diff --git a/images/Others/IDEA-SSH-4.png b/images/Others/IDEA-SSH-4.png new file mode 100644 index 0000000..086f07b Binary files /dev/null and b/images/Others/IDEA-SSH-4.png differ diff --git a/images/Others/IDEA-UML-1.png b/images/Others/IDEA-UML-1.png new file mode 100644 index 0000000..5b13fb5 Binary files /dev/null and b/images/Others/IDEA-UML-1.png differ diff --git a/images/Others/IDEA-UML-2.png b/images/Others/IDEA-UML-2.png new file mode 100644 index 0000000..2cb702d Binary files /dev/null and b/images/Others/IDEA-UML-2.png differ diff --git a/images/Others/IDEA-UML-3.png b/images/Others/IDEA-UML-3.png new file mode 100644 index 0000000..be7035b Binary files /dev/null and b/images/Others/IDEA-UML-3.png differ diff --git a/images/Others/Lombok.gif b/images/Others/Lombok.gif new file mode 100644 index 0000000..c9b1e0b Binary files /dev/null and b/images/Others/Lombok.gif differ diff --git a/images/Others/Maven-Helper.gif b/images/Others/Maven-Helper.gif new file mode 100644 index 0000000..cb95477 Binary files /dev/null and b/images/Others/Maven-Helper.gif differ diff --git a/images/Others/MyBatisCodeHelper-Pro.gif b/images/Others/MyBatisCodeHelper-Pro.gif new file mode 100644 index 0000000..f65bef6 Binary files /dev/null and b/images/Others/MyBatisCodeHelper-Pro.gif differ diff --git a/images/Others/MybatisX.gif b/images/Others/MybatisX.gif new file mode 100644 index 0000000..17a96e3 Binary files /dev/null and b/images/Others/MybatisX.gif differ diff --git a/images/Others/PPT演讲创新思维.jpg b/images/Others/PPT演讲创新思维.jpg new file mode 100644 index 0000000..d935ed1 Binary files /dev/null and b/images/Others/PPT演讲创新思维.jpg differ diff --git a/images/Others/PPT演讲大树模型.jpeg b/images/Others/PPT演讲大树模型.jpeg new file mode 100644 index 0000000..537038d Binary files /dev/null and b/images/Others/PPT演讲大树模型.jpeg differ diff --git a/images/Others/PPT演讲的逻辑结构.jpg b/images/Others/PPT演讲的逻辑结构.jpg new file mode 100644 index 0000000..d03a1b4 Binary files /dev/null and b/images/Others/PPT演讲的逻辑结构.jpg differ diff --git a/images/Others/ProcessOn.png b/images/Others/ProcessOn.png new file mode 100644 index 0000000..e05656d Binary files /dev/null and b/images/Others/ProcessOn.png differ diff --git a/images/Others/Rainbow-Brackets.gif b/images/Others/Rainbow-Brackets.gif new file mode 100644 index 0000000..100d444 Binary files /dev/null and b/images/Others/Rainbow-Brackets.gif differ diff --git a/images/Others/SecureCRT.jpg b/images/Others/SecureCRT.jpg new file mode 100644 index 0000000..b9f1d04 Binary files /dev/null and b/images/Others/SecureCRT.jpg differ diff --git a/images/Others/SequenceDiagram.gif b/images/Others/SequenceDiagram.gif new file mode 100644 index 0000000..f0c2b67 Binary files /dev/null and b/images/Others/SequenceDiagram.gif differ diff --git a/images/Others/Terminal.icu.png b/images/Others/Terminal.icu.png new file mode 100644 index 0000000..60b6bb9 Binary files /dev/null and b/images/Others/Terminal.icu.png differ diff --git a/images/Others/Translation.gif b/images/Others/Translation.gif new file mode 100644 index 0000000..2f7fe5e Binary files /dev/null and b/images/Others/Translation.gif differ diff --git a/images/Others/WinMerge.png b/images/Others/WinMerge.png new file mode 100644 index 0000000..d6fcd22 Binary files /dev/null and b/images/Others/WinMerge.png differ diff --git a/images/Others/XShell.png b/images/Others/XShell.png new file mode 100644 index 0000000..9200ae0 Binary files /dev/null and b/images/Others/XShell.png differ diff --git a/images/Others/addParamAnnotation.gif b/images/Others/addParamAnnotation.gif new file mode 100644 index 0000000..9abda27 Binary files /dev/null and b/images/Others/addParamAnnotation.gif differ diff --git a/images/Others/bV13RH b/images/Others/bV13RH new file mode 100644 index 0000000..8d59285 Binary files /dev/null and b/images/Others/bV13RH differ diff --git a/images/Others/bV13Sa b/images/Others/bV13Sa new file mode 100644 index 0000000..7e92b80 Binary files /dev/null and b/images/Others/bV13Sa differ diff --git a/images/Others/draw.io.jpeg b/images/Others/draw.io.jpeg new file mode 100644 index 0000000..01abf94 Binary files /dev/null and b/images/Others/draw.io.jpeg differ diff --git a/images/Others/ignore.gif b/images/Others/ignore.gif new file mode 100644 index 0000000..fa036ec Binary files /dev/null and b/images/Others/ignore.gif differ diff --git a/images/Others/intellij-idea-zhuangbi-top-5-5.gif b/images/Others/intellij-idea-zhuangbi-top-5-5.gif new file mode 100644 index 0000000..9e6cd69 Binary files /dev/null and b/images/Others/intellij-idea-zhuangbi-top-5-5.gif differ diff --git a/images/Others/iuYryuy.jpg b/images/Others/iuYryuy.jpg new file mode 100644 index 0000000..6be4ff2 Binary files /dev/null and b/images/Others/iuYryuy.jpg differ diff --git a/images/Others/jq22.png b/images/Others/jq22.png new file mode 100644 index 0000000..2fccf1b Binary files /dev/null and b/images/Others/jq22.png differ diff --git a/images/Others/key-promoter-x.gif b/images/Others/key-promoter-x.gif new file mode 100644 index 0000000..476794e Binary files /dev/null and b/images/Others/key-promoter-x.gif differ diff --git a/images/Others/material-theme-ui-tools.png b/images/Others/material-theme-ui-tools.png new file mode 100644 index 0000000..64fed87 Binary files /dev/null and b/images/Others/material-theme-ui-tools.png differ diff --git a/images/Others/oceanic.png b/images/Others/oceanic.png new file mode 100644 index 0000000..6f6d307 Binary files /dev/null and b/images/Others/oceanic.png differ diff --git a/images/Others/pdca.png b/images/Others/pdca.png new file mode 100644 index 0000000..a4abd8f Binary files /dev/null and b/images/Others/pdca.png differ diff --git a/images/Others/removebg.png b/images/Others/removebg.png new file mode 100644 index 0000000..1303445 Binary files /dev/null and b/images/Others/removebg.png differ diff --git a/images/Others/v2-2e63a1d6ddf2782ab8a0cda4d3e41502_hd.jpg b/images/Others/v2-2e63a1d6ddf2782ab8a0cda4d3e41502_hd.jpg new file mode 100644 index 0000000..e0c9e0d Binary files /dev/null and b/images/Others/v2-2e63a1d6ddf2782ab8a0cda4d3e41502_hd.jpg differ diff --git a/images/Others/今日热榜.png b/images/Others/今日热榜.png new file mode 100644 index 0000000..75d4368 Binary files /dev/null and b/images/Others/今日热榜.png differ diff --git a/images/Others/六步准备高光演讲.jpg b/images/Others/六步准备高光演讲.jpg new file mode 100644 index 0000000..cabaf89 Binary files /dev/null and b/images/Others/六步准备高光演讲.jpg differ diff --git a/images/Others/创造性思考的3条法则.jpg b/images/Others/创造性思考的3条法则.jpg new file mode 100644 index 0000000..a8ea2f5 Binary files /dev/null and b/images/Others/创造性思考的3条法则.jpg differ diff --git a/images/Others/即兴交流的7种力量.jpg b/images/Others/即兴交流的7种力量.jpg new file mode 100644 index 0000000..77b523b Binary files /dev/null and b/images/Others/即兴交流的7种力量.jpg differ diff --git a/images/Others/合作性思考的3条法则.jpg b/images/Others/合作性思考的3条法则.jpg new file mode 100644 index 0000000..f565d14 Binary files /dev/null and b/images/Others/合作性思考的3条法则.jpg differ diff --git a/images/Others/图壳.gif b/images/Others/图壳.gif new file mode 100644 index 0000000..ec2d549 Binary files /dev/null and b/images/Others/图壳.gif differ diff --git a/images/Others/坚持健康思考的3条法则.jpg b/images/Others/坚持健康思考的3条法则.jpg new file mode 100644 index 0000000..4cb87d1 Binary files /dev/null and b/images/Others/坚持健康思考的3条法则.jpg differ diff --git a/images/Others/定位PPT演讲主题.jpg b/images/Others/定位PPT演讲主题.jpg new file mode 100644 index 0000000..b500e3b Binary files /dev/null and b/images/Others/定位PPT演讲主题.jpg differ diff --git a/images/Others/小码短连接.png b/images/Others/小码短连接.png new file mode 100644 index 0000000..cabf66e Binary files /dev/null and b/images/Others/小码短连接.png differ diff --git a/images/Others/打造个人品牌营销.jpg b/images/Others/打造个人品牌营销.jpg new file mode 100644 index 0000000..a4ad4db Binary files /dev/null and b/images/Others/打造个人品牌营销.jpg differ diff --git a/images/Others/时间管理-四象限法则.png b/images/Others/时间管理-四象限法则.png new file mode 100644 index 0000000..b2dc452 Binary files /dev/null and b/images/Others/时间管理-四象限法则.png differ diff --git a/images/Others/显示工具条.png b/images/Others/显示工具条.png new file mode 100644 index 0000000..64ec1ad Binary files /dev/null and b/images/Others/显示工具条.png differ diff --git a/images/Others/独立思考的三条法则.jpg b/images/Others/独立思考的三条法则.jpg new file mode 100644 index 0000000..75e2e5f Binary files /dev/null and b/images/Others/独立思考的三条法则.jpg differ diff --git a/images/Others/番茄工作法.jpeg b/images/Others/番茄工作法.jpeg new file mode 100644 index 0000000..8a72fdd Binary files /dev/null and b/images/Others/番茄工作法.jpeg differ diff --git a/images/Others/精简发言的3个重点.jpg b/images/Others/精简发言的3个重点.jpg new file mode 100644 index 0000000..165ca18 Binary files /dev/null and b/images/Others/精简发言的3个重点.jpg differ diff --git a/images/Others/自我介绍公式MTV.jpg b/images/Others/自我介绍公式MTV.jpg new file mode 100644 index 0000000..a6b6a73 Binary files /dev/null and b/images/Others/自我介绍公式MTV.jpg differ diff --git a/images/Others/讲出好故事的HIT大发.jpg b/images/Others/讲出好故事的HIT大发.jpg new file mode 100644 index 0000000..6e98b0c Binary files /dev/null and b/images/Others/讲出好故事的HIT大发.jpg differ diff --git a/images/Others/设置鼠标悬浮提示.png b/images/Others/设置鼠标悬浮提示.png new file mode 100644 index 0000000..4ec24ff Binary files /dev/null and b/images/Others/设置鼠标悬浮提示.png differ diff --git a/images/Others/顶级演讲者的素养.jpg b/images/Others/顶级演讲者的素养.jpg new file mode 100644 index 0000000..667e95d Binary files /dev/null and b/images/Others/顶级演讲者的素养.jpg differ diff --git a/images/Solution/2PC第一阶段.jpg b/images/Solution/2PC第一阶段.jpg new file mode 100644 index 0000000..f977c05 Binary files /dev/null and b/images/Solution/2PC第一阶段.jpg differ diff --git a/images/Solution/2PC第二阶段.jpg b/images/Solution/2PC第二阶段.jpg new file mode 100644 index 0000000..b995734 Binary files /dev/null and b/images/Solution/2PC第二阶段.jpg differ diff --git a/images/Solution/2pc.png b/images/Solution/2pc.png new file mode 100644 index 0000000..efbcd12 Binary files /dev/null and b/images/Solution/2pc.png differ diff --git a/images/Solution/3pc.png b/images/Solution/3pc.png new file mode 100644 index 0000000..ad6f377 Binary files /dev/null and b/images/Solution/3pc.png differ diff --git a/images/Solution/APIGateway产品.png b/images/Solution/APIGateway产品.png new file mode 100644 index 0000000..7f94478 Binary files /dev/null and b/images/Solution/APIGateway产品.png differ diff --git a/images/Solution/API网关-API生命周期管理.png b/images/Solution/API网关-API生命周期管理.png new file mode 100644 index 0000000..aae0a08 Binary files /dev/null and b/images/Solution/API网关-API生命周期管理.png differ diff --git a/images/Solution/API网关-API路由.png b/images/Solution/API网关-API路由.png new file mode 100644 index 0000000..d2f3f69 Binary files /dev/null and b/images/Solution/API网关-API路由.png differ diff --git a/images/Solution/API网关-上下文切换.png b/images/Solution/API网关-上下文切换.png new file mode 100644 index 0000000..75d0641 Binary files /dev/null and b/images/Solution/API网关-上下文切换.png differ diff --git a/images/Solution/API网关-为什么做.png b/images/Solution/API网关-为什么做.png new file mode 100644 index 0000000..9b88554 Binary files /dev/null and b/images/Solution/API网关-为什么做.png differ diff --git a/images/Solution/API网关-功能组件.png b/images/Solution/API网关-功能组件.png new file mode 100644 index 0000000..b71ca43 Binary files /dev/null and b/images/Solution/API网关-功能组件.png differ diff --git a/images/Solution/API网关-协议转换&服务调用.png b/images/Solution/API网关-协议转换&服务调用.png new file mode 100644 index 0000000..3921adb Binary files /dev/null and b/images/Solution/API网关-协议转换&服务调用.png differ diff --git a/images/Solution/API网关-外调链接池化.png b/images/Solution/API网关-外调链接池化.png new file mode 100644 index 0000000..626f2e9 Binary files /dev/null and b/images/Solution/API网关-外调链接池化.png differ diff --git a/images/Solution/API网关-异步外调.png b/images/Solution/API网关-异步外调.png new file mode 100644 index 0000000..710cca8 Binary files /dev/null and b/images/Solution/API网关-异步外调.png differ diff --git a/images/Solution/API网关-故障自愈.png b/images/Solution/API网关-故障自愈.png new file mode 100644 index 0000000..0fc5306 Binary files /dev/null and b/images/Solution/API网关-故障自愈.png differ diff --git a/images/Solution/API网关-服务编排.png b/images/Solution/API网关-服务编排.png new file mode 100644 index 0000000..4ef49c3 Binary files /dev/null and b/images/Solution/API网关-服务编排.png differ diff --git a/images/Solution/API网关-灰度中.png b/images/Solution/API网关-灰度中.png new file mode 100644 index 0000000..998d4a1 Binary files /dev/null and b/images/Solution/API网关-灰度中.png differ diff --git a/images/Solution/API网关-灰度前.png b/images/Solution/API网关-灰度前.png new file mode 100644 index 0000000..0f8fc4c Binary files /dev/null and b/images/Solution/API网关-灰度前.png differ diff --git a/images/Solution/API网关-灰度后.png b/images/Solution/API网关-灰度后.png new file mode 100644 index 0000000..2816264 Binary files /dev/null and b/images/Solution/API网关-灰度后.png differ diff --git a/images/Solution/API网关-灰度场景.png b/images/Solution/API网关-灰度场景.png new file mode 100644 index 0000000..7187193 Binary files /dev/null and b/images/Solution/API网关-灰度场景.png differ diff --git a/images/Solution/API网关-稳定性保障.png b/images/Solution/API网关-稳定性保障.png new file mode 100644 index 0000000..e970db8 Binary files /dev/null and b/images/Solution/API网关-稳定性保障.png differ diff --git a/images/Solution/API网关-自动生成DSL.png b/images/Solution/API网关-自动生成DSL.png new file mode 100644 index 0000000..a6c747c Binary files /dev/null and b/images/Solution/API网关-自动生成DSL.png differ diff --git a/images/Solution/API网关-自定义组件.png b/images/Solution/API网关-自定义组件.png new file mode 100644 index 0000000..f56eb7e Binary files /dev/null and b/images/Solution/API网关-自定义组件.png differ diff --git a/images/Solution/API网关-请求隔离.png b/images/Solution/API网关-请求隔离.png new file mode 100644 index 0000000..958e294 Binary files /dev/null and b/images/Solution/API网关-请求隔离.png differ diff --git a/images/Solution/API网关-配置中心.png b/images/Solution/API网关-配置中心.png new file mode 100644 index 0000000..4e0c93d Binary files /dev/null and b/images/Solution/API网关-配置中心.png differ diff --git a/images/Solution/API网关-集群隔离.png b/images/Solution/API网关-集群隔离.png new file mode 100644 index 0000000..09cd70d Binary files /dev/null and b/images/Solution/API网关-集群隔离.png differ diff --git a/images/Solution/API网关-高性能设计.png b/images/Solution/API网关-高性能设计.png new file mode 100644 index 0000000..349e18e Binary files /dev/null and b/images/Solution/API网关-高性能设计.png differ diff --git a/images/Solution/AT模式.png b/images/Solution/AT模式.png new file mode 100644 index 0000000..7d332f3 Binary files /dev/null and b/images/Solution/AT模式.png differ diff --git a/images/Solution/AT模式一阶段.png b/images/Solution/AT模式一阶段.png new file mode 100644 index 0000000..8fd3af3 Binary files /dev/null and b/images/Solution/AT模式一阶段.png differ diff --git a/images/Solution/AT模式二阶段回滚.png b/images/Solution/AT模式二阶段回滚.png new file mode 100644 index 0000000..ef109ff Binary files /dev/null and b/images/Solution/AT模式二阶段回滚.png differ diff --git a/images/Solution/AT模式二阶段提交.png b/images/Solution/AT模式二阶段提交.png new file mode 100644 index 0000000..fc3aa3c Binary files /dev/null and b/images/Solution/AT模式二阶段提交.png differ diff --git a/images/Solution/Cache-Aside写入流程.png b/images/Solution/Cache-Aside写入流程.png new file mode 100644 index 0000000..1d36ab4 Binary files /dev/null and b/images/Solution/Cache-Aside写入流程.png differ diff --git a/images/Solution/Cache-Aside写入流程案例.png b/images/Solution/Cache-Aside写入流程案例.png new file mode 100644 index 0000000..53deb05 Binary files /dev/null and b/images/Solution/Cache-Aside写入流程案例.png differ diff --git a/images/Solution/Cache-Aside写请求.jpg b/images/Solution/Cache-Aside写请求.jpg new file mode 100644 index 0000000..a1f277a Binary files /dev/null and b/images/Solution/Cache-Aside写请求.jpg differ diff --git a/images/Solution/Cache-Aside读请求.jpg b/images/Solution/Cache-Aside读请求.jpg new file mode 100644 index 0000000..c00ff6d Binary files /dev/null and b/images/Solution/Cache-Aside读请求.jpg differ diff --git a/images/Solution/CircuitBreaker.png b/images/Solution/CircuitBreaker.png new file mode 100644 index 0000000..3f89baa Binary files /dev/null and b/images/Solution/CircuitBreaker.png differ diff --git a/images/Solution/Consul的架构图.png b/images/Solution/Consul的架构图.png new file mode 100644 index 0000000..775dc0f Binary files /dev/null and b/images/Solution/Consul的架构图.png differ diff --git a/images/Solution/DubboArchitecture.png b/images/Solution/DubboArchitecture.png new file mode 100644 index 0000000..530fd53 Binary files /dev/null and b/images/Solution/DubboArchitecture.png differ diff --git a/images/Solution/Gossip协议-拉方式.png b/images/Solution/Gossip协议-拉方式.png new file mode 100644 index 0000000..5642c1b Binary files /dev/null and b/images/Solution/Gossip协议-拉方式.png differ diff --git a/images/Solution/Gossip协议-推拉方式.png b/images/Solution/Gossip协议-推拉方式.png new file mode 100644 index 0000000..d3248cb Binary files /dev/null and b/images/Solution/Gossip协议-推拉方式.png differ diff --git a/images/Solution/Gossip协议-推方式.png b/images/Solution/Gossip协议-推方式.png new file mode 100644 index 0000000..4d8c0ac Binary files /dev/null and b/images/Solution/Gossip协议-推方式.png differ diff --git a/images/Solution/Gossip协议-直接邮寄.png b/images/Solution/Gossip协议-直接邮寄.png new file mode 100644 index 0000000..548be03 Binary files /dev/null and b/images/Solution/Gossip协议-直接邮寄.png differ diff --git a/images/Solution/Gossip协议-谣言传播.png b/images/Solution/Gossip协议-谣言传播.png new file mode 100644 index 0000000..d9b4885 Binary files /dev/null and b/images/Solution/Gossip协议-谣言传播.png differ diff --git a/images/Solution/Gravitee.png b/images/Solution/Gravitee.png new file mode 100644 index 0000000..6400873 Binary files /dev/null and b/images/Solution/Gravitee.png differ diff --git a/images/Solution/Guava布隆过滤器.jpg b/images/Solution/Guava布隆过滤器.jpg new file mode 100644 index 0000000..3d09142 Binary files /dev/null and b/images/Solution/Guava布隆过滤器.jpg differ diff --git a/images/Solution/Job扫描处理超时未支付订单.png b/images/Solution/Job扫描处理超时未支付订单.png new file mode 100644 index 0000000..6118862 Binary files /dev/null and b/images/Solution/Job扫描处理超时未支付订单.png differ diff --git a/images/Solution/Leaf-segment双buffer优化.png b/images/Solution/Leaf-segment双buffer优化.png new file mode 100644 index 0000000..5bfbae4 Binary files /dev/null and b/images/Solution/Leaf-segment双buffer优化.png differ diff --git a/images/Solution/Leaf-segment数据库方案.png b/images/Solution/Leaf-segment数据库方案.png new file mode 100644 index 0000000..2b3e728 Binary files /dev/null and b/images/Solution/Leaf-segment数据库方案.png differ diff --git a/images/Solution/Leaf-segment高可用容灾.png b/images/Solution/Leaf-segment高可用容灾.png new file mode 100644 index 0000000..186f13e Binary files /dev/null and b/images/Solution/Leaf-segment高可用容灾.png differ diff --git a/images/Solution/Leaf-snowflake方案.png b/images/Solution/Leaf-snowflake方案.png new file mode 100644 index 0000000..d3e343d Binary files /dev/null and b/images/Solution/Leaf-snowflake方案.png differ diff --git a/images/Solution/MQ消息事务-RocketMQ.png b/images/Solution/MQ消息事务-RocketMQ.png new file mode 100644 index 0000000..af434b7 Binary files /dev/null and b/images/Solution/MQ消息事务-RocketMQ.png differ diff --git a/images/Solution/MySQL数据库多主模式.jpg b/images/Solution/MySQL数据库多主模式.jpg new file mode 100644 index 0000000..c9a71c6 Binary files /dev/null and b/images/Solution/MySQL数据库多主模式.jpg differ diff --git a/images/Solution/Nacos.png b/images/Solution/Nacos.png new file mode 100644 index 0000000..3de0f56 Binary files /dev/null and b/images/Solution/Nacos.png differ diff --git a/images/Solution/Nginx负载均衡故障转移.jpg b/images/Solution/Nginx负载均衡故障转移.jpg new file mode 100644 index 0000000..97d417f Binary files /dev/null and b/images/Solution/Nginx负载均衡故障转移.jpg differ diff --git a/images/Solution/PBFT算法流程-准备阶段.png b/images/Solution/PBFT算法流程-准备阶段.png new file mode 100644 index 0000000..8754b3f Binary files /dev/null and b/images/Solution/PBFT算法流程-准备阶段.png differ diff --git a/images/Solution/PBFT算法流程-响应.png b/images/Solution/PBFT算法流程-响应.png new file mode 100644 index 0000000..e9f5f82 Binary files /dev/null and b/images/Solution/PBFT算法流程-响应.png differ diff --git a/images/Solution/PBFT算法流程-提交阶段.png b/images/Solution/PBFT算法流程-提交阶段.png new file mode 100644 index 0000000..9e32a42 Binary files /dev/null and b/images/Solution/PBFT算法流程-提交阶段.png differ diff --git a/images/Solution/PBFT算法流程-预准备阶段.png b/images/Solution/PBFT算法流程-预准备阶段.png new file mode 100644 index 0000000..96d3657 Binary files /dev/null and b/images/Solution/PBFT算法流程-预准备阶段.png differ diff --git a/images/Solution/PBFT算法流程.png b/images/Solution/PBFT算法流程.png new file mode 100644 index 0000000..e024ae0 Binary files /dev/null and b/images/Solution/PBFT算法流程.png differ diff --git a/images/Solution/Paxos协议Proposer与Acceptor交互流程.png b/images/Solution/Paxos协议Proposer与Acceptor交互流程.png new file mode 100644 index 0000000..0980268 Binary files /dev/null and b/images/Solution/Paxos协议Proposer与Acceptor交互流程.png differ diff --git a/images/Solution/Paxos选举过程.png b/images/Solution/Paxos选举过程.png new file mode 100644 index 0000000..67e0eab Binary files /dev/null and b/images/Solution/Paxos选举过程.png differ diff --git a/images/Solution/PoW算法-区块链.png b/images/Solution/PoW算法-区块链.png new file mode 100644 index 0000000..5386423 Binary files /dev/null and b/images/Solution/PoW算法-区块链.png differ diff --git a/images/Solution/PoW算法-区块链串.png b/images/Solution/PoW算法-区块链串.png new file mode 100644 index 0000000..5eb5ca3 Binary files /dev/null and b/images/Solution/PoW算法-区块链串.png differ diff --git a/images/Solution/PoW算法-工作量证明.png b/images/Solution/PoW算法-工作量证明.png new file mode 100644 index 0000000..c38df1d Binary files /dev/null and b/images/Solution/PoW算法-工作量证明.png differ diff --git a/images/Solution/PoW算法-攻击链.png b/images/Solution/PoW算法-攻击链.png new file mode 100644 index 0000000..6538ec3 Binary files /dev/null and b/images/Solution/PoW算法-攻击链.png differ diff --git a/images/Solution/QuorumNWR算法-写一致性级别.png b/images/Solution/QuorumNWR算法-写一致性级别.png new file mode 100644 index 0000000..986f729 Binary files /dev/null and b/images/Solution/QuorumNWR算法-写一致性级别.png differ diff --git a/images/Solution/QuorumNWR算法-副本数.png b/images/Solution/QuorumNWR算法-副本数.png new file mode 100644 index 0000000..84003ac Binary files /dev/null and b/images/Solution/QuorumNWR算法-副本数.png differ diff --git a/images/Solution/QuorumNWR算法-读一致性级别.png b/images/Solution/QuorumNWR算法-读一致性级别.png new file mode 100644 index 0000000..4007ebc Binary files /dev/null and b/images/Solution/QuorumNWR算法-读一致性级别.png differ diff --git a/images/Solution/Raft安全性.jpg b/images/Solution/Raft安全性.jpg new file mode 100644 index 0000000..78f0541 Binary files /dev/null and b/images/Solution/Raft安全性.jpg differ diff --git a/images/Solution/Raft日志压缩.png b/images/Solution/Raft日志压缩.png new file mode 100644 index 0000000..e6b38d7 Binary files /dev/null and b/images/Solution/Raft日志压缩.png differ diff --git a/images/Solution/Raft日志复制的过程.png b/images/Solution/Raft日志复制的过程.png new file mode 100644 index 0000000..6b8cffe Binary files /dev/null and b/images/Solution/Raft日志复制的过程.png differ diff --git a/images/Solution/Raft日志的一致性.jpg b/images/Solution/Raft日志的一致性.jpg new file mode 100644 index 0000000..2681078 Binary files /dev/null and b/images/Solution/Raft日志的一致性.jpg differ diff --git a/images/Solution/Raft日志的不正常情况.jpg b/images/Solution/Raft日志的不正常情况.jpg new file mode 100644 index 0000000..30a8240 Binary files /dev/null and b/images/Solution/Raft日志的不正常情况.jpg differ diff --git a/images/Solution/Raft日志的组成.png b/images/Solution/Raft日志的组成.png new file mode 100644 index 0000000..1ec7784 Binary files /dev/null and b/images/Solution/Raft日志的组成.png differ diff --git a/images/Solution/Raft的Leader选举过程.jpg b/images/Solution/Raft的Leader选举过程.jpg new file mode 100644 index 0000000..949421f Binary files /dev/null and b/images/Solution/Raft的Leader选举过程.jpg differ diff --git a/images/Solution/Raft的三种状态转换.png b/images/Solution/Raft的三种状态转换.png new file mode 100644 index 0000000..42127c9 Binary files /dev/null and b/images/Solution/Raft的三种状态转换.png differ diff --git a/images/Solution/Read-Through流程.png b/images/Solution/Read-Through流程.png new file mode 100644 index 0000000..becd8d6 Binary files /dev/null and b/images/Solution/Read-Through流程.png differ diff --git a/images/Solution/Read-Through简要流程.png b/images/Solution/Read-Through简要流程.png new file mode 100644 index 0000000..8c7f84e Binary files /dev/null and b/images/Solution/Read-Through简要流程.png differ diff --git a/images/Solution/RocketMQ事务消息.png b/images/Solution/RocketMQ事务消息.png new file mode 100644 index 0000000..59d3b50 Binary files /dev/null and b/images/Solution/RocketMQ事务消息.png differ diff --git a/images/Solution/Saga模式.png b/images/Solution/Saga模式.png new file mode 100644 index 0000000..2bea07c Binary files /dev/null and b/images/Solution/Saga模式.png differ diff --git a/images/Solution/Seata-下单扣减库存-2PC第一阶段.png b/images/Solution/Seata-下单扣减库存-2PC第一阶段.png new file mode 100644 index 0000000..e34324b Binary files /dev/null and b/images/Solution/Seata-下单扣减库存-2PC第一阶段.png differ diff --git a/images/Solution/Seata-下单扣减库存-2PC第二阶段.png b/images/Solution/Seata-下单扣减库存-2PC第二阶段.png new file mode 100644 index 0000000..253ca83 Binary files /dev/null and b/images/Solution/Seata-下单扣减库存-2PC第二阶段.png differ diff --git a/images/Solution/Seata-下单扣减库存-3PC.png b/images/Solution/Seata-下单扣减库存-3PC.png new file mode 100644 index 0000000..df27ea5 Binary files /dev/null and b/images/Solution/Seata-下单扣减库存-3PC.png differ diff --git a/images/Solution/Seata-下单扣减库存-Seata.png b/images/Solution/Seata-下单扣减库存-Seata.png new file mode 100644 index 0000000..257fb23 Binary files /dev/null and b/images/Solution/Seata-下单扣减库存-Seata.png differ diff --git a/images/Solution/Seata-下单扣减库存-传统模式.png b/images/Solution/Seata-下单扣减库存-传统模式.png new file mode 100644 index 0000000..efed36e Binary files /dev/null and b/images/Solution/Seata-下单扣减库存-传统模式.png differ diff --git a/images/Solution/Seata-下单扣减库存-分库分表.png b/images/Solution/Seata-下单扣减库存-分库分表.png new file mode 100644 index 0000000..d3eb95d Binary files /dev/null and b/images/Solution/Seata-下单扣减库存-分库分表.png differ diff --git a/images/Solution/Seata-下单扣减库存-消息事务.png b/images/Solution/Seata-下单扣减库存-消息事务.png new file mode 100644 index 0000000..14c3d0f Binary files /dev/null and b/images/Solution/Seata-下单扣减库存-消息事务.png differ diff --git a/images/Solution/Seata-第一阶段-数据前镜像.png b/images/Solution/Seata-第一阶段-数据前镜像.png new file mode 100644 index 0000000..0d79474 Binary files /dev/null and b/images/Solution/Seata-第一阶段-数据前镜像.png differ diff --git a/images/Solution/Seata-第一阶段-数据后镜像.png b/images/Solution/Seata-第一阶段-数据后镜像.png new file mode 100644 index 0000000..91a704b Binary files /dev/null and b/images/Solution/Seata-第一阶段-数据后镜像.png differ diff --git a/images/Solution/Seata-第一阶段.png b/images/Solution/Seata-第一阶段.png new file mode 100644 index 0000000..4f82ad5 Binary files /dev/null and b/images/Solution/Seata-第一阶段.png differ diff --git a/images/Solution/Seata-第二阶段-分支回滚.png b/images/Solution/Seata-第二阶段-分支回滚.png new file mode 100644 index 0000000..8e5d41c Binary files /dev/null and b/images/Solution/Seata-第二阶段-分支回滚.png differ diff --git a/images/Solution/Seata-第二阶段.png b/images/Solution/Seata-第二阶段.png new file mode 100644 index 0000000..4f5406f Binary files /dev/null and b/images/Solution/Seata-第二阶段.png differ diff --git a/images/Solution/SeataServer-file.conf.png b/images/Solution/SeataServer-file.conf.png new file mode 100644 index 0000000..9ffcf9c Binary files /dev/null and b/images/Solution/SeataServer-file.conf.png differ diff --git a/images/Solution/SeataServer-registry.conf.png b/images/Solution/SeataServer-registry.conf.png new file mode 100644 index 0000000..7cf0950 Binary files /dev/null and b/images/Solution/SeataServer-registry.conf.png differ diff --git a/images/Solution/SeataServer-全局事务回滚.png b/images/Solution/SeataServer-全局事务回滚.png new file mode 100644 index 0000000..361bbf7 Binary files /dev/null and b/images/Solution/SeataServer-全局事务回滚.png differ diff --git a/images/Solution/SeataServer-回滚后数据表.png b/images/Solution/SeataServer-回滚后数据表.png new file mode 100644 index 0000000..5bd50e6 Binary files /dev/null and b/images/Solution/SeataServer-回滚后数据表.png differ diff --git a/images/Solution/SeataServer-回滚记录.png b/images/Solution/SeataServer-回滚记录.png new file mode 100644 index 0000000..db71d1b Binary files /dev/null and b/images/Solution/SeataServer-回滚记录.png differ diff --git a/images/Solution/SeataServer-控制台两次提交.png b/images/Solution/SeataServer-控制台两次提交.png new file mode 100644 index 0000000..e98f3b8 Binary files /dev/null and b/images/Solution/SeataServer-控制台两次提交.png differ diff --git a/images/Solution/SeataServer-服务调用过程.png b/images/Solution/SeataServer-服务调用过程.png new file mode 100644 index 0000000..257fb23 Binary files /dev/null and b/images/Solution/SeataServer-服务调用过程.png differ diff --git a/images/Solution/SeataServer-测试表-下单数据.png b/images/Solution/SeataServer-测试表-下单数据.png new file mode 100644 index 0000000..a20f311 Binary files /dev/null and b/images/Solution/SeataServer-测试表-下单数据.png differ diff --git a/images/Solution/SeataServer-测试表-原始数据.png b/images/Solution/SeataServer-测试表-原始数据.png new file mode 100644 index 0000000..5dcd1be Binary files /dev/null and b/images/Solution/SeataServer-测试表-原始数据.png differ diff --git a/images/Solution/ServiceDowngrade.png b/images/Solution/ServiceDowngrade.png new file mode 100644 index 0000000..a0f255e Binary files /dev/null and b/images/Solution/ServiceDowngrade.png differ diff --git a/images/Solution/ShepherdAPI网关.png b/images/Solution/ShepherdAPI网关.png new file mode 100644 index 0000000..777fbdd Binary files /dev/null and b/images/Solution/ShepherdAPI网关.png differ diff --git a/images/Solution/Soul.png b/images/Solution/Soul.png new file mode 100644 index 0000000..b2d8a89 Binary files /dev/null and b/images/Solution/Soul.png differ diff --git a/images/Solution/TCC-Cancel.png b/images/Solution/TCC-Cancel.png new file mode 100644 index 0000000..cabc4a0 Binary files /dev/null and b/images/Solution/TCC-Cancel.png differ diff --git a/images/Solution/TCC-Confirm.png b/images/Solution/TCC-Confirm.png new file mode 100644 index 0000000..1b9c020 Binary files /dev/null and b/images/Solution/TCC-Confirm.png differ diff --git a/images/Solution/TCC-Try.png b/images/Solution/TCC-Try.png new file mode 100644 index 0000000..fa5ee74 Binary files /dev/null and b/images/Solution/TCC-Try.png differ diff --git a/images/Solution/TCC-try-cancel.jpg b/images/Solution/TCC-try-cancel.jpg new file mode 100644 index 0000000..28e228e Binary files /dev/null and b/images/Solution/TCC-try-cancel.jpg differ diff --git a/images/Solution/TCC-try-confirm.jpg b/images/Solution/TCC-try-confirm.jpg new file mode 100644 index 0000000..5ad99af Binary files /dev/null and b/images/Solution/TCC-try-confirm.jpg differ diff --git a/images/Solution/TCC案例场景-订单服务.png b/images/Solution/TCC案例场景-订单服务.png new file mode 100644 index 0000000..faf5b32 Binary files /dev/null and b/images/Solution/TCC案例场景-订单服务.png differ diff --git a/images/Solution/TCC模式.png b/images/Solution/TCC模式.png new file mode 100644 index 0000000..d685c36 Binary files /dev/null and b/images/Solution/TCC模式.png differ diff --git a/images/Solution/Token机制实现.jpg b/images/Solution/Token机制实现.jpg new file mode 100644 index 0000000..1db5228 Binary files /dev/null and b/images/Solution/Token机制实现.jpg differ diff --git a/images/Solution/TomcatBuffer.png b/images/Solution/TomcatBuffer.png new file mode 100644 index 0000000..7424495 Binary files /dev/null and b/images/Solution/TomcatBuffer.png differ diff --git a/images/Solution/Try-Confirm-Cancel.png b/images/Solution/Try-Confirm-Cancel.png new file mode 100644 index 0000000..1f279d9 Binary files /dev/null and b/images/Solution/Try-Confirm-Cancel.png differ diff --git a/images/Solution/Write-Through.png b/images/Solution/Write-Through.png new file mode 100644 index 0000000..4639d32 Binary files /dev/null and b/images/Solution/Write-Through.png differ diff --git a/images/Solution/WriteBehind流程.png b/images/Solution/WriteBehind流程.png new file mode 100644 index 0000000..27fedbd Binary files /dev/null and b/images/Solution/WriteBehind流程.png differ diff --git a/images/Solution/XA模式.png b/images/Solution/XA模式.png new file mode 100644 index 0000000..fb0949e Binary files /dev/null and b/images/Solution/XA模式.png differ diff --git a/images/Solution/XA模式实现过程.png b/images/Solution/XA模式实现过程.png new file mode 100644 index 0000000..92f834d Binary files /dev/null and b/images/Solution/XA模式实现过程.png differ diff --git a/images/Solution/XA过程.png b/images/Solution/XA过程.png new file mode 100644 index 0000000..b428660 Binary files /dev/null and b/images/Solution/XA过程.png differ diff --git a/images/Solution/ZAB协议.png b/images/Solution/ZAB协议.png new file mode 100644 index 0000000..66d3100 Binary files /dev/null and b/images/Solution/ZAB协议.png differ diff --git a/images/Solution/ZAB数据同步ZXID.png b/images/Solution/ZAB数据同步ZXID.png new file mode 100644 index 0000000..e02f4da Binary files /dev/null and b/images/Solution/ZAB数据同步ZXID.png differ diff --git a/images/Solution/ZAB消息广播Commit.png b/images/Solution/ZAB消息广播Commit.png new file mode 100644 index 0000000..0159113 Binary files /dev/null and b/images/Solution/ZAB消息广播Commit.png differ diff --git a/images/Solution/ZAB消息广播数据复制至Follwer.png b/images/Solution/ZAB消息广播数据复制至Follwer.png new file mode 100644 index 0000000..77f8291 Binary files /dev/null and b/images/Solution/ZAB消息广播数据复制至Follwer.png differ diff --git a/images/Solution/ZAB消息广播等待Follwer回应ACK.png b/images/Solution/ZAB消息广播等待Follwer回应ACK.png new file mode 100644 index 0000000..51f0138 Binary files /dev/null and b/images/Solution/ZAB消息广播等待Follwer回应ACK.png differ diff --git a/images/Solution/ZK负载均衡故障转移.jpg b/images/Solution/ZK负载均衡故障转移.jpg new file mode 100644 index 0000000..75196b3 Binary files /dev/null and b/images/Solution/ZK负载均衡故障转移.jpg differ diff --git a/images/Solution/Zipkin架构.png b/images/Solution/Zipkin架构.png new file mode 100644 index 0000000..c1f7931 Binary files /dev/null and b/images/Solution/Zipkin架构.png differ diff --git a/images/Solution/Zipkin核心组件.png b/images/Solution/Zipkin核心组件.png new file mode 100644 index 0000000..4c3e23d Binary files /dev/null and b/images/Solution/Zipkin核心组件.png differ diff --git a/images/Solution/apiman.png b/images/Solution/apiman.png new file mode 100644 index 0000000..219c635 Binary files /dev/null and b/images/Solution/apiman.png differ diff --git a/images/Solution/cap.png b/images/Solution/cap.png new file mode 100644 index 0000000..eb11c72 Binary files /dev/null and b/images/Solution/cap.png differ diff --git a/images/Solution/kong.png b/images/Solution/kong.png new file mode 100644 index 0000000..8b547e8 Binary files /dev/null and b/images/Solution/kong.png differ diff --git a/images/Solution/kqcq.png b/images/Solution/kqcq.png new file mode 100644 index 0000000..4016fa6 Binary files /dev/null and b/images/Solution/kqcq.png differ diff --git a/images/Solution/redis-token-bucket-dataflow.png b/images/Solution/redis-token-bucket-dataflow.png new file mode 100644 index 0000000..0bbdee1 Binary files /dev/null and b/images/Solution/redis-token-bucket-dataflow.png differ diff --git a/images/Solution/seata.png b/images/Solution/seata.png new file mode 100644 index 0000000..577858f Binary files /dev/null and b/images/Solution/seata.png differ diff --git a/images/Solution/seata发展历程.png b/images/Solution/seata发展历程.png new file mode 100644 index 0000000..192e86a Binary files /dev/null and b/images/Solution/seata发展历程.png differ diff --git a/images/Solution/seata流程.png b/images/Solution/seata流程.png new file mode 100644 index 0000000..d17f85e Binary files /dev/null and b/images/Solution/seata流程.png differ diff --git a/images/Solution/seata过程.png b/images/Solution/seata过程.png new file mode 100644 index 0000000..0a8e647 Binary files /dev/null and b/images/Solution/seata过程.png differ diff --git a/images/Solution/shardingsphere-hybrid.png b/images/Solution/shardingsphere-hybrid.png new file mode 100644 index 0000000..68b6c4e Binary files /dev/null and b/images/Solution/shardingsphere-hybrid.png differ diff --git a/images/Solution/shardingsphere-jdbc-brief.png b/images/Solution/shardingsphere-jdbc-brief.png new file mode 100644 index 0000000..0288ff3 Binary files /dev/null and b/images/Solution/shardingsphere-jdbc-brief.png differ diff --git a/images/Solution/shardingsphere-proxy-brief.png b/images/Solution/shardingsphere-proxy-brief.png new file mode 100644 index 0000000..e26eec2 Binary files /dev/null and b/images/Solution/shardingsphere-proxy-brief.png differ diff --git a/images/Solution/shardingsphere-scope_cn.png b/images/Solution/shardingsphere-scope_cn.png new file mode 100644 index 0000000..e7db8e2 Binary files /dev/null and b/images/Solution/shardingsphere-scope_cn.png differ diff --git a/images/Solution/shardingsphere-sidecar-brief.png b/images/Solution/shardingsphere-sidecar-brief.png new file mode 100644 index 0000000..f72fca2 Binary files /dev/null and b/images/Solution/shardingsphere-sidecar-brief.png differ diff --git a/images/Solution/traefik.png b/images/Solution/traefik.png new file mode 100644 index 0000000..85cde64 Binary files /dev/null and b/images/Solution/traefik.png differ diff --git a/images/Solution/xa-seata.png b/images/Solution/xa-seata.png new file mode 100644 index 0000000..488314b Binary files /dev/null and b/images/Solution/xa-seata.png differ diff --git a/images/Solution/zookeeper注册中心.jpg b/images/Solution/zookeeper注册中心.jpg new file mode 100644 index 0000000..38d84c5 Binary files /dev/null and b/images/Solution/zookeeper注册中心.jpg differ diff --git a/images/Solution/下单流程图.jpg b/images/Solution/下单流程图.jpg new file mode 100644 index 0000000..71f170b Binary files /dev/null and b/images/Solution/下单流程图.jpg differ diff --git a/images/Solution/两地三中心.jpg b/images/Solution/两地三中心.jpg new file mode 100644 index 0000000..d8b3a83 Binary files /dev/null and b/images/Solution/两地三中心.jpg differ diff --git a/images/Solution/两地三中心主从模式.png b/images/Solution/两地三中心主从模式.png new file mode 100644 index 0000000..e5e2287 Binary files /dev/null and b/images/Solution/两地三中心主从模式.png differ diff --git a/images/Solution/二阶段异步执行.png b/images/Solution/二阶段异步执行.png new file mode 100644 index 0000000..f8f9898 Binary files /dev/null and b/images/Solution/二阶段异步执行.png differ diff --git a/images/Solution/令牌桶算法.png b/images/Solution/令牌桶算法.png new file mode 100644 index 0000000..2ac6115 Binary files /dev/null and b/images/Solution/令牌桶算法.png differ diff --git a/images/Solution/先进先出算法(FIFO).png b/images/Solution/先进先出算法(FIFO).png new file mode 100644 index 0000000..480d0c5 Binary files /dev/null and b/images/Solution/先进先出算法(FIFO).png differ diff --git a/images/Solution/分布式ID方案.jpg b/images/Solution/分布式ID方案.jpg new file mode 100644 index 0000000..68e938b Binary files /dev/null and b/images/Solution/分布式ID方案.jpg differ diff --git a/images/Solution/分布式事务-技术方案.jpg b/images/Solution/分布式事务-技术方案.jpg new file mode 100644 index 0000000..5571cbc Binary files /dev/null and b/images/Solution/分布式事务-技术方案.jpg differ diff --git a/images/Solution/分布式事务-本地消息表.png b/images/Solution/分布式事务-本地消息表.png new file mode 100644 index 0000000..deb93d1 Binary files /dev/null and b/images/Solution/分布式事务-本地消息表.png differ diff --git a/images/Solution/分布式事务-订单库存.jpg b/images/Solution/分布式事务-订单库存.jpg new file mode 100644 index 0000000..456646f Binary files /dev/null and b/images/Solution/分布式事务-订单库存.jpg differ diff --git a/images/Solution/分布式消息中间件.png b/images/Solution/分布式消息中间件.png new file mode 100644 index 0000000..039ab99 Binary files /dev/null and b/images/Solution/分布式消息中间件.png differ diff --git a/images/Solution/分库分表-Hash取模.png b/images/Solution/分库分表-Hash取模.png new file mode 100644 index 0000000..b5ce978 Binary files /dev/null and b/images/Solution/分库分表-Hash取模.png differ diff --git a/images/Solution/分库分表-Range划分.png b/images/Solution/分库分表-Range划分.png new file mode 100644 index 0000000..e549a4f Binary files /dev/null and b/images/Solution/分库分表-Range划分.png differ diff --git a/images/Solution/分库分表-一致性Hash.png b/images/Solution/分库分表-一致性Hash.png new file mode 100644 index 0000000..1d17a28 Binary files /dev/null and b/images/Solution/分库分表-一致性Hash.png differ diff --git a/images/Solution/创建订单-发送MQ消息.jpg b/images/Solution/创建订单-发送MQ消息.jpg new file mode 100644 index 0000000..0003132 Binary files /dev/null and b/images/Solution/创建订单-发送MQ消息.jpg differ diff --git a/images/Solution/创建订单-回执MQ消息.jpg b/images/Solution/创建订单-回执MQ消息.jpg new file mode 100644 index 0000000..ddf4b42 Binary files /dev/null and b/images/Solution/创建订单-回执MQ消息.jpg differ diff --git a/images/Solution/创建订单-消费MQ消息.jpg b/images/Solution/创建订单-消费MQ消息.jpg new file mode 100644 index 0000000..3ae18cc Binary files /dev/null and b/images/Solution/创建订单-消费MQ消息.jpg differ diff --git a/images/Solution/删除缓存重试流程.png b/images/Solution/删除缓存重试流程.png new file mode 100644 index 0000000..3980ebd Binary files /dev/null and b/images/Solution/删除缓存重试流程.png differ diff --git a/images/Solution/压测平台结果输出.png b/images/Solution/压测平台结果输出.png new file mode 100644 index 0000000..c205349 Binary files /dev/null and b/images/Solution/压测平台结果输出.png differ diff --git a/images/Solution/双写顺序-先DB后缓存-案例.jpg b/images/Solution/双写顺序-先DB后缓存-案例.jpg new file mode 100644 index 0000000..c14bf28 Binary files /dev/null and b/images/Solution/双写顺序-先DB后缓存-案例.jpg differ diff --git a/images/Solution/双写顺序-先DB后缓存.jpg b/images/Solution/双写顺序-先DB后缓存.jpg new file mode 100644 index 0000000..63a46fa Binary files /dev/null and b/images/Solution/双写顺序-先DB后缓存.jpg differ diff --git a/images/Solution/双写顺序-先缓存后DB-案例.jpg b/images/Solution/双写顺序-先缓存后DB-案例.jpg new file mode 100644 index 0000000..dfaa4f6 Binary files /dev/null and b/images/Solution/双写顺序-先缓存后DB-案例.jpg differ diff --git a/images/Solution/双写顺序-先缓存后DB.jpg b/images/Solution/双写顺序-先缓存后DB.jpg new file mode 100644 index 0000000..8b9ac8a Binary files /dev/null and b/images/Solution/双写顺序-先缓存后DB.jpg differ diff --git a/images/Solution/固定窗口时间算法-存在问题.png b/images/Solution/固定窗口时间算法-存在问题.png new file mode 100644 index 0000000..df931fb Binary files /dev/null and b/images/Solution/固定窗口时间算法-存在问题.png differ diff --git a/images/Solution/固定窗口计数器原理.png b/images/Solution/固定窗口计数器原理.png new file mode 100644 index 0000000..987224e Binary files /dev/null and b/images/Solution/固定窗口计数器原理.png differ diff --git a/images/Solution/垂直分库.jpg b/images/Solution/垂直分库.jpg new file mode 100644 index 0000000..c5a3b9d Binary files /dev/null and b/images/Solution/垂直分库.jpg differ diff --git a/images/Solution/垂直分表.jpg b/images/Solution/垂直分表.jpg new file mode 100644 index 0000000..d29e3f2 Binary files /dev/null and b/images/Solution/垂直分表.jpg differ diff --git a/images/Solution/基于mysql实现.jpg b/images/Solution/基于mysql实现.jpg new file mode 100644 index 0000000..b42af89 Binary files /dev/null and b/images/Solution/基于mysql实现.jpg differ diff --git a/images/Solution/基于redis实现.jpg b/images/Solution/基于redis实现.jpg new file mode 100644 index 0000000..7698ffb Binary files /dev/null and b/images/Solution/基于redis实现.jpg differ diff --git a/images/Solution/容错案例一.jpg b/images/Solution/容错案例一.jpg new file mode 100644 index 0000000..9f6809a Binary files /dev/null and b/images/Solution/容错案例一.jpg differ diff --git a/images/Solution/容错案例二.jpg b/images/Solution/容错案例二.jpg new file mode 100644 index 0000000..bf71fa7 Binary files /dev/null and b/images/Solution/容错案例二.jpg differ diff --git a/images/Solution/小豹API网关架构.png b/images/Solution/小豹API网关架构.png new file mode 100644 index 0000000..1683b50 Binary files /dev/null and b/images/Solution/小豹API网关架构.png differ diff --git a/images/Solution/布隆过滤器.jpg b/images/Solution/布隆过滤器.jpg new file mode 100644 index 0000000..e969a64 Binary files /dev/null and b/images/Solution/布隆过滤器.jpg differ diff --git a/images/Solution/延时双删流程.png b/images/Solution/延时双删流程.png new file mode 100644 index 0000000..dd98772 Binary files /dev/null and b/images/Solution/延时双删流程.png differ diff --git a/images/Solution/异地多活的示意图.jpg b/images/Solution/异地多活的示意图.jpg new file mode 100644 index 0000000..86c3bed Binary files /dev/null and b/images/Solution/异地多活的示意图.jpg differ diff --git a/images/Solution/异库模式与同库模式.png b/images/Solution/异库模式与同库模式.png new file mode 100644 index 0000000..bf3e97d Binary files /dev/null and b/images/Solution/异库模式与同库模式.png differ diff --git a/images/Solution/打标-方案比较.png b/images/Solution/打标-方案比较.png new file mode 100644 index 0000000..34ce4c6 Binary files /dev/null and b/images/Solution/打标-方案比较.png differ diff --git a/images/Solution/数据存储高可用-数据主从切换.jpg b/images/Solution/数据存储高可用-数据主从切换.jpg new file mode 100644 index 0000000..373e79d Binary files /dev/null and b/images/Solution/数据存储高可用-数据主从切换.jpg differ diff --git a/images/Solution/数据存储高可用-数据主从复制.jpg b/images/Solution/数据存储高可用-数据主从复制.jpg new file mode 100644 index 0000000..399b434 Binary files /dev/null and b/images/Solution/数据存储高可用-数据主从复制.jpg differ diff --git a/images/Solution/数据存储高可用-数据分片.jpg b/images/Solution/数据存储高可用-数据分片.jpg new file mode 100644 index 0000000..a6fd3c9 Binary files /dev/null and b/images/Solution/数据存储高可用-数据分片.jpg differ diff --git a/images/Solution/数据存储高可用.jpg b/images/Solution/数据存储高可用.jpg new file mode 100644 index 0000000..1dc9163 Binary files /dev/null and b/images/Solution/数据存储高可用.jpg differ diff --git a/images/Solution/星状的异地多活.jpg b/images/Solution/星状的异地多活.jpg new file mode 100644 index 0000000..90215d2 Binary files /dev/null and b/images/Solution/星状的异地多活.jpg differ diff --git a/images/Solution/最不经常使用算法(LFU).png b/images/Solution/最不经常使用算法(LFU).png new file mode 100644 index 0000000..a6dadbb Binary files /dev/null and b/images/Solution/最不经常使用算法(LFU).png differ diff --git a/images/Solution/最终一致性-场景案例-事务执行.png b/images/Solution/最终一致性-场景案例-事务执行.png new file mode 100644 index 0000000..6ed2401 Binary files /dev/null and b/images/Solution/最终一致性-场景案例-事务执行.png differ diff --git a/images/Solution/最终一致性-场景案例-交易链路.png b/images/Solution/最终一致性-场景案例-交易链路.png new file mode 100644 index 0000000..c73af19 Binary files /dev/null and b/images/Solution/最终一致性-场景案例-交易链路.png differ diff --git a/images/Solution/最近最少使用算法(LRU).png b/images/Solution/最近最少使用算法(LRU).png new file mode 100644 index 0000000..a0dd73c Binary files /dev/null and b/images/Solution/最近最少使用算法(LRU).png differ diff --git a/images/Solution/服务冗余-冗余策略.jpg b/images/Solution/服务冗余-冗余策略.jpg new file mode 100644 index 0000000..f574168 Binary files /dev/null and b/images/Solution/服务冗余-冗余策略.jpg differ diff --git a/images/Solution/服务冗余-无状态化.jpg b/images/Solution/服务冗余-无状态化.jpg new file mode 100644 index 0000000..f93b8d5 Binary files /dev/null and b/images/Solution/服务冗余-无状态化.jpg differ diff --git a/images/Solution/服务编排.png b/images/Solution/服务编排.png new file mode 100644 index 0000000..025dd86 Binary files /dev/null and b/images/Solution/服务编排.png differ diff --git a/images/Solution/服务高可用-异步化方式一.jpg b/images/Solution/服务高可用-异步化方式一.jpg new file mode 100644 index 0000000..a88de73 Binary files /dev/null and b/images/Solution/服务高可用-异步化方式一.jpg differ diff --git a/images/Solution/服务高可用-异步化方式三.jpg b/images/Solution/服务高可用-异步化方式三.jpg new file mode 100644 index 0000000..2db4ed8 Binary files /dev/null and b/images/Solution/服务高可用-异步化方式三.jpg differ diff --git a/images/Solution/服务高可用-异步化方式二.jpg b/images/Solution/服务高可用-异步化方式二.jpg new file mode 100644 index 0000000..39f1c44 Binary files /dev/null and b/images/Solution/服务高可用-异步化方式二.jpg differ diff --git a/images/Solution/服务高可用-柔性化.jpg b/images/Solution/服务高可用-柔性化.jpg new file mode 100644 index 0000000..50a15af Binary files /dev/null and b/images/Solution/服务高可用-柔性化.jpg differ diff --git a/images/Solution/本地消息表-消费者.png b/images/Solution/本地消息表-消费者.png new file mode 100644 index 0000000..5251304 Binary files /dev/null and b/images/Solution/本地消息表-消费者.png differ diff --git a/images/Solution/本地消息表-生产者.png b/images/Solution/本地消息表-生产者.png new file mode 100644 index 0000000..6a1db28 Binary files /dev/null and b/images/Solution/本地消息表-生产者.png differ diff --git a/images/Solution/本地消息表.png b/images/Solution/本地消息表.png new file mode 100644 index 0000000..14e1856 Binary files /dev/null and b/images/Solution/本地消息表.png differ diff --git a/images/Solution/死信队列处理超时未支付订单.png b/images/Solution/死信队列处理超时未支付订单.png new file mode 100644 index 0000000..d500108 Binary files /dev/null and b/images/Solution/死信队列处理超时未支付订单.png differ diff --git a/images/Solution/水平分库.jpg b/images/Solution/水平分库.jpg new file mode 100644 index 0000000..08874ed Binary files /dev/null and b/images/Solution/水平分库.jpg differ diff --git a/images/Solution/水平分表.jpg b/images/Solution/水平分表.jpg new file mode 100644 index 0000000..e241b2d Binary files /dev/null and b/images/Solution/水平分表.jpg differ diff --git a/images/Solution/注册中心-代理发现.png b/images/Solution/注册中心-代理发现.png new file mode 100644 index 0000000..f196135 Binary files /dev/null and b/images/Solution/注册中心-代理发现.png differ diff --git a/images/Solution/注册中心-代理注册.png b/images/Solution/注册中心-代理注册.png new file mode 100644 index 0000000..25bf863 Binary files /dev/null and b/images/Solution/注册中心-代理注册.png differ diff --git a/images/Solution/注册中心-客户端发现.png b/images/Solution/注册中心-客户端发现.png new file mode 100644 index 0000000..8ebf089 Binary files /dev/null and b/images/Solution/注册中心-客户端发现.png differ diff --git a/images/Solution/注册中心-客户端注册.png b/images/Solution/注册中心-客户端注册.png new file mode 100644 index 0000000..7379433 Binary files /dev/null and b/images/Solution/注册中心-客户端注册.png differ diff --git a/images/Solution/注册中心-心跳机制-主动检测.png b/images/Solution/注册中心-心跳机制-主动检测.png new file mode 100644 index 0000000..ad51877 Binary files /dev/null and b/images/Solution/注册中心-心跳机制-主动检测.png differ diff --git a/images/Solution/注册中心-心跳机制-被动检测.png b/images/Solution/注册中心-心跳机制-被动检测.png new file mode 100644 index 0000000..833c2a3 Binary files /dev/null and b/images/Solution/注册中心-心跳机制-被动检测.png differ diff --git a/images/Solution/消息队列-功能-异步.png b/images/Solution/消息队列-功能-异步.png new file mode 100644 index 0000000..57d5d47 Binary files /dev/null and b/images/Solution/消息队列-功能-异步.png differ diff --git a/images/Solution/消息队列-功能-消峰.png b/images/Solution/消息队列-功能-消峰.png new file mode 100644 index 0000000..192c1d2 Binary files /dev/null and b/images/Solution/消息队列-功能-消峰.png differ diff --git a/images/Solution/消息队列-功能-解耦.png b/images/Solution/消息队列-功能-解耦.png new file mode 100644 index 0000000..cf8ddff Binary files /dev/null and b/images/Solution/消息队列-功能-解耦.png differ diff --git a/images/Solution/消息队列-痛点1.png b/images/Solution/消息队列-痛点1.png new file mode 100644 index 0000000..403b45c Binary files /dev/null and b/images/Solution/消息队列-痛点1.png differ diff --git a/images/Solution/消息队列-痛点2.png b/images/Solution/消息队列-痛点2.png new file mode 100644 index 0000000..456a2aa Binary files /dev/null and b/images/Solution/消息队列-痛点2.png differ diff --git a/images/Solution/消息队列-痛点3-业务场景.png b/images/Solution/消息队列-痛点3-业务场景.png new file mode 100644 index 0000000..21b86cc Binary files /dev/null and b/images/Solution/消息队列-痛点3-业务场景.png differ diff --git a/images/Solution/消息队列-痛点3-请求过多.png b/images/Solution/消息队列-痛点3-请求过多.png new file mode 100644 index 0000000..43abb92 Binary files /dev/null and b/images/Solution/消息队列-痛点3-请求过多.png differ diff --git a/images/Solution/消息队列-解决-数据一致性问题.png b/images/Solution/消息队列-解决-数据一致性问题.png new file mode 100644 index 0000000..ff482e9 Binary files /dev/null and b/images/Solution/消息队列-解决-数据一致性问题.png differ diff --git a/images/Solution/消息队列-解决-消息丢失问题.png b/images/Solution/消息队列-解决-消息丢失问题.png new file mode 100644 index 0000000..375da87 Binary files /dev/null and b/images/Solution/消息队列-解决-消息丢失问题.png differ diff --git a/images/Solution/消息队列-解决-消息堆积1.png b/images/Solution/消息队列-解决-消息堆积1.png new file mode 100644 index 0000000..066717e Binary files /dev/null and b/images/Solution/消息队列-解决-消息堆积1.png differ diff --git a/images/Solution/消息队列-解决-消息堆积2.png b/images/Solution/消息队列-解决-消息堆积2.png new file mode 100644 index 0000000..5c39fec Binary files /dev/null and b/images/Solution/消息队列-解决-消息堆积2.png differ diff --git a/images/Solution/消息队列-解决-消息顺序问题1.png b/images/Solution/消息队列-解决-消息顺序问题1.png new file mode 100644 index 0000000..9255ed2 Binary files /dev/null and b/images/Solution/消息队列-解决-消息顺序问题1.png differ diff --git a/images/Solution/消息队列-解决-消息顺序问题2.png b/images/Solution/消息队列-解决-消息顺序问题2.png new file mode 100644 index 0000000..cb7f46c Binary files /dev/null and b/images/Solution/消息队列-解决-消息顺序问题2.png differ diff --git a/images/Solution/消息队列-解决-消息顺序问题3.png b/images/Solution/消息队列-解决-消息顺序问题3.png new file mode 100644 index 0000000..2a51aab Binary files /dev/null and b/images/Solution/消息队列-解决-消息顺序问题3.png differ diff --git a/images/Solution/消息队列-解决-重复消息问题.png b/images/Solution/消息队列-解决-重复消息问题.png new file mode 100644 index 0000000..04f8852 Binary files /dev/null and b/images/Solution/消息队列-解决-重复消息问题.png differ diff --git a/images/Solution/消息队列-问题-数据一致性问题.png b/images/Solution/消息队列-问题-数据一致性问题.png new file mode 100644 index 0000000..087ac84 Binary files /dev/null and b/images/Solution/消息队列-问题-数据一致性问题.png differ diff --git a/images/Solution/消息队列-问题-消息堆积.png b/images/Solution/消息队列-问题-消息堆积.png new file mode 100644 index 0000000..f481ca4 Binary files /dev/null and b/images/Solution/消息队列-问题-消息堆积.png differ diff --git a/images/Solution/消息队列-问题-消息顺序问题.png b/images/Solution/消息队列-问题-消息顺序问题.png new file mode 100644 index 0000000..94a97a6 Binary files /dev/null and b/images/Solution/消息队列-问题-消息顺序问题.png differ diff --git a/images/Solution/消息队列-问题-系统复杂度提升.png b/images/Solution/消息队列-问题-系统复杂度提升.png new file mode 100644 index 0000000..dd3680e Binary files /dev/null and b/images/Solution/消息队列-问题-系统复杂度提升.png differ diff --git a/images/Solution/消息队列-问题-重复消息问题.png b/images/Solution/消息队列-问题-重复消息问题.png new file mode 100644 index 0000000..c6830cc Binary files /dev/null and b/images/Solution/消息队列-问题-重复消息问题.png differ diff --git a/images/Solution/滑动窗口限流算法.png b/images/Solution/滑动窗口限流算法.png new file mode 100644 index 0000000..0ca0027 Binary files /dev/null and b/images/Solution/滑动窗口限流算法.png differ diff --git a/images/Solution/滴滴(TinyID).png b/images/Solution/滴滴(TinyID).png new file mode 100644 index 0000000..911be90 Binary files /dev/null and b/images/Solution/滴滴(TinyID).png differ diff --git a/images/Solution/漏桶算法.png b/images/Solution/漏桶算法.png new file mode 100644 index 0000000..7a8251a Binary files /dev/null and b/images/Solution/漏桶算法.png differ diff --git a/images/Solution/电商经典模块.jpg b/images/Solution/电商经典模块.jpg new file mode 100644 index 0000000..3246f66 Binary files /dev/null and b/images/Solution/电商经典模块.jpg differ diff --git a/images/Solution/简单的异地双活示意图.jpg b/images/Solution/简单的异地双活示意图.jpg new file mode 100644 index 0000000..82cc08f Binary files /dev/null and b/images/Solution/简单的异地双活示意图.jpg differ diff --git a/images/Solution/缓存击穿.png b/images/Solution/缓存击穿.png new file mode 100644 index 0000000..f732660 Binary files /dev/null and b/images/Solution/缓存击穿.png differ diff --git a/images/Solution/缓存双写一致性.png b/images/Solution/缓存双写一致性.png new file mode 100644 index 0000000..31c90d1 Binary files /dev/null and b/images/Solution/缓存双写一致性.png differ diff --git a/images/Solution/缓存更新-先删缓存再更新数据库.png b/images/Solution/缓存更新-先删缓存再更新数据库.png new file mode 100644 index 0000000..43553eb Binary files /dev/null and b/images/Solution/缓存更新-先删缓存再更新数据库.png differ diff --git a/images/Solution/缓存更新-先更新数据库再删除缓存.png b/images/Solution/缓存更新-先更新数据库再删除缓存.png new file mode 100644 index 0000000..ce28059 Binary files /dev/null and b/images/Solution/缓存更新-先更新数据库再删除缓存.png differ diff --git a/images/Solution/缓存更新-删除缓存原因.png b/images/Solution/缓存更新-删除缓存原因.png new file mode 100644 index 0000000..09e9793 Binary files /dev/null and b/images/Solution/缓存更新-删除缓存原因.png differ diff --git a/images/Solution/缓存更新-请求处理流程.png b/images/Solution/缓存更新-请求处理流程.png new file mode 100644 index 0000000..c482b20 Binary files /dev/null and b/images/Solution/缓存更新-请求处理流程.png differ diff --git a/images/Solution/缓存空对象.png b/images/Solution/缓存空对象.png new file mode 100644 index 0000000..362e785 Binary files /dev/null and b/images/Solution/缓存空对象.png differ diff --git a/images/Solution/缓存穿透.png b/images/Solution/缓存穿透.png new file mode 100644 index 0000000..c94e356 Binary files /dev/null and b/images/Solution/缓存穿透.png differ diff --git a/images/Solution/缓存雪崩.png b/images/Solution/缓存雪崩.png new file mode 100644 index 0000000..54334eb Binary files /dev/null and b/images/Solution/缓存雪崩.png differ diff --git a/images/Solution/读取biglog异步删除缓存.png b/images/Solution/读取biglog异步删除缓存.png new file mode 100644 index 0000000..3b0b635 Binary files /dev/null and b/images/Solution/读取biglog异步删除缓存.png differ diff --git a/images/Solution/达达-业务主流程.png b/images/Solution/达达-业务主流程.png new file mode 100644 index 0000000..c05d8b3 Binary files /dev/null and b/images/Solution/达达-业务主流程.png differ diff --git a/images/Solution/达达-业界全链路压测-流量打标.png b/images/Solution/达达-业界全链路压测-流量打标.png new file mode 100644 index 0000000..05a8f28 Binary files /dev/null and b/images/Solution/达达-业界全链路压测-流量打标.png differ diff --git a/images/Solution/达达-主流程接口请求量分布.png b/images/Solution/达达-主流程接口请求量分布.png new file mode 100644 index 0000000..8de2577 Binary files /dev/null and b/images/Solution/达达-主流程接口请求量分布.png differ diff --git a/images/Solution/达达-压测平台.png b/images/Solution/达达-压测平台.png new file mode 100644 index 0000000..bbf1f0c Binary files /dev/null and b/images/Solution/达达-压测平台.png differ diff --git a/images/Solution/达达-压测平台操作界面.png b/images/Solution/达达-压测平台操作界面.png new file mode 100644 index 0000000..95953cd Binary files /dev/null and b/images/Solution/达达-压测平台操作界面.png differ diff --git a/images/Solution/达达-压测模型.png b/images/Solution/达达-压测模型.png new file mode 100644 index 0000000..473357f Binary files /dev/null and b/images/Solution/达达-压测模型.png differ diff --git a/images/Solution/达达-压测落地方案.png b/images/Solution/达达-压测落地方案.png new file mode 100644 index 0000000..150f7a1 Binary files /dev/null and b/images/Solution/达达-压测落地方案.png differ diff --git a/images/Solution/达达-接口响应上升.png b/images/Solution/达达-接口响应上升.png new file mode 100644 index 0000000..1597822 Binary files /dev/null and b/images/Solution/达达-接口响应上升.png differ diff --git a/images/Solution/达达-流量回放.png b/images/Solution/达达-流量回放.png new file mode 100644 index 0000000..aa25160 Binary files /dev/null and b/images/Solution/达达-流量回放.png differ diff --git a/images/Solution/达达-订单热力分布.png b/images/Solution/达达-订单热力分布.png new file mode 100644 index 0000000..26d3733 Binary files /dev/null and b/images/Solution/达达-订单热力分布.png differ diff --git a/images/Solution/达达-链路梳理.png b/images/Solution/达达-链路梳理.png new file mode 100644 index 0000000..13bd661 Binary files /dev/null and b/images/Solution/达达-链路梳理.png differ diff --git a/images/Solution/达达全链路压测-节点信息.png b/images/Solution/达达全链路压测-节点信息.png new file mode 100644 index 0000000..712c3b7 Binary files /dev/null and b/images/Solution/达达全链路压测-节点信息.png differ diff --git a/images/Solution/达达全链路压测-链路治理SDK.png b/images/Solution/达达全链路压测-链路治理SDK.png new file mode 100644 index 0000000..604dbad Binary files /dev/null and b/images/Solution/达达全链路压测-链路治理SDK.png differ diff --git a/images/Solution/达达全链路压测.png b/images/Solution/达达全链路压测.png new file mode 100644 index 0000000..eea90c0 Binary files /dev/null and b/images/Solution/达达全链路压测.png differ diff --git a/images/Solution/链路追踪-开源组件对比.png b/images/Solution/链路追踪-开源组件对比.png new file mode 100644 index 0000000..03111e6 Binary files /dev/null and b/images/Solution/链路追踪-开源组件对比.png differ diff --git a/images/Solution/雪花算法(SnowFlake).jpg b/images/Solution/雪花算法(SnowFlake).jpg new file mode 100644 index 0000000..fd77c12 Binary files /dev/null and b/images/Solution/雪花算法(SnowFlake).jpg differ diff --git a/images/Solution/饿了么异地双活方案.jpg b/images/Solution/饿了么异地双活方案.jpg new file mode 100644 index 0000000..d093ca0 Binary files /dev/null and b/images/Solution/饿了么异地双活方案.jpg differ diff --git a/images/Solution/高可用-服务监控.jpg b/images/Solution/高可用-服务监控.jpg new file mode 100644 index 0000000..c62a0cf Binary files /dev/null and b/images/Solution/高可用-服务监控.jpg differ diff --git a/support/APIGateway.drawio b/support/APIGateway.drawio new file mode 100644 index 0000000..6dafccc --- /dev/null +++ b/support/APIGateway.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/Algorithm.drawio b/support/Algorithm.drawio new file mode 100644 index 0000000..409f88a --- /dev/null +++ b/support/Algorithm.drawio @@ -0,0 +1 @@ +7Vpbl9o2EP41Oid52BzfsR59gbbppu05PKTJm7CFcdZYxIiF7a+vJEu+gJal6YKTwMuuNLprvplPMxjY0XL3S4VWiw8kxQWwjHQH7BhYlmn4HvvHJU+1xDekIKvyVHZqBdP8H6xGSukmT/G615ESUtB81RcmpCxxQnsyVFVk2+82J0V/1RXK8IFgmqDiUPoxT+lCnsIatfJfcZ4t1MqmB+uWJVKd5UnWC5SSbUdkj4EdVYTQurTcRbjgl6fupR43eaa12ViFS3rKgIfPVvTp/Wcb+o9f/rB+f38/ib/eqWt+RMVGnljulj6pK8ApuxFZJRVdkIyUqBi30rAimzLFfB2D1dYPmCb87CartAPuCVlJ4RdM6ZPUNdpQwkQLuixkKy7TgGuOL7fCZS2Z5EWh5qcVeWi0we4xnJOSTtAyLzjIIrLME3aCKSrX7N+Hqewg1zM9MSGtnv6W84nKJ15556pqvOs2xk+yloi5623W98Qv51mFSNGabKoEH9OCBDaqMkyP9LMb2DB7w2SJ2d7YuAoXiOaP/X0gCfys6ddigxUkPP4LVKwbVH4cqDiDQkWDFK9g2w1nrJDxAhh7IDCA74PxBIQRK0wpSh5UDaoBbP12jJqk2pdwpfWQ6H3dENVwtxbqDFgH013t2sZ2Ly6AbC9jMPZBOAFQSHxX7I79HYEweKOWWq9QqV0qIQWp6mWqbPbGZZfAnCu7QKNXfHu4/v1vkz/fdg5cL9E/IRPXh1TiPctr7YoDbrvIKZ6ukIDSljFz32y62GxMkIN2haucQQBXfGxeZsqMFHXxAVmB1mvZsG8sp1mX3LWYgNlqVF8cO4U99xOcJI3hdlpmvuu4RnPyR1xRvDtuTYfolwMcSXrySdK8NbYtwZuulC065O4ZZ7KX0VV61jN5SPtED+kP6SHhVWq84dI7451h9vnUeYFPRe0v5Z/OS7KnQsgbEkL2IYQUWXXIdcS5NISiYPHCT0AcpoY45tjTE0c6gjPjlYhjnzlMQ8Mc1iWZw9I9ta7IkXwfj3LnRH9hDhrAKWjcsPJDYGXQCM7RkAsUIVHMWSaYCCrx0JITRP2Xk07oCdKBAEIQODeueUWusa2hucbTYMIBoQ98Qzw4IhCYHBwsfucPjqZJFILgAA3sbmhf5ajIM+YD4gLPeQu/vzxBRSDFyzxNax+EWUiPZk0UuSJ5ScVx3RC4MZ+JeZq11Pm3QODQ+P9fxLmnTF+jS0OjS+tcuvSP6XLEMyCh09Fl866sszOR6DMBgSu8ggmgLaw/FAjwuRsIhQTGQuLwptC7IaDzdPQ0EICXhICpsedreg7wGNSA/TdB0+E7CELNU6NQ0x3ypWBqXMkVwGhwpY8GVbou+XA1z7+59czzz5t5rvdKfGHsJal1fHHZVING5zdD/2ZDPzkOhEMauqUJBG9KP7vSOx+IDOHdddF/k1GuwzmRBvA9nhLg+YDxz5xaHsLf27oQ8aL+vn5WvpQDan86Z002COMT3gFXFPa5EPZp3NGo1bxo2Kf5sfmYWh3xRUQsIn6XK/SW2Ono19s3W12Wzr6ofjU/LR+6apnj8ZWio05qR3z3whXt8848f+Nx1YeG6MwsuiaBCMD68xhWuOrUzgEGBk/tqIlfwEBtyPFpYHA553ONMzCwgeMT+P+KMOBCv4cBR+fnXwkDrNp+NSzaOt9e2+N/AQ== \ No newline at end of file diff --git a/support/Cache.drawio b/support/Cache.drawio new file mode 100644 index 0000000..4ec0642 --- /dev/null +++ b/support/Cache.drawio @@ -0,0 +1 @@ +7V1bk5s4Fv41PHYXN4F4hLZ7Zqomu9ntmU2yL1PY0G0mbuPF7vTl168ECIMkbGwjkB0lUxkQQsjSp6Nz+Tho1t3z2y9ZuF58SqN4qZl69KZZE800Dds0NfyfHr0XJZAUPGVJVFbaFTwkH3FZqJelL0kUbxoVt2m63CbrZuE8Xa3i+bZRFmZZ+tqs9pgum09dh08xU/AwD5ds6Zck2i7Ir3B35b/GydOCPNlwvOLKc0gql79kswij9LVWZE016y5L021x9Px2Fy/x4JFxKe67b7ladSyLV9suN/yavf/Xitd/Jn+afwebtel+D92bspUf4fKl/MFlZ7fvZASesvRlzT6M3Bln2/iNNxXhjLSw+7UIJnH6HG+zd1SvvMsqx6cEiF2evu5G23WtomxRG2mHICQsZ/ipank3COigHIcjxsTkjImzRI8NZujgCR88xNmPZB6TcvSY6hIzfq+LZBs/rMM5Pn9FqwRVWmyfUZcmBjp8TFfb+/A5WeLff5c+J3N0+0O42qD/fXooK5TrwgDofLPN0u8VGE1cI1ku79JlmuUPtCIQw8jGNb/H2/mCPAdV+kLGD9+1COeLlyz+BXdoAssaD2W/w5dtWj2r1jY0Z5bjVD+zDoi9+KJR0ooGA1gNOJgEkTU8QJ2FAxCFBuswGv4dR8mmExaQDFjjw/n7MllFMRrU4AA8ZukLqhj9PqsKwvl3vCRX0T9ftqiV+Dwc1eZ2BoGNhrEDwh7hPJ7Pe0HYUDiy3G44gqJwZB/G0af3h3/9fvk4cuYwnj12klRhDB8vC0dAHxlH4PCOHUdIhSlP4+UsfZ3uCoK8AF1YpFnygaYyXOLCVeRjZQlDahluNsm8iZ5Kd8EnUbhZxBG50phj6xQA4fMaKALgWt4Ed+ot2X5FZXp5/A0f34LybPJWuzR5JycrNMZf6ye1u/Dp7rb8jNzXuoX2tAFu0pdsHh9WOrZh9hTva68UI3iK98K3Bk+wZ7vM4mW4TX40lV0eZssnfE4T9HOr1XHjuE3ljaCRNFH87vKuunJKNcQsM5tqqBgYpiGE2vC9Vm2NK2zaO0w9xi5X825BFg3ulmc1pKevWIdZsdatNgWab2u+q009DboavNemrhbcaZ6VX5poHsQHnq7BO23qaB666mhTqAX3uD7ZOrJqi8B10O2onXvcDr6LbRA9y9KCSVnZ9/NHGHmJrQUQV6OFCZKT26YwCJfJ0wodL+NHfAXL0gQZTH5Z/JxEUS5psniTfJRWAF5j5dygVkGgAbzCsezelJKgRTAcliRlV3EDWbpFaE5XZFE3Bcv9veMI1V1Nm9I5PHavMGzOarRFbRYuAz2bBz3TCZ+xvrCabdb5+OgYaT4CkouhEhgaNDGcvEALbD724FTzQX7XFEMUNY0Qi455TSPwAS3ID+AEV0I9CnyM1OsFn+dOdNcVAT4CNo5iYphDgg0e1nD9z59R01/imSSWswAL2XFKKUMBoPjDqhpTHf8dUIPtaAgJM6i9DgqsROqonv+pq6OVCvqtfGgXdXSngX5rKKCd1NFHgP/uRVvP6ijsqI6acquj4ER19MagGtKphlrU0b5URuLbrC0So9i40TZr5hsnzDdntMcGeKPusoP7n3+74g0W6vivSDlq2g1MGBxPtcEDtylKkhoc7z1X6Ud6W6HrI+3N9yQBgan3AILeJ7s+mTpnMi1hk9kedtisw1Vjxpz/veCoUT44N69l73xUxcVdLsZFzy8WQ48vGe76bXfnTnUH2BJE2jfWyj0sMQpLEC2lwpD0/JquVvSEqGtdetelA+cYwhyzF+Ry7w7XQRUQ5DmPYK0bBzdb3A5RfTAJWn+3FMvHcLsuH54yI84KYWSiPewyYuM14HafyYnne6rBHIjQx6ISb6q+Bg18AO0cbQ5eBiWSTM2byoIBAfuoeQdthH2R+2gz4As8zj7qDrqPsrEZ8zg3ScCRKNcLEXG+jIo20oSIpVssRHi+DXEQ6RJ2kchq3QVRTrRAT7F2LyaIYklltRoeRXkgHpqjgyheS8x7KKuVjXQo5sxgzBkgG3PGYKMPijpzAdQZGkijU2eMDpEFxZ2RH0ijc2eMLrEHRZ65OPIMUTwOKn5Ekkii+eGAYCPMQPB4rObHrDRB9Bl7DP6MyQZDrNtCcSj8OhDkTm4fewJL7+Ukd+fc5/5MV/OQHQ+ZlX49BrlwZotj8+f9ILNFmEFusrEPRW25UndQ5UGWhupC5LTiusjFdaH3wbG5Libv7RGJ3YY/J9mFTMph9dGRW308le5iwZbtfSDHockGYBTdZWS6C5CO7mLyYjBjMST8nCswBEOi1dJRhIcxCQ+tuimzSiyeYSSMAGGyQRhFgJCKAAGkI0CYbKRGESDGtHiBdAQIkxeDkdiS+TkJEN0d4a5UlgzoiwJBNzQ0BcLsEGGqApBZukG9OhR9XIazeBlUEUeColWKw47HrROXlbk1QWfnRndRVb81dEd3HKSTGq7rQQM4LL4rtzODb+EOaeCY/Hmu4dXh4NURJR8tNkjBTLzoTDpOc9MwLM9jBsX2eKMiLJeOxUswpChBp+0CFcYuOJtOB8e54gQJp3Kcj6TRSUEWz7WuSEFDk4LOR9LorCCLl5qJNm0UK0gaVhCB3EFriCgfh/ngchHCb1yr6Xu3iDfl6PeYgdFsCXSM7FxCXh2LjQfgxDrs24M1StDufcI7RQk6T47T2W64nCBjSB+Vxbq+FSfoMjykR8BPGhKQ1eH9BkUCOpsEdL6GOTYLyLow3/l1sYC6q4tuV3XRkFtdtEkuiaPVRQdSiqdDtSTYfW6x7nNFBOqXCHSCOJWOCUQ6MD4TaJgUKaxyCvG/uAP50kAd2GPkYE3Wx0QOjiaqOET9c4hO0WWZBWXzLCdhpCGbDdMo0pBI0tApUlg21hB5x0yxhka3iaXNm2LzojQSmz7XRRsS4Ck35TJ9bLepm5qkg0ebPnRLFt2SYNPH5kShOL5sFydwLDyOSOvH0lUKeTlkMscj5OJo2RztDvmiRNOBQHMzcAk3rU5NJy/+DMMGslWCoP7YQBXELpcNZKsMQRzP5+BsoPORNDobyFYpgjjSanA20PlIGp0NZKscQRfFBiKQO2jj2F3fjbDlShKk2EDdvovHyxKUuycn2ItZft/Kz92TFram8IGpBR7PzXnUZ7ZY1zkvgblvYwdX+RqvYh6ds2fI950toLIRyQK/Y72sR8BPGuYRyfOhmEdCmUfna7NjM4/Ahbnfr4t51Fk1Jev5sGoqVwKiK2IeAZWCSDTz6ARxKh3zCEiTg2iojzOdYz6pjzOJ5Bmdorkyy8fi6SPCwmJAJScalmd0isyVjWcEVHYiWSxgaXlG4MJesbgunpEAH7xc+YmuiGcEOPEtrsMaq2fBjnAkh7wckmd0hFwcgmf01x9/PcB/fHz9e/0pff3Px/q3P4xH3ucuGU8gKcCj0skWcFo/1NpmC+t1O6R4TvPZe52P6Hcm6w2GRt9uyDbHYLtr52BQvXIi9hVU5xC9GDpYO8cLNndiw+apa7ytuI8AOh+ShxEpNcfr8TF2euLfMDCLXG9WM/HOmnrXbPqaDYeweAfwNnNnvkNUQrG5RhUXDGZc2A0zwqSFyuLEUcz75m31ixlI6AtjYUblaxqAobXbBg9sYpSJxiWz1w3DfUpD3S5sFxejWYEmFY+oiCfHW4GNdgAdNuuJh+UwS9cwqNV3HhOLO0cqLVNnctRZghmSrxlWs6tzdH9RGZi4U39k+gb/ZwuZ9jn9wKVMP4c3/aI+yseX4u22X+l8mFdjsfMvoK0mHxXG5YC9/Dx2HCVI2jwPV4siysF/Fooc2oHgmiyKeDmjxYGIZ0YqV/5eV76cypo5prJGkgEShz39RdzO30ZrtmN1/LJuX8rUnjTHZ/pzGTfxXgdviye3m+hVDt4zlDxKhR/fwcvNe0jN+Pwl+1HJ3Jq0TtfxqjnlQ0pNdu5R1+4T/PPz642dok+BaXWUmKMGOR36+xGVQ/lYmekaVEsAUi31ZeAyXS514NaeOdRyIvQWoRYxJ73SOStmQG50c32w60fUirEuYsVAi8a5SwndziuGxqVDh4x7WjGQthqpBcD2DI6yYlgjwFWehHYi4Hk6hk6Db3RPAjdvVF3tLTRNm+chOPJNuJ/NeyDUB+XCjtqpOOSwTEA+xVi5n8+NC1Jz77kuO/eDup+5uRtYqeHcHmArNd4oaE3C5ZO3Gbz8jQdD8w3J30qQHVFdfJHDIoqTVYCHKKg81cK1FNZTzXkbbFBPNec1bO+2fPvJy19H8QL8Oso+eaGmvpMTjI50ji8YjknXKPHMwx5mvg9Fknbk2Jx31bgzfAK1Gp1mKXaG7yzWLFwvPqVRjGv8Hw== \ No newline at end of file diff --git a/support/CircuitBreaker.drawio b/support/CircuitBreaker.drawio new file mode 100644 index 0000000..e2ab808 --- /dev/null +++ b/support/CircuitBreaker.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/Feign.drawio b/support/Feign.drawio new file mode 100644 index 0000000..14dda22 --- /dev/null +++ b/support/Feign.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/IO.drawio b/support/IO.drawio new file mode 100644 index 0000000..cc2fe48 --- /dev/null +++ b/support/IO.drawio @@ -0,0 +1 @@ +7V1bl6LGFv41PI6LukHx6K2TnEwykzMnKyePttLTJrb0se255NefKqQQqjYKSAGtZPXKKCIq+9u79v72pRwyffr2w27x/PhLtAo3DnZX3xwyczBGFGNH/rmr74cjXB34vFuvkpOOBz6t/wmTg25y9HW9Cl9yJ+6jaLNfP+cPLqPtNlzuc8cWu130NX/aQ7TJf+rz4nNoHPi0XGzMo3+sV/tH9Sv84/Efw/XnR/XJyAsOrzwt1MnJL3l5XKyir5lDZO6Q6S6K9odHT9+m4UbePHVfDu+7K3g1/WK7cLsv84bX7fOf5Our+4X9Plmsf8X/+W3z+A7Tw2W+LDavyS/+d7hY7qNd8qX339Wd2EWv21UoL4YcMvn6uN6Hn54XS/nqVyF7cexx/7RJXk5/rHyyWdyHm8li+ffn+BrTaCMuT2bbaCvePFlGT+ulett+F/2d3mhxiyYP0XZ/t3habyR+pvGp2P202L6If375lJyQwEbe08mXcLdfC/mNN+vPW3FwH8mv9rDebLTPTX64OD38VnhLUSoogfAwegr3u+/ilOQN2PVH7PCmBN6Y0ETcX49oQUFy7DGDFJ4cWyQA/Zxe/ShD8SARIyzS+W/PP6/5x9e/tj/+6+f95sPH/3r7d8iQqCFKKYfngh8L3pJEuxb36gruyfuCSO6mII+bN8UPkHlTqOtZuisYuCveRnzs5D53b7z/vUqFjGH17iXG1VicgNjzt+OL4tHn+N+57wTUGXNn7jmcOxNfHpmMncmdfCCO84n6GPGt79P36QI5o01VlAAxWI8yCuBgslqE/GEpz/w73C8f1eeIk/5Q4pDvelwsH1934Q/yC814csan5HsvXvdR+lmZa3tLHt4/pD8zi7CTgNVhVwgvPw8vD5ka5wIax2xpHLGBrY+7aLmI7XAv8cNCvqJ28MPxPfE8i/ghhPcLQOYibEh4s44XrbxgpKhW652wzOtIrnUv0as8nhX96bW3yCpUAowvni+S1XYTPuw7sQkI5WWKECRUU6Y4YJaEyvoiVKWqjQvVvqJy0jOheueFevST3fN+cgPq+f51uV4txHeYRtuXaBOeEORSyCfcndTP+K3JjwH84aaUNe81E4+ZcmWAXG0ZYN8QKx45c+ZMpg7H0rkbz5wx24WLlTPnzvgu9vQ8Z4LkyzoAxE3Y56WsKZIeojytV6tYbrtQeAWJky3B8xytt/v4t7KJw2bySmLNfEkkC6zb5VQ8Fa/4xGi/SOyMm7ycQQWecjqbWFRwTDWrTYkJBAxpuC0kcAMJweggd+aM5w6fJq6+CumuUu53d55n1bAzHOQNgG8adihotib2wBA7ckdS1/k81nXm8Dsn8OLwbuIERKIgmDvB9IpRYF/78flVAPLDraFA+RIZGIx/+uDM7+KFgJsrgjMPnGAmkSAfcIkNaSeQOPKT+AbpW6cqint5XmyLY8KvyY+UUeE22j0tNmZc+OtPH0Y4ExQerqj4iMPHBT1BJXYbQKU19HEDbQRyJok1tEFMXQOclLBVYokSuuQtnqS7ub1/eY5PcGPcus6E95NUeODLcGmJlLrnjDKbcPLc/JLWOamAIMqziwA0FWvjAah9oTKa9087D0ARRDZqUg23q7FMfYlny83i5UWmVwqTM6vFy2Oa1ckDgdQINTUfIvBnri+PC8Hsvv9XmvSRz9TzP+Pn6dPZt8TkH559zz77GO7W4g7K6PXgqxSZjkKMGEHwBaFuuMplDU38ZPDBTij9LtyIpe5L9lowZpJP+CgX3yw89Tga5y8hVHe3DJN34UxuULsQwvz0hfaL3edwb1wohnD6sy9AtcmAeqPYxxYeV7xs8ZkMv+SDSeyQC1+LSterH47OmwzCPI5yQsc+7TYIQyUo04FdOytXpKdfA9PThdg1pILy5gVr0qY05tdkdH3wWEXINI4fCIX3FNkiwmwq06ncdE6vR8+th9l5FwZTPgLibNCLsaboJt96mRNjz28pzJsX6vgV+hlIY2qQysBX9TM85p2+kG0/w6R3B+958J7VOlkT1br3bFzINqpN9vomUR1+W++zlxNPM1cTz44Xk0++Z56cUZBjuUthDvwKFYQEeUeSENSMghgXsqwg2OT1BwVpVEFudQXRfGsVaVVVEMKD0xeyrSAlioYtRt5ghVIjkXdqnluJvAl3tXpwoij/M7G3tSSTEuTAqVzIqWiBC1Tm36pczTwAGqk6BRTzoyypUhKrOJ/FTMpsKFi4LGXs5kFAPAMErRKmQPdOi8oN5/UaUe7UaWhFuT2m0RslCVPPmlw7JcKvVq7YL0mEW2tCMnlwNoo7hWayaEeWF80lAw4kvljcX+TGhn0u/67XjNtPfKlqQ4ULBOCi1apTbBLifCRryrgb50V8J7iLy83K9JtdLTDSuNial8dLeO/tAsMkrH0wMR7bB+4dPcDrhYF1+0A9ranX8zv280yClxzWjYnUeCnzmTMh8eJwJy2EOBJMZFnqgILmUOB2XKNOSrCY1ju78xEQEAX7LjHvCWW2XCoCMVdDY3cvGrtTwJbXuZ415hKIPRtKtO3Aq2o1bw14USozH/2CWImC3htr/q4u2KBmn7C1ISykLy39/Sm+r7MaBLq69kCyQ5FqMyqb52CQV46bs5ZQIZ229veVc62jtMYai0smQa3x6aTpetOW6j8aLz6tbKzPFlqozLch4FNlF+UrLAIPRlLVAgsauKcvZLnAgpQoPB2sSz3rAgyXA62LrZY8UqL8cvAISngEeRUlNOjWI1Dk1ltbNXpfLJ4pRHSOVYjqOFyDWBF1h3WhhEHueoGjesqJ+vVWuEArsaXeSDN4lpc4ajKxZDSMTGrcTAq7rBUZQrFTq5kJatKk/sjMRhXMUZIPpg734xK1iTOmVwyPqlmrGvBA3kiNflC2AEMeUqsAMUlOOkIFHd7B1BkfekH9JKMtIMHjIUxjJosYB3jUhwdDeokydoF2UNoqOqDuf5QO4HNjyftJpltiYabqoTwAQQM6mkQH6R4dJuE6VDpb9zKMCe4dexgmO4tHQE7eGMN2xSCwbw7yIMC04wIYCldJGnp/S6M6LVqCNJI0V4B2xW5ypxNtKqP4PgdxH6cyFg9jvMXpiLV1PyN0MNFqj30zWVU6wgW1r2L5n+AhZGgtZABmZ7brFKroNRcyFKFDDxluaHnoJGTAXYcMzKQi6QjwDct23txJ/nKIL+zCJm2+yMLGbxU2Jo3JRsdizbQyUzocVFoV2aAjUIEV1TnJBCIHznuSGR1+Q0xFC/hxjdowsJaTtwogk+b0LrA7N7xudQMgbvZztwwgoGxUJs2IakWg0pioCvTdsS68qenyMTSn4o9moqk4bJJtiPWATCSFK4fmi5OJnPQomysm8lLwajuWZlZa1Jn8k3sw+HFzk2mH/fiLqZeSCGAqOaF07j78Daeyn1L+HF+OkT2ezOR7ZcYqbf9I7Xn8krj50rDPnXGQRBuT8fTj7xn7H0+wlDeNym+YqO/Bf4iPiC8jv0B8b+WnzOSdlA+m8a/24nuFiub7tyF5r67kSxisIxZMycfJPz6vKGfoQ5NOaaiFR8htMj5Kvug2X49dtc0PKJ8/ME2nD/VRWKvxZEBdNmxfpglaC7kCQ8luPhFlD0WqGEQtwQzw4SDeiVrDEVAHfgpHt5WvtgcE3+z18IGAsNXuewbUjd+8p96aLQALQFudU81KVG23vJc2UwtodqFlQFlsWvHe/F2B6p2HplinF02xKWQrBMI964hVCB96Jy+RqhJPxcZJoraxal6snc4Y7WsLTA3JUs1NYuaKAE4101uempOsyZsPkq0l2XwNf9nOJrVvT/OCNflsNjqx5+WQUGsqD1sDPHr0FADgaZXM9kwymw/ZkLeBHuKaI89aRk+JOQt97L6z0S13bOgjOD1w6OijTo2Ovoow6sm0fUb0tqeazeCpL5xeiI04d9P/2t2cwoMGTwwTzHoxway6IfXyYVf3wXSJ0Rc3Nl6qulCRRpH0IJjudJ5EX2cO1JGs1ugCtkpCkrUl2Kb38npT02mOnk7Oyznj41REx2E9PyWDsrsMfcsDJMFQmvpofFgAcrWN5lTBWVUPyHClWLs+j1+C4h3MV/WFifgdz0zxS5C8fYzaej8zpeGILR14n4MPNZt+mjFcTOUMU5zW3WpTS4cSv+0xJ75JdheNObnmIfxVC3Sr2zbsI71DAGosgaIpa6SUbxLiQy+6dSSg8zBotSHVN4ltPILKBhXhAr6Uo2CyL8WVz7J+EHpJWJixuaQWoyldTt4MnuyT3Z434lqmDZhQSgBI2cuh+ibdjdyhud26adFmDkL+c7srjMkG/3q+2R1Ox95813sFPGT73KFeVXsRU5nZxMKdV3cj3NxHX+fHA5P4gHjhMdqt/xG3bbGJg5LaFBG+PKxK9dYMaipzRpYiH9xS5GNG6HWTVjoHrWe/CuIeAYPF98xpia6eCNXyK2MC/MKvxfI3kiSE6lElDl+g2RjMZL/paP7Hh9/fzybvP0x/7ompe5tLIqa+7h4hFfR2NkDQh2jxg38sb5BTIk8qbhaUJ51iGbBl/3/Mih4ufX1NYe1jSpvtDPX6Q3iytupygIAeunzsuVeqxYN6I2JWWiSoCICsaqv9Prxp9toiYa0maCd8dTJEO6GrC8doyydnuOom8ng51t6SB4fylLKIoEdAw1hDXhyimlfE63lxevG2cSHL7LVqscpNqIDZ6+01WbQO2GtN0Khr7poDG80NDdJ9XzGplqnHalhyZw3SHJ5RMrhO7QIBrFRq11uqOHFhAEJDQOAnfGiw7aNdWJQdoHBLDR2tmQUwg9Fq0zw3CW1/mKLXTy9Vb/vBDPAuWm374Sa1GwxbyXSTJ8eMjbBmXsBm41YBAjDB3YzIYwWD0i7pje1iVl6Hs+b4MGvuigyUNUOksW8UKLCnULmOtWFzgUmjDT177ffsFQ1ZKgSSrwVLiAMph1b78wKAlbscSf8OF8t9tOspWFjIV9QOWDi+JwVOUWWwEOL1Di0A96bL83q7OStLECFNgpiUa9zEga3NooMSYw3akaDSwsYl2KgOclRPgiiw1XobQP36mgSvunethhpSTYhQEqPNNrWgTNFlPxL9FrLzhSLtyaAPpKX2EfZrbopNuJ400a9kOd8emHRWm8YCtPGNGIvUyDdtLFICW4ksHQvbmbEo0ZI/GPy8wdemKlBobx9AhtZYw9RNv/XGZIt1Y2lYV2grqvY492tZIlyLJiiquSwhjM5cyfKyhFTWWNtPN+XRD7zoNGZKx3EmLqZMJ2nS/rBHipuk8YWJHHNDn94wlVk+E1fdOAaaccTQnPNW98tDrslnIjwqSLJdr5hPpNSq+zFMtxZQeXOrFYDpsKOhfd2OamOdbgI0u9VOdeSahCGD9k2HZu6rPvQaXedvDQSpu9WE4qtAJA1giNlc2LbiA93lJC6r4PNY4dPyGl8u8zL96cnM66D5ZTVfkzkY8LQsc5OnlI3ksfsm0+xxflGO4U7T2mn2MdNsLh/gYWf1ZtYDqGYXQ+VV1rgM5DbNfHYzVvnyYDMbCidTldNY2DsdCzc9jhkKrgvR15tgWGdWA5q2DVePh/H5i1kPiUtQtdeaE+g52JBuSCkJ6iHNIzozqV/JOsyG3bN6sXtW5cWcBZqR6r4cJC3s77yaoJOtsiqL0CO+ts50Xg+SjhK/2c2TakhRcz0IAgJsILtkrRxUOPaDEKsKUasJodSMjcA0L7cmxBLFddcziSsTxVCWZhoPUQypE8VUj9COAZhHeS4Aw6cDsDcZL+llDR4buTXjJaJSCenFMB5xWnUHm6qjwSjWdJYwosK0oq9qvofq77EzIwyhEpWWPY3nGiA3+q8P2ogxY6+U2iGdcSXbIR0qURHaR07t7Q297xeGTeTVr1P0z1zJOobfKC88YLhhDFNVblmdxNVJOv1K1jEM944Hsg9UZrx8J7iLk1hl2uw07F9P2rPJXDd2NZmnhqyz8SNp8XhuxNlLuAmXAnV3z1G0GYRbbrSIXsWLO58tkkIpt6fwS7T8O9xnNpZWU0OuV85NVqpRvSuWUK9zOZusKEK1N4/2pEWXZp7LRvxgNgCjHEfHdN8AqlT12gWGybQit2CC5RWL2WpBMgWm4bdcvITNSlUy1Ke3suwjg1x0gVq2duvTcQlqfmjByrdg6Y3TAWC7gfxK2rtlQYomI0sP5edzNeMNxYEak+v4cagPOar59apwoxZd56WgjfNadujKToUcpg93UqrKCNYwA0XyrQ4aFvFmNczc+oDZ6kLHej0MFOG3S99goAxzGB9rQcHBfrSWF4XiWsjLhwx+OlB+83hAn/QhUtbXJBEO7sU80x1BZb2ktCWebItJJ4oeVhJ+B8+YPIwpPcwDlPMNveQ6fKw6JKYFhEXcPHHRenhTcwWrc1yuzmWq+qNsK0a7yxsxucyOJpn6wyTTS2+hCLeGUaaDyclNT9aHJyMG1Di2O7g0je616ewXKnsKHi7PmVxVcNUmwQ62CvuWfLLX7fOf5Our+4X9Plmsf8X/+W3z+A6qRk+7Uw56rnKpvWgmubxppF6p9bk+BQMEAFSKTQdGI32fMN8Mx32oPLqBRgUQGGbexQDGai2WAymNfkCjuz6ji4RPXN474ZeqcLykND56DrcXAEBVj0OTrZxyvbxOpdoxE23ix9yt5V2NX1/GXzhbiqvVe4Erx6HU6pQSJkHLoZLq1IkcxlhLxb3ecS9GhWFct66MoUDXB0zKVUdWLXAnzDU+KhkV2lStOqxe0J4WLOYb4rK18STZDEdugnKoZ5jLQANSwfdyOYNdrTQNdYGzldU6r7Sa5hNbgHlcRtutcCqSj3ZS/yarJCcsU6ExdUfHITyJSJWLc2lPcn7ixzuav0D08PASXlrmCP/iMpW6Hdpir9gWo9u0xaxLW0wJGnmuSwmljFFdGRAP8Ii4HucU+4QxX4Nx6QJ24bQQzeSjILBkpqmvm2nqtWGmodSEWW5ytgzpFs20f9JMv3NHLtMo2qbsdKC7Iu+0Agd7phqoYqxmqrOmK9rtH6PP0XaxeR9Fz8nBv8L9/nsi6CQ4Ac17Bmampc+byzM9qKXMf01LrpaN/DSe05NpG7Toar+P8xadwmi+FKrEDPu5RvSU7ygiBv3IsRYfNteQcWLhy8H/x8VWmLCdoQXH4i0p+zPcQK7P6DR9k1Wgy4AtpWZY5L1UxBzRkHxuoQUtzwNQRoxVDpofBg2U1FHTGBGgksg3ZNEwwCWVmLjUpKdZ2i4VrLKl7dJlDCFEEmkUYVyo3Q96sGXmuDRkLjManmk0WMfkITFTToPNsG4z/JI2gxQAqiWbAZEZms24X//TE5PR3a6HFxkFxvhIazDv3ioAdX7j5TJ8jrctHLzDs4be5EDSqoFs8hiqLrDmHZ6o50vVdhELuScK3Xb2+CH04Ozxyg/u3YYSiNSnZhaDd+wDlKor2PbFN3yjht4DQsau7Twxo4LpZh0mjMMVWPmUlbVk6PN5JiEos5dHzRa7cOioeLqLZHnjkQQS9/3xl2glqer5/wE= \ No newline at end of file diff --git a/support/Idempotent.drawio b/support/Idempotent.drawio new file mode 100644 index 0000000..0c4742e --- /dev/null +++ b/support/Idempotent.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/JVM.drawio b/support/JVM.drawio new file mode 100644 index 0000000..0eceaf0 --- /dev/null +++ b/support/JVM.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/Limit.drawio b/support/Limit.drawio new file mode 100644 index 0000000..3f2b02e --- /dev/null +++ b/support/Limit.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/Lock.drawio b/support/Lock.drawio new file mode 100644 index 0000000..0cff0eb --- /dev/null +++ b/support/Lock.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/MQ.drawio b/support/MQ.drawio new file mode 100644 index 0000000..49f8045 --- /dev/null +++ b/support/MQ.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/OAuth2.0.drawio b/support/OAuth2.0.drawio new file mode 100644 index 0000000..ac3d75f --- /dev/null +++ b/support/OAuth2.0.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/Redis.drawio b/support/Redis.drawio new file mode 100644 index 0000000..8f2c8f4 --- /dev/null +++ b/support/Redis.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/ServiceDowngrade.drawio b/support/ServiceDowngrade.drawio new file mode 100644 index 0000000..4c0a13e --- /dev/null +++ b/support/ServiceDowngrade.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/SortAlgorithm.drawio b/support/SortAlgorithm.drawio new file mode 100644 index 0000000..39c92ce --- /dev/null +++ b/support/SortAlgorithm.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/TCP.drawio b/support/TCP.drawio new file mode 100644 index 0000000..76320ea --- /dev/null +++ b/support/TCP.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/Thread.drawio b/support/Thread.drawio new file mode 100644 index 0000000..b054dcd --- /dev/null +++ b/support/Thread.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/ThreadLocal.drawio b/support/ThreadLocal.drawio new file mode 100644 index 0000000..05de322 --- /dev/null +++ b/support/ThreadLocal.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/Throwable.drawio b/support/Throwable.drawio new file mode 100644 index 0000000..200388c --- /dev/null +++ b/support/Throwable.drawio @@ -0,0 +1 @@ +7Vzbdps4FP0aHtsFiOtj7NjTrtU0M5Ppbd4wyEYNRi6WE3u+fiSQzEW4pSkX24V4rcAByeLsvY8OkowCpuv9H4m3Ce9wACNFV4O9Am4VXdcMXVfYRw0OmcURhlWCAn5RbnhA/0FuVLl1hwK4LV1IMI4I2pSNPo5j6JOSzUsS/Fy+bImj8rduvBWUDA++F8nWTyggobgLO7e/gWgVim/WLDc7s/bExfxOtqEX4OeCCcwUME0wJtneej+FEXOe8EtWbn7i7LFhCYxJkwIL1QrCJ/Ptvx8+fPz67s23Tx8T9IrX8uRFO37D94uvzI9Zk8lB+CHBuziArCpNAZPnEBH4sPF8dvaZIk9tIVlH/LSP18jn+9tHSHzmCZUebGCC1pDAhJVF8Yqbj65hBVaRt93y/SWOCeeEZvHjubdGEWPTNP0WXX3w4i39d/cgCvBWc78/wYTA/UmnaUcoKIchpq1LDvQSXgDwSg6Cle5rM7M852zQBMRhgQkWt3mcgKtj3TlGdIfD9BOQ6RJk/4SU596C3nR7qJEEP8IpjnCS1gSsdGMeRlFUsC9N9jcwztlxoVXZRu3tU0A3tYEJACQCzPY+3BCE4y4J4DtwsZQJEHjQWfoXL3TNdUswGwL2wWA2ZJhpf5Z0CPHCMQ1TrdG440P/+iA+h2BuSiBPQ+g/9iHowHYXah3aS2hdAdrABiW0gV4Tt80+sbYkrP/exYR6tQ+0HX0B6vrvwIROYFwd2qZRE757RduW0H5734usrYVl1iVqy6V+BbI2K0DXpWPHlK0XoB0J6Ie/3o1It450XQDvF2lX7qyZw95jMmegjpi3j7krh/F+MRfjUEV5E89/vKf3OY/w85iT/1q/7VQeu2q6bbtXvOURsY8oITsvuvP8EMVwBLxVwOlD2NCIywNq9ztyv7yDa5wcRrjbhdseHG55+OwmQSSktSO/jy7ctWzg1XThUKPPYvbFI17twg178C5cHkl7v4uiPzGKqV9HyFuHvO7hu2fI5XG1txTLfRrYJwzX7Yh767hb2uC4y6MuErwwWEHhBRgtaAqfGyapgZ4IcYL+o+7yImaMgxs2lUzteAPjMgGaeb0AE9wj8pkXZvtfGBqvTX50u+ewpwcHcRAHc8RccVuTKuQzMCfnbLIzYgKbQsonxpgvvg8+dR3eJT78jtM5CYiXrCD5znVWPZkKZKkboBO2BEYeQU/l5tYRiH9DGt4LXJUeNCoczG6Tl9IL0+iViixQrgiYlYoyP0gVpXw+3vYvUFwehRgpXktxSuTk8PlYPT0otIMd5g1Jj0RLBpCGOaQ0QIXRRjU8N5UGsMsVmU6/0tDlsZrrl0a+AOPkko2zlIbeUBrGoNLQO5IGMHqWhjysdX7SaJ/mLxDbABQHQ1K8uioC6C+kuK6VK9KrGVbXFJcHds6P4q0nRsfp+5MT/p3m/lZDimvDZjjVMF7N2S8mw5GHskaOn2uG01gbg8Z/w/0BpZtqw6xow6rmSl1rQx7zG7Vx8doYNP3vTBtm37mRvPbw+rWRL2Q9ufS109zIbMhxd6R4GxT/HYf+X0TxMwj/TaVhn5M0pDGbl0rDqD5idy0NeYnqKI1Ll4ZzldKQpoa7lsbvOJuWry07uRrtLKVhNJRGtqJvsMEmqxLtbVv8zOxn1WGoFZlZTrWqjvUhVm+M+rgmfZxY1jPMYKyuv1gf1d5Dc6SqutbHJcyrjfr4SX2oynXqw+1dHvoFyKNE9dOTyLlkvhTEdEo+guw5v7+U6N052cXLcn5E9kFzJU2k+4LrjvpSrh+nm49zfG7fZK+bgLYiwjmnsJcUiUhpfdux9/5QwIGabkVTQR3CyCp4tU1Ze0Mv0M3NPr00L2St0v8zU5lMFUdPdxz2mRnK5FZxgTKzlQlQJhPRKHqTWbt40aowCdyTsra8CK2o3G59yFbOUwNbPox8L7rhJ9YoCFLdJpA2Nn0lDV/bzJyewmBOFPOW1bUjeMtlWFalbjaVcaHnmaebqIjfBKs48hYwmnj+4ypd1y2KxDiGSivvh9ErFG743pDqE3N7r4epmSOeuYprKPShfeYoN3P2ORewNadxzG5x5bounHT8WZItoVYX9kBnoNVMXspSHkErRXh3aNBqZtVmlnIDFEdlOy5VmpmGXY2F3RHPym8D3cqPvR0ZT03rFdCaOSQaMSdzxVXT0KkpzjRFlmJqjYD+AFBLdToDlB7mr07MMrj8BZRg9j8= \ No newline at end of file diff --git a/support/Transaction.drawio b/support/Transaction.drawio new file mode 100644 index 0000000..9fb7f5f --- /dev/null +++ b/support/Transaction.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/support/lry-darcula.css b/support/lry-darcula.css new file mode 100644 index 0000000..ab1a9dd --- /dev/null +++ b/support/lry-darcula.css @@ -0,0 +1,1066 @@ +@charset "utf-8"; + +:root { + --active-file-bg-color: #dadada; + --active-file-bg-color: rgba(32, 43, 51, 0.63); + --active-file-text-color: white; + --bg-color: #fff; + --text-color: #333; + --side-bar-bg-color: #f5f5f5; + --control-text-color: #666; + + /* 主题定义: 绿色-#1abc9c、蓝色-#16b0ff、橙红色-#ff6666、绿色-#42b983、番茄红-#ff6347*/ + --theme-color: #16b0ff; + + --theme-bg: #181a1b; + + /* 超链接设置 */ + --a-color: var(--theme-color); + --a-border-bottom-color: var(--theme-color); + --a-hover-color: #555; + --a-hover-border-bottom-color: #555; + + /* 删除线 */ + --del-color: #c7264e; + /* 下标字 */ + --sub-color: var(--theme-color); + /* 上标字 */ + --sup-color: var(--theme-color); + /* 字体加粗 */ + --strong-color: var(--theme-color); + /* 背景高亮 */ + --mark-border-left: #ff6666; + --mark-background: rgb(248, 248, 248); /*rgba(66, 185, 131, 0.1);*/ + /*Tip*/ + --em-border-left: #ff6666; + --em-background: rgb(248, 248, 248); /*rgba(66, 185, 131, 0.1);*/ + + /* H1、H2、H3、H4、H5、H6 */ + --sub-h-color: #34495e; + --h1-color: #ff6666; + --h2-color: var(--theme-color); + --h3-color: var(--sub-h-color); + --h4-color: var(--sub-h-color); + --h5-color: var(--sub-h-color); + --h6-color: var(--sub-h-color); + + /* H2左竖线 */ + --h1-border-left-color: var(--h1-color); + + /* 引用 */ + --blockquote-before-color: var(--mark-border-left); + --blockquote-after-color: var(--mark-border-left); + /* 红色代码块 + --code-color: #c7264e; + --code-background-color: #f9f2f4; + */ + /* 绿色代码块 */ + --code-color:#ff6347; + --code-background-color: rgba(248, 117, 117, 0.1); + /* 下划线 */ + --text-decoration-border-bottom-color: #ff6666; +} + +/* 防止用户自定义背景颜色对网页的影响,添加让用户可以自定义字体 */ +html { + color: #34495e; + background: var(--theme-bg); + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + text-rendering: optimizelegibility; + font-size: 14px; + -webkit-font-smoothing: initial; +} + +html, +body { + color: #abc0d0; + background-color: var(--theme-bg); + -webkit-font-smoothing: antialiased; +} +content #write { + background-color: var(--theme-bg); + } +/* sidebar */ +#typora-sidebar { + background-color: var(--theme-bg); + border-right: 1px solid rgba(171, 192, 208, 0.1); +} + +#typora-sidebar #sidebar-loading-template.file-list-item { + border-bottom: transparent !important; + background-color: rgba(171, 192, 208, 0.1); +} +.cm-s-inner .cm-variable { + color: #abc0d0; +} +.CodeMirror-gutters { + border-right: 1px; + background: inherit; + white-space: nowrap; +} + +#write { + max-width: 960px; + padding-top: 2em; + padding-left: 60px; + padding-right: 60px; + min-height: calc(100vh - 6em); + -webkit-font-smoothing: antialiased; + font-size: 16px; +} + +.typora-node #write { + min-height: calc(100% - 6em); +} + +pre.md-meta-block { + background: var(--theme-bg); + padding: 1em; + border-radius: 3px; + font-size: 14px; +} + +pre { + -webkit-font-smoothing: initial; + background-color: var(--theme-bg); + color: #abc0d0; + display: block; + font-family: "Comic Sans MS", "Roboto Mono", Monaco, courier, monospace; + font-size: 0.8rem; + line-height: inherit; + max-width: inherit; + white-space: inherit; + border-radius: 2px; + margin: 0px 2px; + overflow: inherit; + padding: 2.2em 5px; +} +pre.md-fences { + margin-top: 20px; + border-radius: 5px; + box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; +} +div.CodeMirror-gutter-filler { + display: block; + position: static; + margin-bottom: 10px; + height: 15px; + background: url(https://mmbiz.qpic.cn/mmbiz_png/WmXepF87uPm6j7fce4wLfgvjjrPEd3gRG8gqcQdickA5z19YgwFdFRNIrZPkcGyyXgLxzicebwl7RFMqFLanHczw/640?wx_fmt=png) 5px 0px / 40px no-repeat; +} + +@media screen and (max-width: 800px) { + html { + font-size: 14px; + } + + #write { + padding-left: 30px; + padding-right: 30px; + font-size: 14px; + } +} + +@media screen and (min-width: 1100px) { + body, #footer-word-count-info { + background: var(--theme-bg); + } + + body.pin-outline, + .pin-outline #footer-word-count-info, + .pin-outline footer { + background: var(--theme-bg); + } + + #write { + max-width: 1000px; + padding: 40px 60px; + background: var(--theme-bg); + margin: 3em auto 3em; + border: 1px solid var(--theme-bg); + border-width: 0 1px; + } + + .pin-outline #write { + max-width: 1000px; + background: var(--theme-bg); + margin: 0 0 0; + border: 0; + padding-left: 60px; + padding-right: 60px; + } + + footer { + background-color: transparent; + } +} + +@media screen and (min-width: 1300px) { + body.pin-outline, + .pin-outline #footer-word-count-info, + .pin-outline footer { + background: var(--theme-bg); + } + + .pin-outline #write { + max-width: 1000px; + padding: 40px 60px; + background: var(--theme-bg); + margin: 3em auto 3em; + border: 1px solid var(--theme-bg); + border-width: 0 1px; + } + + .pin-outline footer { + background-color: transparent; + } + + #footer-word-count-info { + background: var(--theme-bg); + } +} + +/* 如果你的项目仅支持 IE9+ | Chrome | Firefox 等,推荐在 中添加 .borderbox 这个 class */ +html.borderbox *, html.borderbox *:before, html.borderbox *:after { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* 内外边距通常让各个浏览器样式的表现位置不同 */ +body, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, code, form, fieldset, legend, input, textarea, p, blockquote, th, td, hr, button, article, aside, details, figcaption, figure, footer, header, menu, nav, section { + margin: 0; + padding: 0; +} + +/* 重设 HTML5 标签, IE 需要在 js 中 createElement(TAG) */ +article, aside, details, figcaption, figure, footer, header, menu, nav, section { + display: block; +} + +/* HTML5 媒体文件跟 img 保持一致 */ +audio, canvas, video { + display: inline-block; +} + +/* 要注意表单元素并不继承父级 font 的问题 */ +body, button, input, select, textarea { + font: 300 1em/1.8 "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans; +} + +body { + /* font-family: "Monaco", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans; */ + /* font-family: "TimesNewRomanPS-ItalicMT", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans; */ + font-family: "Comic Sans MS", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans; +} + +.md-toc:before { + content: "Contents"; + font-family: Dosis, "Comic Sans MS", "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; + font-size: 18px; + font-weight: bold; +} + +h1 { + font-weight: 900; + padding-left: 9px; + border-left: 5px solid var(--h1-border-left-color); +} + +h2 { + font-weight: 600; +} + +h3 { + font-weight: 500; +} + +h4 { + font-weight: 400; +} + +h5 { + font-weight: 400; +} + +h6 { + font-weight: 400; +} + +u { + text-decoration: none; + border-bottom: 2px solid var(--text-decoration-border-bottom-color); +} + +h1, h2, h3, h4, h5, h6 { + /*font-family: "TimesNewRomanPS-ItalicMT", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans;*/ + /*font-family: "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans;*/ + font-family: "Comic Sans MS", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans; + -webkit-font-smoothing: initial; + + color: var(--text-color); + line-height: 1.35; + font-variant-numeric: lining-nums; + margin-bottom: 1em; +} + +em { + font-family: Georgia-Italic, STSongti-SC-Light, serif; +} + +strong em, +em strong { + font-family: Georgia-BoldItalic, STSongti-SC-Regular, serif; +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} + +/* 去掉各Table cell 的边距并让其边重合 */ +table { + border-collapse: collapse; + border-spacing: 0; +} + +/* 去除默认边框 */ +fieldset, img { + border: 0; +} + +/* 块/段落引用 */ +blockquote { + font-size: 1em; + font-style: normal; + font-weight: 500; + padding: 30px 38px; + margin: 0 0 15px; + position: relative; + line-height: 1.8; + text-indent: 0; + border: none; + color: #888; + /*特效*/ + margin-top: 30px; + margin-bottom: 30px; + box-shadow: rgb(132 161 168) 0px 10px 15px; + border-radius: 0px 0px 10px 10px; +} + +blockquote:before { + content: "☠"; + left: 12px; + top: 0; + color: var(--blockquote-before-color); + font-size: 4em; + font-family: Arial, serif; + line-height: 1em; + font-weight: 700; + /*position: absolute;*/ +} + +blockquote:after { + content: "”"; + right: 12px; + color: var(--blockquote-after-color); + font-size: 4em; + font-family: Arial, serif; + line-height: 1em; + font-weight: 700; + position: absolute; + bottom: -31px; +} + +#write code { + color: var(--code-color); + background-color: var(--code-background-color); + font-size: .95em; + font-weight: 900; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +@media only screen and ( max-width: 640px ) { + blockquote { + margin: 1em 0; + } +} + +/* Firefox 以外,元素没有下划线,需添加 */ +acronym, abbr { + border-bottom: 1px dotted; + font-variant: normal; +} + +/* 添加鼠标问号,进一步确保应用的语义是正确的(要知道,交互他们也有洁癖,如果你不去掉,那得多花点口舌) */ +abbr { + cursor: help; +} + +address, caption, cite, code, dfn, th, var { + font-style: normal; + font-weight: 400; +} + +/* 去掉列表前的标识, li 会继承,大部分网站通常用列表来很多内容,所以应该当去 */ +ul, ol { + list-style: none; +} + +/* 对齐是排版最重要的因素, 别让什么都居中 */ +caption, th { + text-align: left; +} + +q:before, q:after { + content: ''; +} + +/* 统一上标和下标 */ +sub, sup { + font-size: 75%; + line-height: 0; + position: relative; +} + +:root sub, :root sup { + vertical-align: baseline; /* for ie9 and other modern browsers */ +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* 默认不显示下划线,保持页面简洁 */ +ins, a { + text-decoration: none; +} + +/* 代码片断 */ +pre, code, pre tt { + font-family: 'Comic Sans MS', Monaco, Courier, 'Courier New', monospace; +} + +#write .md-fences { + /* border: 1px solid #ddd; */ + /*background-color: #f8f8f8;*/ + padding: 1em 0.5em; + margin-bottom: 2em; + display: block; + -webkit-overflow-scrolling: touch; +} + +/* 一致化 horizontal rule */ +hr { + border: none; + border-bottom: 1px solid var(--theme-bg); + margin-bottom: 0.8em; + height: 10px; +} + +.code-tooltip.md-hover-tip strong { + color: white; +} + +/* 保证块/段落之间的空白隔行 */ +#write p, #write ul, #write ol, #write dl, #write form, #write hr, #write figure, +#write-p, #write-pre, #write-ul, #write-ol, #write-dl, #write-form, #write-hr, #write-table, blockquote { + margin-bottom: 0.6em +} + +html { + font-family: 'Comic Sans MS', PingFang SC, Verdana, Helvetica Neue, Microsoft Yahei, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif; +} + +/* 让链接在 hover 状态下显示下划线 */ +a { + color: var(--a-color); +} + +a:hover { + text-decoration: underline; +} + +#write a { + border-bottom: 1px solid var(--a-border-bottom-color); +} + +#write a:hover { + color: var(--a-hover-color); + text-decoration: none; + border-bottom-color: var(--a-hover-border-bottom-color); +} + +#write del { + color: var(--del-color); + text-decoration: line-through; +} + +#write sub { + color: var(--sub-color);; + font-weight: 600; +} + +#write sup { + color: var(--sub-color);; + font-weight: 600; +} + +/* 标记,类似于手写的荧光笔的作用 */ +mark { + margin: 0 5px; + background: var(--mark-background); + border-left: 5px solid var(--mark-border-left); + border-radius: 2px; + padding: 12px 15px 12px 15px; + position: relative; +} + +mark:before { + background-color: var(--mark-border-left); + color: rgb(255, 255, 255); + content: "!"; + font-family: Dosis, "Comic Sans MS", "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; + font-size: 14px; + font-weight: bold; + left: -12px; + line-height: 20px; + position: absolute; + height: 20px; + width: 20px; + text-align: center; + top: 27%; + border-radius: 100%; +} + +em { + margin: 0 5px; + background: var(--em-background); + border-left: 5px solid var(--em-border-left); + border-radius: 2px; + padding: 12px 15px 12px 15px; + position: relative; +} + +em:before { + background-color: var(--em-border-left); + color: rgb(255, 255, 255); + content: "?"; + font-family: Dosis, "Comic Sans MS", "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; + font-size: 14px; + font-weight: bold; + left: -12px; + line-height: 20px; + position: absolute; + height: 20px; + width: 20px; + text-align: center; + top: 27%; + border-radius: 100%; +} + +#write em > strong { + color: var(--em-border-left); +} + +#write strong { + font-weight: 900; + color: var(--strong-color); +} + +/* 标题应该更贴紧内容,并与其他块区分,margin 值要相应做优化 */ +#write h1, #write h2, #write h3, #write h4, #write h5, #write h6, +#write-h1, #write-h2, #write-h3, #write-h4, #write-h5, #write-h6 { + margin-top: 1.2em; + margin-bottom: 0.6em; + line-height: 1.35; +} + +#write h1, #write-h1 { + color: var(--h1-color); +} + +#write h2, #write-h2 { + color: var(--h2-color); +} + +#write h3, #write-h3 { + color: var(--h3-color); +} + +#write h4, #write-h4 { + color: var(--h4-color); +} + +#write h5, #write-h5 { + color: var(--h5-color); +} + +#write h6, #write-h6 { + color: var(--h6-color); +} + +#write h1, #write-h1 { + font-size: 2.4em; +} + +#write h2, #write-h2 { + font-size: 1.8em; +} + +#write h3, #write-h3 { + font-size: 1.6em; +} + +#write h4, #write-h4 { + font-size: 1.4em; +} + +#write h5, #write h6, #write-h5, #write-h6 { + font-size: 1.2em; +} + +/* 在文章中,应该还原 ul 和 ol 的样式 */ +#write ul, #write-ul { + margin-left: 1.3em; + list-style: disc; +} + +#write ol, #write-ol { + list-style: decimal; + margin-left: 1.9em; +} + +#write li ul, #write li ol, #write-ul ul, #write-ul ol, #write-ol ul, #write-ol ol { + margin-bottom: 0.8em; + margin-left: 2em; +} + +#write li ul, #write-ul ul, #write-ol ul { + list-style: circle; +} + +#write table th, #write table td { + border: 1px solid #666; + padding: 0.5em 1em; + color: #666; +} + +#write table .md-table-edit th { + border: none; + padding: 0; + color: inherit; +} + +#write table th, #write-table th { + background: var(--theme-bg); +} + +#write table thead th, #write-table thead th { + background: var(--theme-bg); +} + +#write table caption { + border-bottom: none; +} + +#write em { + font-weight: inherit; + font-style: inherit; +} + +li > p { + margin-bottom: 0 !important; +} + +/* Responsive images */ +#write img { + max-width: 100%; + margin-top: 10px; + margin-bottom: 30px; + box-shadow: rgb(132 161 168) 10px 10px 15px; +} + +a.md-toc-inner { + border-bottom: 0 !important; +} + +.md-toc-h1:first-of-type:last-of-type { + display: none; +} + +.md-toc { + font-size: inherit; +} + +.md-toc-h1 .md-toc-inner { + font-weight: normal; +} + +.md-table-edit th { + padding: 0 !important; + border: 0 !important; +} + +.mac-seamless-mode #write { + min-height: calc(100vh - 6em - 20px); +} + +.typora-quick-open-item.active { + color: var(--active-file-text-color); +} + +*.in-text-selection, ::selection { + background: var(--active-file-bg-color); + text-shadow: none; + color: white; +} + +.btn-primary { + background-color: #2d2d2d; + border-color: #020202; +} + +.btn-primary:hover, .btn-primary:focus, .btn-primary.focus, .btn-primary:active, .btn-primary.active, .open > .dropdown-toggle.btn-primary { + background-color: #4e4c4e; + border: #4e4c4e; +} + +#preference-dialog .modal-content { + background: #6e757a; + --bg-color: #6e757a; + --text-color: #f1f1f1; + color: #f1f1f1; +} + +#typora-source, +.typora-sourceview-on { + --bg-color: #eee; + background: #eee; +} + +.cm-s-typora-default .cm-header, .cm-s-typora-default .cm-property { + color: #116098; +} + +.cm-s-typora-default .cm-link { + color: #11987d; +} + +.cm-s-typora-default .cm-em { + font-family: Georgia-Italic, STSongti-SC-Light, serif; + color: #6f6400; +} + +.cm-s-typora-default .cm-em { + color: rgb(0, 22, 45); +} + +.CodeMirror.cm-s-typora-default div.CodeMirror-cursor { + border-left: 3px solid #6e757a; +} + +.cm-s-typora-default .CodeMirror-selectedtext, +.typora-sourceview-on .CodeMirror-focused .CodeMirror-selected { + background: #6e757a; + color: white; +} + +.file-node-icon.fa.fa-folder:before { + color: rgba(32, 43, 51, 0.49); +} + +#preference-dialog .megamenu-menu-panel h1 { + margin-bottom: 1em; +} + +::-webkit-scrollbar-corner { + display: none; + background: transparent; +} + +/*.file-node-icon.fa.fa-folder:before { + content: "\f114"; +} + +#typora-sidebar { + +}*/ + +/*.cm-s-typora-default .cm-header, .cm-s-typora-default .cm-property { + color: #fffff1; +} + +.cm-s-typora-default .cm-link { + color: #86f9e2; + color: #e5f7eb; +} + +.cm-s-typora-default .cm-comment, .cm-s-typora-default .cm-code { + color: rgb(255, 199, 199); +} + +.cm-s-typora-default .cm-atom, .cm-s-typora-default .cm-number { + color: #dec4c7; +} + +.cm-s-typora-default .cm-em { + font-family: Georgia-Italic, STSongti-SC-Light, serif; + color: #f3ff7e; +} + +.typora-sourceview-on .CodeMirror-cursor { + border-left: 3px solid #ffffd6; +} + +.typora-sourceview-on #toggle-sourceview-btn { + background: #505050; +} + +.typora-sourceview-on .cm-s-inner .cm-variable, +.typora-sourceview-on .cm-s-inner .cm-operator, +.typora-sourceview-on .cm-s-inner .cm-property { + color: #b8bfc6; +} + +.typora-sourceview-on .cm-s-inner .cm-keyword { + color: #C88FD0; +} + +.typora-sourceview-on .cm-s-inner .cm-tag { + color: #7DF46A; +} + +.typora-sourceview-on .cm-s-inner .cm-attribute { + color: #7575E4; +} + +.typora-sourceview-on .cm-s-inner .cm-string { + color: #D26B6B; +} + +.typora-sourceview-on .cm-s-inner .cm-comment, +.typora-sourceview-on .cm-s-inner.cm-comment { + color: #DA924A; +} + +.typora-sourceview-on .cm-s-inner .cm-header, +.typora-sourceview-on .cm-s-inner .cm-def, +.typora-sourceview-on .cm-s-inner.cm-header, +.typora-sourceview-on .cm-s-inner.cm-def { + color: #8d8df0; +} + +.typora-sourceview-on .cm-s-inner .cm-quote, +.typora-sourceview-on .cm-s-inner.cm-quote { + color: #57ac57; +} + +.typora-sourceview-on .cm-s-inner .cm-hr { + color: #d8d5d5; +} + +.typora-sourceview-on .cm-s-inner .cm-link { + color: #d3d3ef; +} + +.typora-sourceview-on .cm-s-inner .cm-negative { + color: #d95050; +} + +.typora-sourceview-on .cm-s-inner .cm-positive { + color: #50e650; +} + +.typora-sourceview-on .cm-s-inner .cm-string-2 { + color: #f50; +} + +.typora-sourceview-on .cm-s-inner .cm-meta, +.typora-sourceview-on .cm-s-inner .cm-qualifier { + color: #b7b3b3; +} + +.typora-sourceview-on .cm-s-inner .cm-builtin { + color: #f3b3f8; +} + +.typora-sourceview-on .cm-s-inner .cm-bracket { + color: #997; +} + +.typora-sourceview-on .cm-s-inner .cm-atom, +.typora-sourceview-on .cm-s-inner.cm-atom { + color: #84B6CB; +} + +.typora-sourceview-on .cm-s-inner .cm-number { + color: #64AB8F; +} + +.typora-sourceview-on .cm-s-inner .cm-variable { + color: #b8bfc6; +} + +.typora-sourceview-on .cm-s-inner .cm-variable-2 { + color: #9FBAD5; +} + +.typora-sourceview-on .cm-s-inner .cm-variable-3 { + color: #1cc685; +} + +.typora-sourceview-on .CodeMirror div.CodeMirror-cursor { + border-left: 1px solid #b8bfc6; + z-index: 3; +} + +.cm-s-typora-default .CodeMirror-selectedtext, +.typora-sourceview-on .CodeMirror-focused .CodeMirror-selected { + background: #212324; +} + +.typora-sourceview-on .CodeMirror-linenumber { + color: rgb(255, 255, 255); +}*/ + +/*******TOC开始*******/ +.md-toc-item {color: var(--a-color);font-family: "Comic Sans MS", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans;} +.md-toc-item:hover {color: var(--a-hover-color);} +.md-toc-h1 .md-toc-inner {margin: 0 0 0 0;font-weight: 900;font-size: 22px} +.md-toc-h2 .md-toc-inner {margin-left: 0;font-weight: 500;font-size: 20px} +.md-toc-h3 .md-toc-inner {margin-left: 0;font-weight: 400;font-size: 18px} +.md-toc-h4 .md-toc-inner {margin-left: 0;font-weight: 300;font-size: 16px} +.md-toc-h5 .md-toc-inner {margin-left: 0;font-weight: 300;font-size: 14px} +.md-toc-h6 .md-toc-inner {margin-left: 0;font-weight: 300;font-size: 14px} +.md-toc-content{ counter-reset: md-toc-h1} +.md-toc-h1{ counter-reset: md-toc-h2; font-weight: 900; font-size: 22px; margin-left: 0;} +.md-toc-h2{ counter-reset: md-toc-h3; font-weight: 500; font-size: 20px; margin-left: 20px;} +.md-toc-h3{ counter-reset: md-toc-h4; font-weight: 400; font-size: 18px; margin-left: 55px;} +.md-toc-h4{ counter-reset: md-toc-h5; font-weight: 300; font-size: 16px; margin-left: 90px;} +.md-toc-h5{ counter-reset: md-toc-h6; font-weight: 300; font-size: 14px; margin-left: 90px;} +.md-toc-h6{ font-weight: 300; font-size: 14px; margin-left: 90px;} +#write .md-toc-h1:before { + counter-increment: md-toc-h1; + content: counter(md-toc-h1) " " +} + +#write .md-toc-h2:before { + counter-increment: md-toc-h2; + content: counter(md-toc-h1) "." counter(md-toc-h2) " " +} + +#write .md-toc-h3:before { + counter-increment: md-toc-h3; + content: counter(md-toc-h1) "." counter(md-toc-h2) "." counter(md-toc-h3) " " +} + +#write .md-toc-h4:before { + counter-increment: md-toc-h4; + content: counter(md-toc-h1) "." counter(md-toc-h2) "." counter(md-toc-h3) "." counter(md-toc-h4) " " +} + +#write .md-toc-h5:before { + counter-increment: md-toc-h5; + content: counter(md-toc-h1) "." counter(md-toc-h2) "." counter(md-toc-h3) "." counter(md-toc-h4) "." counter(md-toc-h5) " " +} + +#write .md-toc-h6:before { + counter-increment: md-toc-h6; + content: counter(md-toc-h1) "." counter(md-toc-h2) "." counter(md-toc-h3) "." counter(md-toc-h4) "." counter(md-toc-h5) "." counter(md-toc-h6) " " +} +/*******TOC结束*******/ +/*******自定义标题序号开始*******/ + +/** initialize css counter */ +#write { + counter-reset: h1 +} + +h1 { + counter-reset: h2 +} + +h2 { + counter-reset: h3 +} + +h3 { + counter-reset: h4 +} + +h4 { + counter-reset: h5 +} + +h5 { + counter-reset: h6 +} + +/** put counter result into headings */ +#write h1:before { + counter-increment: h1; + content: counter(h1) " " +} + +#write h2:before,h2.md-focus.md-heading:before +{ + counter-increment: h2; + content: counter(h1) "." counter(h2) " " +} + +#write h3:before,h3.md-focus.md-heading:before +{ + counter-increment: h3; + content: counter(h1) "." counter(h2) "." counter(h3) " " +} + +#write h4:before,h4.md-focus.md-heading:before { + counter-increment: h4; + content: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) " " +} + +#write h5:before,h5.md-focus.md-heading:before { + counter-increment: h5; + content: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) " " +} + +#write h6:before,h6.md-focus.md-heading:before { + counter-increment: h6; + content: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) "." counter(h6) " " +} + +/** override the default style for focused headings */ +#write > h2.md-focus:before, +#write > h3.md-focus:before, +#write > h4.md-focus:before, +#write > h5.md-focus:before, +#write > h6.md-focus:before, +h2.md-focus:before, +h3.md-focus:before, +h4.md-focus:before, +h5.md-focus:before, +h6.md-focus:before { + color: inherit; + border: inherit; + border-radius: inherit; + position: inherit; + left: initial; + float: none; + top: initial; + font-size: inherit; + padding-left: inherit; + padding-right: inherit; + vertical-align: inherit; + font-weight: inherit; + line-height: inherit; +} + +/*******自定义标题序号结束*******/ \ No newline at end of file diff --git a/support/lry.css b/support/lry.css new file mode 100644 index 0000000..e3fcd27 --- /dev/null +++ b/support/lry.css @@ -0,0 +1,1036 @@ +@charset "utf-8"; + +:root { + --active-file-bg-color: #dadada; + --active-file-bg-color: rgba(32, 43, 51, 0.63); + --active-file-text-color: white; + --bg-color: #fff; + --text-color: #333; + --side-bar-bg-color: #f5f5f5; + --control-text-color: #666; + + /* 主题定义: 绿色-#1abc9c、蓝色-#16b0ff、橙红色-#ff6666、绿色-#42b983、番茄红-#ff6347*/ + --theme-color: #16b0ff; + + /* 超链接设置 */ + --a-color: var(--theme-color); + --a-border-bottom-color: var(--theme-color); + --a-hover-color: #555; + --a-hover-border-bottom-color: #555; + + /* 删除线 */ + --del-color: #c7264e; + /* 下标字 */ + --sub-color: var(--theme-color); + /* 上标字 */ + --sup-color: var(--theme-color); + /* 字体加粗 */ + --strong-color: var(--theme-color); + /* 背景高亮 */ + --mark-border-left: #ff6666; + --mark-background: rgb(248, 248, 248); /*rgba(66, 185, 131, 0.1);*/ + /*Tip*/ + --em-border-left: #ff6666; + --em-background: rgb(248, 248, 248); /*rgba(66, 185, 131, 0.1);*/ + + /* H1、H2、H3、H4、H5、H6 */ + --sub-h-color: #34495e; + --h1-color: #ff6666; + --h2-color: var(--theme-color); + --h3-color: var(--sub-h-color); + --h4-color: var(--sub-h-color); + --h5-color: var(--sub-h-color); + --h6-color: var(--sub-h-color); + + /* H2左竖线 */ + --h1-border-left-color: var(--h1-color); + + /* 引用 */ + --blockquote-before-color: var(--mark-border-left); + --blockquote-after-color: var(--mark-border-left); + /* 红色代码块 + --code-color: #c7264e; + --code-background-color: #f9f2f4; + */ + /* 绿色代码块 */ + --code-color:#ff6347; + --code-background-color: rgba(248, 117, 117, 0.1); + /* 下划线 */ + --text-decoration-border-bottom-color: #ff6666; +} + +/* 防止用户自定义背景颜色对网页的影响,添加让用户可以自定义字体 */ +html { + color: #34495e; + background: #fff; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + text-rendering: optimizelegibility; + font-size: 14px; + -webkit-font-smoothing: initial; +} + +#write { + max-width: 960px; + padding-top: 2em; + padding-left: 60px; + padding-right: 60px; + min-height: calc(100vh - 6em); + -webkit-font-smoothing: antialiased; + font-size: 16px; +} + +.typora-node #write { + min-height: calc(100% - 6em); +} + +pre.md-meta-block { + background: #f5f5f5; + padding: 1em; + border-radius: 3px; + font-size: 14px; +} + +pre { + -webkit-font-smoothing: initial; + background-color: rgb(248, 248, 248); + color: rgb(82, 82, 82); + display: block; + font-family: "Comic Sans MS", "Roboto Mono", Monaco, courier, monospace; + font-size: 0.8rem; + line-height: inherit; + max-width: inherit; + white-space: inherit; + border-radius: 2px; + margin: 0px 2px; + overflow: inherit; + padding: 2.2em 5px; +} +pre.md-fences { + margin-top: 20px; + border-radius: 5px; + box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; +} +div.CodeMirror-gutter-filler { + display: block; + position: static; + margin-bottom: 10px; + height: 15px; + background: url(https://mmbiz.qpic.cn/mmbiz_png/WmXepF87uPm6j7fce4wLfgvjjrPEd3gRG8gqcQdickA5z19YgwFdFRNIrZPkcGyyXgLxzicebwl7RFMqFLanHczw/640?wx_fmt=png) 5px 0px / 40px no-repeat rgb(255, 255, 255); +} + +@media screen and (max-width: 800px) { + html { + font-size: 14px; + } + + #write { + padding-left: 30px; + padding-right: 30px; + font-size: 14px; + } +} + +@media screen and (min-width: 1100px) { + body, #footer-word-count-info { + background: #f5f5f5; + } + + body.pin-outline, + .pin-outline #footer-word-count-info, + .pin-outline footer { + background: #fff; + } + + #write { + max-width: 1000px; + padding: 40px 60px; + background: #fff; + margin: 3em auto 3em; + border: 1px solid #ddd; + border-width: 0 1px; + } + + .pin-outline #write { + max-width: 1000px; + background: #fff; + margin: 0 0 0; + border: 0; + padding-left: 60px; + padding-right: 60px; + } + + footer { + background-color: transparent; + } +} + +@media screen and (min-width: 1300px) { + body.pin-outline, + .pin-outline #footer-word-count-info, + .pin-outline footer { + background: #f5f5f5; + } + + .pin-outline #write { + max-width: 1000px; + padding: 40px 60px; + background: #fff; + margin: 3em auto 3em; + border: 1px solid #ddd; + border-width: 0 1px; + } + + .pin-outline footer { + background-color: transparent; + } + + #footer-word-count-info { + background: #f5f5f5; + } +} + +/* 如果你的项目仅支持 IE9+ | Chrome | Firefox 等,推荐在 中添加 .borderbox 这个 class */ +html.borderbox *, html.borderbox *:before, html.borderbox *:after { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* 内外边距通常让各个浏览器样式的表现位置不同 */ +body, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, code, form, fieldset, legend, input, textarea, p, blockquote, th, td, hr, button, article, aside, details, figcaption, figure, footer, header, menu, nav, section { + margin: 0; + padding: 0; +} + +/* 重设 HTML5 标签, IE 需要在 js 中 createElement(TAG) */ +article, aside, details, figcaption, figure, footer, header, menu, nav, section { + display: block; +} + +/* HTML5 媒体文件跟 img 保持一致 */ +audio, canvas, video { + display: inline-block; +} + +/* 要注意表单元素并不继承父级 font 的问题 */ +body, button, input, select, textarea { + font: 300 1em/1.8 "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans; +} + +body { + /* font-family: "Monaco", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans; */ + /* font-family: "TimesNewRomanPS-ItalicMT", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans; */ + font-family: "Comic Sans MS", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans; +} + +.md-toc:before { + content: "Contents"; + font-family: Dosis, "Comic Sans MS", "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; + font-size: 18px; + font-weight: bold; +} + +h1 { + font-weight: 900; + padding-left: 9px; + border-left: 5px solid var(--h1-border-left-color); +} + +h2 { + font-weight: 600; +} + +h3 { + font-weight: 500; +} + +h4 { + font-weight: 400; +} + +h5 { + font-weight: 400; +} + +h6 { + font-weight: 400; +} + +u { + text-decoration: none; + border-bottom: 2px solid var(--text-decoration-border-bottom-color); +} + +h1, h2, h3, h4, h5, h6 { + /*font-family: "TimesNewRomanPS-ItalicMT", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans;*/ + /*font-family: "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans;*/ + font-family: "Comic Sans MS", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans; + -webkit-font-smoothing: initial; + + color: var(--text-color); + line-height: 1.35; + font-variant-numeric: lining-nums; + margin-bottom: 1em; +} + +em { + font-family: Georgia-Italic, STSongti-SC-Light, serif; +} + +strong em, +em strong { + font-family: Georgia-BoldItalic, STSongti-SC-Regular, serif; +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} + +/* 去掉各Table cell 的边距并让其边重合 */ +table { + border-collapse: collapse; + border-spacing: 0; +} + +/* 去除默认边框 */ +fieldset, img { + border: 0; +} + +/* 块/段落引用 */ +blockquote { + font-size: 1em; + font-style: normal; + font-weight: 500; + padding: 30px 38px; + margin: 0 0 15px; + position: relative; + line-height: 1.8; + text-indent: 0; + border: none; + color: #888; + /*特效*/ + margin-top: 30px; + margin-bottom: 30px; + box-shadow: rgb(132 161 168) 0px 10px 15px; + border-radius: 0px 0px 10px 10px; +} + +blockquote:before { + content: "☠"; + left: 12px; + top: 0; + color: var(--blockquote-before-color); + font-size: 3em; + font-family: Arial, serif; + line-height: 1em; + font-weight: 700; + /*position: absolute;*/ +} + +blockquote:after { + content: "”"; + right: 12px; + color: var(--blockquote-after-color); + font-size: 4em; + font-family: Arial, serif; + line-height: 1em; + font-weight: 700; + position: absolute; + bottom: -31px; +} + +#write code { + color: var(--code-color); + background-color: var(--code-background-color); + font-size: .95em; + font-weight: 900; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +@media only screen and ( max-width: 640px ) { + blockquote { + margin: 1em 0; + } +} + +/* Firefox 以外,元素没有下划线,需添加 */ +acronym, abbr { + border-bottom: 1px dotted; + font-variant: normal; +} + +/* 添加鼠标问号,进一步确保应用的语义是正确的(要知道,交互他们也有洁癖,如果你不去掉,那得多花点口舌) */ +abbr { + cursor: help; +} + +address, caption, cite, code, dfn, th, var { + font-style: normal; + font-weight: 400; +} + +/* 去掉列表前的标识, li 会继承,大部分网站通常用列表来很多内容,所以应该当去 */ +ul, ol { + list-style: none; +} + +/* 对齐是排版最重要的因素, 别让什么都居中 */ +caption, th { + text-align: left; +} + +q:before, q:after { + content: ''; +} + +/* 统一上标和下标 */ +sub, sup { + font-size: 75%; + line-height: 0; + position: relative; +} + +:root sub, :root sup { + vertical-align: baseline; /* for ie9 and other modern browsers */ +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* 默认不显示下划线,保持页面简洁 */ +ins, a { + text-decoration: none; +} + +/* 代码片断 */ +pre, code, pre tt { + font-family: 'Comic Sans MS', Monaco, Courier, 'Courier New', monospace; +} + +#write .md-fences { + /* border: 1px solid #ddd; */ + /*background-color: #f8f8f8;*/ + padding: 1em 0.5em; + margin-bottom: 2em; + display: block; + -webkit-overflow-scrolling: touch; +} + +/* 一致化 horizontal rule */ +hr { + border: none; + border-bottom: 1px solid #cfcfcf; + margin-bottom: 0.8em; + height: 10px; +} + +.code-tooltip.md-hover-tip strong { + color: white; +} + +/* 保证块/段落之间的空白隔行 */ +#write p, #write ul, #write ol, #write dl, #write form, #write hr, #write figure, +#write-p, #write-pre, #write-ul, #write-ol, #write-dl, #write-form, #write-hr, #write-table, blockquote { + margin-bottom: 0.6em +} + +html { + font-family: 'Comic Sans MS', PingFang SC, Verdana, Helvetica Neue, Microsoft Yahei, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif; +} + +/* 让链接在 hover 状态下显示下划线 */ +a { + color: var(--a-color); +} + +a:hover { + text-decoration: underline; +} + +#write a { + border-bottom: 1px solid var(--a-border-bottom-color); +} + +#write a:hover { + color: var(--a-hover-color); + text-decoration: none; + border-bottom-color: var(--a-hover-border-bottom-color); +} + +#write del { + color: var(--del-color); + text-decoration: line-through; +} + +#write sub { + color: var(--sub-color);; + font-weight: 600; +} + +#write sup { + color: var(--sub-color);; + font-weight: 600; +} + +/* 标记,类似于手写的荧光笔的作用 */ +mark { + margin: 0 5px; + background: var(--mark-background); + border-left: 5px solid var(--mark-border-left); + border-radius: 2px; + padding: 12px 15px 12px 15px; + position: relative; +} + +mark:before { + background-color: var(--mark-border-left); + color: rgb(255, 255, 255); + content: "!"; + font-family: Dosis, "Comic Sans MS", "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; + font-size: 14px; + font-weight: bold; + left: -12px; + line-height: 20px; + position: absolute; + height: 20px; + width: 20px; + text-align: center; + top: 27%; + border-radius: 100%; +} + +em { + margin: 0 5px; + background: var(--em-background); + border-left: 5px solid var(--em-border-left); + border-radius: 2px; + padding: 12px 15px 12px 15px; + position: relative; +} + +em:before { + background-color: var(--em-border-left); + color: rgb(255, 255, 255); + content: "?"; + font-family: Dosis, "Comic Sans MS", "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; + font-size: 14px; + font-weight: bold; + left: -12px; + line-height: 20px; + position: absolute; + height: 20px; + width: 20px; + text-align: center; + top: 27%; + border-radius: 100%; +} + +#write em > strong { + color: var(--em-border-left); +} + +#write strong { + font-weight: 900; + color: var(--strong-color); +} + +/* 标题应该更贴紧内容,并与其他块区分,margin 值要相应做优化 */ +#write h1, #write h2, #write h3, #write h4, #write h5, #write h6, +#write-h1, #write-h2, #write-h3, #write-h4, #write-h5, #write-h6 { + margin-top: 1.2em; + margin-bottom: 0.6em; + line-height: 1.35; +} + +#write h1, #write-h1 { + color: var(--h1-color); +} + +#write h2, #write-h2 { + color: var(--h2-color); +} + +#write h3, #write-h3 { + color: var(--h3-color); +} + +#write h4, #write-h4 { + color: var(--h4-color); +} + +#write h5, #write-h5 { + color: var(--h5-color); +} + +#write h6, #write-h6 { + color: var(--h6-color); +} + +#write h1, #write-h1 { + font-size: 2.4em; +} + +#write h2, #write-h2 { + font-size: 1.8em; +} + +#write h3, #write-h3 { + font-size: 1.6em; +} + +#write h4, #write-h4 { + font-size: 1.4em; +} + +#write h5, #write h6, #write-h5, #write-h6 { + font-size: 1.2em; +} + +/* 在文章中,应该还原 ul 和 ol 的样式 */ +#write ul, #write-ul { + margin-left: 1.3em; + list-style: disc; +} + +#write ol, #write-ol { + list-style: decimal; + margin-left: 1.9em; +} + +#write li ul, #write li ol, #write-ul ul, #write-ul ol, #write-ol ul, #write-ol ol { + margin-bottom: 0.8em; + margin-left: 2em; +} + +#write li ul, #write-ul ul, #write-ol ul { + list-style: circle; +} + +#write table th, #write table td { + border: 1px solid #ddd; + padding: 0.5em 1em; + color: #666; +} + +#write table .md-table-edit th { + border: none; + padding: 0; + color: inherit; +} + +#write table th, #write-table th { + background: #fbfbfb; +} + +#write table thead th, #write-table thead th { + background: #f1f1f1; +} + +#write table caption { + border-bottom: none; +} + +#write em { + font-weight: inherit; + font-style: inherit; +} + +li > p { + margin-bottom: 0 !important; +} + +/* Responsive images */ +#write img { + max-width: 100%; + margin-top: 10px; + margin-bottom: 30px; + box-shadow: rgb(132 161 168) 10px 10px 15px; +} + +a.md-toc-inner { + border-bottom: 0 !important; +} + +.md-toc-h1:first-of-type:last-of-type { + display: none; +} + +.md-toc { + font-size: inherit; +} + +.md-toc-h1 .md-toc-inner { + font-weight: normal; +} + +.md-table-edit th { + padding: 0 !important; + border: 0 !important; +} + +.mac-seamless-mode #write { + min-height: calc(100vh - 6em - 20px); +} + +.typora-quick-open-item.active { + color: var(--active-file-text-color); +} + +*.in-text-selection, ::selection { + background: var(--active-file-bg-color); + text-shadow: none; + color: white; +} + +.btn-primary { + background-color: #2d2d2d; + border-color: #020202; +} + +.btn-primary:hover, .btn-primary:focus, .btn-primary.focus, .btn-primary:active, .btn-primary.active, .open > .dropdown-toggle.btn-primary { + background-color: #4e4c4e; + border: #4e4c4e; +} + +#preference-dialog .modal-content { + background: #6e757a; + --bg-color: #6e757a; + --text-color: #f1f1f1; + color: #f1f1f1; +} + +#typora-source, +.typora-sourceview-on { + --bg-color: #eee; + background: #eee; +} + +.cm-s-typora-default .cm-header, .cm-s-typora-default .cm-property { + color: #116098; +} + +.cm-s-typora-default .cm-link { + color: #11987d; +} + +.cm-s-typora-default .cm-em { + font-family: Georgia-Italic, STSongti-SC-Light, serif; + color: #6f6400; +} + +.cm-s-typora-default .cm-em { + color: rgb(0, 22, 45); +} + +.CodeMirror.cm-s-typora-default div.CodeMirror-cursor { + border-left: 3px solid #6e757a; +} + +.cm-s-typora-default .CodeMirror-selectedtext, +.typora-sourceview-on .CodeMirror-focused .CodeMirror-selected { + background: #6e757a; + color: white; +} + +.file-node-icon.fa.fa-folder:before { + color: rgba(32, 43, 51, 0.49); +} + +#preference-dialog .megamenu-menu-panel h1 { + margin-bottom: 1em; +} + +::-webkit-scrollbar-corner { + display: none; + background: transparent; +} + +/*.file-node-icon.fa.fa-folder:before { + content: "\f114"; +} + +#typora-sidebar { + +}*/ + +/*.cm-s-typora-default .cm-header, .cm-s-typora-default .cm-property { + color: #fffff1; +} + +.cm-s-typora-default .cm-link { + color: #86f9e2; + color: #e5f7eb; +} + +.cm-s-typora-default .cm-comment, .cm-s-typora-default .cm-code { + color: rgb(255, 199, 199); +} + +.cm-s-typora-default .cm-atom, .cm-s-typora-default .cm-number { + color: #dec4c7; +} + +.cm-s-typora-default .cm-em { + font-family: Georgia-Italic, STSongti-SC-Light, serif; + color: #f3ff7e; +} + +.typora-sourceview-on .CodeMirror-cursor { + border-left: 3px solid #ffffd6; +} + +.typora-sourceview-on #toggle-sourceview-btn { + background: #505050; +} + +.typora-sourceview-on .cm-s-inner .cm-variable, +.typora-sourceview-on .cm-s-inner .cm-operator, +.typora-sourceview-on .cm-s-inner .cm-property { + color: #b8bfc6; +} + +.typora-sourceview-on .cm-s-inner .cm-keyword { + color: #C88FD0; +} + +.typora-sourceview-on .cm-s-inner .cm-tag { + color: #7DF46A; +} + +.typora-sourceview-on .cm-s-inner .cm-attribute { + color: #7575E4; +} + +.typora-sourceview-on .cm-s-inner .cm-string { + color: #D26B6B; +} + +.typora-sourceview-on .cm-s-inner .cm-comment, +.typora-sourceview-on .cm-s-inner.cm-comment { + color: #DA924A; +} + +.typora-sourceview-on .cm-s-inner .cm-header, +.typora-sourceview-on .cm-s-inner .cm-def, +.typora-sourceview-on .cm-s-inner.cm-header, +.typora-sourceview-on .cm-s-inner.cm-def { + color: #8d8df0; +} + +.typora-sourceview-on .cm-s-inner .cm-quote, +.typora-sourceview-on .cm-s-inner.cm-quote { + color: #57ac57; +} + +.typora-sourceview-on .cm-s-inner .cm-hr { + color: #d8d5d5; +} + +.typora-sourceview-on .cm-s-inner .cm-link { + color: #d3d3ef; +} + +.typora-sourceview-on .cm-s-inner .cm-negative { + color: #d95050; +} + +.typora-sourceview-on .cm-s-inner .cm-positive { + color: #50e650; +} + +.typora-sourceview-on .cm-s-inner .cm-string-2 { + color: #f50; +} + +.typora-sourceview-on .cm-s-inner .cm-meta, +.typora-sourceview-on .cm-s-inner .cm-qualifier { + color: #b7b3b3; +} + +.typora-sourceview-on .cm-s-inner .cm-builtin { + color: #f3b3f8; +} + +.typora-sourceview-on .cm-s-inner .cm-bracket { + color: #997; +} + +.typora-sourceview-on .cm-s-inner .cm-atom, +.typora-sourceview-on .cm-s-inner.cm-atom { + color: #84B6CB; +} + +.typora-sourceview-on .cm-s-inner .cm-number { + color: #64AB8F; +} + +.typora-sourceview-on .cm-s-inner .cm-variable { + color: #b8bfc6; +} + +.typora-sourceview-on .cm-s-inner .cm-variable-2 { + color: #9FBAD5; +} + +.typora-sourceview-on .cm-s-inner .cm-variable-3 { + color: #1cc685; +} + +.typora-sourceview-on .CodeMirror div.CodeMirror-cursor { + border-left: 1px solid #b8bfc6; + z-index: 3; +} + +.cm-s-typora-default .CodeMirror-selectedtext, +.typora-sourceview-on .CodeMirror-focused .CodeMirror-selected { + background: #212324; +} + +.typora-sourceview-on .CodeMirror-linenumber { + color: rgb(255, 255, 255); +}*/ + +/*******TOC开始*******/ +.md-toc-item {color: var(--a-color);font-family: "Comic Sans MS", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans;} +.md-toc-item:hover {color: var(--a-hover-color);} +.md-toc-h1 .md-toc-inner {margin: 0 0 0 0;font-weight: 900;font-size: 22px} +.md-toc-h2 .md-toc-inner {margin-left: 0;font-weight: 500;font-size: 20px} +.md-toc-h3 .md-toc-inner {margin-left: 0;font-weight: 400;font-size: 18px} +.md-toc-h4 .md-toc-inner {margin-left: 0;font-weight: 300;font-size: 16px} +.md-toc-h5 .md-toc-inner {margin-left: 0;font-weight: 300;font-size: 14px} +.md-toc-h6 .md-toc-inner {margin-left: 0;font-weight: 300;font-size: 14px} +.md-toc-content{ counter-reset: md-toc-h1} +.md-toc-h1{ counter-reset: md-toc-h2; font-weight: 900; font-size: 22px; margin-left: 0;} +.md-toc-h2{ counter-reset: md-toc-h3; font-weight: 500; font-size: 20px; margin-left: 20px;} +.md-toc-h3{ counter-reset: md-toc-h4; font-weight: 400; font-size: 18px; margin-left: 55px;} +.md-toc-h4{ counter-reset: md-toc-h5; font-weight: 300; font-size: 16px; margin-left: 90px;} +.md-toc-h5{ counter-reset: md-toc-h6; font-weight: 300; font-size: 14px; margin-left: 90px;} +.md-toc-h6{ font-weight: 300; font-size: 14px; margin-left: 90px;} +#write .md-toc-h1:before { + counter-increment: md-toc-h1; + content: counter(md-toc-h1) " " +} + +#write .md-toc-h2:before { + counter-increment: md-toc-h2; + content: counter(md-toc-h1) "." counter(md-toc-h2) " " +} + +#write .md-toc-h3:before { + counter-increment: md-toc-h3; + content: counter(md-toc-h1) "." counter(md-toc-h2) "." counter(md-toc-h3) " " +} + +#write .md-toc-h4:before { + counter-increment: md-toc-h4; + content: counter(md-toc-h1) "." counter(md-toc-h2) "." counter(md-toc-h3) "." counter(md-toc-h4) " " +} + +#write .md-toc-h5:before { + counter-increment: md-toc-h5; + content: counter(md-toc-h1) "." counter(md-toc-h2) "." counter(md-toc-h3) "." counter(md-toc-h4) "." counter(md-toc-h5) " " +} + +#write .md-toc-h6:before { + counter-increment: md-toc-h6; + content: counter(md-toc-h1) "." counter(md-toc-h2) "." counter(md-toc-h3) "." counter(md-toc-h4) "." counter(md-toc-h5) "." counter(md-toc-h6) " " +} +/*******TOC结束*******/ +/*******自定义标题序号开始*******/ + +/** initialize css counter */ +#write { + counter-reset: h1 +} + +h1 { + counter-reset: h2 +} + +h2 { + counter-reset: h3 +} + +h3 { + counter-reset: h4 +} + +h4 { + counter-reset: h5 +} + +h5 { + counter-reset: h6 +} + +/** put counter result into headings */ +#write h1:before { + counter-increment: h1; + content: counter(h1) " " +} + +#write h2:before,h2.md-focus.md-heading:before +{ + counter-increment: h2; + content: counter(h1) "." counter(h2) " " +} + +#write h3:before,h3.md-focus.md-heading:before +{ + counter-increment: h3; + content: counter(h1) "." counter(h2) "." counter(h3) " " +} + +#write h4:before,h4.md-focus.md-heading:before { + counter-increment: h4; + content: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) " " +} + +#write h5:before,h5.md-focus.md-heading:before { + counter-increment: h5; + content: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) " " +} + +#write h6:before,h6.md-focus.md-heading:before { + counter-increment: h6; + content: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) "." counter(h6) " " +} + +/** override the default style for focused headings */ +#write > h2.md-focus:before, +#write > h3.md-focus:before, +#write > h4.md-focus:before, +#write > h5.md-focus:before, +#write > h6.md-focus:before, +h2.md-focus:before, +h3.md-focus:before, +h4.md-focus:before, +h5.md-focus:before, +h6.md-focus:before { + color: inherit; + border: inherit; + border-radius: inherit; + position: inherit; + left: initial; + float: none; + top: initial; + font-size: inherit; + padding-left: inherit; + padding-right: inherit; + vertical-align: inherit; + font-weight: inherit; + line-height: inherit; +} + +/*******自定义标题序号结束*******/ \ No newline at end of file diff --git a/support/素材.drawio b/support/素材.drawio new file mode 100644 index 0000000..5971fac --- /dev/null +++ b/support/素材.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file