diff --git a/2014/12/23/my-new-post/index.html b/2014/12/23/my-new-post/index.html index 86e876be15..0400bf7676 100644 --- a/2014/12/23/my-new-post/index.html +++ b/2014/12/23/my-new-post/index.html @@ -428,20 +428,20 @@
- 100 + 101 posts
- 127 + 129 categories
- 199 + 201 tags
@@ -466,7 +466,7 @@
- +
diff --git a/2014/12/24/MFC 模态对话框/index.html b/2014/12/24/MFC 模态对话框/index.html index b60dc74636..c05de651a0 100644 --- a/2014/12/24/MFC 模态对话框/index.html +++ b/2014/12/24/MFC 模态对话框/index.html @@ -301,7 +301,7 @@ @@ -356,8 +356,8 @@ @@ -457,20 +460,20 @@
- 100 + 101 posts
- 127 + 129 categories
- 199 + 201 tags
@@ -495,7 +498,7 @@
- +
diff --git a/2021/05/01/Leetcode-48-旋转图像-Rotate-Image-题解分析/index.html b/2021/05/01/Leetcode-48-旋转图像-Rotate-Image-题解分析/index.html new file mode 100644 index 0000000000..356c410fda --- /dev/null +++ b/2021/05/01/Leetcode-48-旋转图像-Rotate-Image-题解分析/index.html @@ -0,0 +1,774 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Leetcode 48 旋转图像(Rotate Image) 题解分析 | Nicksxs's Blog + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Nicksxs's Blog

+ +
+

What hurts more, the pain of hard work or the pain of regret?

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + + + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ Leetcode 48 旋转图像(Rotate Image) 题解分析 +

+ + +
+ + + + +
+ + +

题目介绍

You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise).

+

You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.

如图,这道题以前做过,其实一看有点蒙,好像规则很容易描述,但是代码很难写,因为要类似于贪吃蛇那样,后来想着应该会有一些特殊的技巧,比如翻转等

+

代码

直接上码

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public void rotate(int[][] matrix) {
// 这里真的傻了,长宽应该是一致的,所以取一次就够了
int lengthX = matrix[0].length;
int lengthY = matrix.length;
int temp;
System.out.println(lengthY - (lengthY % 2) / 2);
// 这里除错了,应该是减掉余数再除 2
// for (int i = 0; i < lengthY - (lengthY % 2) / 2; i++) {
/**
* 1 2 3 7 8 9
* 4 5 6 => 4 5 6 先沿着 4 5 6 上下交换
* 7 8 9 1 2 3
*/
for (int i = 0; i < (lengthY - (lengthY % 2)) / 2; i++) {
for (int j = 0; j < lengthX; j++) {
temp = matrix[i][j];
matrix[i][j] = matrix[lengthY-i-1][j];
matrix[lengthY-i-1][j] = temp;
}
}

/**
* 7 8 9 7 4 1
* 4 5 6 => 8 5 2 这里再沿着 7 5 3 这条对角线交换
* 1 2 3 9 6 3
*/
for (int i = 0; i < lengthX; i++) {
for (int j = 0; j <= i; j++) {
if (i == j) {
continue;
}
temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
+

还没到可以直接归纳题目类型的水平,主要是几年前做过,可能有那么点模糊的记忆,当然应该也有直接转的方法

+ +
+ + + + + + + + +
+
请我喝杯咖啡
+ + +
+ + + +
+ +
+ + + + +
+ + + + + + +
+ + +
+
+ +
+
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/404.html b/404.html index a5f497fb05..76ac658779 100644 --- a/404.html +++ b/404.html @@ -320,20 +320,20 @@
- 100 + 101 posts
- 127 + 129 categories
- 199 + 201 tags
@@ -358,7 +358,7 @@
- +
diff --git a/404/index.html b/404/index.html index db3bbdf86b..780cb6dbc5 100644 --- a/404/index.html +++ b/404/index.html @@ -307,20 +307,20 @@
- 100 + 101 posts
- 127 + 129 categories
- 199 + 201 tags
@@ -345,7 +345,7 @@
- +
diff --git a/archives/2014/12/index.html b/archives/2014/12/index.html index 1bbf6a2521..f310154d38 100644 --- a/archives/2014/12/index.html +++ b/archives/2014/12/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -350,20 +350,20 @@
- 100 + 101 posts
- 127 + 129 categories
- 199 + 201 tags
@@ -388,7 +388,7 @@
- +
diff --git a/archives/2014/index.html b/archives/2014/index.html index d1e84d17cb..3ac5abddcb 100644 --- a/archives/2014/index.html +++ b/archives/2014/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -350,20 +350,20 @@ @@ -388,7 +388,7 @@
- +
diff --git a/archives/2015/01/index.html b/archives/2015/01/index.html index 9bbf646d64..7ebd06d385 100644 --- a/archives/2015/01/index.html +++ b/archives/2015/01/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -350,20 +350,20 @@ @@ -388,7 +388,7 @@
- +
diff --git a/archives/2015/03/index.html b/archives/2015/03/index.html index e916b8c58e..9cc3c692e7 100644 --- a/archives/2015/03/index.html +++ b/archives/2015/03/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -350,20 +350,20 @@ @@ -388,7 +388,7 @@
- +
diff --git a/archives/2015/04/index.html b/archives/2015/04/index.html index 76fd81166a..08e6c80cf3 100644 --- a/archives/2015/04/index.html +++ b/archives/2015/04/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/archives/2015/06/index.html b/archives/2015/06/index.html index 55ac159191..7d1be28ca0 100644 --- a/archives/2015/06/index.html +++ b/archives/2015/06/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/archives/2015/index.html b/archives/2015/index.html index ac782c1053..dd5afe47c3 100644 --- a/archives/2015/index.html +++ b/archives/2015/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -470,20 +470,20 @@ @@ -508,7 +508,7 @@
- +
diff --git a/archives/2016/07/index.html b/archives/2016/07/index.html index ff3836811a..1ab8c940b8 100644 --- a/archives/2016/07/index.html +++ b/archives/2016/07/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/archives/2016/08/index.html b/archives/2016/08/index.html index 4537524079..93c8a765ae 100644 --- a/archives/2016/08/index.html +++ b/archives/2016/08/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/archives/2016/09/index.html b/archives/2016/09/index.html index e27e00652a..5c1e8c32f7 100644 --- a/archives/2016/09/index.html +++ b/archives/2016/09/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/archives/2016/10/index.html b/archives/2016/10/index.html index 98c162f05c..c0df1563ad 100644 --- a/archives/2016/10/index.html +++ b/archives/2016/10/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/archives/2016/11/index.html b/archives/2016/11/index.html index 11c59fee10..d322fb5d6b 100644 --- a/archives/2016/11/index.html +++ b/archives/2016/11/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/archives/2016/index.html b/archives/2016/index.html index 70a7675cfe..ceff7fe076 100644 --- a/archives/2016/index.html +++ b/archives/2016/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -430,20 +430,20 @@ @@ -468,7 +468,7 @@
- +
diff --git a/archives/2017/03/index.html b/archives/2017/03/index.html index ce485451be..793689530c 100644 --- a/archives/2017/03/index.html +++ b/archives/2017/03/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/archives/2017/04/index.html b/archives/2017/04/index.html index 6db4c9b7b0..372c77bba4 100644 --- a/archives/2017/04/index.html +++ b/archives/2017/04/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/archives/2017/05/index.html b/archives/2017/05/index.html index f22a9cb673..c40ece5c66 100644 --- a/archives/2017/05/index.html +++ b/archives/2017/05/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/archives/2017/index.html b/archives/2017/index.html index 3d3f6e0488..a1c00be9f3 100644 --- a/archives/2017/index.html +++ b/archives/2017/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -350,20 +350,20 @@ @@ -388,7 +388,7 @@
- +
diff --git a/archives/2019/06/index.html b/archives/2019/06/index.html index b3b615fff4..0e2afcc496 100644 --- a/archives/2019/06/index.html +++ b/archives/2019/06/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/archives/2019/09/index.html b/archives/2019/09/index.html index 6a31ee988b..dbc4e2390c 100644 --- a/archives/2019/09/index.html +++ b/archives/2019/09/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/archives/2019/12/index.html b/archives/2019/12/index.html index da54cb0bbd..ed63b6c3ec 100644 --- a/archives/2019/12/index.html +++ b/archives/2019/12/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -390,20 +390,20 @@ @@ -428,7 +428,7 @@
- +
diff --git a/archives/2019/index.html b/archives/2019/index.html index 00f0f407c8..3adc7519b4 100644 --- a/archives/2019/index.html +++ b/archives/2019/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -430,20 +430,20 @@ @@ -468,7 +468,7 @@
- +
diff --git a/archives/2020/01/index.html b/archives/2020/01/index.html index d0b56cd052..33f839226c 100644 --- a/archives/2020/01/index.html +++ b/archives/2020/01/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -390,20 +390,20 @@ @@ -428,7 +428,7 @@
- +
diff --git a/archives/2020/02/index.html b/archives/2020/02/index.html index 5d4d393bd1..45ddc7d80e 100644 --- a/archives/2020/02/index.html +++ b/archives/2020/02/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/archives/2020/03/index.html b/archives/2020/03/index.html index f76b73d4b9..f96f0382c9 100644 --- a/archives/2020/03/index.html +++ b/archives/2020/03/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -390,20 +390,20 @@ @@ -428,7 +428,7 @@
- +
diff --git a/archives/2020/04/index.html b/archives/2020/04/index.html index a8f4cf5ca4..2fa2775378 100644 --- a/archives/2020/04/index.html +++ b/archives/2020/04/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/archives/2020/05/index.html b/archives/2020/05/index.html index 0fa560ac95..c4087a4ee1 100644 --- a/archives/2020/05/index.html +++ b/archives/2020/05/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -390,20 +390,20 @@ @@ -428,7 +428,7 @@
- +
diff --git a/archives/2020/06/index.html b/archives/2020/06/index.html index 659c774eb4..7fedd5c12c 100644 --- a/archives/2020/06/index.html +++ b/archives/2020/06/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/archives/2020/07/index.html b/archives/2020/07/index.html index 40237d78a9..47f4565d6c 100644 --- a/archives/2020/07/index.html +++ b/archives/2020/07/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/archives/2020/08/index.html b/archives/2020/08/index.html index 0838fc6923..94d6628386 100644 --- a/archives/2020/08/index.html +++ b/archives/2020/08/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -390,20 +390,20 @@ @@ -428,7 +428,7 @@
- +
diff --git a/archives/2020/09/index.html b/archives/2020/09/index.html index aa04eea40d..2bc9ac776c 100644 --- a/archives/2020/09/index.html +++ b/archives/2020/09/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/archives/2020/10/index.html b/archives/2020/10/index.html index 3651abd2bb..cdc10b7460 100644 --- a/archives/2020/10/index.html +++ b/archives/2020/10/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/archives/2020/11/index.html b/archives/2020/11/index.html index 3c44c8a858..fbff8167ae 100644 --- a/archives/2020/11/index.html +++ b/archives/2020/11/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -390,20 +390,20 @@ @@ -428,7 +428,7 @@
- +
diff --git a/archives/2020/12/index.html b/archives/2020/12/index.html index ed5200a041..bd959774e6 100644 --- a/archives/2020/12/index.html +++ b/archives/2020/12/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/archives/2020/index.html b/archives/2020/index.html index b33654b02b..a714662230 100644 --- a/archives/2020/index.html +++ b/archives/2020/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/archives/2020/page/2/index.html b/archives/2020/page/2/index.html index f828dfdd44..45d23d34e8 100644 --- a/archives/2020/page/2/index.html +++ b/archives/2020/page/2/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/archives/2020/page/3/index.html b/archives/2020/page/3/index.html index 5b0fc1a10a..c6e3cf0106 100644 --- a/archives/2020/page/3/index.html +++ b/archives/2020/page/3/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/archives/2020/page/4/index.html b/archives/2020/page/4/index.html index ab31ac2470..0e8382fffa 100644 --- a/archives/2020/page/4/index.html +++ b/archives/2020/page/4/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/archives/2020/page/5/index.html b/archives/2020/page/5/index.html index 2fc4aa2167..1e465ce3f8 100644 --- a/archives/2020/page/5/index.html +++ b/archives/2020/page/5/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/archives/2020/page/6/index.html b/archives/2020/page/6/index.html index e828fd1e3d..cee39d6967 100644 --- a/archives/2020/page/6/index.html +++ b/archives/2020/page/6/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -353,20 +353,20 @@ @@ -391,7 +391,7 @@
- +
diff --git a/archives/2021/01/index.html b/archives/2021/01/index.html index f83d7a4254..a751e28357 100644 --- a/archives/2021/01/index.html +++ b/archives/2021/01/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -390,20 +390,20 @@ @@ -428,7 +428,7 @@
- +
diff --git a/archives/2021/02/index.html b/archives/2021/02/index.html index d0e040f9e2..380a60ba02 100644 --- a/archives/2021/02/index.html +++ b/archives/2021/02/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/archives/2021/03/index.html b/archives/2021/03/index.html index 15d038f0a1..f2bd71df64 100644 --- a/archives/2021/03/index.html +++ b/archives/2021/03/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -390,20 +390,20 @@ @@ -428,7 +428,7 @@
- +
diff --git a/archives/2021/04/index.html b/archives/2021/04/index.html index 51a8066e86..390cb56841 100644 --- a/archives/2021/04/index.html +++ b/archives/2021/04/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/archives/2021/05/index.html b/archives/2021/05/index.html new file mode 100644 index 0000000000..9b527b83ef --- /dev/null +++ b/archives/2021/05/index.html @@ -0,0 +1,594 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Archive | Nicksxs's Blog + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Nicksxs's Blog

+ +
+

What hurts more, the pain of hard work or the pain of regret?

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + + + +
+
+
+ + +
+ + + + + +
+
+
+ + Good! 101 posts in total. Keep on posting. +
+ + +
+ 2021 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2021/index.html b/archives/2021/index.html index a9ac623205..fc1880be1f 100644 --- a/archives/2021/index.html +++ b/archives/2021/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -216,6 +216,26 @@ 2021
+ +
@@ -396,26 +416,6 @@
- -
@@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/archives/2021/page/2/index.html b/archives/2021/page/2/index.html index 54de4bef38..3c7bf1ee4a 100644 --- a/archives/2021/page/2/index.html +++ b/archives/2021/page/2/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -216,6 +216,26 @@ 2021
+ +
@@ -453,20 +473,20 @@ @@ -491,7 +511,7 @@
- +
diff --git a/archives/index.html b/archives/index.html index c58ab8df5b..cca9de4d04 100644 --- a/archives/index.html +++ b/archives/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -216,6 +216,26 @@ 2021
+ +
@@ -396,26 +416,6 @@
- -
@@ -425,7 +425,7 @@ @@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/archives/page/10/index.html b/archives/page/10/index.html index 6d003096c8..b9a1c95842 100644 --- a/archives/page/10/index.html +++ b/archives/page/10/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -216,6 +216,26 @@ 2015
+ +
@@ -399,26 +419,6 @@
- -
@@ -428,7 +428,7 @@ @@ -496,20 +496,20 @@ @@ -534,7 +534,7 @@
- +
diff --git a/archives/page/11/index.html b/archives/page/11/index.html new file mode 100644 index 0000000000..baa80b3b6a --- /dev/null +++ b/archives/page/11/index.html @@ -0,0 +1,597 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Archive | Nicksxs's Blog + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Nicksxs's Blog

+ +
+

What hurts more, the pain of hard work or the pain of regret?

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + + + +
+
+
+ + +
+ + + + + +
+
+
+ + Good! 101 posts in total. Keep on posting. +
+ + +
+ 2014 +
+ + + + +
+
+ + + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/page/2/index.html b/archives/page/2/index.html index bcb9667959..85b750b7c6 100644 --- a/archives/page/2/index.html +++ b/archives/page/2/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -216,6 +216,26 @@ 2021
+ +
@@ -399,26 +419,6 @@
- -
@@ -428,7 +428,7 @@ @@ -496,20 +496,20 @@ @@ -534,7 +534,7 @@
- +
diff --git a/archives/page/3/index.html b/archives/page/3/index.html index f4d6e4b0ca..4b02aca175 100644 --- a/archives/page/3/index.html +++ b/archives/page/3/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -216,6 +216,26 @@ 2020
+ +
@@ -396,26 +416,6 @@
- -
@@ -425,7 +425,7 @@ @@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/archives/page/4/index.html b/archives/page/4/index.html index 1b55504f7b..d55574a6f2 100644 --- a/archives/page/4/index.html +++ b/archives/page/4/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -216,6 +216,26 @@ 2020
+ +
@@ -396,26 +416,6 @@
- -
@@ -425,7 +425,7 @@ @@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/archives/page/5/index.html b/archives/page/5/index.html index 4100764f75..0b69bbbe86 100644 --- a/archives/page/5/index.html +++ b/archives/page/5/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -216,6 +216,26 @@ 2020
+ +
@@ -396,26 +416,6 @@
- -
@@ -425,7 +425,7 @@ @@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/archives/page/6/index.html b/archives/page/6/index.html index 3469bf3be0..e755d3d1b3 100644 --- a/archives/page/6/index.html +++ b/archives/page/6/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -216,6 +216,26 @@ 2020
+ +
@@ -396,26 +416,6 @@
- -
@@ -425,7 +425,7 @@ @@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/archives/page/7/index.html b/archives/page/7/index.html index 1da9fcf567..519e841479 100644 --- a/archives/page/7/index.html +++ b/archives/page/7/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -216,6 +216,26 @@ 2020
+ +
@@ -396,26 +416,6 @@
- -
@@ -425,7 +425,7 @@ @@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/archives/page/8/index.html b/archives/page/8/index.html index 51faa35511..c849d6cb50 100644 --- a/archives/page/8/index.html +++ b/archives/page/8/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -216,6 +216,26 @@ 2020
+ +
@@ -402,26 +422,6 @@
- -
@@ -431,7 +431,7 @@ @@ -499,20 +499,20 @@ @@ -537,7 +537,7 @@
- +
diff --git a/archives/page/9/index.html b/archives/page/9/index.html index 1fdd379130..9d099c330b 100644 --- a/archives/page/9/index.html +++ b/archives/page/9/index.html @@ -208,7 +208,7 @@
- Good! 100 posts in total. Keep on posting. + Good! 101 posts in total. Keep on posting.
@@ -216,6 +216,26 @@ 2017
+ +
@@ -402,26 +422,6 @@
- -
@@ -431,7 +431,7 @@ @@ -499,20 +499,20 @@ @@ -537,7 +537,7 @@
- +
diff --git a/atom.xml b/atom.xml index 0d194e9a3f..bd5df97840 100644 --- a/atom.xml +++ b/atom.xml @@ -6,7 +6,7 @@ - 2021-04-18T14:06:53.609Z + 2021-05-01T15:28:26.727Z https://nicksxs.me/ @@ -16,6 +16,45 @@ Hexo + + Leetcode 48 旋转图像(Rotate Image) 题解分析 + + https://nicksxs.me/2021/05/01/Leetcode-48-%E6%97%8B%E8%BD%AC%E5%9B%BE%E5%83%8F-Rotate-Image-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 2021-05-01T15:28:26.000Z + 2021-05-01T15:28:26.727Z + + + + + + + + <h3 id="题目介绍"><a href="#题目介绍" class="headerlink" title="题目介绍"></a>题目介绍</h3><p>You are given an n x n 2D <code>matrix</code> representing an + + + + + + + + + + + + + + + + + + + + + + + + + rust学习笔记-所有权一 @@ -791,10 +830,10 @@ - - + + @@ -812,43 +851,4 @@ - - 从丁仲礼被美国制裁聊点啥 - - https://nicksxs.me/2020/12/20/%E4%BB%8E%E4%B8%81%E4%BB%B2%E7%A4%BC%E8%A2%AB%E7%BE%8E%E5%9B%BD%E5%88%B6%E8%A3%81%E8%81%8A%E7%82%B9%E5%95%A5/ - 2020-12-20T15:18:50.000Z - 2020-12-20T15:18:50.349Z - - - - - - - - <p>几年前看了柴静的《穹顶之下》觉得这个记者调查得很深入,挺有水平,然后再看到了她跟丁仲礼的采访,其实没看完整,也没试着去理解,就觉得环境问题挺严重的,为啥柴静这个对面的这位好像对这个很不屑的样子,最近因为丁仲礼上了美国制裁名单,B - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/baidu_verify_Gl8jtoDV4z.html b/baidu_verify_Gl8jtoDV4z.html index 6466b04aea..ea11a6e311 100644 --- a/baidu_verify_Gl8jtoDV4z.html +++ b/baidu_verify_Gl8jtoDV4z.html @@ -309,20 +309,20 @@ @@ -347,7 +347,7 @@
- +
diff --git a/baidusitemap.xml b/baidusitemap.xml index b178dedb2c..e6abfaac05 100644 --- a/baidusitemap.xml +++ b/baidusitemap.xml @@ -1,6 +1,9 @@ + https://nicksxs.me/2021/05/01/Leetcode-48-%E6%97%8B%E8%BD%AC%E5%9B%BE%E5%83%8F-Rotate-Image-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 2021-05-01 + https://nicksxs.me/2021/04/18/rust%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-%E6%89%80%E6%9C%89%E6%9D%83%E4%BA%8C/ 2021-04-25 @@ -232,25 +235,25 @@ https://nicksxs.me/2015/03/11/Reverse-Bits/ 2020-01-12 - https://nicksxs.me/2016/11/10/php-abstract-class-and-interface/ + https://nicksxs.me/2015/03/13/Reverse-Integer/ 2020-01-12 - https://nicksxs.me/2017/05/09/ambari-summary/ + https://nicksxs.me/2016/11/10/php-abstract-class-and-interface/ 2020-01-12 https://nicksxs.me/2019/06/18/openresty/ 2020-01-12 - https://nicksxs.me/2016/09/29/binary-watch/ + https://nicksxs.me/2015/01/14/Two-Sum/ 2020-01-12 - https://nicksxs.me/2015/03/13/Reverse-Integer/ + https://nicksxs.me/2017/05/09/ambari-summary/ 2020-01-12 https://nicksxs.me/2016/10/11/minimum-size-subarray-sum-209/ 2020-01-12 - https://nicksxs.me/2015/01/14/Two-Sum/ + https://nicksxs.me/2016/09/29/binary-watch/ 2020-01-12 https://nicksxs.me/2016/08/14/docker-mysql-cluster/ diff --git a/categories/Binary-Tree/index.html b/categories/Binary-Tree/index.html index a76d81ed3d..46179a6cc8 100644 --- a/categories/Binary-Tree/index.html +++ b/categories/Binary-Tree/index.html @@ -353,20 +353,20 @@ @@ -391,7 +391,7 @@
- +
diff --git a/categories/C/Mysql/index.html b/categories/C/Mysql/index.html index bcab315a54..e06cd8e391 100644 --- a/categories/C/Mysql/index.html +++ b/categories/C/Mysql/index.html @@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/categories/C/Redis/index.html b/categories/C/Redis/index.html index dcdb32cade..6e8e091e70 100644 --- a/categories/C/Redis/index.html +++ b/categories/C/Redis/index.html @@ -473,20 +473,20 @@ @@ -511,7 +511,7 @@
- +
diff --git a/categories/C/index.html b/categories/C/index.html index 304ceb442e..00e9fceb08 100644 --- a/categories/C/index.html +++ b/categories/C/index.html @@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/categories/C/page/2/index.html b/categories/C/page/2/index.html index 195739dc0e..4e0c0f1845 100644 --- a/categories/C/page/2/index.html +++ b/categories/C/page/2/index.html @@ -356,20 +356,20 @@ @@ -394,7 +394,7 @@
- +
diff --git a/categories/DP/index.html b/categories/DP/index.html index d82ed09e34..b7a1016e89 100644 --- a/categories/DP/index.html +++ b/categories/DP/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Dubbo-RPC-SPI/index.html b/categories/Dubbo-RPC-SPI/index.html index e872236b06..16b69ac9dc 100644 --- a/categories/Dubbo-RPC-SPI/index.html +++ b/categories/Dubbo-RPC-SPI/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/Dubbo-RPC/index.html b/categories/Dubbo-RPC/index.html index 29378734f4..a268d006b8 100644 --- a/categories/Dubbo-RPC/index.html +++ b/categories/Dubbo-RPC/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Dubbo-线程池/index.html b/categories/Dubbo-线程池/index.html index c71f34dde4..5664fc1ae5 100644 --- a/categories/Dubbo-线程池/index.html +++ b/categories/Dubbo-线程池/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Dubbo/SPI/Adaptive/index.html b/categories/Dubbo/SPI/Adaptive/index.html index 39878ab9bd..e6ea48084e 100644 --- a/categories/Dubbo/SPI/Adaptive/index.html +++ b/categories/Dubbo/SPI/Adaptive/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Dubbo/SPI/index.html b/categories/Dubbo/SPI/index.html index 67840c5321..6c587e332f 100644 --- a/categories/Dubbo/SPI/index.html +++ b/categories/Dubbo/SPI/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/Dubbo/index.html b/categories/Dubbo/index.html index 38620be14b..0b6ac5b3b3 100644 --- a/categories/Dubbo/index.html +++ b/categories/Dubbo/index.html @@ -373,20 +373,20 @@ @@ -411,7 +411,7 @@
- +
diff --git a/categories/Dubbo/容错机制/index.html b/categories/Dubbo/容错机制/index.html index bf93199784..af8a14b9b7 100644 --- a/categories/Dubbo/容错机制/index.html +++ b/categories/Dubbo/容错机制/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Dubbo/线程池/ThreadPool/index.html b/categories/Dubbo/线程池/ThreadPool/index.html index e38196607b..4830f1a430 100644 --- a/categories/Dubbo/线程池/ThreadPool/index.html +++ b/categories/Dubbo/线程池/ThreadPool/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Dubbo/线程池/index.html b/categories/Dubbo/线程池/index.html index 844e5e6c53..e4cd29cd6e 100644 --- a/categories/Dubbo/线程池/index.html +++ b/categories/Dubbo/线程池/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Filter/index.html b/categories/Filter/index.html index a001b4b24c..06df4550bc 100644 --- a/categories/Filter/index.html +++ b/categories/Filter/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Interceptor-AOP/index.html b/categories/Interceptor-AOP/index.html index ad880236ee..3ae8c3be14 100644 --- a/categories/Interceptor-AOP/index.html +++ b/categories/Interceptor-AOP/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Java/Apollo/index.html b/categories/Java/Apollo/index.html index aaca2030ea..fde88bba9f 100644 --- a/categories/Java/Apollo/index.html +++ b/categories/Java/Apollo/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Java/Apollo/value/index.html b/categories/Java/Apollo/value/index.html index 37116ab85d..9e47425d06 100644 --- a/categories/Java/Apollo/value/index.html +++ b/categories/Java/Apollo/value/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Java/Design-Patterns/index.html b/categories/Java/Design-Patterns/index.html index 0a6f794bb0..875bbd8620 100644 --- a/categories/Java/Design-Patterns/index.html +++ b/categories/Java/Design-Patterns/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Java/GC/index.html b/categories/Java/GC/index.html index 2709bcb16e..7868775911 100644 --- a/categories/Java/GC/index.html +++ b/categories/Java/GC/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Java/GC/jvm/index.html b/categories/Java/GC/jvm/index.html index dcfcdc1a31..152838c717 100644 --- a/categories/Java/GC/jvm/index.html +++ b/categories/Java/GC/jvm/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Java/JVM/index.html b/categories/Java/JVM/index.html index 52be0a1b0b..aacf2ebab7 100644 --- a/categories/Java/JVM/index.html +++ b/categories/Java/JVM/index.html @@ -333,20 +333,20 @@ @@ -371,7 +371,7 @@
- +
diff --git a/categories/Java/Maven/index.html b/categories/Java/Maven/index.html index cafd9ba1ff..8bd79e1e24 100644 --- a/categories/Java/Maven/index.html +++ b/categories/Java/Maven/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Java/Mybatis/index.html b/categories/Java/Mybatis/index.html index 350895b62c..ecbc2dcc3c 100644 --- a/categories/Java/Mybatis/index.html +++ b/categories/Java/Mybatis/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/Java/Singleton/index.html b/categories/Java/Singleton/index.html index a88c902e88..a2101c9bb5 100644 --- a/categories/Java/Singleton/index.html +++ b/categories/Java/Singleton/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Java/index.html b/categories/Java/index.html index ed84cf8616..fcb0a9ad57 100644 --- a/categories/Java/index.html +++ b/categories/Java/index.html @@ -29,7 +29,7 @@ - + @@ -40,7 +40,7 @@ - + - Category: Java | Nicksxs's Blog + Category: java | Nicksxs's Blog + + + + + + +exT.Pisces +
+ + +
+ + + + +
+ + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Category: Rotate Image | Nicksxs's Blog + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Nicksxs's Blog

+ +
+

What hurts more, the pain of hard work or the pain of regret?

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + + + +
+
+
+ + +
+ + + + + +
+
+
+

Rotate Image + Category +

+
+ + +
+ 2021 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/Java/leetcode/index.html b/categories/Java/leetcode/index.html new file mode 100644 index 0000000000..a165780ed6 --- /dev/null +++ b/categories/Java/leetcode/index.html @@ -0,0 +1,604 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Category: leetcode | Nicksxs's Blog + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Nicksxs's Blog

+ +
+

What hurts more, the pain of hard work or the pain of regret?

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + + + +
+
+
+ + +
+ + + + + +
+
+
+

leetcode + Category +

+
+ + +
+ 2021 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/Java/page/2/index.html b/categories/Java/page/2/index.html index 3244adfa71..da38c2feb3 100644 --- a/categories/Java/page/2/index.html +++ b/categories/Java/page/2/index.html @@ -217,6 +217,26 @@ 2020
+ +
@@ -397,26 +417,6 @@
- -
@@ -493,20 +493,20 @@ @@ -531,7 +531,7 @@
- +
diff --git a/categories/Java/page/3/index.html b/categories/Java/page/3/index.html index 5a98401c47..38733d41e0 100644 --- a/categories/Java/page/3/index.html +++ b/categories/Java/page/3/index.html @@ -217,6 +217,26 @@ 2020
+ +
@@ -456,20 +476,20 @@ @@ -494,7 +514,7 @@
- +
diff --git a/categories/Java/并发/index.html b/categories/Java/并发/index.html index f902d40a73..a694ad27b5 100644 --- a/categories/Java/并发/index.html +++ b/categories/Java/并发/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/Java/类加载/index.html b/categories/Java/类加载/index.html index 2232c71567..940c370f95 100644 --- a/categories/Java/类加载/index.html +++ b/categories/Java/类加载/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Java/集合/index.html b/categories/Java/集合/index.html index 238a0c6674..db5cd7e9c2 100644 --- a/categories/Java/集合/index.html +++ b/categories/Java/集合/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Linux/index.html b/categories/Linux/index.html index 9b9c0065b8..28e645d337 100644 --- a/categories/Linux/index.html +++ b/categories/Linux/index.html @@ -353,20 +353,20 @@ @@ -391,7 +391,7 @@
- +
diff --git a/categories/Linux/命令/echo/index.html b/categories/Linux/命令/echo/index.html index 4f3fba9803..e46dcfe70a 100644 --- a/categories/Linux/命令/echo/index.html +++ b/categories/Linux/命令/echo/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Linux/命令/grep/index.html b/categories/Linux/命令/grep/index.html index 6eaa8c91fd..b8a51b659e 100644 --- a/categories/Linux/命令/grep/index.html +++ b/categories/Linux/命令/grep/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Linux/命令/index.html b/categories/Linux/命令/index.html index 49c45ce331..12ec23613b 100644 --- a/categories/Linux/命令/index.html +++ b/categories/Linux/命令/index.html @@ -353,20 +353,20 @@ @@ -391,7 +391,7 @@
- +
diff --git a/categories/Linux/命令/top/index.html b/categories/Linux/命令/top/index.html index 1c6cbfe2db..6e37b5d736 100644 --- a/categories/Linux/命令/top/index.html +++ b/categories/Linux/命令/top/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/MQ/index.html b/categories/MQ/index.html index 84be48aa6e..3a1a2def4c 100644 --- a/categories/MQ/index.html +++ b/categories/MQ/index.html @@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/categories/Mac/Homebrew/index.html b/categories/Mac/Homebrew/index.html index 7e6519ff67..05c18ff152 100644 --- a/categories/Mac/Homebrew/index.html +++ b/categories/Mac/Homebrew/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Mac/index.html b/categories/Mac/index.html index 96517b9e5e..d23c604dfb 100644 --- a/categories/Mac/index.html +++ b/categories/Mac/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Mybatis/index.html b/categories/Mybatis/index.html index 4916679a23..b44fd69648 100644 --- a/categories/Mybatis/index.html +++ b/categories/Mybatis/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Mybatis/缓存/index.html b/categories/Mybatis/缓存/index.html index 3dba3baced..85d71511f9 100644 --- a/categories/Mybatis/缓存/index.html +++ b/categories/Mybatis/缓存/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Mysql/Sql注入/index.html b/categories/Mysql/Sql注入/index.html index c7cdea2bb1..ce50bdf0e0 100644 --- a/categories/Mysql/Sql注入/index.html +++ b/categories/Mysql/Sql注入/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Mysql/index.html b/categories/Mysql/index.html index ee2013c765..e31a54005e 100644 --- a/categories/Mysql/index.html +++ b/categories/Mysql/index.html @@ -390,20 +390,20 @@ @@ -428,7 +428,7 @@
- +
diff --git a/categories/Mysql/数据结构/index.html b/categories/Mysql/数据结构/index.html index 1048131411..e5f1392fb9 100644 --- a/categories/Mysql/数据结构/index.html +++ b/categories/Mysql/数据结构/index.html @@ -350,20 +350,20 @@ @@ -388,7 +388,7 @@
- +
diff --git a/categories/Mysql/源码/index.html b/categories/Mysql/源码/index.html index 405874414a..6db2ea9818 100644 --- a/categories/Mysql/源码/index.html +++ b/categories/Mysql/源码/index.html @@ -350,20 +350,20 @@ @@ -388,7 +388,7 @@
- +
diff --git a/categories/Mysql/索引/index.html b/categories/Mysql/索引/index.html index 856182b7af..f4c3e2ed79 100644 --- a/categories/Mysql/索引/index.html +++ b/categories/Mysql/索引/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Redis/Distributed-Lock/index.html b/categories/Redis/Distributed-Lock/index.html index 8ae1536241..90ee30a392 100644 --- a/categories/Redis/Distributed-Lock/index.html +++ b/categories/Redis/Distributed-Lock/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Redis/index.html b/categories/Redis/index.html index 54a41fe4c8..2ed9f33cf3 100644 --- a/categories/Redis/index.html +++ b/categories/Redis/index.html @@ -496,20 +496,20 @@ @@ -534,7 +534,7 @@
- +
diff --git a/categories/Redis/应用/index.html b/categories/Redis/应用/index.html index f275e2cf36..da1c5ecf82 100644 --- a/categories/Redis/应用/index.html +++ b/categories/Redis/应用/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Redis/数据结构/index.html b/categories/Redis/数据结构/index.html index 6424866484..22dc61825a 100644 --- a/categories/Redis/数据结构/index.html +++ b/categories/Redis/数据结构/index.html @@ -453,20 +453,20 @@ @@ -491,7 +491,7 @@
- +
diff --git a/categories/Redis/源码/index.html b/categories/Redis/源码/index.html index d13991f98e..e1a6089c1c 100644 --- a/categories/Redis/源码/index.html +++ b/categories/Redis/源码/index.html @@ -453,20 +453,20 @@ @@ -491,7 +491,7 @@
- +
diff --git a/categories/Redis/缓存/index.html b/categories/Redis/缓存/index.html index a7bf8340ef..a715cfa98f 100644 --- a/categories/Redis/缓存/index.html +++ b/categories/Redis/缓存/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/RocketMQ/index.html b/categories/RocketMQ/index.html index e5c79b2b4c..9b497acda3 100644 --- a/categories/RocketMQ/index.html +++ b/categories/RocketMQ/index.html @@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/categories/Rust/index.html b/categories/Rust/index.html index 7ea0f12a5a..5ce7564afc 100644 --- a/categories/Rust/index.html +++ b/categories/Rust/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/Spring/Mybatis/index.html b/categories/Spring/Mybatis/index.html index 79f56d0df4..53722bb4b1 100644 --- a/categories/Spring/Mybatis/index.html +++ b/categories/Spring/Mybatis/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/Spring/Servlet/Interceptor/AOP/index.html b/categories/Spring/Servlet/Interceptor/AOP/index.html index 9e8b3a543d..ef7e29b53d 100644 --- a/categories/Spring/Servlet/Interceptor/AOP/index.html +++ b/categories/Spring/Servlet/Interceptor/AOP/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Spring/Servlet/Interceptor/index.html b/categories/Spring/Servlet/Interceptor/index.html index be65e775af..4d1d30a23f 100644 --- a/categories/Spring/Servlet/Interceptor/index.html +++ b/categories/Spring/Servlet/Interceptor/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Spring/Servlet/index.html b/categories/Spring/Servlet/index.html index a569fd6539..6f98d8c6ee 100644 --- a/categories/Spring/Servlet/index.html +++ b/categories/Spring/Servlet/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/Spring/index.html b/categories/Spring/index.html index 8d4e093855..5bc24eac88 100644 --- a/categories/Spring/index.html +++ b/categories/Spring/index.html @@ -350,20 +350,20 @@ @@ -388,7 +388,7 @@
- +
diff --git a/categories/Thread-dump/index.html b/categories/Thread-dump/index.html index e0035f0c44..fed39b48e4 100644 --- a/categories/Thread-dump/index.html +++ b/categories/Thread-dump/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/data-analysis/index.html b/categories/data-analysis/index.html index 229f480f89..5e63455fd0 100644 --- a/categories/data-analysis/index.html +++ b/categories/data-analysis/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/docker/index.html b/categories/docker/index.html index fa49b2e4d9..94ac71e5f4 100644 --- a/categories/docker/index.html +++ b/categories/docker/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/docker/介绍/index.html b/categories/docker/介绍/index.html index 804affa50c..5b5701105a 100644 --- a/categories/docker/介绍/index.html +++ b/categories/docker/介绍/index.html @@ -350,20 +350,20 @@ @@ -388,7 +388,7 @@
- +
diff --git a/categories/docker/发行版本/index.html b/categories/docker/发行版本/index.html index 6a1bf11503..f551979954 100644 --- a/categories/docker/发行版本/index.html +++ b/categories/docker/发行版本/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/index.html b/categories/index.html index 8d1c99f192..862679b86c 100644 --- a/categories/index.html +++ b/categories/index.html @@ -228,10 +228,10 @@
- 127 categories in total + 129 categories in total
- +
@@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/leetcode/index.html b/categories/leetcode/index.html index e67cd375de..293149c70a 100644 --- a/categories/leetcode/index.html +++ b/categories/leetcode/index.html @@ -499,20 +499,20 @@ @@ -537,7 +537,7 @@
- +
diff --git a/categories/leetcode/java/Binary-Tree/DFS/index.html b/categories/leetcode/java/Binary-Tree/DFS/index.html index 8fd1798498..9e60e2c75a 100644 --- a/categories/leetcode/java/Binary-Tree/DFS/index.html +++ b/categories/leetcode/java/Binary-Tree/DFS/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/leetcode/java/Binary-Tree/index.html b/categories/leetcode/java/Binary-Tree/index.html index 20bd5019e9..f749508ff3 100644 --- a/categories/leetcode/java/Binary-Tree/index.html +++ b/categories/leetcode/java/Binary-Tree/index.html @@ -353,20 +353,20 @@ @@ -391,7 +391,7 @@
- +
diff --git a/categories/leetcode/java/DP/index.html b/categories/leetcode/java/DP/index.html index 4fc96a3e45..e6524e11e1 100644 --- a/categories/leetcode/java/DP/index.html +++ b/categories/leetcode/java/DP/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/leetcode/java/index.html b/categories/leetcode/java/index.html index 3b562075bb..ad375d3d14 100644 --- a/categories/leetcode/java/index.html +++ b/categories/leetcode/java/index.html @@ -473,20 +473,20 @@ @@ -511,7 +511,7 @@
- +
diff --git a/categories/leetcode/java/linked-list/index.html b/categories/leetcode/java/linked-list/index.html index 1ec089c3ed..3aa373024d 100644 --- a/categories/leetcode/java/linked-list/index.html +++ b/categories/leetcode/java/linked-list/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/leetcode/java/stack/index.html b/categories/leetcode/java/stack/index.html index 9810ac1e1d..9cdc7410ed 100644 --- a/categories/leetcode/java/stack/index.html +++ b/categories/leetcode/java/stack/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/leetcode/java/string/index.html b/categories/leetcode/java/string/index.html index 7233039055..ae9f76d456 100644 --- a/categories/leetcode/java/string/index.html +++ b/categories/leetcode/java/string/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/leetcode/page/2/index.html b/categories/leetcode/page/2/index.html index 03395f3e89..cbb0b705ca 100644 --- a/categories/leetcode/page/2/index.html +++ b/categories/leetcode/page/2/index.html @@ -496,20 +496,20 @@ @@ -534,7 +534,7 @@
- +
diff --git a/categories/leetcode/page/3/index.html b/categories/leetcode/page/3/index.html index 78d6b7a0d6..a5e7742810 100644 --- a/categories/leetcode/page/3/index.html +++ b/categories/leetcode/page/3/index.html @@ -336,20 +336,20 @@ @@ -374,7 +374,7 @@
- +
diff --git a/categories/linked-list/index.html b/categories/linked-list/index.html index 9b2b6f300f..ab3f351e60 100644 --- a/categories/linked-list/index.html +++ b/categories/linked-list/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/nginx/index.html b/categories/nginx/index.html index 7f04bc0367..f9a07d049b 100644 --- a/categories/nginx/index.html +++ b/categories/nginx/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/php/icu4c/index.html b/categories/php/icu4c/index.html index aeb4ab97af..73a28d9e59 100644 --- a/categories/php/icu4c/index.html +++ b/categories/php/icu4c/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/php/index.html b/categories/php/index.html index 13452acd85..b5001b99b7 100644 --- a/categories/php/index.html +++ b/categories/php/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/stack/index.html b/categories/stack/index.html index 483024be82..33489af3ee 100644 --- a/categories/stack/index.html +++ b/categories/stack/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/中间件/RocketMQ/index.html b/categories/中间件/RocketMQ/index.html index 73e5ea3d79..12bd5f1059 100644 --- a/categories/中间件/RocketMQ/index.html +++ b/categories/中间件/RocketMQ/index.html @@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/categories/中间件/index.html b/categories/中间件/index.html index c7339fbb44..059fa003f0 100644 --- a/categories/中间件/index.html +++ b/categories/中间件/index.html @@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/categories/分布式事务/index.html b/categories/分布式事务/index.html index 6b315688fa..e299e89eb3 100644 --- a/categories/分布式事务/index.html +++ b/categories/分布式事务/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/分布式事务/三阶段提交/index.html b/categories/分布式事务/三阶段提交/index.html index 8e47d970b4..78d3fbb617 100644 --- a/categories/分布式事务/三阶段提交/index.html +++ b/categories/分布式事务/三阶段提交/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/分布式事务/两阶段提交/index.html b/categories/分布式事务/两阶段提交/index.html index 5f832c0127..962b32f1b5 100644 --- a/categories/分布式事务/两阶段提交/index.html +++ b/categories/分布式事务/两阶段提交/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/字符串-online/index.html b/categories/字符串-online/index.html index 1a5c347df8..164a38a2b9 100644 --- a/categories/字符串-online/index.html +++ b/categories/字符串-online/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/小技巧/grep/index.html b/categories/小技巧/grep/index.html index 37d1a05ff0..c5ed001714 100644 --- a/categories/小技巧/grep/index.html +++ b/categories/小技巧/grep/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/小技巧/grep/查日志/index.html b/categories/小技巧/grep/查日志/index.html index d21f90745e..aae46447cb 100644 --- a/categories/小技巧/grep/查日志/index.html +++ b/categories/小技巧/grep/查日志/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/小技巧/index.html b/categories/小技巧/index.html index 5b79ccd3f9..ba3b884aaf 100644 --- a/categories/小技巧/index.html +++ b/categories/小技巧/index.html @@ -333,20 +333,20 @@ @@ -371,7 +371,7 @@
- +
diff --git a/categories/小技巧/top/index.html b/categories/小技巧/top/index.html index a85b7b5eae..a3ebc8144b 100644 --- a/categories/小技巧/top/index.html +++ b/categories/小技巧/top/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/小技巧/top/排序/index.html b/categories/小技巧/top/排序/index.html index f583d282c2..09911f6cf7 100644 --- a/categories/小技巧/top/排序/index.html +++ b/categories/小技巧/top/排序/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/工具/index.html b/categories/工具/index.html index f7bc73a425..6d203671be 100644 --- a/categories/工具/index.html +++ b/categories/工具/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/年终总结/2020/index.html b/categories/年终总结/2020/index.html index c09283c590..2683fbd4cc 100644 --- a/categories/年终总结/2020/index.html +++ b/categories/年终总结/2020/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/年终总结/index.html b/categories/年终总结/index.html index e45e7e8113..cf5dc8fc55 100644 --- a/categories/年终总结/index.html +++ b/categories/年终总结/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/持续集成/index.html b/categories/持续集成/index.html index b62716d098..c06e6d5029 100644 --- a/categories/持续集成/index.html +++ b/categories/持续集成/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/消息队列/RocketMQ/index.html b/categories/消息队列/RocketMQ/index.html index f513c7d679..1c58d0a724 100644 --- a/categories/消息队列/RocketMQ/index.html +++ b/categories/消息队列/RocketMQ/index.html @@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/categories/消息队列/index.html b/categories/消息队列/index.html index e311ea4960..402a0de08f 100644 --- a/categories/消息队列/index.html +++ b/categories/消息队列/index.html @@ -370,20 +370,20 @@ @@ -408,7 +408,7 @@
- +
diff --git a/categories/生活/index.html b/categories/生活/index.html index ecd423799b..c7a6e69d7b 100644 --- a/categories/生活/index.html +++ b/categories/生活/index.html @@ -496,20 +496,20 @@ @@ -534,7 +534,7 @@
- +
diff --git a/categories/生活/page/2/index.html b/categories/生活/page/2/index.html index d2e069f17b..6dfcd9b66b 100644 --- a/categories/生活/page/2/index.html +++ b/categories/生活/page/2/index.html @@ -456,20 +456,20 @@ @@ -494,7 +494,7 @@
- +
diff --git a/categories/生活/公交/index.html b/categories/生活/公交/index.html index 27a274d5cd..5b7d58c70d 100644 --- a/categories/生活/公交/index.html +++ b/categories/生活/公交/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/生活/吐槽/index.html b/categories/生活/吐槽/index.html index c46c789963..22fb44330c 100644 --- a/categories/生活/吐槽/index.html +++ b/categories/生活/吐槽/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/生活/吐槽/疫情/index.html b/categories/生活/吐槽/疫情/index.html index 506125cfb8..9e04c5bd3d 100644 --- a/categories/生活/吐槽/疫情/index.html +++ b/categories/生活/吐槽/疫情/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/生活/吐槽/疫情/口罩/index.html b/categories/生活/吐槽/疫情/口罩/index.html index 6020937311..5daf1d6216 100644 --- a/categories/生活/吐槽/疫情/口罩/index.html +++ b/categories/生活/吐槽/疫情/口罩/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/生活/吐槽/疫情/美国/index.html b/categories/生活/吐槽/疫情/美国/index.html index f9c65edf30..70174c8ac9 100644 --- a/categories/生活/吐槽/疫情/美国/index.html +++ b/categories/生活/吐槽/疫情/美国/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/生活/年中总结/2020/index.html b/categories/生活/年中总结/2020/index.html index 29620fc057..9f59b7f7ed 100644 --- a/categories/生活/年中总结/2020/index.html +++ b/categories/生活/年中总结/2020/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/生活/年中总结/index.html b/categories/生活/年中总结/index.html index afcb492bb1..5a110895d4 100644 --- a/categories/生活/年中总结/index.html +++ b/categories/生活/年中总结/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/生活/年终总结/2019/index.html b/categories/生活/年终总结/2019/index.html index c6f35048e6..cc23686b6e 100644 --- a/categories/生活/年终总结/2019/index.html +++ b/categories/生活/年终总结/2019/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/生活/年终总结/2020/index.html b/categories/生活/年终总结/2020/index.html index 39b3bec528..1d451e6152 100644 --- a/categories/生活/年终总结/2020/index.html +++ b/categories/生活/年终总结/2020/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/生活/年终总结/index.html b/categories/生活/年终总结/index.html index 2de9bddea8..2c892a2f1b 100644 --- a/categories/生活/年终总结/index.html +++ b/categories/生活/年终总结/index.html @@ -333,20 +333,20 @@ @@ -371,7 +371,7 @@
- +
diff --git a/categories/生活/开车/index.html b/categories/生活/开车/index.html index 694024b53f..f0bf398206 100644 --- a/categories/生活/开车/index.html +++ b/categories/生活/开车/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/生活/影评/2020/index.html b/categories/生活/影评/2020/index.html index 60e44ce230..8969c9e274 100644 --- a/categories/生活/影评/2020/index.html +++ b/categories/生活/影评/2020/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/生活/影评/index.html b/categories/生活/影评/index.html index 85bd7607d1..d33ed380c5 100644 --- a/categories/生活/影评/index.html +++ b/categories/生活/影评/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/生活/旅游/index.html b/categories/生活/旅游/index.html index 6c9e530441..a78053b30e 100644 --- a/categories/生活/旅游/index.html +++ b/categories/生活/旅游/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/生活/运动/index.html b/categories/生活/运动/index.html index d3c4d0402c..58da24c65d 100644 --- a/categories/生活/运动/index.html +++ b/categories/生活/运动/index.html @@ -410,20 +410,20 @@ @@ -448,7 +448,7 @@
- +
diff --git a/categories/生活/运动/跑步/index.html b/categories/生活/运动/跑步/index.html index 3a361a6464..6a9ec86f6c 100644 --- a/categories/生活/运动/跑步/index.html +++ b/categories/生活/运动/跑步/index.html @@ -410,20 +410,20 @@ @@ -448,7 +448,7 @@
- +
diff --git a/categories/生活/运动/跑步/干活/index.html b/categories/生活/运动/跑步/干活/index.html index aa086d87bb..fe25b6d265 100644 --- a/categories/生活/运动/跑步/干活/index.html +++ b/categories/生活/运动/跑步/干活/index.html @@ -390,20 +390,20 @@ @@ -428,7 +428,7 @@
- +
diff --git a/categories/缓存/index.html b/categories/缓存/index.html index 17c3b3c694..9cb09baf36 100644 --- a/categories/缓存/index.html +++ b/categories/缓存/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/缓存/穿透/index.html b/categories/缓存/穿透/index.html index 9a12d80f08..9b81165d42 100644 --- a/categories/缓存/穿透/index.html +++ b/categories/缓存/穿透/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/缓存/穿透/击穿/index.html b/categories/缓存/穿透/击穿/index.html index e7af017a08..d6c5e8c19f 100644 --- a/categories/缓存/穿透/击穿/index.html +++ b/categories/缓存/穿透/击穿/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/缓存/穿透/击穿/雪崩/index.html b/categories/缓存/穿透/击穿/雪崩/index.html index 517dac3390..138ff14111 100644 --- a/categories/缓存/穿透/击穿/雪崩/index.html +++ b/categories/缓存/穿透/击穿/雪崩/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/语言/index.html b/categories/语言/index.html index 45258b1aa1..9d3fc7873e 100644 --- a/categories/语言/index.html +++ b/categories/语言/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/categories/读后感/index.html b/categories/读后感/index.html index 56d54f88b6..53713504df 100644 --- a/categories/读后感/index.html +++ b/categories/读后感/index.html @@ -333,20 +333,20 @@ @@ -371,7 +371,7 @@
- +
diff --git a/categories/读后感/村上春树/index.html b/categories/读后感/村上春树/index.html index 527b552f2a..1355b0d474 100644 --- a/categories/读后感/村上春树/index.html +++ b/categories/读后感/村上春树/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/读后感/白岩松/index.html b/categories/读后感/白岩松/index.html index f0a34d155e..d270f26129 100644 --- a/categories/读后感/白岩松/index.html +++ b/categories/读后感/白岩松/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/读后感/白岩松/幸福了吗/index.html b/categories/读后感/白岩松/幸福了吗/index.html index 9db2590735..0bd4794f32 100644 --- a/categories/读后感/白岩松/幸福了吗/index.html +++ b/categories/读后感/白岩松/幸福了吗/index.html @@ -310,20 +310,20 @@ @@ -348,7 +348,7 @@
- +
diff --git a/categories/问题排查/index.html b/categories/问题排查/index.html index 575c6c9d5b..cc6eb71de2 100644 --- a/categories/问题排查/index.html +++ b/categories/问题排查/index.html @@ -330,20 +330,20 @@ @@ -368,7 +368,7 @@
- +
diff --git a/css/main.css b/css/main.css index 6272611692..826dab0d96 100644 --- a/css/main.css +++ b/css/main.css @@ -1261,7 +1261,7 @@ pre .javascript .function { } .links-of-author a::before, .links-of-author span.exturl::before { - background: #508bff; + background: #c45ed0; border-radius: 50%; content: ' '; display: inline-block; diff --git a/index.html b/index.html index 2b445a2719..f4d7d0f07e 100644 --- a/index.html +++ b/index.html @@ -203,6 +203,108 @@ +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

题目介绍

You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise).

+

You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.

如图,这道题以前做过,其实一看有点蒙,好像规则很容易描述,但是代码很难写,因为要类似于贪吃蛇那样,后来想着应该会有一些特殊的技巧,比如翻转等

+

代码

直接上码

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public void rotate(int[][] matrix) {
// 这里真的傻了,长宽应该是一致的,所以取一次就够了
int lengthX = matrix[0].length;
int lengthY = matrix.length;
int temp;
System.out.println(lengthY - (lengthY % 2) / 2);
// 这里除错了,应该是减掉余数再除 2
// for (int i = 0; i < lengthY - (lengthY % 2) / 2; i++) {
/**
* 1 2 3 7 8 9
* 4 5 6 => 4 5 6 先沿着 4 5 6 上下交换
* 7 8 9 1 2 3
*/
for (int i = 0; i < (lengthY - (lengthY % 2)) / 2; i++) {
for (int j = 0; j < lengthX; j++) {
temp = matrix[i][j];
matrix[i][j] = matrix[lengthY-i-1][j];
matrix[lengthY-i-1][j] = temp;
}
}

/**
* 7 8 9 7 4 1
* 4 5 6 => 8 5 2 这里再沿着 7 5 3 这条对角线交换
* 1 2 3 9 6 3
*/
for (int i = 0; i < lengthX; i++) {
for (int j = 0; j <= i; j++) {
if (i == j) {
continue;
}
temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
+

还没到可以直接归纳题目类型的水平,主要是几年前做过,可能有那么点模糊的记忆,当然应该也有直接转的方法

+ + +
+ + + + + + +
+
+
+
+ + + + + + +
@@ -646,128 +748,10 @@ - - - -
- - - - - -
-

- - -

- - -
- - - - -
- - -

拖更原因

这篇年终总结本来应该在农历过完年就出来的,结果是对没有受疫情影响的春节放假时间空闲情况预估太良好,虽然公司调了几天假,但是因为春节期间疫情状况比较好,本来酒店都不让接待聚餐什么的,后来统统放开,结果就是从初一到初六每天要不就是去亲戚家,要不就是去酒店饭店吃饭,计划很丰满,现实很骨感,时间感觉一下就没了,然后年后感觉有点犯懒了,所以才拖到现在。

-

生活-健身跑步

去年(19 年)的时候跑步突破了 300 公里,然后20 年给自己定了个 400 公里的目标,结果意料之中的没成功,原因可能疫情算一点吧,后面买了跑步机之后,基本周末回家都能跑一下,但是最后还是只跑了300 多公里,总的keep 记录跑量也没超过 1000 公里,所以跑步这个目标还是没成功的,不过还算是比去年多跑一点,这样也算后面好突破点,后面的目标就不定的太高了,每年能比前一年多一点就好,其实跑步已经从一种减肥方式变成一种习惯了,一周一次的跑步已经比较难有效减重了,但是对于保持精力和身体状态还是很有效和重要的,只是对于目前的体重还是要多减下去一些跑步才好,太重了对膝盖负担太大了,可惜还是时间呐,游泳骑车什么的都需要更苛刻的条件和时间,饮食呢控制起来比较难(贪吃
终于在 3 月底之前跑到了 1000 公里,迟了三个月,不过也总算达到了,只是体重控制还是不行,有试着走走楼梯,但是感觉对膝盖负担比较大,得再想想用什么方式

-

-

技术成长

一直提不起笔来写这篇年终总结还有个比较大的原因是觉得20 年的成长不如预期,大小目标都没怎么完成,比如深入了解 jvm,是想能有些深入的见解,而不再是某些点的比较片面的理解,系统性的归纳总结也比较少,每个方向或多或少有些看法和理解,但是不全面,一些东西看过了也会忘记,需要温故而知新,比如 AQS 的内容,第一次读其实理解比较浅,后面就强迫自己去读,去写,才有了一些比之前更深入的理解,因为很多文章都是带有作者思路的引导,适不适合自己都要看是否能从他的思路把它看懂,有些就差别很大,这个跟看书也一样,有些书大众一致推荐,一般情况下大多是经典的好的,但是也有可能是不太适合自己的,可能有时候机缘巧合看到的反而让人茅塞顿开,在 todo 里已经积攒了好多的点和面需要去学习实践,一方面是自己懒,一方面是时间也相对偏少,看看 21 年能不能有所提升,加强“时间管理”,哈哈

-

技术上主要是看了 mysql 的 mvcc 相关内容,rocketmq 的,redis 的代码,还有 mybatis 等,其实每一个都能写很多,也有很多值得学习的,需要全面系统学习,之前想好好画一个思维导图,将整个技术体系都梳理下,还只做了一点点,方式也有点问题,应该从大到小,而不是深度优先,细节有很多,每一个方面都有自己比较熟悉擅长的,也有不太了解的,可以做一个评分,这个也是亟待改善的,希望今年能完成。

-

博客

博客方面 20 年一年整是写了 53 篇,差不多是一周一篇的节奏,这个还是不错的,虽然博客质量参差不齐,但是这个更新频率还是比较好的,并且也定了个潜规则,可以一周技术一周生活,这样能缓解水文的频率,提高些技术文章的质量,虽然结果并没有好多少,不过感觉还是可以这么坚持的,能提高一些技术文章的质量那就更好了

- - -
- - - - - - -
-
-
-
- - - - @@ -835,20 +819,20 @@ @@ -873,7 +857,7 @@
- +
diff --git a/leancloud.memo b/leancloud.memo index 264c469c60..91a8252f10 100644 --- a/leancloud.memo +++ b/leancloud.memo @@ -100,5 +100,6 @@ {"title":"2020 年终总结","url":"/2021/03/31/2020-年终总结/"}, {"title":"聊聊 dubbo 的线程池","url":"/2021/04/04/聊聊-dubbo-的线程池/"}, {"title":"聊聊厦门旅游的好与不好","url":"/2021/04/11/聊聊厦门旅游的好与不好/"}, +{"title":"rust学习笔记-所有权二","url":"/2021/04/18/rust学习笔记-所有权二/"}, {"title":"rust学习笔记-所有权一","url":"/2021/04/18/rust学习笔记/"}, ] \ No newline at end of file diff --git a/leancloud_counter_security_urls.json b/leancloud_counter_security_urls.json index f8ae6843f9..f0a2f1e52f 100644 --- a/leancloud_counter_security_urls.json +++ b/leancloud_counter_security_urls.json @@ -1 +1 @@ -[{"title":"村上春树《1Q84》读后感","url":"/2019/12/18/1Q84读后感/"},{"title":"2019年终总结","url":"/2020/02/01/2019年终总结/"},{"title":"2020年中总结","url":"/2020/07/11/2020年中总结/"},{"title":"34_Search_for_a_Range","url":"/2016/08/14/34-Search-for-a-Range/"},{"title":"AbstractQueuedSynchronizer","url":"/2019/09/23/AbstractQueuedSynchronizer/"},{"title":"add-two-number","url":"/2015/04/14/Add-Two-Number/"},{"title":"Apollo 的 value 注解是怎么自动更新的","url":"/2020/11/01/Apollo-的-value-注解是怎么自动更新的/"},{"title":"Clone Graph Part I","url":"/2014/12/30/Clone-Graph-Part-I/"},{"title":"Comparator使用小记","url":"/2020/04/05/Comparator使用小记/"},{"title":"G1收集器概述","url":"/2020/02/09/G1收集器概述/"},{"title":"Leetcode 155 最小栈(Min Stack) 题解分析","url":"/2020/12/06/Leetcode-155-最小栈-Min-Stack-题解分析/"},{"title":"Leetcode 121 买卖股票的最佳时机(Best Time to Buy and Sell Stock) 题解分析","url":"/2021/03/14/Leetcode-121-买卖股票的最佳时机-Best-Time-to-Buy-and-Sell-Stock-题解分析/"},{"title":"Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析","url":"/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/"},{"title":"Leetcode 160 相交链表(intersection-of-two-linked-lists) 题解分析","url":"/2021/01/10/Leetcode-160-相交链表-intersection-of-two-linked-lists-题解分析/"},{"title":"2020 年终总结","url":"/2021/03/31/2020-年终总结/"},{"title":"Leetcode 3 Longest Substring Without Repeating Characters 题解分析","url":"/2020/09/20/Leetcode-3-Longest-Substring-Without-Repeating-Characters-题解分析/"},{"title":"Leetcode 234 回文链表(Palindrome Linked List) 题解分析","url":"/2020/11/15/Leetcode-234-回文联表-Palindrome-Linked-List-题解分析/"},{"title":"leetcode no.3","url":"/2015/04/15/Leetcode-No-3/"},{"title":"Linux 下 grep 命令的一点小技巧","url":"/2020/08/06/Linux-下-grep-命令的一点小技巧/"},{"title":"MFC 模态对话框","url":"/2014/12/24/MFC 模态对话框/"},{"title":"Maven实用小技巧","url":"/2020/02/16/Maven实用小技巧/"},{"title":"Number of 1 Bits","url":"/2015/03/11/Number-Of-1-Bits/"},{"title":"Path Sum","url":"/2015/01/04/Path-Sum/"},{"title":"Redis_分布式锁","url":"/2019/12/10/Redis-Part-1/"},{"title":"Reverse Bits","url":"/2015/03/11/Reverse-Bits/"},{"title":"ambari-summary","url":"/2017/05/09/ambari-summary/"},{"title":"binary-watch","url":"/2016/09/29/binary-watch/"},{"title":"Reverse Integer","url":"/2015/03/13/Reverse-Integer/"},{"title":"two sum","url":"/2015/01/14/Two-Sum/"},{"title":"docker-mysql-cluster","url":"/2016/08/14/docker-mysql-cluster/"},{"title":"docker比一般多一点的初学者介绍","url":"/2020/03/08/docker比一般多一点的初学者介绍/"},{"title":"docker比一般多一点的初学者介绍三","url":"/2020/03/21/docker比一般多一点的初学者介绍三/"},{"title":"docker比一般多一点的初学者介绍二","url":"/2020/03/15/docker比一般多一点的初学者介绍二/"},{"title":"docker使用中发现的echo命令的一个小技巧及其他","url":"/2020/03/29/echo命令的一个小技巧/"},{"title":"gogs使用webhook部署react单页应用","url":"/2020/02/22/gogs使用webhook部署react单页应用/"},{"title":"invert-binary-tree","url":"/2015/06/22/invert-binary-tree/"},{"title":"minimum-size-subarray-sum-209","url":"/2016/10/11/minimum-size-subarray-sum-209/"},{"title":"C++ 指针使用中的一个小问题","url":"/2014/12/23/my-new-post/"},{"title":"mybatis 的 $ 和 # 是有啥区别","url":"/2020/09/06/mybatis-的-和-是有啥区别/"},{"title":"openresty","url":"/2019/06/18/openresty/"},{"title":"pcre-intro-and-a-simple-package","url":"/2015/01/16/pcre-intro-and-a-simple-package/"},{"title":"php-abstract-class-and-interface","url":"/2016/11/10/php-abstract-class-and-interface/"},{"title":"rabbitmq-tips","url":"/2017/04/25/rabbitmq-tips/"},{"title":"redis数据结构介绍三-第三部分 整数集合","url":"/2020/01/10/redis数据结构介绍三/"},{"title":"redis数据结构介绍二-第二部分 跳表","url":"/2020/01/04/redis数据结构介绍二/"},{"title":"redis数据结构介绍五-第五部分 对象","url":"/2020/01/20/redis数据结构介绍五/"},{"title":"redis数据结构介绍四-第四部分 压缩表","url":"/2020/01/19/redis数据结构介绍四/"},{"title":"rust学习笔记-所有权二","url":"/2021/04/18/rust学习笔记-所有权二/"},{"title":"spark-little-tips","url":"/2017/03/28/spark-little-tips/"},{"title":"rust学习笔记-所有权一","url":"/2021/04/18/rust学习笔记/"},{"title":"summary-ranges-228","url":"/2016/10/12/summary-ranges-228/"},{"title":"swoole-websocket-test","url":"/2016/07/13/swoole-websocket-test/"},{"title":"《垃圾回收算法手册读书》笔记之整理算法","url":"/2021/03/07/《垃圾回收算法手册读书》笔记之整理算法/"},{"title":"关于公共交通再吐个槽","url":"/2021/03/21/关于公共交通再吐个槽/"},{"title":"周末我在老丈人家打了天小工","url":"/2020/08/16/周末我在老丈人家打了天小工/"},{"title":"从清华美院学姐聊聊我们身边的恶人","url":"/2020/11/29/从清华美院学姐聊聊我们身边的恶人/"},{"title":"从丁仲礼被美国制裁聊点啥","url":"/2020/12/20/从丁仲礼被美国制裁聊点啥/"},{"title":"在老丈人家的小工记三","url":"/2020/09/13/在老丈人家的小工记三/"},{"title":"在老丈人家的小工记五","url":"/2020/10/18/在老丈人家的小工记五/"},{"title":"关于读书打卡与分享","url":"/2021/02/07/关于读书打卡与分享/"},{"title":"在老丈人家的小工记四","url":"/2020/09/26/在老丈人家的小工记四/"},{"title":"寄生虫观后感","url":"/2020/03/01/寄生虫观后感/"},{"title":"聊聊 Dubbo 的 SPI","url":"/2020/05/31/聊聊-Dubbo-的-SPI/"},{"title":"我是如何走上跑步这条不归路的","url":"/2020/07/26/我是如何走上跑步这条不归路的/"},{"title":"聊聊 Dubbo 的 SPI 续之自适应拓展","url":"/2020/06/06/聊聊-Dubbo-的-SPI-续之自适应拓展/"},{"title":"Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析","url":"/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/"},{"title":"聊聊 Java 的类加载机制一","url":"/2020/11/08/聊聊-Java-的类加载机制/"},{"title":"Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析","url":"/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/"},{"title":"聊聊 Linux 下的 top 命令","url":"/2021/03/28/聊聊-Linux-下的-top-命令/"},{"title":"聊聊 mysql 的 MVCC 续续篇之锁分析","url":"/2020/05/10/聊聊-mysql-的-MVCC-续续篇之加锁分析/"},{"title":"聊聊 mysql 的 MVCC 续篇","url":"/2020/05/02/聊聊-mysql-的-MVCC-续篇/"},{"title":"Leetcode 2 Add Two Numbers 题解分析","url":"/2020/10/11/Leetcode-2-Add-Two-Numbers-题解分析/"},{"title":"聊聊 mysql 的 MVCC","url":"/2020/04/26/聊聊-mysql-的-MVCC/"},{"title":"聊聊 dubbo 的线程池","url":"/2021/04/04/聊聊-dubbo-的线程池/"},{"title":"聊聊Java中的单例模式","url":"/2019/12/21/聊聊Java中的单例模式/"},{"title":"聊聊 Java 的 equals 和 hashCode 方法","url":"/2021/01/03/聊聊-Java-的-equals-和-hashCode-方法/"},{"title":"聊聊一次 brew update 引发的血案","url":"/2020/06/13/聊聊一次-brew-update-引发的血案/"},{"title":"聊聊我刚学会的应用诊断方法","url":"/2020/05/22/聊聊我刚学会的应用诊断方法/"},{"title":"聊聊厦门旅游的好与不好","url":"/2021/04/11/聊聊厦门旅游的好与不好/"},{"title":"聊聊 mysql 索引的一些细节","url":"/2020/12/27/聊聊-mysql-索引的一些细节/"},{"title":"这周末我又在老丈人家打了天小工","url":"/2020/08/30/这周末我又在老丈人家打了天小工/"},{"title":"闲聊下乘公交的用户体验","url":"/2021/02/28/闲聊下乘公交的用户体验/"},{"title":"聊聊那些加塞狗","url":"/2021/01/17/聊聊那些加塞狗/"},{"title":"聊聊 redis 缓存的应用问题","url":"/2021/01/31/聊聊-redis-缓存的应用问题/"},{"title":"AQS篇一","url":"/2021/02/14/AQS篇一/"},{"title":"mybatis 的缓存是怎么回事","url":"/2020/10/03/mybatis-的缓存是怎么回事/"},{"title":"redis数据结构介绍-第一部分 SDS,链表,字典","url":"/2019/12/26/redis数据结构介绍/"},{"title":"redis数据结构介绍六 快表","url":"/2020/01/22/redis数据结构介绍六/"},{"title":"介绍一下 RocketMQ","url":"/2020/06/21/介绍一下-RocketMQ/"},{"title":"聊聊我理解的分布式事务","url":"/2020/05/17/聊聊我理解的分布式事务/"},{"title":"聊聊 Dubbo 的容错机制","url":"/2020/11/22/聊聊-Dubbo-的容错机制/"},{"title":"AQS篇二 之 Condition 浅析笔记","url":"/2021/02/21/AQS-之-Condition-浅析笔记/"},{"title":"Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥?","url":"/2020/08/22/Filter-Intercepter-Aop-啥-啥-啥-这些都是啥/"},{"title":"redis系列介绍七-过期策略","url":"/2020/04/12/redis系列介绍七/"},{"title":"JVM源码分析之G1垃圾收集器分析一","url":"/2019/12/07/JVM-G1-Part-1/"},{"title":"聊一下 RocketMQ 的 NameServer 源码","url":"/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/"},{"title":"redis系列介绍八-淘汰策略","url":"/2020/04/18/redis系列介绍八/"},{"title":"聊聊 RocketMQ 的 Broker 源码","url":"/2020/07/19/聊聊-RocketMQ-的-Broker-源码/"},{"title":"聊聊 Java 自带的那些*逆天*工具","url":"/2020/08/02/聊聊-Java-自带的那些逆天工具/"},{"title":"聊一下 RocketMQ 的 DefaultMQPushConsumer 源码","url":"/2020/06/26/聊一下-RocketMQ-的-Consumer/"}] \ No newline at end of file +[{"title":"村上春树《1Q84》读后感","url":"/2019/12/18/1Q84读后感/"},{"title":"2019年终总结","url":"/2020/02/01/2019年终总结/"},{"title":"2020年中总结","url":"/2020/07/11/2020年中总结/"},{"title":"34_Search_for_a_Range","url":"/2016/08/14/34-Search-for-a-Range/"},{"title":"AbstractQueuedSynchronizer","url":"/2019/09/23/AbstractQueuedSynchronizer/"},{"title":"Apollo 的 value 注解是怎么自动更新的","url":"/2020/11/01/Apollo-的-value-注解是怎么自动更新的/"},{"title":"add-two-number","url":"/2015/04/14/Add-Two-Number/"},{"title":"Clone Graph Part I","url":"/2014/12/30/Clone-Graph-Part-I/"},{"title":"Comparator使用小记","url":"/2020/04/05/Comparator使用小记/"},{"title":"G1收集器概述","url":"/2020/02/09/G1收集器概述/"},{"title":"Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析","url":"/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/"},{"title":"Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析","url":"/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/"},{"title":"2020 年终总结","url":"/2021/03/31/2020-年终总结/"},{"title":"Leetcode 160 相交链表(intersection-of-two-linked-lists) 题解分析","url":"/2021/01/10/Leetcode-160-相交链表-intersection-of-two-linked-lists-题解分析/"},{"title":"Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析","url":"/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/"},{"title":"Leetcode 2 Add Two Numbers 题解分析","url":"/2020/10/11/Leetcode-2-Add-Two-Numbers-题解分析/"},{"title":"Leetcode 48 旋转图像(Rotate Image) 题解分析","url":"/2021/05/01/Leetcode-48-旋转图像-Rotate-Image-题解分析/"},{"title":"Leetcode 121 买卖股票的最佳时机(Best Time to Buy and Sell Stock) 题解分析","url":"/2021/03/14/Leetcode-121-买卖股票的最佳时机-Best-Time-to-Buy-and-Sell-Stock-题解分析/"},{"title":"Leetcode 3 Longest Substring Without Repeating Characters 题解分析","url":"/2020/09/20/Leetcode-3-Longest-Substring-Without-Repeating-Characters-题解分析/"},{"title":"Linux 下 grep 命令的一点小技巧","url":"/2020/08/06/Linux-下-grep-命令的一点小技巧/"},{"title":"leetcode no.3","url":"/2015/04/15/Leetcode-No-3/"},{"title":"MFC 模态对话框","url":"/2014/12/24/MFC 模态对话框/"},{"title":"Maven实用小技巧","url":"/2020/02/16/Maven实用小技巧/"},{"title":"Leetcode 155 最小栈(Min Stack) 题解分析","url":"/2020/12/06/Leetcode-155-最小栈-Min-Stack-题解分析/"},{"title":"Number of 1 Bits","url":"/2015/03/11/Number-Of-1-Bits/"},{"title":"Path Sum","url":"/2015/01/04/Path-Sum/"},{"title":"Redis_分布式锁","url":"/2019/12/10/Redis-Part-1/"},{"title":"Reverse Bits","url":"/2015/03/11/Reverse-Bits/"},{"title":"Reverse Integer","url":"/2015/03/13/Reverse-Integer/"},{"title":"two sum","url":"/2015/01/14/Two-Sum/"},{"title":"ambari-summary","url":"/2017/05/09/ambari-summary/"},{"title":"binary-watch","url":"/2016/09/29/binary-watch/"},{"title":"docker比一般多一点的初学者介绍三","url":"/2020/03/21/docker比一般多一点的初学者介绍三/"},{"title":"docker-mysql-cluster","url":"/2016/08/14/docker-mysql-cluster/"},{"title":"docker比一般多一点的初学者介绍","url":"/2020/03/08/docker比一般多一点的初学者介绍/"},{"title":"docker比一般多一点的初学者介绍二","url":"/2020/03/15/docker比一般多一点的初学者介绍二/"},{"title":"docker使用中发现的echo命令的一个小技巧及其他","url":"/2020/03/29/echo命令的一个小技巧/"},{"title":"invert-binary-tree","url":"/2015/06/22/invert-binary-tree/"},{"title":"gogs使用webhook部署react单页应用","url":"/2020/02/22/gogs使用webhook部署react单页应用/"},{"title":"mybatis 的 $ 和 # 是有啥区别","url":"/2020/09/06/mybatis-的-和-是有啥区别/"},{"title":"minimum-size-subarray-sum-209","url":"/2016/10/11/minimum-size-subarray-sum-209/"},{"title":"C++ 指针使用中的一个小问题","url":"/2014/12/23/my-new-post/"},{"title":"openresty","url":"/2019/06/18/openresty/"},{"title":"php-abstract-class-and-interface","url":"/2016/11/10/php-abstract-class-and-interface/"},{"title":"pcre-intro-and-a-simple-package","url":"/2015/01/16/pcre-intro-and-a-simple-package/"},{"title":"rabbitmq-tips","url":"/2017/04/25/rabbitmq-tips/"},{"title":"redis数据结构介绍三-第三部分 整数集合","url":"/2020/01/10/redis数据结构介绍三/"},{"title":"Leetcode 234 回文链表(Palindrome Linked List) 题解分析","url":"/2020/11/15/Leetcode-234-回文联表-Palindrome-Linked-List-题解分析/"},{"title":"redis数据结构介绍二-第二部分 跳表","url":"/2020/01/04/redis数据结构介绍二/"},{"title":"redis数据结构介绍五-第五部分 对象","url":"/2020/01/20/redis数据结构介绍五/"},{"title":"redis数据结构介绍四-第四部分 压缩表","url":"/2020/01/19/redis数据结构介绍四/"},{"title":"spark-little-tips","url":"/2017/03/28/spark-little-tips/"},{"title":"rust学习笔记-所有权二","url":"/2021/04/18/rust学习笔记-所有权二/"},{"title":"summary-ranges-228","url":"/2016/10/12/summary-ranges-228/"},{"title":"swoole-websocket-test","url":"/2016/07/13/swoole-websocket-test/"},{"title":"《垃圾回收算法手册读书》笔记之整理算法","url":"/2021/03/07/《垃圾回收算法手册读书》笔记之整理算法/"},{"title":"周末我在老丈人家打了天小工","url":"/2020/08/16/周末我在老丈人家打了天小工/"},{"title":"从清华美院学姐聊聊我们身边的恶人","url":"/2020/11/29/从清华美院学姐聊聊我们身边的恶人/"},{"title":"rust学习笔记-所有权一","url":"/2021/04/18/rust学习笔记/"},{"title":"在老丈人家的小工记三","url":"/2020/09/13/在老丈人家的小工记三/"},{"title":"从丁仲礼被美国制裁聊点啥","url":"/2020/12/20/从丁仲礼被美国制裁聊点啥/"},{"title":"在老丈人家的小工记五","url":"/2020/10/18/在老丈人家的小工记五/"},{"title":"在老丈人家的小工记四","url":"/2020/09/26/在老丈人家的小工记四/"},{"title":"寄生虫观后感","url":"/2020/03/01/寄生虫观后感/"},{"title":"我是如何走上跑步这条不归路的","url":"/2020/07/26/我是如何走上跑步这条不归路的/"},{"title":"聊聊 Dubbo 的 SPI 续之自适应拓展","url":"/2020/06/06/聊聊-Dubbo-的-SPI-续之自适应拓展/"},{"title":"聊聊 Dubbo 的 SPI","url":"/2020/05/31/聊聊-Dubbo-的-SPI/"},{"title":"关于公共交通再吐个槽","url":"/2021/03/21/关于公共交通再吐个槽/"},{"title":"聊聊 Java 的类加载机制一","url":"/2020/11/08/聊聊-Java-的类加载机制/"},{"title":"聊聊 mysql 的 MVCC 续篇","url":"/2020/05/02/聊聊-mysql-的-MVCC-续篇/"},{"title":"关于读书打卡与分享","url":"/2021/02/07/关于读书打卡与分享/"},{"title":"聊聊 Java 的 equals 和 hashCode 方法","url":"/2021/01/03/聊聊-Java-的-equals-和-hashCode-方法/"},{"title":"聊聊 mysql 的 MVCC 续续篇之锁分析","url":"/2020/05/10/聊聊-mysql-的-MVCC-续续篇之加锁分析/"},{"title":"聊聊 mysql 的 MVCC","url":"/2020/04/26/聊聊-mysql-的-MVCC/"},{"title":"聊聊一次 brew update 引发的血案","url":"/2020/06/13/聊聊一次-brew-update-引发的血案/"},{"title":"聊聊 Linux 下的 top 命令","url":"/2021/03/28/聊聊-Linux-下的-top-命令/"},{"title":"聊聊Java中的单例模式","url":"/2019/12/21/聊聊Java中的单例模式/"},{"title":"聊聊我刚学会的应用诊断方法","url":"/2020/05/22/聊聊我刚学会的应用诊断方法/"},{"title":"聊聊 mysql 索引的一些细节","url":"/2020/12/27/聊聊-mysql-索引的一些细节/"},{"title":"聊聊 redis 缓存的应用问题","url":"/2021/01/31/聊聊-redis-缓存的应用问题/"},{"title":"这周末我又在老丈人家打了天小工","url":"/2020/08/30/这周末我又在老丈人家打了天小工/"},{"title":"聊聊 dubbo 的线程池","url":"/2021/04/04/聊聊-dubbo-的线程池/"},{"title":"聊聊那些加塞狗","url":"/2021/01/17/聊聊那些加塞狗/"},{"title":"闲聊下乘公交的用户体验","url":"/2021/02/28/闲聊下乘公交的用户体验/"},{"title":"聊聊厦门旅游的好与不好","url":"/2021/04/11/聊聊厦门旅游的好与不好/"},{"title":"AQS篇一","url":"/2021/02/14/AQS篇一/"},{"title":"mybatis 的缓存是怎么回事","url":"/2020/10/03/mybatis-的缓存是怎么回事/"},{"title":"redis数据结构介绍-第一部分 SDS,链表,字典","url":"/2019/12/26/redis数据结构介绍/"},{"title":"redis数据结构介绍六 快表","url":"/2020/01/22/redis数据结构介绍六/"},{"title":"介绍一下 RocketMQ","url":"/2020/06/21/介绍一下-RocketMQ/"},{"title":"聊聊 Dubbo 的容错机制","url":"/2020/11/22/聊聊-Dubbo-的容错机制/"},{"title":"聊聊我理解的分布式事务","url":"/2020/05/17/聊聊我理解的分布式事务/"},{"title":"Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥?","url":"/2020/08/22/Filter-Intercepter-Aop-啥-啥-啥-这些都是啥/"},{"title":"AQS篇二 之 Condition 浅析笔记","url":"/2021/02/21/AQS-之-Condition-浅析笔记/"},{"title":"redis系列介绍七-过期策略","url":"/2020/04/12/redis系列介绍七/"},{"title":"JVM源码分析之G1垃圾收集器分析一","url":"/2019/12/07/JVM-G1-Part-1/"},{"title":"redis系列介绍八-淘汰策略","url":"/2020/04/18/redis系列介绍八/"},{"title":"聊一下 RocketMQ 的 NameServer 源码","url":"/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/"},{"title":"聊聊 RocketMQ 的 Broker 源码","url":"/2020/07/19/聊聊-RocketMQ-的-Broker-源码/"},{"title":"聊聊 Java 自带的那些*逆天*工具","url":"/2020/08/02/聊聊-Java-自带的那些逆天工具/"},{"title":"聊一下 RocketMQ 的 DefaultMQPushConsumer 源码","url":"/2020/06/26/聊一下-RocketMQ-的-Consumer/"}] \ No newline at end of file diff --git a/lib/pace/README.html b/lib/pace/README.html index 10664fbf71..592b9cf67b 100644 --- a/lib/pace/README.html +++ b/lib/pace/README.html @@ -351,20 +351,20 @@ @@ -389,7 +389,7 @@
- +
diff --git a/page/10/index.html b/page/10/index.html index 69d8294e73..9f08d30990 100644 --- a/page/10/index.html +++ b/page/10/index.html @@ -203,6 +203,154 @@ +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

首先看下官方的小 demo

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public static void main(String[] args) throws InterruptedException, MQClientException {

/*
* Instantiate with specified consumer group name.
* 首先是new 一个对象出来,然后指定 Consumer 的 Group
* 同一类Consumer的集合,这类Consumer通常消费同一类消息且消费逻辑一致。消费者组使得在消息消费方面,实现负载均衡和容错的目标变得非常容易。要注意的是,消费者组的消费者实例必须订阅完全相同的Topic。RocketMQ 支持两种消息模式:集群消费(Clustering)和广播消费(Broadcasting)。
*/
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");

/*
* Specify name server addresses.
* <p/>
* 这里可以通知指定环境变量或者设置对象参数的形式指定名字空间服务的地址
*
* Alternatively, you may specify name server addresses via exporting environmental variable: NAMESRV_ADDR
* <pre>
* {@code
* consumer.setNamesrvAddr("name-server1-ip:9876;name-server2-ip:9876");
* }
* </pre>
*/

/*
* Specify where to start in case the specified consumer group is a brand new one.
* 指定消费起始点
*/
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

/*
* Subscribe one more more topics to consume.
* 指定订阅的 topic 跟 tag,注意后面的是个表达式,可以以 tag1 || tag2 || tag3 传入
*/
consumer.subscribe("TopicTest", "*");

/*
* Register callback to execute on arrival of messages fetched from brokers.
* 注册具体获得消息后的处理方法
*/
consumer.registerMessageListener(new MessageListenerConcurrently() {

@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});

/*
* Launch the consumer instance.
* 启动消费者
*/
consumer.start();

System.out.printf("Consumer Started.%n");
}
+ +

然后就是看看 start 的过程了

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* This method gets internal infrastructure readily to serve. Instances must call this method after configuration.
*
* @throws MQClientException if there is any client error.
*/
@Override
public void start() throws MQClientException {
setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup));
this.defaultMQPushConsumerImpl.start();
if (null != traceDispatcher) {
try {
traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
} catch (MQClientException e) {
log.warn("trace dispatcher start failed ", e);
}
}
}
+

具体的逻辑在this.defaultMQPushConsumerImpl.start(),这个 defaultMQPushConsumerImpl 就是

+
1
2
3
4
/**
* Internal implementation. Most of the functions herein are delegated to it.
*/
protected final transient DefaultMQPushConsumerImpl defaultMQPushConsumerImpl;
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public synchronized void start() throws MQClientException {
switch (this.serviceState) {
case CREATE_JUST:
log.info("the consumer [{}] start beginning. messageModel={}, isUnitMode={}", this.defaultMQPushConsumer.getConsumerGroup(),
this.defaultMQPushConsumer.getMessageModel(), this.defaultMQPushConsumer.isUnitMode());
// 这里比较巧妙,相当于想设立了个屏障,防止并发启动,不过这里并不是悲观锁,也不算个严格的乐观锁
this.serviceState = ServiceState.START_FAILED;

this.checkConfig();

this.copySubscription();

if (this.defaultMQPushConsumer.getMessageModel() == MessageModel.CLUSTERING) {
this.defaultMQPushConsumer.changeInstanceNameToPID();
}

// 这个mQClientFactory,负责管理client(consumer、producer),并提供多中功能接口供各个Service(Rebalance、PullMessage等)调用;大部分逻辑均在这个类中完成
this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQPushConsumer, this.rpcHook);

// 这个 rebalanceImpl 主要负责决定,当前的consumer应该从哪些Queue中消费消息;
this.rebalanceImpl.setConsumerGroup(this.defaultMQPushConsumer.getConsumerGroup());
this.rebalanceImpl.setMessageModel(this.defaultMQPushConsumer.getMessageModel());
this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPushConsumer.getAllocateMessageQueueStrategy());
this.rebalanceImpl.setmQClientFactory(this.mQClientFactory);

// 长连接,负责从broker处拉取消息,然后利用ConsumeMessageService回调用户的Listener执行消息消费逻辑
this.pullAPIWrapper = new PullAPIWrapper(
mQClientFactory,
this.defaultMQPushConsumer.getConsumerGroup(), isUnitMode());
this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList);

if (this.defaultMQPushConsumer.getOffsetStore() != null) {
this.offsetStore = this.defaultMQPushConsumer.getOffsetStore();
} else {
switch (this.defaultMQPushConsumer.getMessageModel()) {
case BROADCASTING:
this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
break;
case CLUSTERING:
this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
break;
default:
break;
}
this.defaultMQPushConsumer.setOffsetStore(this.offsetStore);
}
// offsetStore 维护当前consumer的消费记录(offset);有两种实现,Local和Rmote,Local存储在本地磁盘上,适用于BROADCASTING广播消费模式;而Remote则将消费进度存储在Broker上,适用于CLUSTERING集群消费模式;
this.offsetStore.load();

if (this.getMessageListenerInner() instanceof MessageListenerOrderly) {
this.consumeOrderly = true;
this.consumeMessageService =
new ConsumeMessageOrderlyService(this, (MessageListenerOrderly) this.getMessageListenerInner());
} else if (this.getMessageListenerInner() instanceof MessageListenerConcurrently) {
this.consumeOrderly = false;
this.consumeMessageService =
new ConsumeMessageConcurrentlyService(this, (MessageListenerConcurrently) this.getMessageListenerInner());
}

// 实现所谓的"Push-被动"消费机制;从Broker拉取的消息后,封装成ConsumeRequest提交给ConsumeMessageSerivce,此service负责回调用户的Listener消费消息;
this.consumeMessageService.start();

boolean registerOK = mQClientFactory.registerConsumer(this.defaultMQPushConsumer.getConsumerGroup(), this);
if (!registerOK) {
this.serviceState = ServiceState.CREATE_JUST;
this.consumeMessageService.shutdown();
throw new MQClientException("The consumer group[" + this.defaultMQPushConsumer.getConsumerGroup()
+ "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),
null);
}

mQClientFactory.start();
log.info("the consumer [{}] start OK.", this.defaultMQPushConsumer.getConsumerGroup());
this.serviceState = ServiceState.RUNNING;
break;
case RUNNING:
case START_FAILED:
case SHUTDOWN_ALREADY:
throw new MQClientException("The PushConsumer service state not OK, maybe started once, "
+ this.serviceState
+ FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
null);
default:
break;
}

this.updateTopicSubscribeInfoWhenSubscriptionChanged();
this.mQClientFactory.checkClientInBroker();
this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
this.mQClientFactory.rebalanceImmediately();
}
+

然后我们往下看主要的目光聚焦mQClientFactory.start()

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void start() throws MQClientException {

synchronized (this) {
switch (this.serviceState) {
case CREATE_JUST:
this.serviceState = ServiceState.START_FAILED;
// If not specified,looking address from name server
if (null == this.clientConfig.getNamesrvAddr()) {
this.mQClientAPIImpl.fetchNameServerAddr();
}
// Start request-response channel
// 这里主要是初始化了个网络客户端
this.mQClientAPIImpl.start();
// Start various schedule tasks
// 定时任务
this.startScheduledTask();
// Start pull service
// 这里重点说下
this.pullMessageService.start();
// Start rebalance service
this.rebalanceService.start();
// Start push service
this.defaultMQProducer.getDefaultMQProducerImpl().start(false);
log.info("the client factory [{}] start OK", this.clientId);
this.serviceState = ServiceState.RUNNING;
break;
case START_FAILED:
throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null);
default:
break;
}
}
}
+

我们来看下这个 pullMessageService,org.apache.rocketmq.client.impl.consumer.PullMessageService,

实现了 runnable 接口,
然后可以看到 run 方法

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void run() {
log.info(this.getServiceName() + " service started");

while (!this.isStopped()) {
try {
PullRequest pullRequest = this.pullRequestQueue.take();
this.pullMessage(pullRequest);
} catch (InterruptedException ignored) {
} catch (Exception e) {
log.error("Pull Message Service Run Method exception", e);
}
}

log.info(this.getServiceName() + " service end");
}
+

接着在看 pullMessage 方法

+
1
2
3
4
5
6
7
8
9
private void pullMessage(final PullRequest pullRequest) {
final MQConsumerInner consumer = this.mQClientFactory.selectConsumer(pullRequest.getConsumerGroup());
if (consumer != null) {
DefaultMQPushConsumerImpl impl = (DefaultMQPushConsumerImpl) consumer;
impl.pullMessage(pullRequest);
} else {
log.warn("No matched consumer for the PullRequest {}, drop it", pullRequest);
}
}
+

实际上调用了这个方法,这个方法很长,我在代码里注释下下每一段的功能

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
public void pullMessage(final PullRequest pullRequest) {
final ProcessQueue processQueue = pullRequest.getProcessQueue();
// 这里开始就是检查状态,确定是否往下执行
if (processQueue.isDropped()) {
log.info("the pull request[{}] is dropped.", pullRequest.toString());
return;
}

pullRequest.getProcessQueue().setLastPullTimestamp(System.currentTimeMillis());

try {
this.makeSureStateOK();
} catch (MQClientException e) {
log.warn("pullMessage exception, consumer state not ok", e);
this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
return;
}

if (this.isPause()) {
log.warn("consumer was paused, execute pull request later. instanceName={}, group={}", this.defaultMQPushConsumer.getInstanceName(), this.defaultMQPushConsumer.getConsumerGroup());
this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_SUSPEND);
return;
}

// 这块其实是个类似于限流的功能块,对消息数量和消息大小做限制
long cachedMessageCount = processQueue.getMsgCount().get();
long cachedMessageSizeInMiB = processQueue.getMsgSize().get() / (1024 * 1024);

if (cachedMessageCount > this.defaultMQPushConsumer.getPullThresholdForQueue()) {
this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL);
if ((queueFlowControlTimes++ % 1000) == 0) {
log.warn(
"the cached message count exceeds the threshold {}, so do flow control, minOffset={}, maxOffset={}, count={}, size={} MiB, pullRequest={}, flowControlTimes={}",
this.defaultMQPushConsumer.getPullThresholdForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, pullRequest, queueFlowControlTimes);
}
return;
}

if (cachedMessageSizeInMiB > this.defaultMQPushConsumer.getPullThresholdSizeForQueue()) {
this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL);
if ((queueFlowControlTimes++ % 1000) == 0) {
log.warn(
"the cached message size exceeds the threshold {} MiB, so do flow control, minOffset={}, maxOffset={}, count={}, size={} MiB, pullRequest={}, flowControlTimes={}",
this.defaultMQPushConsumer.getPullThresholdSizeForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, pullRequest, queueFlowControlTimes);
}
return;
}

// 若不是顺序消费(即DefaultMQPushConsumerImpl.consumeOrderly等于false),则检查ProcessQueue对象的msgTreeMap:TreeMap<Long,MessageExt>变量的第一个key值与最后一个key值之间的差额,该key值表示查询的队列偏移量queueoffset;若差额大于阈值(由DefaultMQPushConsumer. consumeConcurrentlyMaxSpan指定,默认是2000),则调用PullMessageService.executePullRequestLater方法,在50毫秒之后重新将该PullRequest请求放入PullMessageService.pullRequestQueue队列中;并跳出该方法;这里的意思主要就是消息有堆积了,等会再来拉取
if (!this.consumeOrderly) {
if (processQueue.getMaxSpan() > this.defaultMQPushConsumer.getConsumeConcurrentlyMaxSpan()) {
this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL);
if ((queueMaxSpanFlowControlTimes++ % 1000) == 0) {
log.warn(
"the queue's messages, span too long, so do flow control, minOffset={}, maxOffset={}, maxSpan={}, pullRequest={}, flowControlTimes={}",
processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), processQueue.getMaxSpan(),
pullRequest, queueMaxSpanFlowControlTimes);
}
return;
}
} else {
if (processQueue.isLocked()) {
if (!pullRequest.isLockedFirst()) {
final long offset = this.rebalanceImpl.computePullFromWhere(pullRequest.getMessageQueue());
boolean brokerBusy = offset < pullRequest.getNextOffset();
log.info("the first time to pull message, so fix offset from broker. pullRequest: {} NewOffset: {} brokerBusy: {}",
pullRequest, offset, brokerBusy);
if (brokerBusy) {
log.info("[NOTIFYME]the first time to pull message, but pull request offset larger than broker consume offset. pullRequest: {} NewOffset: {}",
pullRequest, offset);
}

pullRequest.setLockedFirst(true);
pullRequest.setNextOffset(offset);
}
} else {
this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
log.info("pull message later because not locked in broker, {}", pullRequest);
return;
}
}

// 以PullRequest.messageQueue对象的topic值为参数从RebalanceImpl.subscriptionInner: ConcurrentHashMap, SubscriptionData>中获取对应的SubscriptionData对象,若该对象为null,考虑到并发的关系,调用executePullRequestLater方法,稍后重试;并跳出该方法;
final SubscriptionData subscriptionData = this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic());
if (null == subscriptionData) {
this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
log.warn("find the consumer's subscription failed, {}", pullRequest);
return;
}

final long beginTimestamp = System.currentTimeMillis();

// 异步拉取回调,先不讨论细节
PullCallback pullCallback = new PullCallback() {
@Override
public void onSuccess(PullResult pullResult) {
if (pullResult != null) {
pullResult = DefaultMQPushConsumerImpl.this.pullAPIWrapper.processPullResult(pullRequest.getMessageQueue(), pullResult,
subscriptionData);

switch (pullResult.getPullStatus()) {
case FOUND:
long prevRequestOffset = pullRequest.getNextOffset();
pullRequest.setNextOffset(pullResult.getNextBeginOffset());
long pullRT = System.currentTimeMillis() - beginTimestamp;
DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullRT(pullRequest.getConsumerGroup(),
pullRequest.getMessageQueue().getTopic(), pullRT);

long firstMsgOffset = Long.MAX_VALUE;
if (pullResult.getMsgFoundList() == null || pullResult.getMsgFoundList().isEmpty()) {
DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
} else {
firstMsgOffset = pullResult.getMsgFoundList().get(0).getQueueOffset();

DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullTPS(pullRequest.getConsumerGroup(),
pullRequest.getMessageQueue().getTopic(), pullResult.getMsgFoundList().size());

boolean dispatchToConsume = processQueue.putMessage(pullResult.getMsgFoundList());
DefaultMQPushConsumerImpl.this.consumeMessageService.submitConsumeRequest(
pullResult.getMsgFoundList(),
processQueue,
pullRequest.getMessageQueue(),
dispatchToConsume);

if (DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval() > 0) {
DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest,
DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval());
} else {
DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
}
}

if (pullResult.getNextBeginOffset() < prevRequestOffset
|| firstMsgOffset < prevRequestOffset) {
log.warn(
"[BUG] pull message result maybe data wrong, nextBeginOffset: {} firstMsgOffset: {} prevRequestOffset: {}",
pullResult.getNextBeginOffset(),
firstMsgOffset,
prevRequestOffset);
}

break;
case NO_NEW_MSG:
pullRequest.setNextOffset(pullResult.getNextBeginOffset());

DefaultMQPushConsumerImpl.this.correctTagsOffset(pullRequest);

DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
break;
case NO_MATCHED_MSG:
pullRequest.setNextOffset(pullResult.getNextBeginOffset());

DefaultMQPushConsumerImpl.this.correctTagsOffset(pullRequest);

DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
break;
case OFFSET_ILLEGAL:
log.warn("the pull request offset illegal, {} {}",
pullRequest.toString(), pullResult.toString());
pullRequest.setNextOffset(pullResult.getNextBeginOffset());

pullRequest.getProcessQueue().setDropped(true);
DefaultMQPushConsumerImpl.this.executeTaskLater(new Runnable() {

@Override
public void run() {
try {
DefaultMQPushConsumerImpl.this.offsetStore.updateOffset(pullRequest.getMessageQueue(),
pullRequest.getNextOffset(), false);

DefaultMQPushConsumerImpl.this.offsetStore.persist(pullRequest.getMessageQueue());

DefaultMQPushConsumerImpl.this.rebalanceImpl.removeProcessQueue(pullRequest.getMessageQueue());

log.warn("fix the pull request offset, {}", pullRequest);
} catch (Throwable e) {
log.error("executeTaskLater Exception", e);
}
}
}, 10000);
break;
default:
break;
}
}
}

@Override
public void onException(Throwable e) {
if (!pullRequest.getMessageQueue().getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
log.warn("execute the pull request exception", e);
}

DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
}
};
// 如果为集群模式,即可置commitOffsetEnable为 true
boolean commitOffsetEnable = false;
long commitOffsetValue = 0L;
if (MessageModel.CLUSTERING == this.defaultMQPushConsumer.getMessageModel()) {
commitOffsetValue = this.offsetStore.readOffset(pullRequest.getMessageQueue(), ReadOffsetType.READ_FROM_MEMORY);
if (commitOffsetValue > 0) {
commitOffsetEnable = true;
}
}

// 将上面获得的commitOffsetEnable更新到订阅关系里
String subExpression = null;
boolean classFilter = false;
SubscriptionData sd = this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic());
if (sd != null) {
if (this.defaultMQPushConsumer.isPostSubscriptionWhenPull() && !sd.isClassFilterMode()) {
subExpression = sd.getSubString();
}

classFilter = sd.isClassFilterMode();
}

// 组成 sysFlag
int sysFlag = PullSysFlag.buildSysFlag(
commitOffsetEnable, // commitOffset
true, // suspend
subExpression != null, // subscription
classFilter // class filter
);
// 调用真正的拉取消息接口
try {
this.pullAPIWrapper.pullKernelImpl(
pullRequest.getMessageQueue(),
subExpression,
subscriptionData.getExpressionType(),
subscriptionData.getSubVersion(),
pullRequest.getNextOffset(),
this.defaultMQPushConsumer.getPullBatchSize(),
sysFlag,
commitOffsetValue,
BROKER_SUSPEND_MAX_TIME_MILLIS,
CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND,
CommunicationMode.ASYNC,
pullCallback
);
} catch (Exception e) {
log.error("pullKernelImpl exception", e);
this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
}
}
+

以下就是拉取消息的底层 api,不够不是特别复杂,主要是在找 broker,和设置请求参数

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public PullResult pullKernelImpl(
final MessageQueue mq,
final String subExpression,
final String expressionType,
final long subVersion,
final long offset,
final int maxNums,
final int sysFlag,
final long commitOffset,
final long brokerSuspendMaxTimeMillis,
final long timeoutMillis,
final CommunicationMode communicationMode,
final PullCallback pullCallback
) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
FindBrokerResult findBrokerResult =
this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
this.recalculatePullFromWhichNode(mq), false);
if (null == findBrokerResult) {
this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
findBrokerResult =
this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
this.recalculatePullFromWhichNode(mq), false);
}

if (findBrokerResult != null) {
{
// check version
if (!ExpressionType.isTagType(expressionType)
&& findBrokerResult.getBrokerVersion() < MQVersion.Version.V4_1_0_SNAPSHOT.ordinal()) {
throw new MQClientException("The broker[" + mq.getBrokerName() + ", "
+ findBrokerResult.getBrokerVersion() + "] does not upgrade to support for filter message by " + expressionType, null);
}
}
int sysFlagInner = sysFlag;

if (findBrokerResult.isSlave()) {
sysFlagInner = PullSysFlag.clearCommitOffsetFlag(sysFlagInner);
}

PullMessageRequestHeader requestHeader = new PullMessageRequestHeader();
requestHeader.setConsumerGroup(this.consumerGroup);
requestHeader.setTopic(mq.getTopic());
requestHeader.setQueueId(mq.getQueueId());
requestHeader.setQueueOffset(offset);
requestHeader.setMaxMsgNums(maxNums);
requestHeader.setSysFlag(sysFlagInner);
requestHeader.setCommitOffset(commitOffset);
requestHeader.setSuspendTimeoutMillis(brokerSuspendMaxTimeMillis);
requestHeader.setSubscription(subExpression);
requestHeader.setSubVersion(subVersion);
requestHeader.setExpressionType(expressionType);

String brokerAddr = findBrokerResult.getBrokerAddr();
if (PullSysFlag.hasClassFilterFlag(sysFlagInner)) {
brokerAddr = computPullFromWhichFilterServer(mq.getTopic(), brokerAddr);
}

PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage(
brokerAddr,
requestHeader,
timeoutMillis,
communicationMode,
pullCallback);

return pullResult;
}

throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
}
+

再看下一步的

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public PullResult pullMessage(
final String addr,
final PullMessageRequestHeader requestHeader,
final long timeoutMillis,
final CommunicationMode communicationMode,
final PullCallback pullCallback
) throws RemotingException, MQBrokerException, InterruptedException {
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader);

switch (communicationMode) {
case ONEWAY:
assert false;
return null;
case ASYNC:
this.pullMessageAsync(addr, request, timeoutMillis, pullCallback);
return null;
case SYNC:
return this.pullMessageSync(addr, request, timeoutMillis);
default:
assert false;
break;
}

return null;
}
+

通过 communicationMode 判断是同步拉取还是异步拉取,异步就调用

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void pullMessageAsync(
final String addr,
final RemotingCommand request,
final long timeoutMillis,
final PullCallback pullCallback
) throws RemotingException, InterruptedException {
this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
@Override
public void operationComplete(ResponseFuture responseFuture) {
异步
RemotingCommand response = responseFuture.getResponseCommand();
if (response != null) {
try {
PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response);
assert pullResult != null;
pullCallback.onSuccess(pullResult);
} catch (Exception e) {
pullCallback.onException(e);
}
} else {
if (!responseFuture.isSendRequestOK()) {
pullCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
} else if (responseFuture.isTimeout()) {
pullCallback.onException(new MQClientException("wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
responseFuture.getCause()));
} else {
pullCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
}
}
}
});
}
+

并且会调用前面 pullCallback 的onSuccess和onException方法,同步的就是调用

+
1
2
3
4
5
6
7
8
9
private PullResult pullMessageSync(
final String addr,
final RemotingCommand request,
final long timeoutMillis
) throws RemotingException, InterruptedException, MQBrokerException {
RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis);
assert response != null;
return this.processPullResponse(response);
}
+

然后就是这个 remotingClient 的 invokeAsync 跟 invokeSync 方法

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Override
public void invokeAsync(String addr, RemotingCommand request, long timeoutMillis, InvokeCallback invokeCallback)
throws InterruptedException, RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException,
RemotingSendRequestException {
long beginStartTime = System.currentTimeMillis();
final Channel channel = this.getAndCreateChannel(addr);
if (channel != null && channel.isActive()) {
try {
doBeforeRpcHooks(addr, request);
long costTime = System.currentTimeMillis() - beginStartTime;
if (timeoutMillis < costTime) {
throw new RemotingTooMuchRequestException("invokeAsync call timeout");
}
this.invokeAsyncImpl(channel, request, timeoutMillis - costTime, invokeCallback);
} catch (RemotingSendRequestException e) {
log.warn("invokeAsync: send request exception, so close the channel[{}]", addr);
this.closeChannel(addr, channel);
throw e;
}
} else {
this.closeChannel(addr, channel);
throw new RemotingConnectException(addr);
}
}
@Override
public RemotingCommand invokeSync(String addr, final RemotingCommand request, long timeoutMillis)
throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException {
long beginStartTime = System.currentTimeMillis();
final Channel channel = this.getAndCreateChannel(addr);
if (channel != null && channel.isActive()) {
try {
doBeforeRpcHooks(addr, request);
long costTime = System.currentTimeMillis() - beginStartTime;
if (timeoutMillis < costTime) {
throw new RemotingTimeoutException("invokeSync call timeout");
}
RemotingCommand response = this.invokeSyncImpl(channel, request, timeoutMillis - costTime);
doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(channel), request, response);
return response;
} catch (RemotingSendRequestException e) {
log.warn("invokeSync: send request exception, so close the channel[{}]", addr);
this.closeChannel(addr, channel);
throw e;
} catch (RemotingTimeoutException e) {
if (nettyClientConfig.isClientCloseSocketIfTimeout()) {
this.closeChannel(addr, channel);
log.warn("invokeSync: close socket because of timeout, {}ms, {}", timeoutMillis, addr);
}
log.warn("invokeSync: wait response timeout exception, the channel[{}]", addr);
throw e;
}
} else {
this.closeChannel(addr, channel);
throw new RemotingConnectException(addr);
}
}
+

再往下看

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
final long timeoutMillis)
throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
final int opaque = request.getOpaque();

try {
同步跟异步都是会把结果用ResponseFuture抱起来
final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
this.responseTable.put(opaque, responseFuture);
final SocketAddress addr = channel.remoteAddress();
channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture f) throws Exception {
if (f.isSuccess()) {
responseFuture.setSendRequestOK(true);
return;
} else {
responseFuture.setSendRequestOK(false);
}

responseTable.remove(opaque);
responseFuture.setCause(f.cause());
responseFuture.putResponse(null);
log.warn("send a request command to channel <" + addr + "> failed.");
}
});
// 区别是同步的是在这等待
RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
if (null == responseCommand) {
if (responseFuture.isSendRequestOK()) {
throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
responseFuture.getCause());
} else {
throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
}
}

return responseCommand;
} finally {
this.responseTable.remove(opaque);
}
}

public void invokeAsyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis,
final InvokeCallback invokeCallback)
throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
long beginStartTime = System.currentTimeMillis();
final int opaque = request.getOpaque();
boolean acquired = this.semaphoreAsync.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
if (acquired) {
final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreAsync);
long costTime = System.currentTimeMillis() - beginStartTime;
if (timeoutMillis < costTime) {
once.release();
throw new RemotingTimeoutException("invokeAsyncImpl call timeout");
}

final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis - costTime, invokeCallback, once);
this.responseTable.put(opaque, responseFuture);
try {
channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture f) throws Exception {
if (f.isSuccess()) {
responseFuture.setSendRequestOK(true);
return;
}
requestFail(opaque);
log.warn("send a request command to channel <{}> failed.", RemotingHelper.parseChannelRemoteAddr(channel));
}
});
} catch (Exception e) {
responseFuture.release();
log.warn("send a request command to channel <" + RemotingHelper.parseChannelRemoteAddr(channel) + "> Exception", e);
throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
}
} else {
if (timeoutMillis <= 0) {
throw new RemotingTooMuchRequestException("invokeAsyncImpl invoke too fast");
} else {
String info =
String.format("invokeAsyncImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d",
timeoutMillis,
this.semaphoreAsync.getQueueLength(),
this.semaphoreAsync.availablePermits()
);
log.warn(info);
throw new RemotingTimeoutException(info);
}
}
}
+ + + + + +
+ + + + + + +
+
+
+
+ + + + + + +
@@ -717,121 +865,10 @@ - - - -
- - - - - -
-

- - -

- - -
- - - - -
- - -

因为传说中的出身问题,我以前写的是PHP,在使用 swoole 之前,基本的应用调试手段就是简单粗暴的 var_dump,exit,对于单进程模型的 PHP 也是简单有效,技术栈换成 Java 之后,就变得没那么容易,一方面是需要编译,另一方面是一般都是基于 spring 的项目,如果问题定位比较模糊,那框架层的是很难靠简单的 System.out.println 或者打 log 解决,(PS:我觉得可能我写的东西比较适合从 PHP 这种弱类型语言转到 Java 的小白同学)这个时候一方面因为是 Java,有了非常好用的 idea IDE 的支持,可以各种花式调试,条件断点尤其牛叉,但是又因为有 Spring+Java 的双重原因,有些情况下单步调试可以把手按废掉,这也是我之前一直比较困惑苦逼的点,后来随着慢慢精(jiang)进(you)之后,比如对于一个 oom 的情况,我们可以通过启动参数加上-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=xx/xx 来配置溢出时的堆dump 日志,获取到这个文件后,我们可以通过像 Memory Analyzer (MAT)[https://www.eclipse.org/mat/] (The Eclipse Memory Analyzer is a fast and feature-rich Java heap analyzer that helps you find memory leaks and reduce memory consumption.)来查看诊断问题所在,之前用到的时候是因为有个死循环一直往链表里塞数据,属于比较简单的,后来一次是由于运维进行应用迁移时按默认的统一配置了堆内存大小,导致内存的确不够用,所以溢出了,
今天想说的其实主要是我们的 thread dump,这也是我最近才真正用的一个方法,可能真的很小白了,用过 ide 的单步调试其实都知道会有一个一层层的玩意,比如函数从 A,调用了 B,再从 B 调用了 C,一直往下(因为是 Java,所以还有很多🤦‍♂️),这个其实也是大部分语言的调用模型,利用了栈这个数据结构,通过这个结构我们可以知道代码的调用链路,由于对于一个 spring 应用,在本身框架代码量非常庞大的情况下,外加如果应用代码也是非常多的时候,有时候通过单步调试真的很难短时间定位到问题,需要非常大的耐心和仔细观察,当然不是说完全不行,举个例子当我的应用经常启动需要非常长的时间,因为本身应用有非常多个 bean,比较难说究竟是 bean 的加载的确很慢还是有什么异常原因,这种时候就可以使用 thread dump 了,具体怎么操作呢

如果在idea 中运行或者调试时,可以直接点击这个照相机一样的按钮,右边就会出现了左边会显示所有的线程,右边会显示线程栈,

-
1
2
3
4
5
6
7
"main@1" prio=5 tid=0x1 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at TreeDistance.treeDist(TreeDistance.java:64)
at TreeDistance.treeDist(TreeDistance.java:65)
at TreeDistance.treeDist(TreeDistance.java:65)
at TreeDistance.treeDist(TreeDistance.java:65)
at TreeDistance.main(TreeDistance.java:45)
-

这就是我们主线程的堆栈信息了,main 表示这个线程名,prio表示优先级,默认是 5,tid 表示线程 id,nid 表示对应的系统线程,后面的runnable 表示目前线程状态,因为是被我打了断点,所以是就许状态,然后下面就是对应的线程栈内容了,在TreeDistance类的 treeDist方法中,对应的文件行数是 64 行。
这里使用 thread dump一般也不会是上面我截图代码里的这种代码量很少的,一般是大型项目,有时候跑着跑着没反应,又不知道跑到哪了,特别是一些刚接触的大项目或者需要定位一个大项目的一个疑难问题,一时没思路时,可以使用这个方法,个人觉得非常有帮助。

- - -
- - - - - - -
-
-
-
- - - - @@ -899,20 +936,20 @@ @@ -937,7 +974,7 @@
- +
diff --git a/page/11/index.html b/page/11/index.html index f6daf3a4b8..e5e3e7bffe 100644 --- a/page/11/index.html +++ b/page/11/index.html @@ -203,6 +203,117 @@ +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

因为传说中的出身问题,我以前写的是PHP,在使用 swoole 之前,基本的应用调试手段就是简单粗暴的 var_dump,exit,对于单进程模型的 PHP 也是简单有效,技术栈换成 Java 之后,就变得没那么容易,一方面是需要编译,另一方面是一般都是基于 spring 的项目,如果问题定位比较模糊,那框架层的是很难靠简单的 System.out.println 或者打 log 解决,(PS:我觉得可能我写的东西比较适合从 PHP 这种弱类型语言转到 Java 的小白同学)这个时候一方面因为是 Java,有了非常好用的 idea IDE 的支持,可以各种花式调试,条件断点尤其牛叉,但是又因为有 Spring+Java 的双重原因,有些情况下单步调试可以把手按废掉,这也是我之前一直比较困惑苦逼的点,后来随着慢慢精(jiang)进(you)之后,比如对于一个 oom 的情况,我们可以通过启动参数加上-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=xx/xx 来配置溢出时的堆dump 日志,获取到这个文件后,我们可以通过像 Memory Analyzer (MAT)[https://www.eclipse.org/mat/] (The Eclipse Memory Analyzer is a fast and feature-rich Java heap analyzer that helps you find memory leaks and reduce memory consumption.)来查看诊断问题所在,之前用到的时候是因为有个死循环一直往链表里塞数据,属于比较简单的,后来一次是由于运维进行应用迁移时按默认的统一配置了堆内存大小,导致内存的确不够用,所以溢出了,
今天想说的其实主要是我们的 thread dump,这也是我最近才真正用的一个方法,可能真的很小白了,用过 ide 的单步调试其实都知道会有一个一层层的玩意,比如函数从 A,调用了 B,再从 B 调用了 C,一直往下(因为是 Java,所以还有很多🤦‍♂️),这个其实也是大部分语言的调用模型,利用了栈这个数据结构,通过这个结构我们可以知道代码的调用链路,由于对于一个 spring 应用,在本身框架代码量非常庞大的情况下,外加如果应用代码也是非常多的时候,有时候通过单步调试真的很难短时间定位到问题,需要非常大的耐心和仔细观察,当然不是说完全不行,举个例子当我的应用经常启动需要非常长的时间,因为本身应用有非常多个 bean,比较难说究竟是 bean 的加载的确很慢还是有什么异常原因,这种时候就可以使用 thread dump 了,具体怎么操作呢

如果在idea 中运行或者调试时,可以直接点击这个照相机一样的按钮,右边就会出现了左边会显示所有的线程,右边会显示线程栈,

+
1
2
3
4
5
6
7
"main@1" prio=5 tid=0x1 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at TreeDistance.treeDist(TreeDistance.java:64)
at TreeDistance.treeDist(TreeDistance.java:65)
at TreeDistance.treeDist(TreeDistance.java:65)
at TreeDistance.treeDist(TreeDistance.java:65)
at TreeDistance.main(TreeDistance.java:45)
+

这就是我们主线程的堆栈信息了,main 表示这个线程名,prio表示优先级,默认是 5,tid 表示线程 id,nid 表示对应的系统线程,后面的runnable 表示目前线程状态,因为是被我打了断点,所以是就许状态,然后下面就是对应的线程栈内容了,在TreeDistance类的 treeDist方法中,对应的文件行数是 64 行。
这里使用 thread dump一般也不会是上面我截图代码里的这种代码量很少的,一般是大型项目,有时候跑着跑着没反应,又不知道跑到哪了,特别是一些刚接触的大项目或者需要定位一个大项目的一个疑难问题,一时没思路时,可以使用这个方法,个人觉得非常有帮助。

+ + +
+ + + + + + +
+
+
+
+ + + + + + +
@@ -368,11 +479,11 @@ - + , - + , @@ -493,11 +604,11 @@ - + , - + , @@ -613,11 +724,11 @@ - + , - + , @@ -703,153 +814,10 @@ - - - -
- - - - - -
-

- - -

- - -
- - - - -
- - -

LRU

说完了过期策略再说下淘汰策略,redis 使用的策略是近似的 lru 策略,为什么是近似的呢,先来看下什么是 lru,看下 wiki 的介绍
,图中一共有四个槽的存储空间,依次访问顺序是 A B C D E D F,
当第一次访问 D 时刚好占满了坑,并且值是 4,这个值越小代表越先被淘汰,当 E 进来时,看了下已经存在的四个里 A 是最小的,代表是最早存在并且最早被访问的,那就先淘汰它了,E 占领了 A 的位置,并设置值为 4,然后又访问 D 了,D 已经存在了,不过又被访问到了,得更新值为 5,然后是 F 进来了,这时 B 是最老的且最近未被访问,所以就淘汰它了。以上是一个 lru 的简要说明,但是 redis 没有严格按照这个去执行,理由跟前面过期策略一致,最严格的过期策略应该是每个 key 都有对应的定时器,当超时时马上就能清除,但是问题是这样的cpu 消耗太大,所换来的内存效率不太值得,淘汰策略也是这样,类似于上图,要维护所有 key 的一个有序 lru 值,并且遍历将最小的淘汰,redis 采用的是抽样的形式,最初的实现方式是随机从 dict 抽取 5 个 key,淘汰一个 lru 最小的,这样子勉强能达到淘汰的目的,但是效果不是特别好,后面在 redis 3.0开始,将随机抽取改成了维护一个 pool,pool 的大小默认是 16,每次放入的都是按lru 值有序排列好,每一次放入的必须是 lru小于 pool 中最小的 lru 才允许放入,直到放满,后面再有新的就会将大的踢出。
redis 针对这个策略的改进做了一个实验,这里借用下图

首先背景是这图中的所有点都对应一个 redis 的 key,灰色部分加入后被顺序访问过一遍,然后又加入了绿色部分,那么按照理论的 lru 算法,应该是图左上中,浅灰色部分全都被淘汰,那么对比来看看图右上,左下和右下,左下表示 2.8 版本就是随机抽样 5 个 key,淘汰其中 lru 最小的一个,发现是灰色和浅灰色的都有被淘汰的,右下的 3.0 版本抽样数量不变的情况下,稍好一些,当 3.0 版本的抽样数量调整成 10 后,已经较为接近理论上的 lru 策略了,通过代码来简要分析下

-
1
2
3
4
5
6
7
8
9
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount;
void *ptr;
} robj;
-

对于 lru 策略来说,lru 字段记录的就是redisObj 的LRU time,
redis 在访问数据时,都会调用lookupKey方法

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/* Low level key lookup API, not actually called directly from commands
* implementations that should instead rely on lookupKeyRead(),
* lookupKeyWrite() and lookupKeyReadWithFlags(). */
robj *lookupKey(redisDb *db, robj *key, int flags) {
dictEntry *de = dictFind(db->dict,key->ptr);
if (de) {
robj *val = dictGetVal(de);

/* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
// 这个是后面一节的内容
updateLFU(val);
} else {
// 对于这个分支,访问时就会去更新 lru 值
val->lru = LRU_CLOCK();
}
}
return val;
} else {
return NULL;
}
}
/* This function is used to obtain the current LRU clock.
* If the current resolution is lower than the frequency we refresh the
* LRU clock (as it should be in production servers) we return the
* precomputed value, otherwise we need to resort to a system call. */
unsigned int LRU_CLOCK(void) {
unsigned int lruclock;
if (1000/server.hz <= LRU_CLOCK_RESOLUTION) {
// 如果服务器的频率server.hz大于 1 时就是用系统预设的 lruclock
lruclock = server.lruclock;
} else {
lruclock = getLRUClock();
}
return lruclock;
}
/* Return the LRU clock, based on the clock resolution. This is a time
* in a reduced-bits format that can be used to set and check the
* object->lru field of redisObject structures. */
unsigned int getLRUClock(void) {
return (mstime()/LRU_CLOCK_RESOLUTION) & LRU_CLOCK_MAX;
}
-

redis 处理命令是在这里processCommand

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* If this function gets called we already read a whole
* command, arguments are in the client argv/argc fields.
* processCommand() execute the command or prepare the
* server for a bulk read from the client.
*
* If C_OK is returned the client is still alive and valid and
* other operations can be performed by the caller. Otherwise
* if C_ERR is returned the client was destroyed (i.e. after QUIT). */
int processCommand(client *c) {
moduleCallCommandFilters(c);



/* Handle the maxmemory directive.
*
* Note that we do not want to reclaim memory if we are here re-entering
* the event loop since there is a busy Lua script running in timeout
* condition, to avoid mixing the propagation of scripts with the
* propagation of DELs due to eviction. */
if (server.maxmemory && !server.lua_timedout) {
int out_of_memory = freeMemoryIfNeededAndSafe() == C_ERR;
/* freeMemoryIfNeeded may flush slave output buffers. This may result
* into a slave, that may be the active client, to be freed. */
if (server.current_client == NULL) return C_ERR;

/* It was impossible to free enough memory, and the command the client
* is trying to execute is denied during OOM conditions or the client
* is in MULTI/EXEC context? Error. */
if (out_of_memory &&
(c->cmd->flags & CMD_DENYOOM ||
(c->flags & CLIENT_MULTI &&
c->cmd->proc != execCommand &&
c->cmd->proc != discardCommand)))
{
flagTransaction(c);
addReply(c, shared.oomerr);
return C_OK;
}
}
}
-

这里只摘了部分,当需要清理内存时就会调用, 然后调用了freeMemoryIfNeededAndSafe

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/* This is a wrapper for freeMemoryIfNeeded() that only really calls the
* function if right now there are the conditions to do so safely:
*
* - There must be no script in timeout condition.
* - Nor we are loading data right now.
*
*/
int freeMemoryIfNeededAndSafe(void) {
if (server.lua_timedout || server.loading) return C_OK;
return freeMemoryIfNeeded();
}
/* This function is periodically called to see if there is memory to free
* according to the current "maxmemory" settings. In case we are over the
* memory limit, the function will try to free some memory to return back
* under the limit.
*
* The function returns C_OK if we are under the memory limit or if we
* were over the limit, but the attempt to free memory was successful.
* Otehrwise if we are over the memory limit, but not enough memory
* was freed to return back under the limit, the function returns C_ERR. */
int freeMemoryIfNeeded(void) {
int keys_freed = 0;
/* By default replicas should ignore maxmemory
* and just be masters exact copies. */
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;

size_t mem_reported, mem_tofree, mem_freed;
mstime_t latency, eviction_latency;
long long delta;
int slaves = listLength(server.slaves);

/* When clients are paused the dataset should be static not just from the
* POV of clients not being able to write, but also from the POV of
* expires and evictions of keys not being performed. */
if (clientsArePaused()) return C_OK;
if (getMaxmemoryState(&mem_reported,NULL,&mem_tofree,NULL) == C_OK)
return C_OK;

mem_freed = 0;

if (server.maxmemory_policy == MAXMEMORY_NO_EVICTION)
goto cant_free; /* We need to free memory, but policy forbids. */

latencyStartMonitor(latency);
while (mem_freed < mem_tofree) {
int j, k, i;
static unsigned int next_db = 0;
sds bestkey = NULL;
int bestdbid;
redisDb *db;
dict *dict;
dictEntry *de;

if (server.maxmemory_policy & (MAXMEMORY_FLAG_LRU|MAXMEMORY_FLAG_LFU) ||
server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL)
{
struct evictionPoolEntry *pool = EvictionPoolLRU;

while(bestkey == NULL) {
unsigned long total_keys = 0, keys;

/* We don't want to make local-db choices when expiring keys,
* so to start populate the eviction pool sampling keys from
* every DB. */
for (i = 0; i < server.dbnum; i++) {
db = server.db+i;
dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ?
db->dict : db->expires;
if ((keys = dictSize(dict)) != 0) {
evictionPoolPopulate(i, dict, db->dict, pool);
total_keys += keys;
}
}
if (!total_keys) break; /* No keys to evict. */

/* Go backward from best to worst element to evict. */
for (k = EVPOOL_SIZE-1; k >= 0; k--) {
if (pool[k].key == NULL) continue;
bestdbid = pool[k].dbid;

if (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) {
de = dictFind(server.db[pool[k].dbid].dict,
pool[k].key);
} else {
de = dictFind(server.db[pool[k].dbid].expires,
pool[k].key);
}

/* Remove the entry from the pool. */
if (pool[k].key != pool[k].cached)
sdsfree(pool[k].key);
pool[k].key = NULL;
pool[k].idle = 0;

/* If the key exists, is our pick. Otherwise it is
* a ghost and we need to try the next element. */
if (de) {
bestkey = dictGetKey(de);
break;
} else {
/* Ghost... Iterate again. */
}
}
}
}

/* volatile-random and allkeys-random policy */
else if (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM ||
server.maxmemory_policy == MAXMEMORY_VOLATILE_RANDOM)
{
/* When evicting a random key, we try to evict a key for
* each DB, so we use the static 'next_db' variable to
* incrementally visit all DBs. */
for (i = 0; i < server.dbnum; i++) {
j = (++next_db) % server.dbnum;
db = server.db+j;
dict = (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM) ?
db->dict : db->expires;
if (dictSize(dict) != 0) {
de = dictGetRandomKey(dict);
bestkey = dictGetKey(de);
bestdbid = j;
break;
}
}
}

/* Finally remove the selected key. */
if (bestkey) {
db = server.db+bestdbid;
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
propagateExpire(db,keyobj,server.lazyfree_lazy_eviction);
/* We compute the amount of memory freed by db*Delete() alone.
* It is possible that actually the memory needed to propagate
* the DEL in AOF and replication link is greater than the one
* we are freeing removing the key, but we can't account for
* that otherwise we would never exit the loop.
*
* AOF and Output buffer memory will be freed eventually so
* we only care about memory used by the key space. */
delta = (long long) zmalloc_used_memory();
latencyStartMonitor(eviction_latency);
if (server.lazyfree_lazy_eviction)
dbAsyncDelete(db,keyobj);
else
dbSyncDelete(db,keyobj);
latencyEndMonitor(eviction_latency);
latencyAddSampleIfNeeded("eviction-del",eviction_latency);
latencyRemoveNestedEvent(latency,eviction_latency);
delta -= (long long) zmalloc_used_memory();
mem_freed += delta;
server.stat_evictedkeys++;
notifyKeyspaceEvent(NOTIFY_EVICTED, "evicted",
keyobj, db->id);
decrRefCount(keyobj);
keys_freed++;

/* When the memory to free starts to be big enough, we may
* start spending so much time here that is impossible to
* deliver data to the slaves fast enough, so we force the
* transmission here inside the loop. */
if (slaves) flushSlavesOutputBuffers();

/* Normally our stop condition is the ability to release
* a fixed, pre-computed amount of memory. However when we
* are deleting objects in another thread, it's better to
* check, from time to time, if we already reached our target
* memory, since the "mem_freed" amount is computed only
* across the dbAsyncDelete() call, while the thread can
* release the memory all the time. */
if (server.lazyfree_lazy_eviction && !(keys_freed % 16)) {
if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
/* Let's satisfy our stop condition. */
mem_freed = mem_tofree;
}
}
} else {
latencyEndMonitor(latency);
latencyAddSampleIfNeeded("eviction-cycle",latency);
goto cant_free; /* nothing to free... */
}
}
latencyEndMonitor(latency);
latencyAddSampleIfNeeded("eviction-cycle",latency);
return C_OK;

cant_free:
/* We are here if we are not able to reclaim memory. There is only one
* last thing we can try: check if the lazyfree thread has jobs in queue
* and wait... */
while(bioPendingJobsOfType(BIO_LAZY_FREE)) {
if (((mem_reported - zmalloc_used_memory()) + mem_freed) >= mem_tofree)
break;
usleep(1000);
}
return C_ERR;
}
-

这里就是根据具体策略去淘汰 key,首先是要往 pool 更新 key,更新key 的方法是evictionPoolPopulate

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
int j, k, count;
dictEntry *samples[server.maxmemory_samples];

count = dictGetSomeKeys(sampledict,samples,server.maxmemory_samples);
for (j = 0; j < count; j++) {
unsigned long long idle;
sds key;
robj *o;
dictEntry *de;

de = samples[j];
key = dictGetKey(de);

/* If the dictionary we are sampling from is not the main
* dictionary (but the expires one) we need to lookup the key
* again in the key dictionary to obtain the value object. */
if (server.maxmemory_policy != MAXMEMORY_VOLATILE_TTL) {
if (sampledict != keydict) de = dictFind(keydict, key);
o = dictGetVal(de);
}

/* Calculate the idle time according to the policy. This is called
* idle just because the code initially handled LRU, but is in fact
* just a score where an higher score means better candidate. */
if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
idle = estimateObjectIdleTime(o);
} else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
/* When we use an LRU policy, we sort the keys by idle time
* so that we expire keys starting from greater idle time.
* However when the policy is an LFU one, we have a frequency
* estimation, and we want to evict keys with lower frequency
* first. So inside the pool we put objects using the inverted
* frequency subtracting the actual frequency to the maximum
* frequency of 255. */
idle = 255-LFUDecrAndReturn(o);
} else if (server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
/* In this case the sooner the expire the better. */
idle = ULLONG_MAX - (long)dictGetVal(de);
} else {
serverPanic("Unknown eviction policy in evictionPoolPopulate()");
}

/* Insert the element inside the pool.
* First, find the first empty bucket or the first populated
* bucket that has an idle time smaller than our idle time. */
k = 0;
while (k < EVPOOL_SIZE &&
pool[k].key &&
pool[k].idle < idle) k++;
if (k == 0 && pool[EVPOOL_SIZE-1].key != NULL) {
/* Can't insert if the element is < the worst element we have
* and there are no empty buckets. */
continue;
} else if (k < EVPOOL_SIZE && pool[k].key == NULL) {
/* Inserting into empty position. No setup needed before insert. */
} else {
/* Inserting in the middle. Now k points to the first element
* greater than the element to insert. */
if (pool[EVPOOL_SIZE-1].key == NULL) {
/* Free space on the right? Insert at k shifting
* all the elements from k to end to the right. */

/* Save SDS before overwriting. */
sds cached = pool[EVPOOL_SIZE-1].cached;
memmove(pool+k+1,pool+k,
sizeof(pool[0])*(EVPOOL_SIZE-k-1));
pool[k].cached = cached;
} else {
/* No free space on right? Insert at k-1 */
k--;
/* Shift all elements on the left of k (included) to the
* left, so we discard the element with smaller idle time. */
sds cached = pool[0].cached; /* Save SDS before overwriting. */
if (pool[0].key != pool[0].cached) sdsfree(pool[0].key);
memmove(pool,pool+1,sizeof(pool[0])*k);
pool[k].cached = cached;
}
}

/* Try to reuse the cached SDS string allocated in the pool entry,
* because allocating and deallocating this object is costly
* (according to the profiler, not my fantasy. Remember:
* premature optimizbla bla bla bla. */
int klen = sdslen(key);
if (klen > EVPOOL_CACHED_SDS_SIZE) {
pool[k].key = sdsdup(key);
} else {
memcpy(pool[k].cached,key,klen+1);
sdssetlen(pool[k].cached,klen);
pool[k].key = pool[k].cached;
}
pool[k].idle = idle;
pool[k].dbid = dbid;
}
}
-

Redis随机选择maxmemory_samples数量的key,然后计算这些key的空闲时间idle time,当满足条件时(比pool中的某些键的空闲时间还大)就可以进poolpool更新之后,就淘汰pool中空闲时间最大的键。

-

estimateObjectIdleTime用来计算Redis对象的空闲时间:

-
1
2
3
4
5
6
7
8
9
10
11
/* Given an object returns the min number of milliseconds the object was never
* requested, using an approximated LRU algorithm. */
unsigned long long estimateObjectIdleTime(robj *o) {
unsigned long long lruclock = LRU_CLOCK();
if (lruclock >= o->lru) {
return (lruclock - o->lru) * LRU_CLOCK_RESOLUTION;
} else {
return (lruclock + (LRU_CLOCK_MAX - o->lru)) *
LRU_CLOCK_RESOLUTION;
}
}
-

空闲时间第一种是 lurclock 大于对象的 lru,那么就是减一下乘以精度,因为 lruclock 有可能是已经预生成的,所以会可能走下面这个

-

LFU

上面介绍了LRU 的算法,但是考虑一种场景

-
1
2
3
4
~~~~~A~~~~~A~~~~~A~~~~A~~~~~A~~~~~A~~|
~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~|
~~~~~~~~~~C~~~~~~~~~C~~~~~~~~~C~~~~~~|
~~~~~D~~~~~~~~~~D~~~~~~~~~D~~~~~~~~~D|
-

可以发现,当采用 lru 的淘汰策略的时候,D 是最新的,会被认为是最值得保留的,但是事实上还不如 A 跟 B,然后 antirez 大神就想到了LFU (Least Frequently Used) 这个算法, 显然对于上面的四个 key 的访问频率,保留优先级应该是 B > A > C = D
那要怎么来实现这个 LFU 算法呢,其实像LRU,理想的情况就是维护个链表,把最新访问的放到头上去,但是这个会影响访问速度,注意到前面代码的应该可以看到,redisObject 的 lru 字段其实是两用的,当策略是 LFU 时,这个字段就另作他用了,它的 24 位长度被分成两部分

-
1
2
3
4
      16 bits      8 bits
+----------------+--------+
+ Last decr time | LOG_C |
+----------------+--------+
-

前16位字段是最后一次递减时间,因此Redis知道 上一次计数器递减,后8位是 计数器 counter。
LFU 的主体策略就是当这个 key 被访问的次数越多频率越高他就越容易被保留下来,并且是最近被访问的频率越高。这其实有两个事情要做,一个是在访问的时候增加计数值,在一定长时间不访问时进行衰减,所以这里用了两个值,前 16 位记录上一次衰减的时间,后 8 位记录具体的计数值。
Redis4.0之后为maxmemory_policy淘汰策略添加了两个LFU模式:

-

volatile-lfu:对有过期时间的key采用LFU淘汰策略
allkeys-lfu:对全部key采用LFU淘汰策略
还有2个配置可以调整LFU算法:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
lfu-log-factor 10
lfu-decay-time 1
```
`lfu-log-factor` 可以调整计数器counter的增长速度,lfu-log-factor越大,counter增长的越慢。

`lfu-decay-time`是一个以分钟为单位的数值,可以调整counter的减少速度
这里有个问题是 8 位大小够计么,访问一次加 1 的话的确不够,不过大神就是大神,才不会这么简单的加一。往下看代码
```C
/* Low level key lookup API, not actually called directly from commands
* implementations that should instead rely on lookupKeyRead(),
* lookupKeyWrite() and lookupKeyReadWithFlags(). */
robj *lookupKey(redisDb *db, robj *key, int flags) {
dictEntry *de = dictFind(db->dict,key->ptr);
if (de) {
robj *val = dictGetVal(de);

/* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
// 当淘汰策略是 LFU 时,就会调用这个updateLFU
updateLFU(val);
} else {
val->lru = LRU_CLOCK();
}
}
return val;
} else {
return NULL;
}
}
-

updateLFU 这个其实个入口,调用了两个重要的方法

-
1
2
3
4
5
6
7
8
/* Update LFU when an object is accessed.
* Firstly, decrement the counter if the decrement time is reached.
* Then logarithmically increment the counter, and update the access time. */
void updateLFU(robj *val) {
unsigned long counter = LFUDecrAndReturn(val);
counter = LFULogIncr(counter);
val->lru = (LFUGetTimeInMinutes()<<8) | counter;
}
-

首先来看看LFUDecrAndReturn,这个方法的作用是根据上一次衰减时间和系统配置的 lfu-decay-time 参数来确定需要将 counter 减去多少

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* If the object decrement time is reached decrement the LFU counter but
* do not update LFU fields of the object, we update the access time
* and counter in an explicit way when the object is really accessed.
* And we will times halve the counter according to the times of
* elapsed time than server.lfu_decay_time.
* Return the object frequency counter.
*
* This function is used in order to scan the dataset for the best object
* to fit: as we check for the candidate, we incrementally decrement the
* counter of the scanned objects if needed. */
unsigned long LFUDecrAndReturn(robj *o) {
// 右移 8 位,拿到上次衰减时间
unsigned long ldt = o->lru >> 8;
// 对 255 做与操作,拿到 counter 值
unsigned long counter = o->lru & 255;
// 根据lfu_decay_time来算出过了多少个衰减周期
unsigned long num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0;
if (num_periods)
counter = (num_periods > counter) ? 0 : counter - num_periods;
return counter;
}
-

然后是加,调用了LFULogIncr

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Logarithmically increment a counter. The greater is the current counter value
* the less likely is that it gets really implemented. Saturate it at 255. */
uint8_t LFULogIncr(uint8_t counter) {
// 最大值就是 255,到顶了就不加了
if (counter == 255) return 255;
// 生成个随机小数
double r = (double)rand()/RAND_MAX;
// 减去个基础值,LFU_INIT_VAL = 5,防止刚进来就被逐出
double baseval = counter - LFU_INIT_VAL;
// 如果是小于 0,
if (baseval < 0) baseval = 0;
// 如果 baseval 是 0,那么 p 就是 1了,后面 counter 直接加一,如果不是的话,得看系统参数lfu_log_factor,这个越大,除出来的 p 越小,那么 counter++的可能性也越小,这样子就把前面的疑问给解决了,不是直接+1 的
double p = 1.0/(baseval*server.lfu_log_factor+1);
if (r < p) counter++;
return counter;
}
-

大概的变化速度可以参考

-
1
2
3
4
5
6
7
8
9
10
11
+--------+------------+------------+------------+------------+------------+
| factor | 100 hits | 1000 hits | 100K hits | 1M hits | 10M hits |
+--------+------------+------------+------------+------------+------------+
| 0 | 104 | 255 | 255 | 255 | 255 |
+--------+------------+------------+------------+------------+------------+
| 1 | 18 | 49 | 255 | 255 | 255 |
+--------+------------+------------+------------+------------+------------+
| 10 | 10 | 18 | 142 | 255 | 255 |
+--------+------------+------------+------------+------------+------------+
| 100 | 8 | 11 | 49 | 143 | 255 |
+--------+------------+------------+------------+------------+------------+
-

简而言之就是 lfu_log_factor 越大变化的越慢

-

总结

总结一下,redis 实现了近似的 lru 淘汰策略,通过增加了淘汰 key 的池子(pool),并且增大每次抽样的 key 的数量来将淘汰效果更进一步地接近于 lru,这是 lru 策略,但是对于前面举的一个例子,其实 lru 并不能保证 key 的淘汰就如我们预期,所以在后期又引入了 lfu 的策略,lfu的策略比较巧妙,复用了 redis 对象的 lru 字段,并且使用了factor 参数来控制计数器递增的速度,防止 8 位的计数器太早溢出。

- - -
- - - - - - -
-
-
-
- - - - @@ -917,20 +885,20 @@ @@ -955,7 +923,7 @@
- +
diff --git a/page/12/index.html b/page/12/index.html index 0ed7a3442a..be7cc6045d 100644 --- a/page/12/index.html +++ b/page/12/index.html @@ -204,7 +204,7 @@
- +