From a460f9730923747018a672705ed4d8dc0fdd00cb Mon Sep 17 00:00:00 2001 From: nicksxs Date: Mon, 30 May 2022 17:09:56 +0800 Subject: [PATCH] Site updated: 2022-05-30 17:09:54 --- 2017/04/25/rabbitmq-tips/index.html | 18 +- 2019/06/18/openresty/index.html | 4 +- .../index.html | 4 +- .../index.html | 4 +- .../index.html | 4 +- 2021/07/18/2021-年中总结/index.html | 2 +- 2021/07/25/redis过期策略复习/index.html | 32 +- .../index.html | 2 +- atom.xml | 8 +- index.html | 2 +- leancloud.memo | 1 + leancloud_counter_security_urls.json | 2 +- page/10/index.html | 32 +- page/29/index.html | 18 +- search.xml | 4894 ++++++++--------- sitemap.xml | 1282 ++--- tags/GC/index.html | 2 +- 17 files changed, 3159 insertions(+), 3152 deletions(-) diff --git a/2017/04/25/rabbitmq-tips/index.html b/2017/04/25/rabbitmq-tips/index.html index 8f4ce3eadd..7d1129501c 100644 --- a/2017/04/25/rabbitmq-tips/index.html +++ b/2017/04/25/rabbitmq-tips/index.html @@ -1,10 +1,10 @@ -rabbitmq-tips | Nicksxs's Blog

Nicksxs's Blog

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

0%

rabbitmq-tips

rabbitmq 介绍

接触了一下rabbitmq,原来在选型的时候是在rabbitmq跟kafka之间做选择,网上搜了一下之后发现kafka的优势在于吞吐量,而rabbitmq相对注重可靠性,因为应用在im上,需要保证消息不能丢失所以就暂时选定rabbitmq,
Message Queue的需求由来已久,80年代最早在金融交易中,高盛等公司采用Teknekron公司的产品,当时的Message queuing软件叫做:the information bus(TIB)。 TIB被电信和通讯公司采用,路透社收购了Teknekron公司。之后,IBM开发了MQSeries,微软开发了Microsoft Message Queue(MSMQ)。这些商业MQ供应商的问题是厂商锁定,价格高昂。2001年,Java Message queuing试图解决锁定和交互性的问题,但对应用来说反而更加麻烦了。
RabbitMQ采用Erlang语言开发。Erlang语言由Ericson设计,专门为开发concurrent和distribution系统的一种语言,在电信领域使用广泛。OTP(Open Telecom Platform)作为Erlang语言的一部分,包含了很多基于Erlang开发的中间件/库/工具,如mnesia/SASL,极大方便了Erlang应用的开发。OTP就类似于Python语言中众多的module,用户借助这些module可以很方便的开发应用。
于是2004年,摩根大通和iMatrix开始着手Advanced Message Queuing Protocol (AMQP)开放标准的开发。2006年,AMQP规范发布。2007年,Rabbit技术公司基于AMQP标准开发的RabbitMQ 1.0 发布。所有主要的编程语言均有与代理接口通讯的客户端库。

简单的使用经验

通俗的理解

这里介绍下其中的一些概念,connection表示和队列服务器的连接,一般情况下是tcp连接, channel表示通道,可以在一个连接上建立多个通道,这里主要是节省了tcp连接握手的成本,exchange可以理解成一个路由器,将消息推送给对应的队列queue,其实是像一个订阅的模式。

集群经验

rabbitmqctl stop这个是关闭rabbitmq,在搭建集群时候先关闭服务,然后使用rabbitmq-server -detached静默启动,这时候使用rabbitmqctl cluster_status查看集群状态,因为还没将节点加入集群,所以只能看到类似

Cluster status of node rabbit@rabbit1 ...
-[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
- {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]}]
-...done.

然后就可以把当前节点加入集群,

rabbit2$ rabbitmqctl stop_app #这个stop_app与stop的区别是前者停的是rabbitmq应用,保留erlang节点,
-                              #后者是停止了rabbitmq和erlang节点
-Stopping node rabbit@rabbit2 ...done.
-rabbit2$ rabbitmqctl join_cluster rabbit@rabbit1 #这里可以用--ram指定将当前节点作为内存节点加入集群
-Clustering node rabbit@rabbit2 with [rabbit@rabbit1] ...done.
+rabbitmq-tips | Nicksxs's Blog

Nicksxs's Blog

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

0%

rabbitmq-tips

rabbitmq 介绍

接触了一下rabbitmq,原来在选型的时候是在rabbitmq跟kafka之间做选择,网上搜了一下之后发现kafka的优势在于吞吐量,而rabbitmq相对注重可靠性,因为应用在im上,需要保证消息不能丢失所以就暂时选定rabbitmq,
Message Queue的需求由来已久,80年代最早在金融交易中,高盛等公司采用Teknekron公司的产品,当时的Message queuing软件叫做:the information bus(TIB)。 TIB被电信和通讯公司采用,路透社收购了Teknekron公司。之后,IBM开发了MQSeries,微软开发了Microsoft Message Queue(MSMQ)。这些商业MQ供应商的问题是厂商锁定,价格高昂。2001年,Java Message queuing试图解决锁定和交互性的问题,但对应用来说反而更加麻烦了。
RabbitMQ采用Erlang语言开发。Erlang语言由Ericson设计,专门为开发concurrent和distribution系统的一种语言,在电信领域使用广泛。OTP(Open Telecom Platform)作为Erlang语言的一部分,包含了很多基于Erlang开发的中间件/库/工具,如mnesia/SASL,极大方便了Erlang应用的开发。OTP就类似于Python语言中众多的module,用户借助这些module可以很方便的开发应用。
于是2004年,摩根大通和iMatrix开始着手Advanced Message Queuing Protocol (AMQP)开放标准的开发。2006年,AMQP规范发布。2007年,Rabbit技术公司基于AMQP标准开发的RabbitMQ 1.0 发布。所有主要的编程语言均有与代理接口通讯的客户端库。

简单的使用经验

通俗的理解

这里介绍下其中的一些概念,connection表示和队列服务器的连接,一般情况下是tcp连接, channel表示通道,可以在一个连接上建立多个通道,这里主要是节省了tcp连接握手的成本,exchange可以理解成一个路由器,将消息推送给对应的队列queue,其实是像一个订阅的模式。

集群经验

rabbitmqctl stop这个是关闭rabbitmq,在搭建集群时候先关闭服务,然后使用rabbitmq-server -detached静默启动,这时候使用rabbitmqctl cluster_status查看集群状态,因为还没将节点加入集群,所以只能看到类似

Cluster status of node rabbit@rabbit1 ...
+[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
+ {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]}]
+...done.

然后就可以把当前节点加入集群,

rabbit2$ rabbitmqctl stop_app #这个stop_app与stop的区别是前者停的是rabbitmq应用,保留erlang节点,
+                              #后者是停止了rabbitmq和erlang节点
+Stopping node rabbit@rabbit2 ...done.
+rabbit2$ rabbitmqctl join_cluster rabbit@rabbit1 #这里可以用--ram指定将当前节点作为内存节点加入集群
+Clustering node rabbit@rabbit2 with [rabbit@rabbit1] ...done.
 rabbit2$ rabbitmqctl start_app
-Starting node rabbit@rabbit2 ...done.

其他可以参考官方文档

一些坑

消息丢失

这里碰到过一个坑,对于使用exchange来做消息路由的,会有一个情况,就是在routing_key没被订阅的时候,会将该条找不到路由对应的queue的消息丢掉What happens if we break our contract and send a message with one or four words, like "orange" or "quick.orange.male.rabbit"? Well, these messages won't match any bindings and will be lost.对应链接,而当使用空的exchange时,会保留消息,当出现消费者的时候就可以将收到之前生产者所推送的消息对应链接,这里就是用了空的exchange。

集群搭建

集群搭建的时候有个erlang vm生成的random cookie,这个是用来做集群之间认证的,相同的cookie才能连接,但是如果通过vim打开复制后在其他几点新建文件写入会多一个换行,导致集群建立是报错,所以这里最好使用scp等传输命令直接传输cookie文件,同时要注意下cookie的文件权限。
另外在集群搭建的时候如果更改过hostname,那么要把rabbitmq的数据库删除,否则启动后会马上挂掉

\ No newline at end of file +Starting node rabbit@rabbit2 ...done.

其他可以参考官方文档

一些坑

消息丢失

这里碰到过一个坑,对于使用exchange来做消息路由的,会有一个情况,就是在routing_key没被订阅的时候,会将该条找不到路由对应的queue的消息丢掉What happens if we break our contract and send a message with one or four words, like "orange" or "quick.orange.male.rabbit"? Well, these messages won't match any bindings and will be lost.对应链接,而当使用空的exchange时,会保留消息,当出现消费者的时候就可以将收到之前生产者所推送的消息对应链接,这里就是用了空的exchange。

集群搭建

集群搭建的时候有个erlang vm生成的random cookie,这个是用来做集群之间认证的,相同的cookie才能连接,但是如果通过vim打开复制后在其他几点新建文件写入会多一个换行,导致集群建立是报错,所以这里最好使用scp等传输命令直接传输cookie文件,同时要注意下cookie的文件权限。
另外在集群搭建的时候如果更改过hostname,那么要把rabbitmq的数据库删除,否则启动后会马上挂掉

\ No newline at end of file diff --git a/2019/06/18/openresty/index.html b/2019/06/18/openresty/index.html index 5a0fdfe94d..3d0db3a2dc 100644 --- a/2019/06/18/openresty/index.html +++ b/2019/06/18/openresty/index.html @@ -1,4 +1,4 @@ -openresty | Nicksxs's Blog

Nicksxs's Blog

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

0%

openresty

目前公司要对一些新的产品功能做灰度测试,因为在后端业务代码层面添加判断比较麻烦,所以想在nginx上做点手脚,就想到了openresty
前后也踩了不少坑,这边先写一点

首先是日志
error_log logs/error.log debug;
需要nginx开启日志的debug才能看到日志

使用 lua_code_cache off即可, 另外注意只有使用 content_by_lua_file 才会生效

http {
+openresty | Nicksxs's Blog

Nicksxs's Blog

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

0%

openresty

目前公司要对一些新的产品功能做灰度测试,因为在后端业务代码层面添加判断比较麻烦,所以想在nginx上做点手脚,就想到了openresty
前后也踩了不少坑,这边先写一点

首先是日志
error_log logs/error.log debug;
需要nginx开启日志的debug才能看到日志

使用 lua_code_cache off即可, 另外注意只有使用 content_by_lua_file 才会生效

http {
   lua_code_cache off;
 }
 
@@ -20,4 +20,4 @@ location ~*local t = json.decode(str)
 if t then
     ngx.say(" --> ", type(t))
-end

cjson.safe包会在解析失败的时候返回nil

  • 还有一个是redis链接时如果host使用的是域名的话会提示“failed to connect: no resolver defined to resolve “redis.xxxxxx.com””,这里需要使用nginx的resolver指令,
    resolver 8.8.8.8 valid=3600s;

  • 还有一点补充下
    就是业务在使用redis的时候使用了db的特性,所以在lua访问redis的时候也需要执行db,这里lua的redis库也支持了这个特性,可以使用instance:select(config:get(‘db’))来切换db

  • 性能优化tips
    建议是尽量少使用阶段钩子,例如content_by_lua_file,*_by_lua

  • 发现一个不错的openresty站点
    地址

  • \ No newline at end of file +end

    cjson.safe包会在解析失败的时候返回nil

  • 还有一个是redis链接时如果host使用的是域名的话会提示“failed to connect: no resolver defined to resolve “redis.xxxxxx.com””,这里需要使用nginx的resolver指令,
    resolver 8.8.8.8 valid=3600s;

  • 还有一点补充下
    就是业务在使用redis的时候使用了db的特性,所以在lua访问redis的时候也需要执行db,这里lua的redis库也支持了这个特性,可以使用instance:select(config:get(‘db’))来切换db

  • 性能优化tips
    建议是尽量少使用阶段钩子,例如content_by_lua_file,*_by_lua

  • 发现一个不错的openresty站点
    地址

  • \ No newline at end of file diff --git a/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/index.html b/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/index.html index e08f1cbcec..908f3d964c 100644 --- a/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/index.html +++ b/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/index.html @@ -1,4 +1,4 @@ -Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析

    题目介绍

    给定一个二叉树,找出其最大深度。

    二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

    说明: 叶子节点是指没有子节点的节点。

    示例:
    给定二叉树 [3,9,20,null,null,15,7],

      3
    +Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析

    题目介绍

    给定一个二叉树,找出其最大深度。

    二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

    说明: 叶子节点是指没有子节点的节点。

    示例:
    给定二叉树 [3,9,20,null,null,15,7],

      3
      / \
     9  20
       /  \
    @@ -20,4 +20,4 @@
         }
         // 前面返回后,左右取大者
         return Math.max(left + 1, right + 1);
    -}

    分析

    其实对于树这类题,一般是以递归形式比较方便,只是要注意退出条件

    \ No newline at end of file +}

    分析

    其实对于树这类题,一般是以递归形式比较方便,只是要注意退出条件

    \ No newline at end of file diff --git a/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/index.html b/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/index.html index d50c98892e..0f2abd1cec 100644 --- a/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/index.html +++ b/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/index.html @@ -1,4 +1,4 @@ -Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析

    题目介绍

    Given preorder and inorder traversal of a tree, construct the binary tree.
    给定一棵树的前序和中序遍历,构造出一棵二叉树

    注意

    You may assume that duplicates do not exist in the tree.
    你可以假设树中没有重复的元素。(PS: 不然就没法做了呀)

    例子:

    preorder = [3,9,20,15,7]
    +Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析

    题目介绍

    Given preorder and inorder traversal of a tree, construct the binary tree.
    给定一棵树的前序和中序遍历,构造出一棵二叉树

    注意

    You may assume that duplicates do not exist in the tree.
    你可以假设树中没有重复的元素。(PS: 不然就没法做了呀)

    例子:

    preorder = [3,9,20,15,7]
     inorder = [9,3,15,20,7]

    返回的二叉树

      3
      / \
     9  20
    @@ -32,4 +32,4 @@ inorder = [9,3,15,20,7]
    \ No newline at end of file +}
    \ No newline at end of file diff --git a/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/index.html b/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/index.html index 9267c2954d..ad2a0d8f14 100644 --- a/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/index.html +++ b/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/index.html @@ -1,4 +1,4 @@ -Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析

    题目介绍

    A path in a binary tree is a sequence of nodes where each pair of adjacent nodes in the sequence has an edge connecting them. A node can only appear in the sequence at most once. Note that the path does not need to pass through the root.

    The path sum of a path is the sum of the node’s values in the path.

    Given the root of a binary tree, return the maximum path sum of any path.

    路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。该路径 至少包含一个 节点,且不一定经过根节点。

    路径和 是路径中各节点值的总和。

    给你一个二叉树的根节点 root ,返回其 最大路径和

    简要分析

    其实这个题目会被误解成比较简单,左子树最大的,或者右子树最大的,或者两边加一下,仔细想想都不对,其实有可能是产生于左子树中,或者右子树中,这两个都是指跟左子树根还有右子树根没关系的,这么说感觉不太容易理解,画个图

    可以看到图里,其实最长路径和是左边这个子树组成的,跟根节点还有右子树完全没关系,然后再想一种情况,如果是整棵树就是图中的左子树,那么这个最长路径和就是左子树加右子树加根节点了,所以不是我一开始想得那么简单,在代码实现中也需要一些技巧

    代码

    int ansNew = Integer.MIN_VALUE;
    +Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析

    题目介绍

    A path in a binary tree is a sequence of nodes where each pair of adjacent nodes in the sequence has an edge connecting them. A node can only appear in the sequence at most once. Note that the path does not need to pass through the root.

    The path sum of a path is the sum of the node’s values in the path.

    Given the root of a binary tree, return the maximum path sum of any path.

    路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。该路径 至少包含一个 节点,且不一定经过根节点。

    路径和 是路径中各节点值的总和。

    给你一个二叉树的根节点 root ,返回其 最大路径和

    简要分析

    其实这个题目会被误解成比较简单,左子树最大的,或者右子树最大的,或者两边加一下,仔细想想都不对,其实有可能是产生于左子树中,或者右子树中,这两个都是指跟左子树根还有右子树根没关系的,这么说感觉不太容易理解,画个图

    可以看到图里,其实最长路径和是左边这个子树组成的,跟根节点还有右子树完全没关系,然后再想一种情况,如果是整棵树就是图中的左子树,那么这个最长路径和就是左子树加右子树加根节点了,所以不是我一开始想得那么简单,在代码实现中也需要一些技巧

    代码

    int ansNew = Integer.MIN_VALUE;
     public int maxPathSum(TreeNode root) {
             maxSumNew(root);
             return ansNew;
    @@ -21,4 +21,4 @@
         int res = Math.max(left + right + root.val, currentSum);
         ans = Math.max(res, ans);
         return currentSum;
    -}

    这里非常重要的就是 ansNew 是最后的一个结果,而对于 maxSumNew 这个函数的返回值其实是需要包含了一个连续结果,因为要返回继续去算路径和,所以返回的是 currentSum,最终结果是 ansNew

    结果图

    难得有个 100%,贴个图哈哈

    \ No newline at end of file +}

    这里非常重要的就是 ansNew 是最后的一个结果,而对于 maxSumNew 这个函数的返回值其实是需要包含了一个连续结果,因为要返回继续去算路径和,所以返回的是 currentSum,最终结果是 ansNew

    结果图

    难得有个 100%,贴个图哈哈

    \ No newline at end of file diff --git a/2021/07/18/2021-年中总结/index.html b/2021/07/18/2021-年中总结/index.html index 09f1879e68..1ec3755e74 100644 --- a/2021/07/18/2021-年中总结/index.html +++ b/2021/07/18/2021-年中总结/index.html @@ -1 +1 @@ -2021 年中总结 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    2021 年中总结

    又到半年总结时,第一次写总结类型的文章感觉挺好写的,但是后面总觉得这过去的一段时间所做的事情,能力上的成长低于预期,但是是需要总结下,找找问题,顺便展望下未来。

    这一年做的最让自己满意的应该就是看了一些书,由折腾群洋总发起的读书打卡活动,到目前为止已经读完了这几本书,《cUrl 必知必会》,《古董局中局 1》,《古董局中局 2》,《算法图解》,《每天 5 分钟玩转 Kubernetes》《幸福了吗?》《高可用可伸缩微服务架构:基于 Dubbo、Spring Cloud和 Service Mesh》《Rust 权威指南》后面可以写个专题说说看的这些书,虽然每天打卡如果时间安排不好,并且看的书像 rust 这样比较难的话还是会有点小焦虑,不过也是个调整过程,一方面可以在白天就抽空看一会,然后也不必要每次都看很大一章,注重吸收。

    技术上的成长的话,有一些比较小的长进吧,对于一些之前忽视的 synchronized,ThreadLocal 和 AQS 等知识点做了下查漏补缺了,然后多了解了一些 Java 垃圾回收的内容,但是在实操上还是比较欠缺,成型的技术方案,架构上所谓的优化也比较少,一些想法也还有考虑不周全的地方,还需要多花时间和心思去学习加强,特别是在目前已经有的基础上如何做系统深层次的优化,既不要是鸡毛蒜皮的,也不能出现一些不可接受的问题和故障,这是个很重要的课题,需要好好学习,后面考虑定一些周期性目标,两个月左右能有一些成果和总结。

    另外一部分是自己的服务,因为 ucloud 的机器太贵就没续费了,所以都迁移到腾讯云的小机器上了,顺便折腾了一点点 traefik,但是还很不熟练,不太习惯这一套,一方面是 docker 还不习惯,这也加重了对这套环境的不适应,还是习惯裸机部署,另一方面就是 k8s 了,家里的机器还没虚拟化,没有很好的条件可以做实验,这也是读书打卡的一个没做好的点,整体的学习效果受限于深度和实操,后面是看都是用 traefik,也找到了一篇文章可以 traefik 转发到裸机应用,因为主仓库用的是裸机的 gogs。

    还有就是运动减肥上,唉,这又是很大的一个痛点,基本没效果,只是还算稳定,昨天看到一个视频说还需要力量训练来增肌,以此可以提升基础代谢,打算往这个方向尝试下,因为今天没有疫情限制了,在 6 月底完成了 200 公里的跑步小目标,只是有些膝盖跟大腿根外侧不适,抽空得去看下医生,后面打算每天也能做点卷腹跟俯卧撑。

    下半年还希望能继续多看看书,比很多网上各种乱七八糟的文章会好很多,结合豆瓣评分,找一些评价高一些的文章,但也不是说分稍低点的就不行,有些也看人是不是适合,一般 6 分以上评价比较多的就可以试试。

    \ No newline at end of file +2021 年中总结 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    2021 年中总结

    又到半年总结时,第一次写总结类型的文章感觉挺好写的,但是后面总觉得这过去的一段时间所做的事情,能力上的成长低于预期,但是是需要总结下,找找问题,顺便展望下未来。

    这一年做的最让自己满意的应该就是看了一些书,由折腾群洋总发起的读书打卡活动,到目前为止已经读完了这几本书,《cUrl 必知必会》,《古董局中局 1》,《古董局中局 2》,《算法图解》,《每天 5 分钟玩转 Kubernetes》《幸福了吗?》《高可用可伸缩微服务架构:基于 Dubbo、Spring Cloud和 Service Mesh》《Rust 权威指南》后面可以写个专题说说看的这些书,虽然每天打卡如果时间安排不好,并且看的书像 rust 这样比较难的话还是会有点小焦虑,不过也是个调整过程,一方面可以在白天就抽空看一会,然后也不必要每次都看很大一章,注重吸收。

    技术上的成长的话,有一些比较小的长进吧,对于一些之前忽视的 synchronized,ThreadLocal 和 AQS 等知识点做了下查漏补缺了,然后多了解了一些 Java 垃圾回收的内容,但是在实操上还是比较欠缺,成型的技术方案,架构上所谓的优化也比较少,一些想法也还有考虑不周全的地方,还需要多花时间和心思去学习加强,特别是在目前已经有的基础上如何做系统深层次的优化,既不要是鸡毛蒜皮的,也不能出现一些不可接受的问题和故障,这是个很重要的课题,需要好好学习,后面考虑定一些周期性目标,两个月左右能有一些成果和总结。

    另外一部分是自己的服务,因为 ucloud 的机器太贵就没续费了,所以都迁移到腾讯云的小机器上了,顺便折腾了一点点 traefik,但是还很不熟练,不太习惯这一套,一方面是 docker 还不习惯,这也加重了对这套环境的不适应,还是习惯裸机部署,另一方面就是 k8s 了,家里的机器还没虚拟化,没有很好的条件可以做实验,这也是读书打卡的一个没做好的点,整体的学习效果受限于深度和实操,后面是看都是用 traefik,也找到了一篇文章可以 traefik 转发到裸机应用,因为主仓库用的是裸机的 gogs。

    还有就是运动减肥上,唉,这又是很大的一个痛点,基本没效果,只是还算稳定,昨天看到一个视频说还需要力量训练来增肌,以此可以提升基础代谢,打算往这个方向尝试下,因为今天没有疫情限制了,在 6 月底完成了 200 公里的跑步小目标,只是有些膝盖跟大腿根外侧不适,抽空得去看下医生,后面打算每天也能做点卷腹跟俯卧撑。

    下半年还希望能继续多看看书,比很多网上各种乱七八糟的文章会好很多,结合豆瓣评分,找一些评价高一些的文章,但也不是说分稍低点的就不行,有些也看人是不是适合,一般 6 分以上评价比较多的就可以试试。

    \ No newline at end of file diff --git a/2021/07/25/redis过期策略复习/index.html b/2021/07/25/redis过期策略复习/index.html index 64bc77d305..bad98f6dc6 100644 --- a/2021/07/25/redis过期策略复习/index.html +++ b/2021/07/25/redis过期策略复习/index.html @@ -1,16 +1,16 @@ -redis过期策略复习 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    redis过期策略复习

    redis过期策略复习

    之前其实写过redis的过期的一些原理,这次主要是记录下,一些使用上的概念,主要是redis使用的过期策略是懒过期和定时清除,懒过期的其实比较简单,即是在key被访问的时候会顺带着判断下这个key是否已过期了,如果已经过期了,就不返回了,但是这种策略有个漏洞是如果有些key之后一直不会被访问了,就等于沉在池底了,所以需要有一个定时的清理机制,去从设置了过期的key池子(expires)里随机地捞key,具体的策略我们看下官网的解释

    1. Test 20 random keys from the set of keys with an associated expire.
    2. Delete all the keys found expired.
    3. If more than 25% of keys were expired, start again from step 1.

    从池子里随机获取20个key,将其中过期的key删掉,如果这其中有超过25%的key已经过期了,那就再来一次,以此保持过期的key不超过25%(左右),并且这个定时策略可以在redis的配置文件

    # Redis calls an internal function to perform many background tasks, like
    -# closing connections of clients in timeout, purging expired keys that are
    -# never requested, and so forth.
    -#
    -# Not all tasks are performed with the same frequency, but Redis checks for
    -# tasks to perform according to the specified "hz" value.
    -#
    -# By default "hz" is set to 10. Raising the value will use more CPU when
    -# Redis is idle, but at the same time will make Redis more responsive when
    -# there are many keys expiring at the same time, and timeouts may be
    -# handled with more precision.
    -#
    -# The range is between 1 and 500, however a value over 100 is usually not
    -# a good idea. Most users should use the default of 10 and raise this up to
    -# 100 only in environments where very low latency is required.
    -hz 10

    可以配置这个hz的值,代表的含义是每秒的执行次数,默认是10,其实也用了hz的普遍含义。有兴趣可以看看之前写的一篇文章redis系列介绍七-过期策略

    \ No newline at end of file +redis过期策略复习 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    redis过期策略复习

    redis过期策略复习

    之前其实写过redis的过期的一些原理,这次主要是记录下,一些使用上的概念,主要是redis使用的过期策略是懒过期和定时清除,懒过期的其实比较简单,即是在key被访问的时候会顺带着判断下这个key是否已过期了,如果已经过期了,就不返回了,但是这种策略有个漏洞是如果有些key之后一直不会被访问了,就等于沉在池底了,所以需要有一个定时的清理机制,去从设置了过期的key池子(expires)里随机地捞key,具体的策略我们看下官网的解释

    1. Test 20 random keys from the set of keys with an associated expire.
    2. Delete all the keys found expired.
    3. If more than 25% of keys were expired, start again from step 1.

    从池子里随机获取20个key,将其中过期的key删掉,如果这其中有超过25%的key已经过期了,那就再来一次,以此保持过期的key不超过25%(左右),并且这个定时策略可以在redis的配置文件

    # Redis calls an internal function to perform many background tasks, like
    +# closing connections of clients in timeout, purging expired keys that are
    +# never requested, and so forth.
    +#
    +# Not all tasks are performed with the same frequency, but Redis checks for
    +# tasks to perform according to the specified "hz" value.
    +#
    +# By default "hz" is set to 10. Raising the value will use more CPU when
    +# Redis is idle, but at the same time will make Redis more responsive when
    +# there are many keys expiring at the same time, and timeouts may be
    +# handled with more precision.
    +#
    +# The range is between 1 and 500, however a value over 100 is usually not
    +# a good idea. Most users should use the default of 10 and raise this up to
    +# 100 only in environments where very low latency is required.
    +hz 10

    可以配置这个hz的值,代表的含义是每秒的执行次数,默认是10,其实也用了hz的普遍含义。有兴趣可以看看之前写的一篇文章redis系列介绍七-过期策略

    \ No newline at end of file diff --git a/2022/05/22/闲话篇-也算碰到了为老不尊和坏人变老了的典型案例/index.html b/2022/05/22/闲话篇-也算碰到了为老不尊和坏人变老了的典型案例/index.html index 51a3374f8a..dde2c26469 100644 --- a/2022/05/22/闲话篇-也算碰到了为老不尊和坏人变老了的典型案例/index.html +++ b/2022/05/22/闲话篇-也算碰到了为老不尊和坏人变老了的典型案例/index.html @@ -1 +1 @@ -闲话篇-也算碰到了为老不尊和坏人变老了的典型案例 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    闲话篇-也算碰到了为老不尊和坏人变老了的典型案例

    在目前的房子也差不多租了四五年了,楼下邻居换了两拨了,我们这栋楼装修了不知道多少次,因为是学区的原因,房子交易的频率还是比较高的,不过比较神奇的我们对门的没换过,而且一直也没什么交集(除了后面说的水管爆裂),就进出的时候偶尔看到应该是住着一对老夫妻,感觉年纪也有个七八十了。

    对对面这户人家的印象,就是对面的老头子经常是我出门上班去了他回来,看着他颤颤巍巍地走楼梯,我看到了都靠边走,而且有几次还听见好像是他儿子在说他,”年假这么大了,还是少出去吧”,说实话除了这次的事情,之前就有一次水管阀门爆裂了,算是有点交集,那次大概是去年冬天,天气已经很冷了,我们周日下午回来看到楼梯有点湿,但是没什么特别的异常就没怎么注意,到晚上洗完澡,楼下的邻居就来敲门,说我们门外的水表那一直在流水,出门一看真的是懵了,外面水表那在哗哗哗地流水,导致楼梯那就跟水帘洞一样,仔细看看是对面家的水表阀门那在漏水,我只能先用塑料袋包一下,然后大冬天(刚洗完澡)穿着凉拖跑下去找物业保安,走到一楼的时候发现水一直流到一楼了,楼梯上都是水流下来,五楼那是最惨的,感觉门框周边都浸透了,五楼的也是态度比较差的让我一定要把水弄好了,但是前面也说了谁是从对门那户的水表阀那出来的,理论上应该让对面的处理,结果我敲门敲了半天对面都没反应,想着我放着不管也不太好,就去找了物业保安,保安上来看了只能先把总阀关了,我也打电话给维修自来水管的,自来水公司的人过了会也是真的来修了,我那会是挺怕不来修,自来水公司的师傅到了以后拿开一看是对面那户的有个阀门估计是自己换上去的,跟我们这的完全不一样,看上去就比较劣质,师傅也挺气的,大晚上被叫过来,我又尝试着去敲门也还是没人应,也没办法,对面老人家我敲太响到时候出来说我吓到他们啥的,第二天去说也没现场了。

    前面的这件事是个重要铺垫,前几天 LD 下班后把厨余垃圾套好袋子放在门口,打算等我下班了因为要去做核酸(hz 48 小时核酸)顺便带下去,结果到了七点多,说对面的老太太在那疯狂砸门了,LD 被吓到了不敢开门,老太太在外面一边砸门一边骂,“你们年轻人怎么素质这么差”(他们家也经常在门口放垃圾,我们刚来住的时候在楼梯转角他们就放这个废弃的洗衣机,每次走楼梯带点东西都要小心翼翼地走,不然都过不去,然后我赶紧赶回去,结果她听到我回家了,还特意开门继续骂,“你们年轻人怎么素质这么差,垃圾放在这里”,我说我们刚才放在这,打算待会做核酸的时候去扔掉,结果他们家老头,都已经没了牙齿,在那瞪大眼睛说,“你们早上就放在这了的,”我说是LD 刚才下班了放的,争论了一会,我说这个事情我们门口放了垃圾,这会我就去扔掉了,但是你们家老太太这么砸门总不太好,像之前门口水管爆掉了,我敲了门没人应,我也没要砸门一定把你们叫醒,结果老头老太说我们的水管从来没换过,不可能破的(其实到这,再往后说就没意思了,跟这么不要脸的人说多了也只是瞎扯),一会又回到这个垃圾的问题,那个老头说“你们昨天就放在这里了的”,睁着眼说瞎话可真是 666,感觉不是老太太拦着点他马上就要冲上来揍我了一样,事后我想想,这种情况我大概只能躺地上装死了,当这个事情发生之前我真的快把前面说的事情(水管阀坏了)给忘了,虽然这是理论上不该我来处理,除非是老头老太太请求我帮忙,这事后面我也从没说起过,本来完全没交集,对他们的是怎么样的人也没概念,总觉得年纪大了可能还比较心宽和蔼点,结果没想到就是一典型的坏人变老了,我说你们这么砸门,我老婆都被吓得不敢开门,结果对面老头老太太的儿子也出来了说,“我们就是敲下门,我母亲是机关单位退休的,所以肯定不会敲门很大声的,你老婆觉得吓到了是你们人生观价值观有问题”,听到这话我差点笑出来,连着两个可笑至极的脑残逻辑,无语他妈给无语开门,无语到家了。对门家我们之前有个印象就是因为我们都是顶楼,这边老小区以前都是把前后阳台包进来的,然后社区就来咨询大家的意见是不是统一把包进来的违建拆掉,还特地上来六楼跟他们说,结果对面的老头就说,“我要去住建局投诉你们”,本来这个事情是违法的,但是社区的意思也是征求各位业主的意见,结果感觉是社区上门强拆了一样,为老不尊,坏人变老了的典范了。

    \ No newline at end of file +闲话篇-也算碰到了为老不尊和坏人变老了的典型案例 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    闲话篇-也算碰到了为老不尊和坏人变老了的典型案例

    在目前的房子也差不多租了四五年了,楼下邻居换了两拨了,我们这栋楼装修了不知道多少次,因为是学区的原因,房子交易的频率还是比较高的,不过比较神奇的我们对门的没换过,而且一直也没什么交集(除了后面说的水管爆裂),就进出的时候偶尔看到应该是住着一对老夫妻,感觉年纪也有个七八十了。

    对对面这户人家的印象,就是对面的老头子经常是我出门上班去了他回来,看着他颤颤巍巍地走楼梯,我看到了都靠边走,而且有几次还听见好像是他儿子在说他,”年假这么大了,还是少出去吧”,说实话除了这次的事情,之前就有一次水管阀门爆裂了,算是有点交集,那次大概是去年冬天,天气已经很冷了,我们周日下午回来看到楼梯有点湿,但是没什么特别的异常就没怎么注意,到晚上洗完澡,楼下的邻居就来敲门,说我们门外的水表那一直在流水,出门一看真的是懵了,外面水表那在哗哗哗地流水,导致楼梯那就跟水帘洞一样,仔细看看是对面家的水表阀门那在漏水,我只能先用塑料袋包一下,然后大冬天(刚洗完澡)穿着凉拖跑下去找物业保安,走到一楼的时候发现水一直流到一楼了,楼梯上都是水流下来,五楼那是最惨的,感觉门框周边都浸透了,五楼的也是态度比较差的让我一定要把水弄好了,但是前面也说了谁是从对门那户的水表阀那出来的,理论上应该让对面的处理,结果我敲门敲了半天对面都没反应,想着我放着不管也不太好,就去找了物业保安,保安上来看了只能先把总阀关了,我也打电话给维修自来水管的,自来水公司的人过了会也是真的来修了,我那会是挺怕不来修,自来水公司的师傅到了以后拿开一看是对面那户的有个阀门估计是自己换上去的,跟我们这的完全不一样,看上去就比较劣质,师傅也挺气的,大晚上被叫过来,我又尝试着去敲门也还是没人应,也没办法,对面老人家我敲太响到时候出来说我吓到他们啥的,第二天去说也没现场了。

    前面的这件事是个重要铺垫,前几天 LD 下班后把厨余垃圾套好袋子放在门口,打算等我下班了因为要去做核酸(hz 48 小时核酸)顺便带下去,结果到了七点多,说对面的老太太在那疯狂砸门了,LD 被吓到了不敢开门,老太太在外面一边砸门一边骂,“你们年轻人怎么素质这么差”(他们家也经常在门口放垃圾,我们刚来住的时候在楼梯转角他们就放这个废弃的洗衣机,每次走楼梯带点东西都要小心翼翼地走,不然都过不去,然后我赶紧赶回去,结果她听到我回家了,还特意开门继续骂,“你们年轻人怎么素质这么差,垃圾放在这里”,我说我们刚才放在这,打算待会做核酸的时候去扔掉,结果他们家老头,都已经没了牙齿,在那瞪大眼睛说,“你们早上就放在这了的,”我说是LD 刚才下班了放的,争论了一会,我说这个事情我们门口放了垃圾,这会我就去扔掉了,但是你们家老太太这么砸门总不太好,像之前门口水管爆掉了,我敲了门没人应,我也没要砸门一定把你们叫醒,结果老头老太说我们的水管从来没换过,不可能破的(其实到这,再往后说就没意思了,跟这么不要脸的人说多了也只是瞎扯),一会又回到这个垃圾的问题,那个老头说“你们昨天就放在这里了的”,睁着眼说瞎话可真是 666,感觉不是老太太拦着点他马上就要冲上来揍我了一样,事后我想想,这种情况我大概只能躺地上装死了,当这个事情发生之前我真的快把前面说的事情(水管阀坏了)给忘了,虽然这是理论上不该我来处理,除非是老头老太太请求我帮忙,这事后面我也从没说起过,本来完全没交集,对他们的是怎么样的人也没概念,总觉得年纪大了可能还比较心宽和蔼点,结果没想到就是一典型的坏人变老了,我说你们这么砸门,我老婆都被吓得不敢开门,结果对面老头老太太的儿子也出来了说,“我们就是敲下门,我母亲是机关单位退休的,所以肯定不会敲门很大声的,你老婆觉得吓到了是你们人生观价值观有问题”,听到这话我差点笑出来,连着两个可笑至极的脑残逻辑,无语他妈给无语开门,无语到家了。对门家我们之前有个印象就是因为我们都是顶楼,这边老小区以前都是把前后阳台包进来的,然后社区就来咨询大家的意见是不是统一把包进来的违建拆掉,还特地上来六楼跟他们说,结果对面的老头就说,“我要去住建局投诉你们”,本来这个事情是违法的,但是社区的意思也是征求各位业主的意见,结果感觉是社区上门强拆了一样,为老不尊,坏人变老了的典范了。

    \ No newline at end of file diff --git a/atom.xml b/atom.xml index f41cff660b..52ebebb41e 100644 --- a/atom.xml +++ b/atom.xml @@ -50,7 +50,13 @@ 2022-05-22T12:43:50.000Z - 为老不尊,坏人变老了的典范了 + + + + + <p>在目前的房子也差不多租了四五年了,楼下邻居换了两拨了,我们这栋楼装修了不知道多少次,因为是学区的原因,房子交易的频率还是比较高的,不过比较神奇的我们对门的没换过,而且一直也没什么交集(除了后面说的水管爆裂),就进出的时候偶尔看到应该是住着一对老夫妻,感觉年纪也有个七八十了。 + + diff --git a/index.html b/index.html index 6b0ca4cb54..db7c0de1c0 100644 --- a/index.html +++ b/index.html @@ -19,7 +19,7 @@ server.2=192.168< server.3=192.168.2.3:2888:3888 server.4=192.168.2.4:2888:3888 server.5=192.168.2.5:2888:3888 -server.6=192.168.2.6:2888:3888

    然后为了集群完全更新,就继续在 zk4zk5 加上其他节点,这样我的 6 节点集群就起来了

    下节点

    这里我踩了另外一个坑,或者说没搞清楚两种方式的差别,

    第一种

    首先说说我没采用的第一种方式,(也是比较合理的)其实上面这个集群有个明显的问题,老集群其实还是各自认了一个三节点的集群,其中 zk3 是主节点,对于 zk1,zk2,zk3 来说它们能看到的就只有这三个节点,对于后三个 zk4,zk5,zk6 节点来说他们能连上其余五个节点,可以认为这是个六节点的集群,那么比较合理的操作应该是在老的三节点上把后面三个也都加进来,即每个节点的配置里 server 都有 6 个,然后我再对老的节点进行下线,这里下线需要注意的比较理想的是下一个节点就要修改配置,挪掉下线的节点后进行一遍重启,比如我知道了集群中的 leader 是在 zk3 上面,那么我先将 zk1 和 zk2 下掉,那么在我将 zk1 下线的之后,我将其他的五个节点都删除 zk1 的配置,然后重启,这样其实不是必须,但相对会可靠些,理论上我也可以在下掉 zk1 和 zk2 之后再修改配置重启其余节点。而当只剩下 zk3,zk4,zk5,zk6 四个节点的集群后,并且每个节点里的配置也只有这四个 server,我再下线 zk3 这个 leader 的时候,就会进行选举,再选出新的 leader,因为刚好是三节点,同样保证了最小可用。

    第二种

    这也是我踩坑的一种方式,就是我没有修改原来三节点的配置,并且我一开始以为可以通过下线 zk1,zk2,zk3(进行选举)的方式完成下线,然后再进行重启,但是这种方式就是我上面说的,原来的三节点里我下掉 zk1 还是能够正常运行,但是我下线 zk2 的时候,这个集群就等于是挂了,小于最小可用了,这样三节点都挂了,而且对于新加入的三个节点来说,又回到了最初起不来一样状态,六节点里只有三节点在线,导致整个集群都挂了,所以对于我这样的操作来说,我需要滚动修改启动,在下线 zk1 的时候就需要把 zk4,zk5,zk6 中的 zk1 移除后重启,当然这样唯一的好处就是可以少重启几个,同样继续下线 zk2 的时候,把 zk2 移除掉再重启,其实在移除 zk1 后修改重启后,在下线 zk2 的时候,集群就会重新选举了,因为 zk2 下线的时候,zk3 还是会一起下线。这个是我们需要特别注意的

    我们日常在代码里处理一些集合逻辑的时候用到 Stream 其实还挺多的,普通的取值过滤集合一般都是结合 ide 的提示就能搞定了,但是有些不太常用的就在这记录下,争取后面都更新记录下来。

    自定义 distinctByKey 对结果进行去重

    stream 中自带的 distinct 只能对元素进行去重
    比如下面代码

    public static void main(String[] args) {
    +server.6=192.168.2.6:2888:3888

    然后为了集群完全更新,就继续在 zk4zk5 加上其他节点,这样我的 6 节点集群就起来了

    下节点

    这里我踩了另外一个坑,或者说没搞清楚两种方式的差别,

    第一种

    首先说说我没采用的第一种方式,(也是比较合理的)其实上面这个集群有个明显的问题,老集群其实还是各自认了一个三节点的集群,其中 zk3 是主节点,对于 zk1,zk2,zk3 来说它们能看到的就只有这三个节点,对于后三个 zk4,zk5,zk6 节点来说他们能连上其余五个节点,可以认为这是个六节点的集群,那么比较合理的操作应该是在老的三节点上把后面三个也都加进来,即每个节点的配置里 server 都有 6 个,然后我再对老的节点进行下线,这里下线需要注意的比较理想的是下一个节点就要修改配置,挪掉下线的节点后进行一遍重启,比如我知道了集群中的 leader 是在 zk3 上面,那么我先将 zk1 和 zk2 下掉,那么在我将 zk1 下线的之后,我将其他的五个节点都删除 zk1 的配置,然后重启,这样其实不是必须,但相对会可靠些,理论上我也可以在下掉 zk1 和 zk2 之后再修改配置重启其余节点。而当只剩下 zk3,zk4,zk5,zk6 四个节点的集群后,并且每个节点里的配置也只有这四个 server,我再下线 zk3 这个 leader 的时候,就会进行选举,再选出新的 leader,因为刚好是三节点,同样保证了最小可用。

    第二种

    这也是我踩坑的一种方式,就是我没有修改原来三节点的配置,并且我一开始以为可以通过下线 zk1,zk2,zk3(进行选举)的方式完成下线,然后再进行重启,但是这种方式就是我上面说的,原来的三节点里我下掉 zk1 还是能够正常运行,但是我下线 zk2 的时候,这个集群就等于是挂了,小于最小可用了,这样三节点都挂了,而且对于新加入的三个节点来说,又回到了最初起不来一样状态,六节点里只有三节点在线,导致整个集群都挂了,所以对于我这样的操作来说,我需要滚动修改启动,在下线 zk1 的时候就需要把 zk4,zk5,zk6 中的 zk1 移除后重启,当然这样唯一的好处就是可以少重启几个,同样继续下线 zk2 的时候,把 zk2 移除掉再重启,其实在移除 zk1 后修改重启后,在下线 zk2 的时候,集群就会重新选举了,因为 zk2 下线的时候,zk3 还是会一起下线。这个是我们需要特别注意的

    在目前的房子也差不多租了四五年了,楼下邻居换了两拨了,我们这栋楼装修了不知道多少次,因为是学区的原因,房子交易的频率还是比较高的,不过比较神奇的我们对门的没换过,而且一直也没什么交集(除了后面说的水管爆裂),就进出的时候偶尔看到应该是住着一对老夫妻,感觉年纪也有个七八十了。

    对对面这户人家的印象,就是对面的老头子经常是我出门上班去了他回来,看着他颤颤巍巍地走楼梯,我看到了都靠边走,而且有几次还听见好像是他儿子在说他,”年假这么大了,还是少出去吧”,说实话除了这次的事情,之前就有一次水管阀门爆裂了,算是有点交集,那次大概是去年冬天,天气已经很冷了,我们周日下午回来看到楼梯有点湿,但是没什么特别的异常就没怎么注意,到晚上洗完澡,楼下的邻居就来敲门,说我们门外的水表那一直在流水,出门一看真的是懵了,外面水表那在哗哗哗地流水,导致楼梯那就跟水帘洞一样,仔细看看是对面家的水表阀门那在漏水,我只能先用塑料袋包一下,然后大冬天(刚洗完澡)穿着凉拖跑下去找物业保安,走到一楼的时候发现水一直流到一楼了,楼梯上都是水流下来,五楼那是最惨的,感觉门框周边都浸透了,五楼的也是态度比较差的让我一定要把水弄好了,但是前面也说了谁是从对门那户的水表阀那出来的,理论上应该让对面的处理,结果我敲门敲了半天对面都没反应,想着我放着不管也不太好,就去找了物业保安,保安上来看了只能先把总阀关了,我也打电话给维修自来水管的,自来水公司的人过了会也是真的来修了,我那会是挺怕不来修,自来水公司的师傅到了以后拿开一看是对面那户的有个阀门估计是自己换上去的,跟我们这的完全不一样,看上去就比较劣质,师傅也挺气的,大晚上被叫过来,我又尝试着去敲门也还是没人应,也没办法,对面老人家我敲太响到时候出来说我吓到他们啥的,第二天去说也没现场了。

    前面的这件事是个重要铺垫,前几天 LD 下班后把厨余垃圾套好袋子放在门口,打算等我下班了因为要去做核酸(hz 48 小时核酸)顺便带下去,结果到了七点多,说对面的老太太在那疯狂砸门了,LD 被吓到了不敢开门,老太太在外面一边砸门一边骂,“你们年轻人怎么素质这么差”(他们家也经常在门口放垃圾,我们刚来住的时候在楼梯转角他们就放这个废弃的洗衣机,每次走楼梯带点东西都要小心翼翼地走,不然都过不去,然后我赶紧赶回去,结果她听到我回家了,还特意开门继续骂,“你们年轻人怎么素质这么差,垃圾放在这里”,我说我们刚才放在这,打算待会做核酸的时候去扔掉,结果他们家老头,都已经没了牙齿,在那瞪大眼睛说,“你们早上就放在这了的,”我说是LD 刚才下班了放的,争论了一会,我说这个事情我们门口放了垃圾,这会我就去扔掉了,但是你们家老太太这么砸门总不太好,像之前门口水管爆掉了,我敲了门没人应,我也没要砸门一定把你们叫醒,结果老头老太说我们的水管从来没换过,不可能破的(其实到这,再往后说就没意思了,跟这么不要脸的人说多了也只是瞎扯),一会又回到这个垃圾的问题,那个老头说“你们昨天就放在这里了的”,睁着眼说瞎话可真是 666,感觉不是老太太拦着点他马上就要冲上来揍我了一样,事后我想想,这种情况我大概只能躺地上装死了,当这个事情发生之前我真的快把前面说的事情(水管阀坏了)给忘了,虽然这是理论上不该我来处理,除非是老头老太太请求我帮忙,这事后面我也从没说起过,本来完全没交集,对他们的是怎么样的人也没概念,总觉得年纪大了可能还比较心宽和蔼点,结果没想到就是一典型的坏人变老了,我说你们这么砸门,我老婆都被吓得不敢开门,结果对面老头老太太的儿子也出来了说,“我们就是敲下门,我母亲是机关单位退休的,所以肯定不会敲门很大声的,你老婆觉得吓到了是你们人生观价值观有问题”,听到这话我差点笑出来,连着两个可笑至极的脑残逻辑,无语他妈给无语开门,无语到家了。对门家我们之前有个印象就是因为我们都是顶楼,这边老小区以前都是把前后阳台包进来的,然后社区就来咨询大家的意见是不是统一把包进来的违建拆掉,还特地上来六楼跟他们说,结果对面的老头就说,“我要去住建局投诉你们”,本来这个事情是违法的,但是社区的意思也是征求各位业主的意见,结果感觉是社区上门强拆了一样,为老不尊,坏人变老了的典范了。

    我们日常在代码里处理一些集合逻辑的时候用到 Stream 其实还挺多的,普通的取值过滤集合一般都是结合 ide 的提示就能搞定了,但是有些不太常用的就在这记录下,争取后面都更新记录下来。

    自定义 distinctByKey 对结果进行去重

    stream 中自带的 distinct 只能对元素进行去重
    比如下面代码

    public static void main(String[] args) {
             List<Integer> list = new ArrayList<>();
             list.add(1);
             list.add(1);
    diff --git a/leancloud.memo b/leancloud.memo
    index 3ffd89eefa..6372ecaaeb 100644
    --- a/leancloud.memo
    +++ b/leancloud.memo
    @@ -162,4 +162,5 @@
     {"title":"闲话篇-路遇神逻辑骑车带娃爹","url":"/2022/05/08/闲话篇-路遇神逻辑骑车带娃爹/"},
     {"title":"记录下 Java Stream 的一些高效操作","url":"/2022/05/15/记录下-Java-Lambda-的一些高效操作/"},
     {"title":"闲话篇-也算碰到了为老不尊和坏人变老了的典型案例","url":"/2022/05/22/闲话篇-也算碰到了为老不尊和坏人变老了的典型案例/"},
    +{"title":"记录下 zookeeper 集群迁移和易错点","url":"/2022/05/29/记录下-zookeeper-集群迁移/"},
     ]
    \ No newline at end of file
    diff --git a/leancloud_counter_security_urls.json b/leancloud_counter_security_urls.json
    index ba617bfc42..2c88e94fe9 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":"/2021/03/31/2020-年终总结/"},{"title":"2020年中总结","url":"/2020/07/11/2020年中总结/"},{"title":"2021 年终总结","url":"/2022/01/22/2021-年终总结/"},{"title":"2021 年中总结","url":"/2021/07/18/2021-年中总结/"},{"title":"AQS篇二 之 Condition 浅析笔记","url":"/2021/02/21/AQS-之-Condition-浅析笔记/"},{"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":"Disruptor 系列二","url":"/2022/02/27/Disruptor-系列二/"},{"title":"AQS篇一","url":"/2021/02/14/AQS篇一/"},{"title":"Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥?","url":"/2020/08/22/Filter-Intercepter-Aop-啥-啥-啥-这些都是啥/"},{"title":"Dubbo 使用的几个记忆点","url":"/2022/04/02/Dubbo-使用的几个记忆点/"},{"title":"JVM源码分析之G1垃圾收集器分析一","url":"/2019/12/07/JVM-G1-Part-1/"},{"title":"Disruptor 系列一","url":"/2022/02/13/Disruptor-系列一/"},{"title":"Leetcode 021 合并两个有序链表 ( Merge Two Sorted Lists ) 题解分析","url":"/2021/10/07/Leetcode-021-合并两个有序链表-Merge-Two-Sorted-Lists-题解分析/"},{"title":"Leetcode 028 实现 strStr() ( Implement strStr() ) 题解分析","url":"/2021/10/31/Leetcode-028-实现-strStr-Implement-strStr-题解分析/"},{"title":"Leetcode 053 最大子序和 ( Maximum Subarray ) 题解分析","url":"/2021/11/28/Leetcode-053-最大子序和-Maximum-Subarray-题解分析/"},{"title":"Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析","url":"/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/"},{"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 1115 交替打印 FooBar ( Print FooBar Alternately *Medium* ) 题解分析","url":"/2022/05/01/Leetcode-1115-交替打印-FooBar-Print-FooBar-Alternately-Medium-题解分析/"},{"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 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析","url":"/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/"},{"title":"Leetcode 155 最小栈(Min Stack) 题解分析","url":"/2020/12/06/Leetcode-155-最小栈-Min-Stack-题解分析/"},{"title":"Leetcode 160 相交链表(intersection-of-two-linked-lists) 题解分析","url":"/2021/01/10/Leetcode-160-相交链表-intersection-of-two-linked-lists-题解分析/"},{"title":"Leetcode 2 Add Two Numbers 题解分析","url":"/2020/10/11/Leetcode-2-Add-Two-Numbers-题解分析/"},{"title":"Leetcode 234 回文链表(Palindrome Linked List) 题解分析","url":"/2020/11/15/Leetcode-234-回文联表-Palindrome-Linked-List-题解分析/"},{"title":"Leetcode 236 二叉树的最近公共祖先(Lowest Common Ancestor of a Binary Tree) 题解分析","url":"/2021/05/23/Leetcode-236-二叉树的最近公共祖先-Lowest-Common-Ancestor-of-a-Binary-Tree-题解分析/"},{"title":"Leetcode 3 Longest Substring Without Repeating Characters 题解分析","url":"/2020/09/20/Leetcode-3-Longest-Substring-Without-Repeating-Characters-题解分析/"},{"title":"Leetcode 349 两个数组的交集 ( Intersection of Two Arrays *Easy* ) 题解分析","url":"/2022/03/07/Leetcode-349-两个数组的交集-Intersection-of-Two-Arrays-Easy-题解分析/"},{"title":"Leetcode 42 接雨水 (Trapping Rain Water) 题解分析","url":"/2021/07/04/Leetcode-42-接雨水-Trapping-Rain-Water-题解分析/"},{"title":"Leetcode 48 旋转图像(Rotate Image) 题解分析","url":"/2021/05/01/Leetcode-48-旋转图像-Rotate-Image-题解分析/"},{"title":"34_Search_for_a_Range","url":"/2016/08/14/34-Search-for-a-Range/"},{"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":"Leetcode 83 删除排序链表中的重复元素 ( Remove Duplicates from Sorted List *Easy* ) 题解分析","url":"/2022/03/13/Leetcode-83-删除排序链表中的重复元素-Remove-Duplicates-from-Sorted-List-Easy-题解分析/"},{"title":"Redis_分布式锁","url":"/2019/12/10/Redis-Part-1/"},{"title":"Path Sum","url":"/2015/01/04/Path-Sum/"},{"title":"Reverse Bits","url":"/2015/03/11/Reverse-Bits/"},{"title":"Reverse Integer","url":"/2015/03/13/Reverse-Integer/"},{"title":"ambari-summary","url":"/2017/05/09/ambari-summary/"},{"title":"Leetcode 4 寻找两个正序数组的中位数 ( Median of Two Sorted Arrays *Hard* ) 题解分析","url":"/2022/03/27/Leetcode-4-寻找两个正序数组的中位数-Median-of-Two-Sorted-Arrays-Hard-题解分析/"},{"title":"binary-watch","url":"/2016/09/29/binary-watch/"},{"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":"mybatis 的缓存是怎么回事","url":"/2020/10/03/mybatis-的缓存是怎么回事/"},{"title":"nginx 日志小记","url":"/2022/04/17/nginx-日志小记/"},{"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 的 rdb 和 COW 介绍","url":"/2021/08/15/redis-的-rdb-和-COW-介绍/"},{"title":"redis数据结构介绍-第一部分 SDS,链表,字典","url":"/2019/12/26/redis数据结构介绍/"},{"title":"redis数据结构介绍三-第三部分 整数集合","url":"/2020/01/10/redis数据结构介绍三/"},{"title":"redis数据结构介绍二-第二部分 跳表","url":"/2020/01/04/redis数据结构介绍二/"},{"title":"redis数据结构介绍六 快表","url":"/2020/01/22/redis数据结构介绍六/"},{"title":"redis数据结构介绍五-第五部分 对象","url":"/2020/01/20/redis数据结构介绍五/"},{"title":"redis数据结构介绍四-第四部分 压缩表","url":"/2020/01/19/redis数据结构介绍四/"},{"title":"redis淘汰策略复习","url":"/2021/08/01/redis淘汰策略复习/"},{"title":"redis系列介绍七-过期策略","url":"/2020/04/12/redis系列介绍七/"},{"title":"redis系列介绍八-淘汰策略","url":"/2020/04/18/redis系列介绍八/"},{"title":"redis过期策略复习","url":"/2021/07/25/redis过期策略复习/"},{"title":"rust学习笔记-所有权三之切片","url":"/2021/05/16/rust学习笔记-所有权三之切片/"},{"title":"rust学习笔记-所有权二","url":"/2021/04/18/rust学习笔记-所有权二/"},{"title":"rust学习笔记-所有权一","url":"/2021/04/18/rust学习笔记/"},{"title":"spark-little-tips","url":"/2017/03/28/spark-little-tips/"},{"title":"spring event 介绍","url":"/2022/01/30/spring-event-介绍/"},{"title":"summary-ranges-228","url":"/2016/10/12/summary-ranges-228/"},{"title":"swoole-websocket-test","url":"/2016/07/13/swoole-websocket-test/"},{"title":"wordpress 忘记密码的一种解决方法","url":"/2021/12/05/wordpress-忘记密码的一种解决方法/"},{"title":"《垃圾回收算法手册读书》笔记之整理算法","url":"/2021/03/07/《垃圾回收算法手册读书》笔记之整理算法/"},{"title":"上次的其他 外行聊国足","url":"/2022/03/06/上次的其他-外行聊国足/"},{"title":"介绍一下 RocketMQ","url":"/2020/06/21/介绍一下-RocketMQ/"},{"title":"介绍下最近比较实用的端口转发","url":"/2021/11/14/介绍下最近比较实用的端口转发/"},{"title":"从丁仲礼被美国制裁聊点啥","url":"/2020/12/20/从丁仲礼被美国制裁聊点啥/"},{"title":"从清华美院学姐聊聊我们身边的恶人","url":"/2020/11/29/从清华美院学姐聊聊我们身边的恶人/"},{"title":"关于公共交通再吐个槽","url":"/2021/03/21/关于公共交通再吐个槽/"},{"title":"关于读书打卡与分享","url":"/2021/02/07/关于读书打卡与分享/"},{"title":"分享记录一下一个 git 操作方法","url":"/2022/02/06/分享记录一下一个-git-操作方法/"},{"title":"分享记录一下一个 scp 操作方法","url":"/2022/02/06/分享记录一下一个-scp-操作方法/"},{"title":"周末我在老丈人家打了天小工","url":"/2020/08/16/周末我在老丈人家打了天小工/"},{"title":"在老丈人家的小工记三","url":"/2020/09/13/在老丈人家的小工记三/"},{"title":"在老丈人家的小工记五","url":"/2020/10/18/在老丈人家的小工记五/"},{"title":"在老丈人家的小工记四","url":"/2020/09/26/在老丈人家的小工记四/"},{"title":"寄生虫观后感","url":"/2020/03/01/寄生虫观后感/"},{"title":"我是如何走上跑步这条不归路的","url":"/2020/07/26/我是如何走上跑步这条不归路的/"},{"title":"屯菜惊魂记","url":"/2022/04/24/屯菜惊魂记/"},{"title":"看完了扫黑风暴,聊聊感想","url":"/2021/10/24/看完了扫黑风暴-聊聊感想/"},{"title":"给小电驴上牌","url":"/2022/03/20/给小电驴上牌/"},{"title":"搬运两个 StackOverflow 上的 Mysql 编码相关的问题解答","url":"/2022/01/16/搬运两个-StackOverflow-上的-Mysql-编码相关的问题解答/"},{"title":"聊一下 RocketMQ 的 DefaultMQPushConsumer 源码","url":"/2020/06/26/聊一下-RocketMQ-的-Consumer/"},{"title":"聊一下 RocketMQ 的 NameServer 源码","url":"/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/"},{"title":"聊一下 RocketMQ 的消息存储之 MMAP","url":"/2021/09/04/聊一下-RocketMQ-的消息存储/"},{"title":"聊一下 RocketMQ 的消息存储二","url":"/2021/09/12/聊一下-RocketMQ-的消息存储二/"},{"title":"聊一下 RocketMQ 的消息存储三","url":"/2021/10/03/聊一下-RocketMQ-的消息存储三/"},{"title":"聊一下 RocketMQ 的消息存储四","url":"/2021/10/17/聊一下-RocketMQ-的消息存储四/"},{"title":"聊一下 RocketMQ 的顺序消息","url":"/2021/08/29/聊一下-RocketMQ-的顺序消息/"},{"title":"聊一下 SpringBoot 中使用的 cglib 作为动态代理中的一个注意点","url":"/2021/09/19/聊一下-SpringBoot-中使用的-cglib-作为动态代理中的一个注意点/"},{"title":"聊一下 SpringBoot 中动态切换数据源的方法","url":"/2021/09/26/聊一下-SpringBoot-中动态切换数据源的方法/"},{"title":"聊在东京奥运会闭幕式这天-二","url":"/2021/08/19/聊在东京奥运会闭幕式这天-二/"},{"title":"聊在东京奥运会闭幕式这天","url":"/2021/08/08/聊在东京奥运会闭幕式这天/"},{"title":"聊聊 Dubbo 的 SPI 续之自适应拓展","url":"/2020/06/06/聊聊-Dubbo-的-SPI-续之自适应拓展/"},{"title":"聊聊 Dubbo 的 SPI","url":"/2020/05/31/聊聊-Dubbo-的-SPI/"},{"title":"聊聊 Dubbo 的容错机制","url":"/2020/11/22/聊聊-Dubbo-的容错机制/"},{"title":"聊聊 Java 中绕不开的 Synchronized 关键字-二","url":"/2021/06/27/聊聊-Java-中绕不开的-Synchronized-关键字-二/"},{"title":"聊聊 Java 中绕不开的 Synchronized 关键字","url":"/2021/06/20/聊聊-Java-中绕不开的-Synchronized-关键字/"},{"title":"聊聊 Java 的 equals 和 hashCode 方法","url":"/2021/01/03/聊聊-Java-的-equals-和-hashCode-方法/"},{"title":"聊聊 Java 的类加载机制一","url":"/2020/11/08/聊聊-Java-的类加载机制/"},{"title":"聊聊 Java 的类加载机制二","url":"/2021/06/13/聊聊-Java-的类加载机制二/"},{"title":"聊聊 Java 自带的那些*逆天*工具","url":"/2020/08/02/聊聊-Java-自带的那些逆天工具/"},{"title":"聊聊 Linux 下的 top 命令","url":"/2021/03/28/聊聊-Linux-下的-top-命令/"},{"title":"聊聊 RocketMQ 的 Broker 源码","url":"/2020/07/19/聊聊-RocketMQ-的-Broker-源码/"},{"title":"聊聊 Sharding-Jdbc 的简单使用","url":"/2021/12/12/聊聊-Sharding-Jdbc-的简单使用/"},{"title":"聊聊 dubbo 的线程池","url":"/2021/04/04/聊聊-dubbo-的线程池/"},{"title":"聊聊 Sharding-Jdbc 的简单原理初篇","url":"/2021/12/26/聊聊-Sharding-Jdbc-的简单原理初篇/"},{"title":"聊聊 mysql 的 MVCC 续篇","url":"/2020/05/02/聊聊-mysql-的-MVCC-续篇/"},{"title":"聊聊 mysql 的 MVCC 续续篇之锁分析","url":"/2020/05/10/聊聊-mysql-的-MVCC-续续篇之加锁分析/"},{"title":"聊聊 mysql 索引的一些细节","url":"/2020/12/27/聊聊-mysql-索引的一些细节/"},{"title":"聊聊 mysql 的 MVCC","url":"/2020/04/26/聊聊-mysql-的-MVCC/"},{"title":"聊聊 redis 缓存的应用问题","url":"/2021/01/31/聊聊-redis-缓存的应用问题/"},{"title":"聊聊Java中的单例模式","url":"/2019/12/21/聊聊Java中的单例模式/"},{"title":"聊聊 SpringBoot 自动装配","url":"/2021/07/11/聊聊SpringBoot-自动装配/"},{"title":"聊聊 Sharding-Jdbc 分库分表下的分页方案","url":"/2022/01/09/聊聊-Sharding-Jdbc-分库分表下的分页方案/"},{"title":"聊聊传说中的 ThreadLocal","url":"/2021/05/30/聊聊传说中的-ThreadLocal/"},{"title":"聊聊厦门旅游的好与不好","url":"/2021/04/11/聊聊厦门旅游的好与不好/"},{"title":"聊聊如何识别和意识到日常生活中的各类危险","url":"/2021/06/06/聊聊如何识别和意识到日常生活中的各类危险/"},{"title":"聊聊我刚学会的应用诊断方法","url":"/2020/05/22/聊聊我刚学会的应用诊断方法/"},{"title":"聊聊我理解的分布式事务","url":"/2020/05/17/聊聊我理解的分布式事务/"},{"title":"聊聊最近平淡的生活之又聊通勤","url":"/2021/11/07/聊聊最近平淡的生活/"},{"title":"聊聊最近平淡的生活之《花束般的恋爱》观后感","url":"/2021/12/31/聊聊最近平淡的生活之《花束般的恋爱》观后感/"},{"title":"G1收集器概述","url":"/2020/02/09/G1收集器概述/"},{"title":"聊聊最近平淡的生活之看看老剧","url":"/2021/11/21/聊聊最近平淡的生活之看看老剧/"},{"title":"聊聊给亲戚朋友的老电脑重装系统那些事儿","url":"/2021/05/09/聊聊给亲戚朋友的老电脑重装系统那些事儿/"},{"title":"聊聊这次换车牌及其他","url":"/2022/02/20/聊聊这次换车牌及其他/"},{"title":"聊聊那些加塞狗","url":"/2021/01/17/聊聊那些加塞狗/"},{"title":"聊聊部分公交车的设计bug","url":"/2021/12/05/聊聊部分公交车的设计bug/"},{"title":"记录下 Java Stream 的一些高效操作","url":"/2022/05/15/记录下-Java-Lambda-的一些高效操作/"},{"title":"记录下 zookeeper 集群迁移和易错点","url":"/2022/05/29/记录下-zookeeper-集群迁移/"},{"title":"这周末我又在老丈人家打了天小工","url":"/2020/08/30/这周末我又在老丈人家打了天小工/"},{"title":"重看了下《蛮荒记》说说感受","url":"/2021/10/10/重看了下《蛮荒记》说说感受/"},{"title":"闲聊下乘公交的用户体验","url":"/2021/02/28/闲聊下乘公交的用户体验/"},{"title":"闲话篇-也算碰到了为老不尊和坏人变老了的典型案例","url":"/2022/05/22/闲话篇-也算碰到了为老不尊和坏人变老了的典型案例/"},{"title":"two sum","url":"/2015/01/14/Two-Sum/"},{"title":"闲话篇-路遇神逻辑骑车带娃爹","url":"/2022/05/08/闲话篇-路遇神逻辑骑车带娃爹/"},{"title":"聊聊一次 brew update 引发的血案","url":"/2020/06/13/聊聊一次-brew-update-引发的血案/"},{"title":"难得的大扫除","url":"/2022/04/10/难得的大扫除/"},{"title":"聊聊最近平淡的生活之看《神探狄仁杰》","url":"/2021/12/19/聊聊最近平淡的生活之看《神探狄仁杰》/"}]
    \ No newline at end of file
    +[{"title":"2019年终总结","url":"/2020/02/01/2019年终总结/"},{"title":"2020 年终总结","url":"/2021/03/31/2020-年终总结/"},{"title":"2021 年终总结","url":"/2022/01/22/2021-年终总结/"},{"title":"村上春树《1Q84》读后感","url":"/2019/12/18/1Q84读后感/"},{"title":"AQS篇二 之 Condition 浅析笔记","url":"/2021/02/21/AQS-之-Condition-浅析笔记/"},{"title":"2021 年中总结","url":"/2021/07/18/2021-年中总结/"},{"title":"2020年中总结","url":"/2020/07/11/2020年中总结/"},{"title":"add-two-number","url":"/2015/04/14/Add-Two-Number/"},{"title":"AbstractQueuedSynchronizer","url":"/2019/09/23/AbstractQueuedSynchronizer/"},{"title":"AQS篇一","url":"/2021/02/14/AQS篇一/"},{"title":"Clone Graph Part I","url":"/2014/12/30/Clone-Graph-Part-I/"},{"title":"Comparator使用小记","url":"/2020/04/05/Comparator使用小记/"},{"title":"Apollo 的 value 注解是怎么自动更新的","url":"/2020/11/01/Apollo-的-value-注解是怎么自动更新的/"},{"title":"34_Search_for_a_Range","url":"/2016/08/14/34-Search-for-a-Range/"},{"title":"Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥?","url":"/2020/08/22/Filter-Intercepter-Aop-啥-啥-啥-这些都是啥/"},{"title":"G1收集器概述","url":"/2020/02/09/G1收集器概述/"},{"title":"Disruptor 系列一","url":"/2022/02/13/Disruptor-系列一/"},{"title":"JVM源码分析之G1垃圾收集器分析一","url":"/2019/12/07/JVM-G1-Part-1/"},{"title":"Leetcode 028 实现 strStr() ( Implement strStr() ) 题解分析","url":"/2021/10/31/Leetcode-028-实现-strStr-Implement-strStr-题解分析/"},{"title":"Dubbo 使用的几个记忆点","url":"/2022/04/02/Dubbo-使用的几个记忆点/"},{"title":"Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析","url":"/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/"},{"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 053 最大子序和 ( Maximum Subarray ) 题解分析","url":"/2021/11/28/Leetcode-053-最大子序和-Maximum-Subarray-题解分析/"},{"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 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析","url":"/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/"},{"title":"Leetcode 155 最小栈(Min Stack) 题解分析","url":"/2020/12/06/Leetcode-155-最小栈-Min-Stack-题解分析/"},{"title":"Leetcode 021 合并两个有序链表 ( Merge Two Sorted Lists ) 题解分析","url":"/2021/10/07/Leetcode-021-合并两个有序链表-Merge-Two-Sorted-Lists-题解分析/"},{"title":"Leetcode 160 相交链表(intersection-of-two-linked-lists) 题解分析","url":"/2021/01/10/Leetcode-160-相交链表-intersection-of-two-linked-lists-题解分析/"},{"title":"Leetcode 234 回文链表(Palindrome Linked List) 题解分析","url":"/2020/11/15/Leetcode-234-回文联表-Palindrome-Linked-List-题解分析/"},{"title":"Leetcode 2 Add Two Numbers 题解分析","url":"/2020/10/11/Leetcode-2-Add-Two-Numbers-题解分析/"},{"title":"Leetcode 236 二叉树的最近公共祖先(Lowest Common Ancestor of a Binary Tree) 题解分析","url":"/2021/05/23/Leetcode-236-二叉树的最近公共祖先-Lowest-Common-Ancestor-of-a-Binary-Tree-题解分析/"},{"title":"Leetcode 3 Longest Substring Without Repeating Characters 题解分析","url":"/2020/09/20/Leetcode-3-Longest-Substring-Without-Repeating-Characters-题解分析/"},{"title":"Leetcode 4 寻找两个正序数组的中位数 ( Median of Two Sorted Arrays *Hard* ) 题解分析","url":"/2022/03/27/Leetcode-4-寻找两个正序数组的中位数-Median-of-Two-Sorted-Arrays-Hard-题解分析/"},{"title":"Leetcode 42 接雨水 (Trapping Rain Water) 题解分析","url":"/2021/07/04/Leetcode-42-接雨水-Trapping-Rain-Water-题解分析/"},{"title":"Leetcode 48 旋转图像(Rotate Image) 题解分析","url":"/2021/05/01/Leetcode-48-旋转图像-Rotate-Image-题解分析/"},{"title":"Disruptor 系列二","url":"/2022/02/27/Disruptor-系列二/"},{"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":"Leetcode 83 删除排序链表中的重复元素 ( Remove Duplicates from Sorted List *Easy* ) 题解分析","url":"/2022/03/13/Leetcode-83-删除排序链表中的重复元素-Remove-Duplicates-from-Sorted-List-Easy-题解分析/"},{"title":"Leetcode 1115 交替打印 FooBar ( Print FooBar Alternately *Medium* ) 题解分析","url":"/2022/05/01/Leetcode-1115-交替打印-FooBar-Print-FooBar-Alternately-Medium-题解分析/"},{"title":"Maven实用小技巧","url":"/2020/02/16/Maven实用小技巧/"},{"title":"Path Sum","url":"/2015/01/04/Path-Sum/"},{"title":"Reverse Bits","url":"/2015/03/11/Reverse-Bits/"},{"title":"Number of 1 Bits","url":"/2015/03/11/Number-Of-1-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-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":"mybatis 的缓存是怎么回事","url":"/2020/10/03/mybatis-的缓存是怎么回事/"},{"title":"openresty","url":"/2019/06/18/openresty/"},{"title":"Redis_分布式锁","url":"/2019/12/10/Redis-Part-1/"},{"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 的 rdb 和 COW 介绍","url":"/2021/08/15/redis-的-rdb-和-COW-介绍/"},{"title":"redis数据结构介绍-第一部分 SDS,链表,字典","url":"/2019/12/26/redis数据结构介绍/"},{"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/22/redis数据结构介绍六/"},{"title":"redis数据结构介绍四-第四部分 压缩表","url":"/2020/01/19/redis数据结构介绍四/"},{"title":"redis淘汰策略复习","url":"/2021/08/01/redis淘汰策略复习/"},{"title":"redis系列介绍七-过期策略","url":"/2020/04/12/redis系列介绍七/"},{"title":"redis系列介绍八-淘汰策略","url":"/2020/04/18/redis系列介绍八/"},{"title":"redis过期策略复习","url":"/2021/07/25/redis过期策略复习/"},{"title":"rust学习笔记-所有权三之切片","url":"/2021/05/16/rust学习笔记-所有权三之切片/"},{"title":"rust学习笔记-所有权一","url":"/2021/04/18/rust学习笔记/"},{"title":"rust学习笔记-所有权二","url":"/2021/04/18/rust学习笔记-所有权二/"},{"title":"spark-little-tips","url":"/2017/03/28/spark-little-tips/"},{"title":"summary-ranges-228","url":"/2016/10/12/summary-ranges-228/"},{"title":"spring event 介绍","url":"/2022/01/30/spring-event-介绍/"},{"title":"swoole-websocket-test","url":"/2016/07/13/swoole-websocket-test/"},{"title":"wordpress 忘记密码的一种解决方法","url":"/2021/12/05/wordpress-忘记密码的一种解决方法/"},{"title":"《垃圾回收算法手册读书》笔记之整理算法","url":"/2021/03/07/《垃圾回收算法手册读书》笔记之整理算法/"},{"title":"介绍一下 RocketMQ","url":"/2020/06/21/介绍一下-RocketMQ/"},{"title":"nginx 日志小记","url":"/2022/04/17/nginx-日志小记/"},{"title":"介绍下最近比较实用的端口转发","url":"/2021/11/14/介绍下最近比较实用的端口转发/"},{"title":"从丁仲礼被美国制裁聊点啥","url":"/2020/12/20/从丁仲礼被美国制裁聊点啥/"},{"title":"从清华美院学姐聊聊我们身边的恶人","url":"/2020/11/29/从清华美院学姐聊聊我们身边的恶人/"},{"title":"关于公共交通再吐个槽","url":"/2021/03/21/关于公共交通再吐个槽/"},{"title":"关于读书打卡与分享","url":"/2021/02/07/关于读书打卡与分享/"},{"title":"分享记录一下一个 git 操作方法","url":"/2022/02/06/分享记录一下一个-git-操作方法/"},{"title":"分享记录一下一个 scp 操作方法","url":"/2022/02/06/分享记录一下一个-scp-操作方法/"},{"title":"周末我在老丈人家打了天小工","url":"/2020/08/16/周末我在老丈人家打了天小工/"},{"title":"在老丈人家的小工记三","url":"/2020/09/13/在老丈人家的小工记三/"},{"title":"在老丈人家的小工记五","url":"/2020/10/18/在老丈人家的小工记五/"},{"title":"在老丈人家的小工记四","url":"/2020/09/26/在老丈人家的小工记四/"},{"title":"寄生虫观后感","url":"/2020/03/01/寄生虫观后感/"},{"title":"我是如何走上跑步这条不归路的","url":"/2020/07/26/我是如何走上跑步这条不归路的/"},{"title":"上次的其他 外行聊国足","url":"/2022/03/06/上次的其他-外行聊国足/"},{"title":"看完了扫黑风暴,聊聊感想","url":"/2021/10/24/看完了扫黑风暴-聊聊感想/"},{"title":"Leetcode 349 两个数组的交集 ( Intersection of Two Arrays *Easy* ) 题解分析","url":"/2022/03/07/Leetcode-349-两个数组的交集-Intersection-of-Two-Arrays-Easy-题解分析/"},{"title":"给小电驴上牌","url":"/2022/03/20/给小电驴上牌/"},{"title":"聊一下 RocketMQ 的 DefaultMQPushConsumer 源码","url":"/2020/06/26/聊一下-RocketMQ-的-Consumer/"},{"title":"聊一下 RocketMQ 的 NameServer 源码","url":"/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/"},{"title":"聊一下 RocketMQ 的消息存储之 MMAP","url":"/2021/09/04/聊一下-RocketMQ-的消息存储/"},{"title":"聊一下 RocketMQ 的消息存储三","url":"/2021/10/03/聊一下-RocketMQ-的消息存储三/"},{"title":"聊一下 RocketMQ 的消息存储二","url":"/2021/09/12/聊一下-RocketMQ-的消息存储二/"},{"title":"聊一下 RocketMQ 的消息存储四","url":"/2021/10/17/聊一下-RocketMQ-的消息存储四/"},{"title":"聊一下 RocketMQ 的顺序消息","url":"/2021/08/29/聊一下-RocketMQ-的顺序消息/"},{"title":"聊一下 SpringBoot 中动态切换数据源的方法","url":"/2021/09/26/聊一下-SpringBoot-中动态切换数据源的方法/"},{"title":"聊一下 SpringBoot 中使用的 cglib 作为动态代理中的一个注意点","url":"/2021/09/19/聊一下-SpringBoot-中使用的-cglib-作为动态代理中的一个注意点/"},{"title":"搬运两个 StackOverflow 上的 Mysql 编码相关的问题解答","url":"/2022/01/16/搬运两个-StackOverflow-上的-Mysql-编码相关的问题解答/"},{"title":"屯菜惊魂记","url":"/2022/04/24/屯菜惊魂记/"},{"title":"聊在东京奥运会闭幕式这天-二","url":"/2021/08/19/聊在东京奥运会闭幕式这天-二/"},{"title":"聊在东京奥运会闭幕式这天","url":"/2021/08/08/聊在东京奥运会闭幕式这天/"},{"title":"聊聊 Dubbo 的 SPI 续之自适应拓展","url":"/2020/06/06/聊聊-Dubbo-的-SPI-续之自适应拓展/"},{"title":"聊聊 Dubbo 的 SPI","url":"/2020/05/31/聊聊-Dubbo-的-SPI/"},{"title":"聊聊 Dubbo 的容错机制","url":"/2020/11/22/聊聊-Dubbo-的容错机制/"},{"title":"聊聊 Java 中绕不开的 Synchronized 关键字-二","url":"/2021/06/27/聊聊-Java-中绕不开的-Synchronized-关键字-二/"},{"title":"聊聊 Java 中绕不开的 Synchronized 关键字","url":"/2021/06/20/聊聊-Java-中绕不开的-Synchronized-关键字/"},{"title":"聊聊 Java 的 equals 和 hashCode 方法","url":"/2021/01/03/聊聊-Java-的-equals-和-hashCode-方法/"},{"title":"聊聊 Java 的类加载机制一","url":"/2020/11/08/聊聊-Java-的类加载机制/"},{"title":"聊聊 Java 的类加载机制二","url":"/2021/06/13/聊聊-Java-的类加载机制二/"},{"title":"聊聊 Java 自带的那些*逆天*工具","url":"/2020/08/02/聊聊-Java-自带的那些逆天工具/"},{"title":"聊聊 Linux 下的 top 命令","url":"/2021/03/28/聊聊-Linux-下的-top-命令/"},{"title":"聊聊 RocketMQ 的 Broker 源码","url":"/2020/07/19/聊聊-RocketMQ-的-Broker-源码/"},{"title":"聊聊 Sharding-Jdbc 的简单使用","url":"/2021/12/12/聊聊-Sharding-Jdbc-的简单使用/"},{"title":"聊聊 dubbo 的线程池","url":"/2021/04/04/聊聊-dubbo-的线程池/"},{"title":"聊聊 mysql 的 MVCC 续篇","url":"/2020/05/02/聊聊-mysql-的-MVCC-续篇/"},{"title":"聊聊 mysql 的 MVCC 续续篇之锁分析","url":"/2020/05/10/聊聊-mysql-的-MVCC-续续篇之加锁分析/"},{"title":"聊聊 mysql 的 MVCC","url":"/2020/04/26/聊聊-mysql-的-MVCC/"},{"title":"聊聊 Sharding-Jdbc 的简单原理初篇","url":"/2021/12/26/聊聊-Sharding-Jdbc-的简单原理初篇/"},{"title":"聊聊 mysql 索引的一些细节","url":"/2020/12/27/聊聊-mysql-索引的一些细节/"},{"title":"聊聊 Sharding-Jdbc 分库分表下的分页方案","url":"/2022/01/09/聊聊-Sharding-Jdbc-分库分表下的分页方案/"},{"title":"聊聊 redis 缓存的应用问题","url":"/2021/01/31/聊聊-redis-缓存的应用问题/"},{"title":"聊聊Java中的单例模式","url":"/2019/12/21/聊聊Java中的单例模式/"},{"title":"聊聊 SpringBoot 自动装配","url":"/2021/07/11/聊聊SpringBoot-自动装配/"},{"title":"聊聊一次 brew update 引发的血案","url":"/2020/06/13/聊聊一次-brew-update-引发的血案/"},{"title":"聊聊传说中的 ThreadLocal","url":"/2021/05/30/聊聊传说中的-ThreadLocal/"},{"title":"聊聊如何识别和意识到日常生活中的各类危险","url":"/2021/06/06/聊聊如何识别和意识到日常生活中的各类危险/"},{"title":"聊聊厦门旅游的好与不好","url":"/2021/04/11/聊聊厦门旅游的好与不好/"},{"title":"聊聊我刚学会的应用诊断方法","url":"/2020/05/22/聊聊我刚学会的应用诊断方法/"},{"title":"聊聊我理解的分布式事务","url":"/2020/05/17/聊聊我理解的分布式事务/"},{"title":"聊聊最近平淡的生活之又聊通勤","url":"/2021/11/07/聊聊最近平淡的生活/"},{"title":"聊聊最近平淡的生活之看《神探狄仁杰》","url":"/2021/12/19/聊聊最近平淡的生活之看《神探狄仁杰》/"},{"title":"聊聊最近平淡的生活之看看老剧","url":"/2021/11/21/聊聊最近平淡的生活之看看老剧/"},{"title":"聊聊给亲戚朋友的老电脑重装系统那些事儿","url":"/2021/05/09/聊聊给亲戚朋友的老电脑重装系统那些事儿/"},{"title":"聊聊这次换车牌及其他","url":"/2022/02/20/聊聊这次换车牌及其他/"},{"title":"聊聊那些加塞狗","url":"/2021/01/17/聊聊那些加塞狗/"},{"title":"聊聊部分公交车的设计bug","url":"/2021/12/05/聊聊部分公交车的设计bug/"},{"title":"聊聊最近平淡的生活之《花束般的恋爱》观后感","url":"/2021/12/31/聊聊最近平淡的生活之《花束般的恋爱》观后感/"},{"title":"记录下 zookeeper 集群迁移和易错点","url":"/2022/05/29/记录下-zookeeper-集群迁移/"},{"title":"这周末我又在老丈人家打了天小工","url":"/2020/08/30/这周末我又在老丈人家打了天小工/"},{"title":"重看了下《蛮荒记》说说感受","url":"/2021/10/10/重看了下《蛮荒记》说说感受/"},{"title":"闲聊下乘公交的用户体验","url":"/2021/02/28/闲聊下乘公交的用户体验/"},{"title":"闲话篇-也算碰到了为老不尊和坏人变老了的典型案例","url":"/2022/05/22/闲话篇-也算碰到了为老不尊和坏人变老了的典型案例/"},{"title":"记录下 Java Stream 的一些高效操作","url":"/2022/05/15/记录下-Java-Lambda-的一些高效操作/"},{"title":"闲话篇-路遇神逻辑骑车带娃爹","url":"/2022/05/08/闲话篇-路遇神逻辑骑车带娃爹/"},{"title":"难得的大扫除","url":"/2022/04/10/难得的大扫除/"}]
    \ No newline at end of file
    diff --git a/page/10/index.html b/page/10/index.html
    index 2374f65752..107751d258 100644
    --- a/page/10/index.html
    +++ b/page/10/index.html
    @@ -1,16 +1,16 @@
    -Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    redis 在使用 rdb 策略进行备份时,rdb 的意思是会在开启备份的时候将开启时间点的内存数据进行备份,并且可以设置时间,这样子就是这个策略其实还是不完全可靠的,如果是在这个间隔中宕机了,或者间隔过长,不过这个不在这次的要说的内容中,如果自己去写这个 rdb 的策略可能就有点类似于 mvcc 的 redolog,需要知道这个时间点之前的数据是怎么样的,防止后面更改的干扰,但是这样一方面需要有比较复杂的 mvcc 实现,另一方面是很占用存储空间,所以 redis 在这里面使用了 COW (Copy On Write) 技术,这个技术呢以前听过,也大致了解是怎么个意思,这次稍微具体地来看下,其实 redis 的 copy-on-write 就是来自于 linux 的 cow

    Linux中的CopyOnWrite

    fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。这个操作其实可以类比为写屏障,正常的读取是没问题的,当有写入时就会分裂。

    CopyOnWrite的好处:

    1、减少分配和复制资源时带来的瞬时延迟;
    2、减少不必要的资源分配;
    CopyOnWrite的缺点:
    1、如果父子进程都需要进行大量的写操作,会产生大量的分页错误(页异常中断page-fault);

    Redis中的CopyOnWrite

    Redis在持久化时,如果是采用BGSAVE命令或者BGREWRITEAOF的方式,那Redis会fork出一个子进程来读取数据,从而写到磁盘中。
    总体来看,Redis还是读操作比较多。如果子进程存在期间,发生了大量的写操作,那可能就会出现很多的分页错误(页异常中断page-fault),这样就得耗费不少性能在复制上。
    而在rehash阶段上,写操作是无法避免的。所以Redis在fork出子进程之后,将负载因子阈值提高,尽量减少写操作,避免不必要的内存写入操作,最大限度地节约内存。这里其实更巧妙了,在细节上去优化会产生大量页异常中断的情况。

    这届奥运会有可能是我除了 08 年之外关注度最高的一届奥运会,原因可能是因为最近也没什么电影综艺啥的比较好看,前面看跑男倒还行,不是说多好,也就图一乐,最开始看向往的生活觉得也挺不错的,后面变成了统一来了就看黄磊做饭,然后夸黄磊做饭好吃,然后无聊的说这种生活多么多么美好,单调无聊,差不多弃了,这里面还包括大华不在了,大华其实个人还是有点呱噪的,但是挺能搞气氛,并且也有才华,彭彭跟子枫人是不讨厌,但是撑不起来,所以也导致了前面说的结果,都变成了黄磊彩虹屁现场,虽然偶尔怀疑他是否做得好吃,但是整体还是承认的,可对于一个这么多季了的综艺来说,这样也有点单调了。

    还有奥运会像乒乓球,篮球,跳水这几个都是比较喜欢的项目,篮球🏀是从初中开始就也有在自己在玩的,虽然因为身高啊体质基本没什么天赋,但也算是热爱驱动,差不多到了大学因为比较懒才放下了,初中高中还是有很多时间花在上面,不像别人经常打球跑跑跳跳还能长高,我反而一直都没长个子,也因为这个其实蛮遗憾的,后面想想可能是初中的时候远走他乡去住宿读初中,伙食营养跟不上导致的,可能也是自己的一厢情愿吧,总觉得应该还能再长点个,这一点以后我自己的小孩我应该会特别注意这段时间他/她的营养摄入了;然后像乒乓球🏓的话其实小时候是比较讨厌的,因为家里人,父母都没有这类爱好习惯,我也完全不会,但是小学那会班里的“恶霸”就以公平之名要我们男生每个人都排队打几个,我这种不会的反而又要被嘲笑,这个小时候的阴影让我有了比较不好的印象,对它🏓的改观是在工作以后,前司跟一个同样不会的同事经常在饭点会打打,而且那会因为这个其实身体得到了锻炼,感觉是个不错的健身方式,然后又是中国的优势项目,小时候跟着我爸看孔令辉,那时候完全不懂,印象就觉得老瓦很牛,后面其实也没那么关注,上一届好像看了马龙的比赛;跳水也是中国的优势项目,而且也比较简单,不是说真的很简单,就是我们外行观众看着就看看水花大小图一乐。

    这次的观赛过程其实主要还是在乒乓球上面,现在都有点怪我的乌鸦嘴,混双我一直就不太放心(关我什么事,我也不专业),然后一直觉得混双是不是不太稳,结果那天看的时候也是因为央视一套跟五套都没放,我家的有线电视又是没有五加体育,然后用电脑投屏就很卡,看得也很不爽,同时那天因为看的时候已经是 2:0还是再后面点了,一方面是不懂每队只有一次暂停,另一方面不知道已经用过暂停了,所以就特别怀疑马林是不是只会无脑鼓掌,感觉作为教练,并且是前冠军,应该也能在擦汗间隙,或者局间休息调整的时候多给些战略战术的指导,类似于后面男团小胖打奥恰洛夫,像解说都看出来了,其实奥恰那会的反手特别顺,打得特别凶,那就不能让他能特别顺手的上反手位,这当然是外行比较粗浅的看法,在混双过程中其实除了这个,还有让人很不爽的就是我们的许昕跟刘诗雯有种拿不出破釜沉舟的勇气的感觉,在气势上完全被对面两位日本乒乓球最讨厌的两位对手压制着,我都要输了,我就每一颗都要不让你好过,因为真的不是说没有实力,对面水谷隼也不是多么多么强的,可能上一届男团许昕输给他还留着阴影,但是以许昕 19 年男单世界第一的实力,目前也排在世界前三,输一场不应该成为这种阻力,有一些失误也很可惜,后面孙颖莎真的打得很解气,第二局一度以为又要被翻盘了,结果来了个大逆转,女团的时候也是,感觉在心态上孙颖莎还是很值得肯定的,少年老成这个词很适合,看其他的视频也觉得莎莎萌萌哒,陈梦总感觉还欠一点王者霸气,王曼昱还是可以的,反手很凶,我觉得其实这一届日本女乒就是打得非常凶,即使像平野这种看着很弱的妹子,打的球可一点都不弱,也是这种凶狠的打法,有点要压制中国的感觉,这方面我觉得是需要改善的,打这种要不就是实力上的完全碾压,要不就是我实力虽然比较没强多少,但是你狠我打得比你还狠,越保守越要输,我不太成熟的想法是这样的,还有就是面对逆境,这个就要说到男队的了,樊振东跟马龙在半决赛的时候,特别是男团的第二盘,樊振东打奥恰很好地表现了这个心态,当然樊振东我不是特别了解,据说他是比较善于打相持,比较善于焦灼的情况,不过整体看下来樊振东还是有一些欠缺,就是面对情况的快速转变应对,这一点也是马龙特别强的,虽然看起来马龙真的是年纪大了点,没有 16 年那会满头发胶,油光锃亮的大背头和满脸胶原蛋白的意气风发,大范围运动能力也弱了一点,但是经验和能力的全面性也让他最终能再次站上巅峰,还是非常佩服的,这里提一下张继科,虽然可能天赋上是张继科更强点,但是男乒一直都是有强者出现,能为国家队付出这么多并且一直坚持的可不是人人都可以,即使现在同台竞技马龙打不过张继科我还是更喜欢马龙。再来说说我们的对手,主要分三部分,德国男乒,里面有波尔(我刚听到的时候在想怎么又出来个叫波尔的,是不是像举重的石智勇一样,又来一个同名的,结果是同一个,已经四十岁了),这真是个让人敬佩的对手,实力强,经验丰富,虽然男单有点可惜,但是帮助男团获得银牌,真的是起到了定海神针的作用;奥恰洛夫,以前完全不认识,或者说看过也忘了,这次是真的有点意外,竟然有这么个马龙护法,其实他也坦言非常想赢一次马龙,并且在半决赛也非常接近赢得比赛,是个实力非常强的对手,就是男团半决赛输给张本智和有点可惜,有点被打蒙的感觉,佛朗西斯卡的话也是实力不错的选手,就是可能被奥恰跟波尔的光芒掩盖了,跟波尔在男团第一盘男双的比赛中打败日本那对男双也是非常给力的,说实话,最后打国乒的时候的确是国乒实力更胜一筹,但是即使德国赢了我也是充满尊敬,拼的就是硬实力,就像第二盘奥恰打樊振东,反手是真的很强,反过来看奥恰可能也不是很善于快速调整,樊振东打出来自己的节奏,主攻奥恰的中路,他好像没什么好办法解决。再来说我最讨厌的日本,嗯,小日本,张本智和、水谷隼、伊藤美诚,一一评价下(我是外行,绝对主观评价),张本智和,父母也是中国人,原来叫张智和,改日本籍后加了个本,被微博网友笑称日本尖叫鸡,男单输给了斯洛文尼亚选手,男团里是赢了两场,但是在我看来其实实力上可能比不上全力的奥恰,主要是特别能叫,会干扰对手,如果觉得这种也是种能力我也无话可说,要是有那种吼声能直接把对手震聋的,都不需要打比赛了,我简单记了下,赢一颗球,他要叫八声,用 LD 的话来说烦都烦死了,心态是在面对一些困境顺境的应对调整适应能力,而不是对这种噪音的适应能力,至少我是这么看的,所以我很期待樊振东能好好地虐虐他,因为其他像林昀儒真的是非常优秀的新选手,所谓的国乒克星估计也是小日本自己说说的,国乒其实有很多对手,马龙跟樊振东在男单半决赛碰到的这两个几乎都差点把他们掀翻了,所以还是练好自己的实力再来吹吧,免得打脸;水谷隼的话真的是长相就是特别地讨厌,还搞出那套不打比赛的姿态,男团里被波尔干掉就是很好的例子,波尔虽然真的很强,但毕竟 40 岁了,跟伊藤美诚一起说了吧,伊藤实力说实话是有的,混双中很大一部分的赢面来自于她,刘诗雯做了手术状态不好,许昕失误稍多,但是这种赢球了就感觉我赢了你一辈子一场没输的感觉,还有那种不知道怎么形容的笑,实力强的正常打比赛的我都佩服,像女团决赛里,平野跟石川佳纯的打法其实也很凶狠,但是都是正常的比赛,即使中国队两位实力不济输了也很正常,这种就真的需要像孙颖莎这样的小魔王无视各种魔法攻击,无视你各种花里胡哨的打法的人好好教训一下,混双输了以后了解了下她,感觉实力真的不错,是个大威胁,但是其实我们孙颖莎也是经历了九个月的继续成长,像张怡宁也评价了她,可能后面就没什么空间了,当然如果由张怡宁来打她就更适合了,净整这些有的没的,就打得你没脾气。

    乒乓球的说的有点多,就分篇说了,第一篇先到这。

    前面复习了 redis 的过期策略,这里再复习下淘汰策略,淘汰跟过期的区别有时候会被混淆了,过期主要针对那些设置了过期时间的 key,应该说是一种逻辑策略,是主动的还是被动的加定时的,两种有各自的取舍,而淘汰也可以看成是一种保持系统稳定的策略,因为如果内存满了,不采取任何策略处理,那大概率会导致系统故障,之前其实主要从源码角度分析过redis 的 LRU 和 LFU,但这个是偏底层的实现,抠得比较细,那么具体的系统层面的配置是有哪些策略,来看下 redis labs 的介绍

    PolicyDescription
    noeviction 不逐出Returns an error if the memory limit has been reached when trying to insert more data,插入更多数据时,如果内存达到上限了,返回错误
    allkeys-lru 所有的 key 使用 lru 逐出Evicts the least recently used keys out of all keys 在所有 key 中逐出最近最少使用的
    allkeys-lfu 所有的 key 使用 lfu 逐出Evicts the least frequently used keys out of all keys 在所有 key 中逐出最近最不频繁使用的
    allkeys-random 所有的 key 中随机逐出Randomly evicts keys out of all keys 在所有 key 中随机逐出
    volatile-lruEvicts the least recently used keys out of all keys with an “expire” field set 在设置了过期时间的 key 空间 expire 中使用 lru 策略逐出
    volatile-lfuEvicts the least frequently used keys out of all keys with an “expire” field set 在设置了过期时间的 key 空间 expire 中使用 lfu 策略逐出
    volatile-randomRandomly evicts keys with an “expire” field set 在设置了过期时间的 key 空间 expire 中随机逐出
    volatile-ttlEvicts the shortest time-to-live keys out of all keys with an “expire” field set.在设置了过期时间的 key 空间 expire 中逐出更早过期的

    而在这其中默认使用的策略是 volatile-lru,对 lru 跟 lfu 想有更多的了解可以看下我之前的文章redis系列介绍八-淘汰策略

    redis过期策略复习

    之前其实写过redis的过期的一些原理,这次主要是记录下,一些使用上的概念,主要是redis使用的过期策略是懒过期和定时清除,懒过期的其实比较简单,即是在key被访问的时候会顺带着判断下这个key是否已过期了,如果已经过期了,就不返回了,但是这种策略有个漏洞是如果有些key之后一直不会被访问了,就等于沉在池底了,所以需要有一个定时的清理机制,去从设置了过期的key池子(expires)里随机地捞key,具体的策略我们看下官网的解释

    1. Test 20 random keys from the set of keys with an associated expire.
    2. Delete all the keys found expired.
    3. If more than 25% of keys were expired, start again from step 1.

    从池子里随机获取20个key,将其中过期的key删掉,如果这其中有超过25%的key已经过期了,那就再来一次,以此保持过期的key不超过25%(左右),并且这个定时策略可以在redis的配置文件

    # Redis calls an internal function to perform many background tasks, like
    -# closing connections of clients in timeout, purging expired keys that are
    -# never requested, and so forth.
    -#
    -# Not all tasks are performed with the same frequency, but Redis checks for
    -# tasks to perform according to the specified "hz" value.
    -#
    -# By default "hz" is set to 10. Raising the value will use more CPU when
    -# Redis is idle, but at the same time will make Redis more responsive when
    -# there are many keys expiring at the same time, and timeouts may be
    -# handled with more precision.
    -#
    -# The range is between 1 and 500, however a value over 100 is usually not
    -# a good idea. Most users should use the default of 10 and raise this up to
    -# 100 only in environments where very low latency is required.
    -hz 10

    可以配置这个hz的值,代表的含义是每秒的执行次数,默认是10,其实也用了hz的普遍含义。有兴趣可以看看之前写的一篇文章redis系列介绍七-过期策略

    又到半年总结时,第一次写总结类型的文章感觉挺好写的,但是后面总觉得这过去的一段时间所做的事情,能力上的成长低于预期,但是是需要总结下,找找问题,顺便展望下未来。

    这一年做的最让自己满意的应该就是看了一些书,由折腾群洋总发起的读书打卡活动,到目前为止已经读完了这几本书,《cUrl 必知必会》,《古董局中局 1》,《古董局中局 2》,《算法图解》,《每天 5 分钟玩转 Kubernetes》《幸福了吗?》《高可用可伸缩微服务架构:基于 Dubbo、Spring Cloud和 Service Mesh》《Rust 权威指南》后面可以写个专题说说看的这些书,虽然每天打卡如果时间安排不好,并且看的书像 rust 这样比较难的话还是会有点小焦虑,不过也是个调整过程,一方面可以在白天就抽空看一会,然后也不必要每次都看很大一章,注重吸收。

    技术上的成长的话,有一些比较小的长进吧,对于一些之前忽视的 synchronized,ThreadLocal 和 AQS 等知识点做了下查漏补缺了,然后多了解了一些 Java 垃圾回收的内容,但是在实操上还是比较欠缺,成型的技术方案,架构上所谓的优化也比较少,一些想法也还有考虑不周全的地方,还需要多花时间和心思去学习加强,特别是在目前已经有的基础上如何做系统深层次的优化,既不要是鸡毛蒜皮的,也不能出现一些不可接受的问题和故障,这是个很重要的课题,需要好好学习,后面考虑定一些周期性目标,两个月左右能有一些成果和总结。

    另外一部分是自己的服务,因为 ucloud 的机器太贵就没续费了,所以都迁移到腾讯云的小机器上了,顺便折腾了一点点 traefik,但是还很不熟练,不太习惯这一套,一方面是 docker 还不习惯,这也加重了对这套环境的不适应,还是习惯裸机部署,另一方面就是 k8s 了,家里的机器还没虚拟化,没有很好的条件可以做实验,这也是读书打卡的一个没做好的点,整体的学习效果受限于深度和实操,后面是看都是用 traefik,也找到了一篇文章可以 traefik 转发到裸机应用,因为主仓库用的是裸机的 gogs。

    还有就是运动减肥上,唉,这又是很大的一个痛点,基本没效果,只是还算稳定,昨天看到一个视频说还需要力量训练来增肌,以此可以提升基础代谢,打算往这个方向尝试下,因为今天没有疫情限制了,在 6 月底完成了 200 公里的跑步小目标,只是有些膝盖跟大腿根外侧不适,抽空得去看下医生,后面打算每天也能做点卷腹跟俯卧撑。

    下半年还希望能继续多看看书,比很多网上各种乱七八糟的文章会好很多,结合豆瓣评分,找一些评价高一些的文章,但也不是说分稍低点的就不行,有些也看人是不是适合,一般 6 分以上评价比较多的就可以试试。

    \ No newline at end of file +Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    redis 在使用 rdb 策略进行备份时,rdb 的意思是会在开启备份的时候将开启时间点的内存数据进行备份,并且可以设置时间,这样子就是这个策略其实还是不完全可靠的,如果是在这个间隔中宕机了,或者间隔过长,不过这个不在这次的要说的内容中,如果自己去写这个 rdb 的策略可能就有点类似于 mvcc 的 redolog,需要知道这个时间点之前的数据是怎么样的,防止后面更改的干扰,但是这样一方面需要有比较复杂的 mvcc 实现,另一方面是很占用存储空间,所以 redis 在这里面使用了 COW (Copy On Write) 技术,这个技术呢以前听过,也大致了解是怎么个意思,这次稍微具体地来看下,其实 redis 的 copy-on-write 就是来自于 linux 的 cow

    Linux中的CopyOnWrite

    fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。这个操作其实可以类比为写屏障,正常的读取是没问题的,当有写入时就会分裂。

    CopyOnWrite的好处:

    1、减少分配和复制资源时带来的瞬时延迟;
    2、减少不必要的资源分配;
    CopyOnWrite的缺点:
    1、如果父子进程都需要进行大量的写操作,会产生大量的分页错误(页异常中断page-fault);

    Redis中的CopyOnWrite

    Redis在持久化时,如果是采用BGSAVE命令或者BGREWRITEAOF的方式,那Redis会fork出一个子进程来读取数据,从而写到磁盘中。
    总体来看,Redis还是读操作比较多。如果子进程存在期间,发生了大量的写操作,那可能就会出现很多的分页错误(页异常中断page-fault),这样就得耗费不少性能在复制上。
    而在rehash阶段上,写操作是无法避免的。所以Redis在fork出子进程之后,将负载因子阈值提高,尽量减少写操作,避免不必要的内存写入操作,最大限度地节约内存。这里其实更巧妙了,在细节上去优化会产生大量页异常中断的情况。

    这届奥运会有可能是我除了 08 年之外关注度最高的一届奥运会,原因可能是因为最近也没什么电影综艺啥的比较好看,前面看跑男倒还行,不是说多好,也就图一乐,最开始看向往的生活觉得也挺不错的,后面变成了统一来了就看黄磊做饭,然后夸黄磊做饭好吃,然后无聊的说这种生活多么多么美好,单调无聊,差不多弃了,这里面还包括大华不在了,大华其实个人还是有点呱噪的,但是挺能搞气氛,并且也有才华,彭彭跟子枫人是不讨厌,但是撑不起来,所以也导致了前面说的结果,都变成了黄磊彩虹屁现场,虽然偶尔怀疑他是否做得好吃,但是整体还是承认的,可对于一个这么多季了的综艺来说,这样也有点单调了。

    还有奥运会像乒乓球,篮球,跳水这几个都是比较喜欢的项目,篮球🏀是从初中开始就也有在自己在玩的,虽然因为身高啊体质基本没什么天赋,但也算是热爱驱动,差不多到了大学因为比较懒才放下了,初中高中还是有很多时间花在上面,不像别人经常打球跑跑跳跳还能长高,我反而一直都没长个子,也因为这个其实蛮遗憾的,后面想想可能是初中的时候远走他乡去住宿读初中,伙食营养跟不上导致的,可能也是自己的一厢情愿吧,总觉得应该还能再长点个,这一点以后我自己的小孩我应该会特别注意这段时间他/她的营养摄入了;然后像乒乓球🏓的话其实小时候是比较讨厌的,因为家里人,父母都没有这类爱好习惯,我也完全不会,但是小学那会班里的“恶霸”就以公平之名要我们男生每个人都排队打几个,我这种不会的反而又要被嘲笑,这个小时候的阴影让我有了比较不好的印象,对它🏓的改观是在工作以后,前司跟一个同样不会的同事经常在饭点会打打,而且那会因为这个其实身体得到了锻炼,感觉是个不错的健身方式,然后又是中国的优势项目,小时候跟着我爸看孔令辉,那时候完全不懂,印象就觉得老瓦很牛,后面其实也没那么关注,上一届好像看了马龙的比赛;跳水也是中国的优势项目,而且也比较简单,不是说真的很简单,就是我们外行观众看着就看看水花大小图一乐。

    这次的观赛过程其实主要还是在乒乓球上面,现在都有点怪我的乌鸦嘴,混双我一直就不太放心(关我什么事,我也不专业),然后一直觉得混双是不是不太稳,结果那天看的时候也是因为央视一套跟五套都没放,我家的有线电视又是没有五加体育,然后用电脑投屏就很卡,看得也很不爽,同时那天因为看的时候已经是 2:0还是再后面点了,一方面是不懂每队只有一次暂停,另一方面不知道已经用过暂停了,所以就特别怀疑马林是不是只会无脑鼓掌,感觉作为教练,并且是前冠军,应该也能在擦汗间隙,或者局间休息调整的时候多给些战略战术的指导,类似于后面男团小胖打奥恰洛夫,像解说都看出来了,其实奥恰那会的反手特别顺,打得特别凶,那就不能让他能特别顺手的上反手位,这当然是外行比较粗浅的看法,在混双过程中其实除了这个,还有让人很不爽的就是我们的许昕跟刘诗雯有种拿不出破釜沉舟的勇气的感觉,在气势上完全被对面两位日本乒乓球最讨厌的两位对手压制着,我都要输了,我就每一颗都要不让你好过,因为真的不是说没有实力,对面水谷隼也不是多么多么强的,可能上一届男团许昕输给他还留着阴影,但是以许昕 19 年男单世界第一的实力,目前也排在世界前三,输一场不应该成为这种阻力,有一些失误也很可惜,后面孙颖莎真的打得很解气,第二局一度以为又要被翻盘了,结果来了个大逆转,女团的时候也是,感觉在心态上孙颖莎还是很值得肯定的,少年老成这个词很适合,看其他的视频也觉得莎莎萌萌哒,陈梦总感觉还欠一点王者霸气,王曼昱还是可以的,反手很凶,我觉得其实这一届日本女乒就是打得非常凶,即使像平野这种看着很弱的妹子,打的球可一点都不弱,也是这种凶狠的打法,有点要压制中国的感觉,这方面我觉得是需要改善的,打这种要不就是实力上的完全碾压,要不就是我实力虽然比较没强多少,但是你狠我打得比你还狠,越保守越要输,我不太成熟的想法是这样的,还有就是面对逆境,这个就要说到男队的了,樊振东跟马龙在半决赛的时候,特别是男团的第二盘,樊振东打奥恰很好地表现了这个心态,当然樊振东我不是特别了解,据说他是比较善于打相持,比较善于焦灼的情况,不过整体看下来樊振东还是有一些欠缺,就是面对情况的快速转变应对,这一点也是马龙特别强的,虽然看起来马龙真的是年纪大了点,没有 16 年那会满头发胶,油光锃亮的大背头和满脸胶原蛋白的意气风发,大范围运动能力也弱了一点,但是经验和能力的全面性也让他最终能再次站上巅峰,还是非常佩服的,这里提一下张继科,虽然可能天赋上是张继科更强点,但是男乒一直都是有强者出现,能为国家队付出这么多并且一直坚持的可不是人人都可以,即使现在同台竞技马龙打不过张继科我还是更喜欢马龙。再来说说我们的对手,主要分三部分,德国男乒,里面有波尔(我刚听到的时候在想怎么又出来个叫波尔的,是不是像举重的石智勇一样,又来一个同名的,结果是同一个,已经四十岁了),这真是个让人敬佩的对手,实力强,经验丰富,虽然男单有点可惜,但是帮助男团获得银牌,真的是起到了定海神针的作用;奥恰洛夫,以前完全不认识,或者说看过也忘了,这次是真的有点意外,竟然有这么个马龙护法,其实他也坦言非常想赢一次马龙,并且在半决赛也非常接近赢得比赛,是个实力非常强的对手,就是男团半决赛输给张本智和有点可惜,有点被打蒙的感觉,佛朗西斯卡的话也是实力不错的选手,就是可能被奥恰跟波尔的光芒掩盖了,跟波尔在男团第一盘男双的比赛中打败日本那对男双也是非常给力的,说实话,最后打国乒的时候的确是国乒实力更胜一筹,但是即使德国赢了我也是充满尊敬,拼的就是硬实力,就像第二盘奥恰打樊振东,反手是真的很强,反过来看奥恰可能也不是很善于快速调整,樊振东打出来自己的节奏,主攻奥恰的中路,他好像没什么好办法解决。再来说我最讨厌的日本,嗯,小日本,张本智和、水谷隼、伊藤美诚,一一评价下(我是外行,绝对主观评价),张本智和,父母也是中国人,原来叫张智和,改日本籍后加了个本,被微博网友笑称日本尖叫鸡,男单输给了斯洛文尼亚选手,男团里是赢了两场,但是在我看来其实实力上可能比不上全力的奥恰,主要是特别能叫,会干扰对手,如果觉得这种也是种能力我也无话可说,要是有那种吼声能直接把对手震聋的,都不需要打比赛了,我简单记了下,赢一颗球,他要叫八声,用 LD 的话来说烦都烦死了,心态是在面对一些困境顺境的应对调整适应能力,而不是对这种噪音的适应能力,至少我是这么看的,所以我很期待樊振东能好好地虐虐他,因为其他像林昀儒真的是非常优秀的新选手,所谓的国乒克星估计也是小日本自己说说的,国乒其实有很多对手,马龙跟樊振东在男单半决赛碰到的这两个几乎都差点把他们掀翻了,所以还是练好自己的实力再来吹吧,免得打脸;水谷隼的话真的是长相就是特别地讨厌,还搞出那套不打比赛的姿态,男团里被波尔干掉就是很好的例子,波尔虽然真的很强,但毕竟 40 岁了,跟伊藤美诚一起说了吧,伊藤实力说实话是有的,混双中很大一部分的赢面来自于她,刘诗雯做了手术状态不好,许昕失误稍多,但是这种赢球了就感觉我赢了你一辈子一场没输的感觉,还有那种不知道怎么形容的笑,实力强的正常打比赛的我都佩服,像女团决赛里,平野跟石川佳纯的打法其实也很凶狠,但是都是正常的比赛,即使中国队两位实力不济输了也很正常,这种就真的需要像孙颖莎这样的小魔王无视各种魔法攻击,无视你各种花里胡哨的打法的人好好教训一下,混双输了以后了解了下她,感觉实力真的不错,是个大威胁,但是其实我们孙颖莎也是经历了九个月的继续成长,像张怡宁也评价了她,可能后面就没什么空间了,当然如果由张怡宁来打她就更适合了,净整这些有的没的,就打得你没脾气。

    乒乓球的说的有点多,就分篇说了,第一篇先到这。

    前面复习了 redis 的过期策略,这里再复习下淘汰策略,淘汰跟过期的区别有时候会被混淆了,过期主要针对那些设置了过期时间的 key,应该说是一种逻辑策略,是主动的还是被动的加定时的,两种有各自的取舍,而淘汰也可以看成是一种保持系统稳定的策略,因为如果内存满了,不采取任何策略处理,那大概率会导致系统故障,之前其实主要从源码角度分析过redis 的 LRU 和 LFU,但这个是偏底层的实现,抠得比较细,那么具体的系统层面的配置是有哪些策略,来看下 redis labs 的介绍

    PolicyDescription
    noeviction 不逐出Returns an error if the memory limit has been reached when trying to insert more data,插入更多数据时,如果内存达到上限了,返回错误
    allkeys-lru 所有的 key 使用 lru 逐出Evicts the least recently used keys out of all keys 在所有 key 中逐出最近最少使用的
    allkeys-lfu 所有的 key 使用 lfu 逐出Evicts the least frequently used keys out of all keys 在所有 key 中逐出最近最不频繁使用的
    allkeys-random 所有的 key 中随机逐出Randomly evicts keys out of all keys 在所有 key 中随机逐出
    volatile-lruEvicts the least recently used keys out of all keys with an “expire” field set 在设置了过期时间的 key 空间 expire 中使用 lru 策略逐出
    volatile-lfuEvicts the least frequently used keys out of all keys with an “expire” field set 在设置了过期时间的 key 空间 expire 中使用 lfu 策略逐出
    volatile-randomRandomly evicts keys with an “expire” field set 在设置了过期时间的 key 空间 expire 中随机逐出
    volatile-ttlEvicts the shortest time-to-live keys out of all keys with an “expire” field set.在设置了过期时间的 key 空间 expire 中逐出更早过期的

    而在这其中默认使用的策略是 volatile-lru,对 lru 跟 lfu 想有更多的了解可以看下我之前的文章redis系列介绍八-淘汰策略

    redis过期策略复习

    之前其实写过redis的过期的一些原理,这次主要是记录下,一些使用上的概念,主要是redis使用的过期策略是懒过期和定时清除,懒过期的其实比较简单,即是在key被访问的时候会顺带着判断下这个key是否已过期了,如果已经过期了,就不返回了,但是这种策略有个漏洞是如果有些key之后一直不会被访问了,就等于沉在池底了,所以需要有一个定时的清理机制,去从设置了过期的key池子(expires)里随机地捞key,具体的策略我们看下官网的解释

    1. Test 20 random keys from the set of keys with an associated expire.
    2. Delete all the keys found expired.
    3. If more than 25% of keys were expired, start again from step 1.

    从池子里随机获取20个key,将其中过期的key删掉,如果这其中有超过25%的key已经过期了,那就再来一次,以此保持过期的key不超过25%(左右),并且这个定时策略可以在redis的配置文件

    # Redis calls an internal function to perform many background tasks, like
    +# closing connections of clients in timeout, purging expired keys that are
    +# never requested, and so forth.
    +#
    +# Not all tasks are performed with the same frequency, but Redis checks for
    +# tasks to perform according to the specified "hz" value.
    +#
    +# By default "hz" is set to 10. Raising the value will use more CPU when
    +# Redis is idle, but at the same time will make Redis more responsive when
    +# there are many keys expiring at the same time, and timeouts may be
    +# handled with more precision.
    +#
    +# The range is between 1 and 500, however a value over 100 is usually not
    +# a good idea. Most users should use the default of 10 and raise this up to
    +# 100 only in environments where very low latency is required.
    +hz 10

    可以配置这个hz的值,代表的含义是每秒的执行次数,默认是10,其实也用了hz的普遍含义。有兴趣可以看看之前写的一篇文章redis系列介绍七-过期策略

    又到半年总结时,第一次写总结类型的文章感觉挺好写的,但是后面总觉得这过去的一段时间所做的事情,能力上的成长低于预期,但是是需要总结下,找找问题,顺便展望下未来。

    这一年做的最让自己满意的应该就是看了一些书,由折腾群洋总发起的读书打卡活动,到目前为止已经读完了这几本书,《cUrl 必知必会》,《古董局中局 1》,《古董局中局 2》,《算法图解》,《每天 5 分钟玩转 Kubernetes》《幸福了吗?》《高可用可伸缩微服务架构:基于 Dubbo、Spring Cloud和 Service Mesh》《Rust 权威指南》后面可以写个专题说说看的这些书,虽然每天打卡如果时间安排不好,并且看的书像 rust 这样比较难的话还是会有点小焦虑,不过也是个调整过程,一方面可以在白天就抽空看一会,然后也不必要每次都看很大一章,注重吸收。

    技术上的成长的话,有一些比较小的长进吧,对于一些之前忽视的 synchronized,ThreadLocal 和 AQS 等知识点做了下查漏补缺了,然后多了解了一些 Java 垃圾回收的内容,但是在实操上还是比较欠缺,成型的技术方案,架构上所谓的优化也比较少,一些想法也还有考虑不周全的地方,还需要多花时间和心思去学习加强,特别是在目前已经有的基础上如何做系统深层次的优化,既不要是鸡毛蒜皮的,也不能出现一些不可接受的问题和故障,这是个很重要的课题,需要好好学习,后面考虑定一些周期性目标,两个月左右能有一些成果和总结。

    另外一部分是自己的服务,因为 ucloud 的机器太贵就没续费了,所以都迁移到腾讯云的小机器上了,顺便折腾了一点点 traefik,但是还很不熟练,不太习惯这一套,一方面是 docker 还不习惯,这也加重了对这套环境的不适应,还是习惯裸机部署,另一方面就是 k8s 了,家里的机器还没虚拟化,没有很好的条件可以做实验,这也是读书打卡的一个没做好的点,整体的学习效果受限于深度和实操,后面是看都是用 traefik,也找到了一篇文章可以 traefik 转发到裸机应用,因为主仓库用的是裸机的 gogs。

    还有就是运动减肥上,唉,这又是很大的一个痛点,基本没效果,只是还算稳定,昨天看到一个视频说还需要力量训练来增肌,以此可以提升基础代谢,打算往这个方向尝试下,因为今天没有疫情限制了,在 6 月底完成了 200 公里的跑步小目标,只是有些膝盖跟大腿根外侧不适,抽空得去看下医生,后面打算每天也能做点卷腹跟俯卧撑。

    下半年还希望能继续多看看书,比很多网上各种乱七八糟的文章会好很多,结合豆瓣评分,找一些评价高一些的文章,但也不是说分稍低点的就不行,有些也看人是不是适合,一般 6 分以上评价比较多的就可以试试。

    \ No newline at end of file diff --git a/page/29/index.html b/page/29/index.html index ead81f6377..877922fcbb 100644 --- a/page/29/index.html +++ b/page/29/index.html @@ -1,13 +1,13 @@ -Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    rabbitmq 介绍

    接触了一下rabbitmq,原来在选型的时候是在rabbitmq跟kafka之间做选择,网上搜了一下之后发现kafka的优势在于吞吐量,而rabbitmq相对注重可靠性,因为应用在im上,需要保证消息不能丢失所以就暂时选定rabbitmq,
    Message Queue的需求由来已久,80年代最早在金融交易中,高盛等公司采用Teknekron公司的产品,当时的Message queuing软件叫做:the information bus(TIB)。 TIB被电信和通讯公司采用,路透社收购了Teknekron公司。之后,IBM开发了MQSeries,微软开发了Microsoft Message Queue(MSMQ)。这些商业MQ供应商的问题是厂商锁定,价格高昂。2001年,Java Message queuing试图解决锁定和交互性的问题,但对应用来说反而更加麻烦了。
    RabbitMQ采用Erlang语言开发。Erlang语言由Ericson设计,专门为开发concurrent和distribution系统的一种语言,在电信领域使用广泛。OTP(Open Telecom Platform)作为Erlang语言的一部分,包含了很多基于Erlang开发的中间件/库/工具,如mnesia/SASL,极大方便了Erlang应用的开发。OTP就类似于Python语言中众多的module,用户借助这些module可以很方便的开发应用。
    于是2004年,摩根大通和iMatrix开始着手Advanced Message Queuing Protocol (AMQP)开放标准的开发。2006年,AMQP规范发布。2007年,Rabbit技术公司基于AMQP标准开发的RabbitMQ 1.0 发布。所有主要的编程语言均有与代理接口通讯的客户端库。

    简单的使用经验

    通俗的理解

    这里介绍下其中的一些概念,connection表示和队列服务器的连接,一般情况下是tcp连接, channel表示通道,可以在一个连接上建立多个通道,这里主要是节省了tcp连接握手的成本,exchange可以理解成一个路由器,将消息推送给对应的队列queue,其实是像一个订阅的模式。

    集群经验

    rabbitmqctl stop这个是关闭rabbitmq,在搭建集群时候先关闭服务,然后使用rabbitmq-server -detached静默启动,这时候使用rabbitmqctl cluster_status查看集群状态,因为还没将节点加入集群,所以只能看到类似

    Cluster status of node rabbit@rabbit1 ...
    -[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
    - {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]}]
    -...done.

    然后就可以把当前节点加入集群,

    rabbit2$ rabbitmqctl stop_app #这个stop_app与stop的区别是前者停的是rabbitmq应用,保留erlang节点,
    -                              #后者是停止了rabbitmq和erlang节点
    -Stopping node rabbit@rabbit2 ...done.
    -rabbit2$ rabbitmqctl join_cluster rabbit@rabbit1 #这里可以用--ram指定将当前节点作为内存节点加入集群
    -Clustering node rabbit@rabbit2 with [rabbit@rabbit1] ...done.
    +Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    rabbitmq 介绍

    接触了一下rabbitmq,原来在选型的时候是在rabbitmq跟kafka之间做选择,网上搜了一下之后发现kafka的优势在于吞吐量,而rabbitmq相对注重可靠性,因为应用在im上,需要保证消息不能丢失所以就暂时选定rabbitmq,
    Message Queue的需求由来已久,80年代最早在金融交易中,高盛等公司采用Teknekron公司的产品,当时的Message queuing软件叫做:the information bus(TIB)。 TIB被电信和通讯公司采用,路透社收购了Teknekron公司。之后,IBM开发了MQSeries,微软开发了Microsoft Message Queue(MSMQ)。这些商业MQ供应商的问题是厂商锁定,价格高昂。2001年,Java Message queuing试图解决锁定和交互性的问题,但对应用来说反而更加麻烦了。
    RabbitMQ采用Erlang语言开发。Erlang语言由Ericson设计,专门为开发concurrent和distribution系统的一种语言,在电信领域使用广泛。OTP(Open Telecom Platform)作为Erlang语言的一部分,包含了很多基于Erlang开发的中间件/库/工具,如mnesia/SASL,极大方便了Erlang应用的开发。OTP就类似于Python语言中众多的module,用户借助这些module可以很方便的开发应用。
    于是2004年,摩根大通和iMatrix开始着手Advanced Message Queuing Protocol (AMQP)开放标准的开发。2006年,AMQP规范发布。2007年,Rabbit技术公司基于AMQP标准开发的RabbitMQ 1.0 发布。所有主要的编程语言均有与代理接口通讯的客户端库。

    简单的使用经验

    通俗的理解

    这里介绍下其中的一些概念,connection表示和队列服务器的连接,一般情况下是tcp连接, channel表示通道,可以在一个连接上建立多个通道,这里主要是节省了tcp连接握手的成本,exchange可以理解成一个路由器,将消息推送给对应的队列queue,其实是像一个订阅的模式。

    集群经验

    rabbitmqctl stop这个是关闭rabbitmq,在搭建集群时候先关闭服务,然后使用rabbitmq-server -detached静默启动,这时候使用rabbitmqctl cluster_status查看集群状态,因为还没将节点加入集群,所以只能看到类似

    Cluster status of node rabbit@rabbit1 ...
    +[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
    + {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]}]
    +...done.

    然后就可以把当前节点加入集群,

    rabbit2$ rabbitmqctl stop_app #这个stop_app与stop的区别是前者停的是rabbitmq应用,保留erlang节点,
    +                              #后者是停止了rabbitmq和erlang节点
    +Stopping node rabbit@rabbit2 ...done.
    +rabbit2$ rabbitmqctl join_cluster rabbit@rabbit1 #这里可以用--ram指定将当前节点作为内存节点加入集群
    +Clustering node rabbit@rabbit2 with [rabbit@rabbit1] ...done.
     rabbit2$ rabbitmqctl start_app
    -Starting node rabbit@rabbit2 ...done.

    其他可以参考官方文档

    一些坑

    消息丢失

    这里碰到过一个坑,对于使用exchange来做消息路由的,会有一个情况,就是在routing_key没被订阅的时候,会将该条找不到路由对应的queue的消息丢掉What happens if we break our contract and send a message with one or four words, like "orange" or "quick.orange.male.rabbit"? Well, these messages won't match any bindings and will be lost.对应链接,而当使用空的exchange时,会保留消息,当出现消费者的时候就可以将收到之前生产者所推送的消息对应链接,这里就是用了空的exchange。

    集群搭建

    集群搭建的时候有个erlang vm生成的random cookie,这个是用来做集群之间认证的,相同的cookie才能连接,但是如果通过vim打开复制后在其他几点新建文件写入会多一个换行,导致集群建立是报错,所以这里最好使用scp等传输命令直接传输cookie文件,同时要注意下cookie的文件权限。
    另外在集群搭建的时候如果更改过hostname,那么要把rabbitmq的数据库删除,否则启动后会马上挂掉

    spark 的一些粗浅使用经验

    工作中学习使用了一下Spark做数据分析,主要是用spark的python接口,首先是pyspark.SparkContext(appName=xxx),这是初始化一个Spark应用实例或者说会话,不能重复,
    返回的实例句柄就可以调用textFile(path)读取文本文件,这里的文本文件可以是HDFS上的文本文件,也可以普通文本文件,但是需要在Spark的所有集群上都存在,否则会
    读取失败,parallelize则可以将python生成的集合数据读取后转换成rdd(A Resilient Distributed Dataset (RDD),一种spark下的基本抽象数据集),基于这个RDD就可以做
    数据的流式计算,例如map reduce,在Spark中可以非常方便地实现

    简单的mapreduce word count示例

    textFile = sc.parallelize([(1,1), (2,1), (3,1), (4,1), (5,1),(1,1), (2,1), (3,1), (4,1), (5,1)])
    +Starting node rabbit@rabbit2 ...done.

    其他可以参考官方文档

    一些坑

    消息丢失

    这里碰到过一个坑,对于使用exchange来做消息路由的,会有一个情况,就是在routing_key没被订阅的时候,会将该条找不到路由对应的queue的消息丢掉What happens if we break our contract and send a message with one or four words, like "orange" or "quick.orange.male.rabbit"? Well, these messages won't match any bindings and will be lost.对应链接,而当使用空的exchange时,会保留消息,当出现消费者的时候就可以将收到之前生产者所推送的消息对应链接,这里就是用了空的exchange。

    集群搭建

    集群搭建的时候有个erlang vm生成的random cookie,这个是用来做集群之间认证的,相同的cookie才能连接,但是如果通过vim打开复制后在其他几点新建文件写入会多一个换行,导致集群建立是报错,所以这里最好使用scp等传输命令直接传输cookie文件,同时要注意下cookie的文件权限。
    另外在集群搭建的时候如果更改过hostname,那么要把rabbitmq的数据库删除,否则启动后会马上挂掉

    spark 的一些粗浅使用经验

    工作中学习使用了一下Spark做数据分析,主要是用spark的python接口,首先是pyspark.SparkContext(appName=xxx),这是初始化一个Spark应用实例或者说会话,不能重复,
    返回的实例句柄就可以调用textFile(path)读取文本文件,这里的文本文件可以是HDFS上的文本文件,也可以普通文本文件,但是需要在Spark的所有集群上都存在,否则会
    读取失败,parallelize则可以将python生成的集合数据读取后转换成rdd(A Resilient Distributed Dataset (RDD),一种spark下的基本抽象数据集),基于这个RDD就可以做
    数据的流式计算,例如map reduce,在Spark中可以非常方便地实现

    简单的mapreduce word count示例

    textFile = sc.parallelize([(1,1), (2,1), (3,1), (4,1), (5,1),(1,1), (2,1), (3,1), (4,1), (5,1)])
     data = textFile.reduceByKey(lambda x, y: x + y).collect()
     for _ in data:
         print(_)

    结果

    (3, 2)
    diff --git a/search.xml b/search.xml
    index d785f06a78..cd7d080d94 100644
    --- a/search.xml
    +++ b/search.xml
    @@ -1,25 +1,5 @@
     
     
    -  
    -    村上春树《1Q84》读后感
    -    /2019/12/18/1Q84%E8%AF%BB%E5%90%8E%E6%84%9F/
    -    看完了村上春树的《1Q84》,这应该是第五本看的他的书了,继 跑步,挪威的森林,刺杀骑士团长,海边的卡夫卡之后,不是其中最长的,好像是海边的卡夫卡还是刺杀骑士团长比较长一点,都是在微信读书上看的,比较方便,最开始在上面看的是高晓松的《鱼羊野史》,不知道为啥取这个名字,但是还是满吸引我的,不过由于去年的种种,没有很多心思把它看完,而且本身的组织形式就是比较松散的,看到哪算哪,其实一些野史部分是我比较喜欢,有些谈到人物的就不太有兴趣,而且类似于大祥哥吃的东西,反正都是哇,怎么这么好吃,嗯,太爱(niu)你(bi)了,高晓松就是这个人是我最喜欢的 xxx 家,我也没去细究过他有没有说重复过,反正是不太爱,后来因为这书还一度对战争史有了浓厚的兴趣,然而事实告诉我,大部头的战争史,其实正史我是真的啃不下去,我可能只对其中 10%的内容感兴趣,不过终于也在今年把它看完了,好像高晓松的晓说也最终季了,貌似其中讲朝鲜战争的还被和谐了,看样子是说出了一些故事(truth)。

    -

    本来只是想把 《1Q84》的读后感写下,现在觉得还是把这篇当成我今年的读书总结吧,不过先从《1Q84》说起。

    -

    严格来讲,这不是很书面化的读后感,可能我想写的也只是像聊天一样的说下我读过的书,包括的技术博客其实也是类似的,以后或许会转变,但是目前水平如此吧,写多了可能会变好,也可能不会。

    -

    开始正文吧,这书有点类似于海边的卡夫卡,一开始是通过两条故事线,穿插着叙述,一条是青豆的,不算是个职业杀手的女杀手,要去解决一个经常家暴的斯文败类,穿着描述得比较性感吧,杀人方式是通过比较长的细针,从脖子后面一个精巧的位置插入,可以造成是未知原因死亡的假象,可能会推断成心梗之类的,这里有个前置的细节,就是青豆是乘坐一辆很高级的出租车,内饰什么的都非常有质感,有点不像一辆出租车,然后车里放了一首比较小众的歌,雅纳切克的《小交响曲》,但是青豆知道它,这跟后面的情节也有些许关系,这是女主人公青豆的出场;相应的男主的出场印象不是太深刻,男主叫天吾,是个不知名的作家,跟一个叫小松的编辑有比较好的关系,虽然天吾还没有拿到比较有分量的奖项,但是小松很看好他,也让他帮忙审校一个新作家奖的投稿文章,虽然天吾自身还没获得过这个奖,天吾还有个正式工作,是当数学老师,天吾在学生时代是个数学天才,但后面有对文学产生了兴趣,文学还不足以养活自己,靠着教课还是能保持温饱;

    -

    接下来是正式故事的起点了,就是小松收到了一部小说投稿,名叫《空气蛹》,是个叫深绘里的女孩子投的稿,小松对他赋予了很高的评价,这里好像记岔了,好像是天吾对这部小说很有好感,但是小松比较怀疑,然后小松看了之后也有了浓厚的兴趣,这里就是开端了,小松想让天吾来重写润色这部《空气蛹》,因为故事本身很有分量,但是描写手法叙事方式等都很拙劣,而天吾正好擅长这个,小松对天吾的评价是,描写技巧无可挑剔,就是故事主体的火花还没际遇迸发,需要一个导火索,这个就可以类比我们程序员,很多比较初中级的程序员主要擅长在原来的代码上修修改改或者给他分配个小功能,比较高级的程序员就需要能做一些项目的架构设计,核心的技术方案设计,以前我也觉得写文档这个比较无聊,但是当一个项目真的比较庞大,复杂的时候,整体和核心部分的架构设计和方案还是需要有文档沉淀的,不然别人不知道没法接受,自己过段时间也会忘记。

    -

    对于小松的这个建议,他的初衷是想搅一搅这个死气沉沉套路颇深的文坛,因为本身《空气蛹》这部小说的内容很吸引人,小松想通过天吾的润色补充让这部小说冲击新人奖,有种恶作剧的意图,天吾对此表示很多担心和顾虑,小松的这个建议其实也是一种文学作假,有两方面的担心,一方面是原作者深绘里是否同意如此操作,一方面是外界如果发现了这个事实会有什么样的后果,但是小松表示不用担心,前一步由小松牵线,让天吾跟原作者深绘里当面沟通这个代写是否被允许,结果当然是被允许了,这里有了对深绘里的初步描写,按我的理解是比较仙的感觉,然后语言沟通有些吃力,或者说有她自己的特色,当面沟通时貌似是让深绘里回去再考虑下,然后后面再由天吾去深绘里寄宿的戎野老师家沟通具体的细节。

    -

    2019年12月18日23:37:19 更新
    去到戎野老师家之后,天吾知道了关于深绘里的一些事情,深绘里的父亲与戎野老师应该是老友,深绘里的父亲在当初成立了一个叫”先驱”的公社,一个独立运行的社会组织,以运营农场作为物资来源,追求更为松散的共同体,即不过分激进地公有制,进行松散的共同生活,承认私有财产,简而言之就是这样一个能稳定存活下来的独立社会组织,但是随着稳定运行,内部的激进派和稳健派开始出现分歧,不可磨合,后来两派就分裂了,深绘里的父亲,深田保留在了稳健派,但是此时其实深田保内心是矛盾的,以为一开始其实是他倡导的独立革命才组织起了这群人,然而现在他又认清了现实社会已经不太相信能通过革命来独立的可能性,后来激进派便开始越加封闭,而且进行军事训练和思想教育,而后这个先驱的激进派别便有了新的名字”黎明”,深绘里也是在此时从先驱逃离来投靠戎野老师
    暂时先写到这,未完待续~

    -]]>
    - - 生活 - 读后感 - 村上春树 - - - 读后感 - -
    2019年终总结 /2020/02/01/2019%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ @@ -74,25 +54,6 @@ 拖更 - - 2020年中总结 - /2020/07/11/2020%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/ - 很快2020 年就过了一半了,而且是今年这么特殊的一年,很多事情都发生的出乎意料,疫情这个绕不过去的话题,之前写了点比较愤青的文字,感觉不太适合发出来就烂在草稿箱里吧,这个目前一大影响估计是今年都没办法完全摘下口罩了,前面几个月来回杭州都开车,因为彭埠大桥不通行了,实在是非常不方便,每条路都灰常堵,心累,吐槽下杭州的交通规划和交警同志,工作实在做的不咋地。

    -

    另外一件是就是蜗壳,从前不知道黝黑蜗壳是啥意思,只是经常会在他的视频里看到,大学的时候在缘网下了一个集锦,炒鸡帅气,各种空接扣篮,越来越能明白那句“你永远不知道意外和明天不知道哪个会先来,且行且珍惜”的含义,只是听了很多道理,依然活不好这一生,知易行难,王阳明真的是这方面的大师,有空可以看看这方面的书,一直想写写我跟篮球跟蜗壳的这十几年,争取能早日写好吧,不过得找个静得下来的时候写。

    -

    正事方面上半年还是挺让人失望的,没有达成一些目标,应该还是能力不足吧,技术方面分析一下还是停留在看的表面层,有些实操的,或者结合业务场景的能力不太行,算是在坚持写写 blog,主要是被这个每周一篇的目标推着走,有时会比较焦虑,内容产出也还比较差,希望能在后面有些改善,可能会降低频率,只是觉得降低了也不一定能有比较好的提升,无法战胜自己的惰性,所以暂时还是坚持下这个目标吧,还有就是 coding 能力,有时候也应该刷刷题,提升思维敏捷度,大脑用太少可能生锈了,况且本来就不是很有优势,虽然失望也只能继续努力吧,日拱一卒,来日方长,加油吧~😔

    -

    还有就是跑步减肥了,截止今天,上半年跑了 136 公里了,因为疫情影响,农历年后是从 4 月 17 号开始跑的,去年跑到了 300 公里,奖励自己了一个手表(真的挺后悔的,还不如 200 块买个手表),今年希望可以能在这个基础上再进一步,一直跟领导说,跑步算是我坚持下来的唯一一个好习惯了,618 买了个跑步机,周末回家了可以不受天气影响的多跑跑,不过如果天气好可能还是会出去跑跑,跑步机跑道多少还是有点拘束,只是感觉可能是我还是吃得太多了🤦‍♂️,效果不是很明显,还在 80 这个坎徘徊,等于浪费了大半年,可能是年初的项目太费心力,压力比较大,吃得更多,是不是可以算工伤😄,这方面也需要好好调整,可以放得开一点,虽然不太可能一下子到位,但是总要去努力下,随着年龄成长总要承担更多,也要看得开一点,没法事事如愿,尽力就好了,减肥这个事情还在结合一些俯卧撑啥的,希望也能坚持下去,加油吧,不知道原话怎么说的,意思是人类最大的勇敢就是看透了人世间的苦难,仍然热爱生活。我当然没可能让内心变得这么强大,试着去努力吧,奥力给!

    -]]>
    - - 生活 - 年中总结 - 2020 - - - 生活 - 2020 - 年中总结 - -
    2021 年终总结 /2022/01/22/2021-%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ @@ -111,26 +72,23 @@ - 2021 年中总结 - /2021/07/18/2021-%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/ - 又到半年总结时,第一次写总结类型的文章感觉挺好写的,但是后面总觉得这过去的一段时间所做的事情,能力上的成长低于预期,但是是需要总结下,找找问题,顺便展望下未来。

    -

    这一年做的最让自己满意的应该就是看了一些书,由折腾群洋总发起的读书打卡活动,到目前为止已经读完了这几本书,《cUrl 必知必会》,《古董局中局 1》,《古董局中局 2》,《算法图解》,《每天 5 分钟玩转 Kubernetes》《幸福了吗?》《高可用可伸缩微服务架构:基于 Dubbo、Spring Cloud和 Service Mesh》《Rust 权威指南》后面可以写个专题说说看的这些书,虽然每天打卡如果时间安排不好,并且看的书像 rust 这样比较难的话还是会有点小焦虑,不过也是个调整过程,一方面可以在白天就抽空看一会,然后也不必要每次都看很大一章,注重吸收。

    -

    技术上的成长的话,有一些比较小的长进吧,对于一些之前忽视的 synchronized,ThreadLocal 和 AQS 等知识点做了下查漏补缺了,然后多了解了一些 Java 垃圾回收的内容,但是在实操上还是比较欠缺,成型的技术方案,架构上所谓的优化也比较少,一些想法也还有考虑不周全的地方,还需要多花时间和心思去学习加强,特别是在目前已经有的基础上如何做系统深层次的优化,既不要是鸡毛蒜皮的,也不能出现一些不可接受的问题和故障,这是个很重要的课题,需要好好学习,后面考虑定一些周期性目标,两个月左右能有一些成果和总结。

    -

    另外一部分是自己的服务,因为 ucloud 的机器太贵就没续费了,所以都迁移到腾讯云的小机器上了,顺便折腾了一点点 traefik,但是还很不熟练,不太习惯这一套,一方面是 docker 还不习惯,这也加重了对这套环境的不适应,还是习惯裸机部署,另一方面就是 k8s 了,家里的机器还没虚拟化,没有很好的条件可以做实验,这也是读书打卡的一个没做好的点,整体的学习效果受限于深度和实操,后面是看都是用 traefik,也找到了一篇文章可以 traefik 转发到裸机应用,因为主仓库用的是裸机的 gogs。

    -

    还有就是运动减肥上,唉,这又是很大的一个痛点,基本没效果,只是还算稳定,昨天看到一个视频说还需要力量训练来增肌,以此可以提升基础代谢,打算往这个方向尝试下,因为今天没有疫情限制了,在 6 月底完成了 200 公里的跑步小目标,只是有些膝盖跟大腿根外侧不适,抽空得去看下医生,后面打算每天也能做点卷腹跟俯卧撑。

    -

    下半年还希望能继续多看看书,比很多网上各种乱七八糟的文章会好很多,结合豆瓣评分,找一些评价高一些的文章,但也不是说分稍低点的就不行,有些也看人是不是适合,一般 6 分以上评价比较多的就可以试试。

    + 村上春树《1Q84》读后感 + /2019/12/18/1Q84%E8%AF%BB%E5%90%8E%E6%84%9F/ + 看完了村上春树的《1Q84》,这应该是第五本看的他的书了,继 跑步,挪威的森林,刺杀骑士团长,海边的卡夫卡之后,不是其中最长的,好像是海边的卡夫卡还是刺杀骑士团长比较长一点,都是在微信读书上看的,比较方便,最开始在上面看的是高晓松的《鱼羊野史》,不知道为啥取这个名字,但是还是满吸引我的,不过由于去年的种种,没有很多心思把它看完,而且本身的组织形式就是比较松散的,看到哪算哪,其实一些野史部分是我比较喜欢,有些谈到人物的就不太有兴趣,而且类似于大祥哥吃的东西,反正都是哇,怎么这么好吃,嗯,太爱(niu)你(bi)了,高晓松就是这个人是我最喜欢的 xxx 家,我也没去细究过他有没有说重复过,反正是不太爱,后来因为这书还一度对战争史有了浓厚的兴趣,然而事实告诉我,大部头的战争史,其实正史我是真的啃不下去,我可能只对其中 10%的内容感兴趣,不过终于也在今年把它看完了,好像高晓松的晓说也最终季了,貌似其中讲朝鲜战争的还被和谐了,看样子是说出了一些故事(truth)。

    +

    本来只是想把 《1Q84》的读后感写下,现在觉得还是把这篇当成我今年的读书总结吧,不过先从《1Q84》说起。

    +

    严格来讲,这不是很书面化的读后感,可能我想写的也只是像聊天一样的说下我读过的书,包括的技术博客其实也是类似的,以后或许会转变,但是目前水平如此吧,写多了可能会变好,也可能不会。

    +

    开始正文吧,这书有点类似于海边的卡夫卡,一开始是通过两条故事线,穿插着叙述,一条是青豆的,不算是个职业杀手的女杀手,要去解决一个经常家暴的斯文败类,穿着描述得比较性感吧,杀人方式是通过比较长的细针,从脖子后面一个精巧的位置插入,可以造成是未知原因死亡的假象,可能会推断成心梗之类的,这里有个前置的细节,就是青豆是乘坐一辆很高级的出租车,内饰什么的都非常有质感,有点不像一辆出租车,然后车里放了一首比较小众的歌,雅纳切克的《小交响曲》,但是青豆知道它,这跟后面的情节也有些许关系,这是女主人公青豆的出场;相应的男主的出场印象不是太深刻,男主叫天吾,是个不知名的作家,跟一个叫小松的编辑有比较好的关系,虽然天吾还没有拿到比较有分量的奖项,但是小松很看好他,也让他帮忙审校一个新作家奖的投稿文章,虽然天吾自身还没获得过这个奖,天吾还有个正式工作,是当数学老师,天吾在学生时代是个数学天才,但后面有对文学产生了兴趣,文学还不足以养活自己,靠着教课还是能保持温饱;

    +

    接下来是正式故事的起点了,就是小松收到了一部小说投稿,名叫《空气蛹》,是个叫深绘里的女孩子投的稿,小松对他赋予了很高的评价,这里好像记岔了,好像是天吾对这部小说很有好感,但是小松比较怀疑,然后小松看了之后也有了浓厚的兴趣,这里就是开端了,小松想让天吾来重写润色这部《空气蛹》,因为故事本身很有分量,但是描写手法叙事方式等都很拙劣,而天吾正好擅长这个,小松对天吾的评价是,描写技巧无可挑剔,就是故事主体的火花还没际遇迸发,需要一个导火索,这个就可以类比我们程序员,很多比较初中级的程序员主要擅长在原来的代码上修修改改或者给他分配个小功能,比较高级的程序员就需要能做一些项目的架构设计,核心的技术方案设计,以前我也觉得写文档这个比较无聊,但是当一个项目真的比较庞大,复杂的时候,整体和核心部分的架构设计和方案还是需要有文档沉淀的,不然别人不知道没法接受,自己过段时间也会忘记。

    +

    对于小松的这个建议,他的初衷是想搅一搅这个死气沉沉套路颇深的文坛,因为本身《空气蛹》这部小说的内容很吸引人,小松想通过天吾的润色补充让这部小说冲击新人奖,有种恶作剧的意图,天吾对此表示很多担心和顾虑,小松的这个建议其实也是一种文学作假,有两方面的担心,一方面是原作者深绘里是否同意如此操作,一方面是外界如果发现了这个事实会有什么样的后果,但是小松表示不用担心,前一步由小松牵线,让天吾跟原作者深绘里当面沟通这个代写是否被允许,结果当然是被允许了,这里有了对深绘里的初步描写,按我的理解是比较仙的感觉,然后语言沟通有些吃力,或者说有她自己的特色,当面沟通时貌似是让深绘里回去再考虑下,然后后面再由天吾去深绘里寄宿的戎野老师家沟通具体的细节。

    +

    2019年12月18日23:37:19 更新
    去到戎野老师家之后,天吾知道了关于深绘里的一些事情,深绘里的父亲与戎野老师应该是老友,深绘里的父亲在当初成立了一个叫”先驱”的公社,一个独立运行的社会组织,以运营农场作为物资来源,追求更为松散的共同体,即不过分激进地公有制,进行松散的共同生活,承认私有财产,简而言之就是这样一个能稳定存活下来的独立社会组织,但是随着稳定运行,内部的激进派和稳健派开始出现分歧,不可磨合,后来两派就分裂了,深绘里的父亲,深田保留在了稳健派,但是此时其实深田保内心是矛盾的,以为一开始其实是他倡导的独立革命才组织起了这群人,然而现在他又认清了现实社会已经不太相信能通过革命来独立的可能性,后来激进派便开始越加封闭,而且进行军事训练和思想教育,而后这个先驱的激进派别便有了新的名字”黎明”,深绘里也是在此时从先驱逃离来投靠戎野老师
    暂时先写到这,未完待续~

    ]]>
    生活 - 年中总结 - 2021 + 读后感 + 村上春树 - 生活 - 2021 - 年中总结 - 技术 - 读书 + 读后感
    @@ -631,69 +589,45 @@ - AbstractQueuedSynchronizer - /2019/09/23/AbstractQueuedSynchronizer/ - 最近看了大神的 AQS 的文章,之前总是断断续续地看一点,每次都知难而退,下次看又从头开始,昨天总算硬着头皮看完了第一部分
    首先 AQS 只要有这些属性

    -
    // 头结点,你直接把它当做 当前持有锁的线程 可能是最好理解的
    -private transient volatile Node head;
    -
    -// 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个链表
    -private transient volatile Node tail;
    -
    -// 这个是最重要的,代表当前锁的状态,0代表没有被占用,大于 0 代表有线程持有当前锁
    -// 这个值可以大于 1,是因为锁可以重入,每次重入都加上 1
    -private volatile int state;
    -
    -// 代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入
    -// reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁
    -// if (currentThread == getExclusiveOwnerThread()) {state++}
    -private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer
    -

    大概了解了 aqs 底层的双向等待队列,
    结构是这样的

    每个 node 里面主要是的代码结构也比较简单

    -
    static final class Node {
    -    // 标识节点当前在共享模式下
    -    static final Node SHARED = new Node();
    -    // 标识节点当前在独占模式下
    -    static final Node EXCLUSIVE = null;
    -
    -    // ======== 下面的几个int常量是给waitStatus用的 ===========
    -    /** waitStatus value to indicate thread has cancelled */
    -    // 代码此线程取消了争抢这个锁
    -    static final int CANCELLED =  1;
    -    /** waitStatus value to indicate successor's thread needs unparking */
    -    // 官方的描述是,其表示当前node的后继节点对应的线程需要被唤醒
    -    static final int SIGNAL    = -1;
    -    /** waitStatus value to indicate thread is waiting on condition */
    -    // 本文不分析condition,所以略过吧,下一篇文章会介绍这个
    -    static final int CONDITION = -2;
    -    /**
    -     * waitStatus value to indicate the next acquireShared should
    -     * unconditionally propagate
    -     */
    -    // 同样的不分析,略过吧
    -    static final int PROPAGATE = -3;
    -    // =====================================================
    -
    -
    -    // 取值为上面的1、-1、-2、-3,或者0(以后会讲到)
    -    // 这么理解,暂时只需要知道如果这个值 大于0 代表此线程取消了等待,
    -    //    ps: 半天抢不到锁,不抢了,ReentrantLock是可以指定timeouot的。。。
    -    volatile int waitStatus;
    -    // 前驱节点的引用
    -    volatile Node prev;
    -    // 后继节点的引用
    -    volatile Node next;
    -    // 这个就是线程本尊
    -    volatile Thread thread;
    -
    -}
    -

    其实可以主要关注这个 waitStatus 因为这个是后面的节点给前面的节点设置的,等于-1 的时候代表后面有节点等待,需要去唤醒,
    这里使用了一个变种的 CLH 队列实现,CLH 队列相关内容可以查看这篇 自旋锁、排队自旋锁、MCS锁、CLH锁

    + 2021 年中总结 + /2021/07/18/2021-%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/ + 又到半年总结时,第一次写总结类型的文章感觉挺好写的,但是后面总觉得这过去的一段时间所做的事情,能力上的成长低于预期,但是是需要总结下,找找问题,顺便展望下未来。

    +

    这一年做的最让自己满意的应该就是看了一些书,由折腾群洋总发起的读书打卡活动,到目前为止已经读完了这几本书,《cUrl 必知必会》,《古董局中局 1》,《古董局中局 2》,《算法图解》,《每天 5 分钟玩转 Kubernetes》《幸福了吗?》《高可用可伸缩微服务架构:基于 Dubbo、Spring Cloud和 Service Mesh》《Rust 权威指南》后面可以写个专题说说看的这些书,虽然每天打卡如果时间安排不好,并且看的书像 rust 这样比较难的话还是会有点小焦虑,不过也是个调整过程,一方面可以在白天就抽空看一会,然后也不必要每次都看很大一章,注重吸收。

    +

    技术上的成长的话,有一些比较小的长进吧,对于一些之前忽视的 synchronized,ThreadLocal 和 AQS 等知识点做了下查漏补缺了,然后多了解了一些 Java 垃圾回收的内容,但是在实操上还是比较欠缺,成型的技术方案,架构上所谓的优化也比较少,一些想法也还有考虑不周全的地方,还需要多花时间和心思去学习加强,特别是在目前已经有的基础上如何做系统深层次的优化,既不要是鸡毛蒜皮的,也不能出现一些不可接受的问题和故障,这是个很重要的课题,需要好好学习,后面考虑定一些周期性目标,两个月左右能有一些成果和总结。

    +

    另外一部分是自己的服务,因为 ucloud 的机器太贵就没续费了,所以都迁移到腾讯云的小机器上了,顺便折腾了一点点 traefik,但是还很不熟练,不太习惯这一套,一方面是 docker 还不习惯,这也加重了对这套环境的不适应,还是习惯裸机部署,另一方面就是 k8s 了,家里的机器还没虚拟化,没有很好的条件可以做实验,这也是读书打卡的一个没做好的点,整体的学习效果受限于深度和实操,后面是看都是用 traefik,也找到了一篇文章可以 traefik 转发到裸机应用,因为主仓库用的是裸机的 gogs。

    +

    还有就是运动减肥上,唉,这又是很大的一个痛点,基本没效果,只是还算稳定,昨天看到一个视频说还需要力量训练来增肌,以此可以提升基础代谢,打算往这个方向尝试下,因为今天没有疫情限制了,在 6 月底完成了 200 公里的跑步小目标,只是有些膝盖跟大腿根外侧不适,抽空得去看下医生,后面打算每天也能做点卷腹跟俯卧撑。

    +

    下半年还希望能继续多看看书,比很多网上各种乱七八糟的文章会好很多,结合豆瓣评分,找一些评价高一些的文章,但也不是说分稍低点的就不行,有些也看人是不是适合,一般 6 分以上评价比较多的就可以试试。

    ]]>
    - java + 生活 + 年中总结 + 2021 - java - aqs + 生活 + 2021 + 技术 + 读书 + 年中总结 + +
    + + 2020年中总结 + /2020/07/11/2020%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/ + 很快2020 年就过了一半了,而且是今年这么特殊的一年,很多事情都发生的出乎意料,疫情这个绕不过去的话题,之前写了点比较愤青的文字,感觉不太适合发出来就烂在草稿箱里吧,这个目前一大影响估计是今年都没办法完全摘下口罩了,前面几个月来回杭州都开车,因为彭埠大桥不通行了,实在是非常不方便,每条路都灰常堵,心累,吐槽下杭州的交通规划和交警同志,工作实在做的不咋地。

    +

    另外一件是就是蜗壳,从前不知道黝黑蜗壳是啥意思,只是经常会在他的视频里看到,大学的时候在缘网下了一个集锦,炒鸡帅气,各种空接扣篮,越来越能明白那句“你永远不知道意外和明天不知道哪个会先来,且行且珍惜”的含义,只是听了很多道理,依然活不好这一生,知易行难,王阳明真的是这方面的大师,有空可以看看这方面的书,一直想写写我跟篮球跟蜗壳的这十几年,争取能早日写好吧,不过得找个静得下来的时候写。

    +

    正事方面上半年还是挺让人失望的,没有达成一些目标,应该还是能力不足吧,技术方面分析一下还是停留在看的表面层,有些实操的,或者结合业务场景的能力不太行,算是在坚持写写 blog,主要是被这个每周一篇的目标推着走,有时会比较焦虑,内容产出也还比较差,希望能在后面有些改善,可能会降低频率,只是觉得降低了也不一定能有比较好的提升,无法战胜自己的惰性,所以暂时还是坚持下这个目标吧,还有就是 coding 能力,有时候也应该刷刷题,提升思维敏捷度,大脑用太少可能生锈了,况且本来就不是很有优势,虽然失望也只能继续努力吧,日拱一卒,来日方长,加油吧~😔

    +

    还有就是跑步减肥了,截止今天,上半年跑了 136 公里了,因为疫情影响,农历年后是从 4 月 17 号开始跑的,去年跑到了 300 公里,奖励自己了一个手表(真的挺后悔的,还不如 200 块买个手表),今年希望可以能在这个基础上再进一步,一直跟领导说,跑步算是我坚持下来的唯一一个好习惯了,618 买了个跑步机,周末回家了可以不受天气影响的多跑跑,不过如果天气好可能还是会出去跑跑,跑步机跑道多少还是有点拘束,只是感觉可能是我还是吃得太多了🤦‍♂️,效果不是很明显,还在 80 这个坎徘徊,等于浪费了大半年,可能是年初的项目太费心力,压力比较大,吃得更多,是不是可以算工伤😄,这方面也需要好好调整,可以放得开一点,虽然不太可能一下子到位,但是总要去努力下,随着年龄成长总要承担更多,也要看得开一点,没法事事如愿,尽力就好了,减肥这个事情还在结合一些俯卧撑啥的,希望也能坚持下去,加油吧,不知道原话怎么说的,意思是人类最大的勇敢就是看透了人世间的苦难,仍然热爱生活。我当然没可能让内心变得这么强大,试着去努力吧,奥力给!

    +]]>
    + + 生活 + 年中总结 + 2020 + + + 生活 + 2020 + 年中总结
    @@ -800,283 +734,69 @@ public: - Apollo 的 value 注解是怎么自动更新的 - /2020/11/01/Apollo-%E7%9A%84-value-%E6%B3%A8%E8%A7%A3%E6%98%AF%E6%80%8E%E4%B9%88%E8%87%AA%E5%8A%A8%E6%9B%B4%E6%96%B0%E7%9A%84/ - 在前司和目前公司,用的配置中心都是使用的 Apollo,经过了业界验证,比较强大的配置管理系统,特别是在0.10 后开始支持对使用 value 注解的配置值进行自动更新,今天刚好有个同学问到我,就顺便写篇文章记录下,其实也是借助于 spring 强大的 bean 生命周期管理,可以实现BeanPostProcessor接口,使用postProcessBeforeInitialization方法,来对bean 内部的属性和方法进行判断,是否有 value 注解,如果有就是将它注册到一个 map 中,可以看到这个方法com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor#processField

    -
    @Override
    -  protected void processField(Object bean, String beanName, Field field) {
    -    // register @Value on field
    -    Value value = field.getAnnotation(Value.class);
    -    if (value == null) {
    -      return;
    -    }
    -    Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
    -
    -    if (keys.isEmpty()) {
    -      return;
    -    }
    +    AbstractQueuedSynchronizer
    +    /2019/09/23/AbstractQueuedSynchronizer/
    +    最近看了大神的 AQS 的文章,之前总是断断续续地看一点,每次都知难而退,下次看又从头开始,昨天总算硬着头皮看完了第一部分
    首先 AQS 只要有这些属性

    +
    // 头结点,你直接把它当做 当前持有锁的线程 可能是最好理解的
    +private transient volatile Node head;
     
    -    for (String key : keys) {
    -      SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
    -      springValueRegistry.register(beanFactory, key, springValue);
    -      logger.debug("Monitoring {}", springValue);
    -    }
    -  }
    -

    然后我们看下这个springValueRegistry是啥玩意

    -
    public class SpringValueRegistry {
    -  private static final long CLEAN_INTERVAL_IN_SECONDS = 5;
    -  private final Map<BeanFactory, Multimap<String, SpringValue>> registry = Maps.newConcurrentMap();
    -  private final AtomicBoolean initialized = new AtomicBoolean(false);
    -  private final Object LOCK = new Object();
    +// 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个链表
    +private transient volatile Node tail;
     
    -  public void register(BeanFactory beanFactory, String key, SpringValue springValue) {
    -    if (!registry.containsKey(beanFactory)) {
    -      synchronized (LOCK) {
    -        if (!registry.containsKey(beanFactory)) {
    -          registry.put(beanFactory, LinkedListMultimap.<String, SpringValue>create());
    -        }
    -      }
    -    }
    +// 这个是最重要的,代表当前锁的状态,0代表没有被占用,大于 0 代表有线程持有当前锁
    +// 这个值可以大于 1,是因为锁可以重入,每次重入都加上 1
    +private volatile int state;
     
    -    registry.get(beanFactory).put(key, springValue);
    +// 代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入
    +// reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁
    +// if (currentThread == getExclusiveOwnerThread()) {state++}
    +private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer
    +

    大概了解了 aqs 底层的双向等待队列,
    结构是这样的

    每个 node 里面主要是的代码结构也比较简单

    +
    static final class Node {
    +    // 标识节点当前在共享模式下
    +    static final Node SHARED = new Node();
    +    // 标识节点当前在独占模式下
    +    static final Node EXCLUSIVE = null;
     
    -    // lazy initialize
    -    if (initialized.compareAndSet(false, true)) {
    -      initialize();
    -    }
    -  }
    -

    这类其实就是个 map 来存放 springvalue,然后有com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener来监听更新操作,当有变更时

    -
    @Override
    - public void onChange(ConfigChangeEvent changeEvent) {
    -   Set<String> keys = changeEvent.changedKeys();
    -   if (CollectionUtils.isEmpty(keys)) {
    -     return;
    -   }
    -   for (String key : keys) {
    -     // 1. check whether the changed key is relevant
    -     Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
    -     if (targetValues == null || targetValues.isEmpty()) {
    -       continue;
    -     }
    +    // ======== 下面的几个int常量是给waitStatus用的 ===========
    +    /** waitStatus value to indicate thread has cancelled */
    +    // 代码此线程取消了争抢这个锁
    +    static final int CANCELLED =  1;
    +    /** waitStatus value to indicate successor's thread needs unparking */
    +    // 官方的描述是,其表示当前node的后继节点对应的线程需要被唤醒
    +    static final int SIGNAL    = -1;
    +    /** waitStatus value to indicate thread is waiting on condition */
    +    // 本文不分析condition,所以略过吧,下一篇文章会介绍这个
    +    static final int CONDITION = -2;
    +    /**
    +     * waitStatus value to indicate the next acquireShared should
    +     * unconditionally propagate
    +     */
    +    // 同样的不分析,略过吧
    +    static final int PROPAGATE = -3;
    +    // =====================================================
     
    -     // 2. check whether the value is really changed or not (since spring property sources have hierarchies)
    -     // 这里其实有一点比较绕,是因为 Apollo 里的 namespace 划分,会出现 key 相同,但是 namespace 不同的情况,所以会有个优先级存在,所以需要去校验 environment 里面的是否已经更新,如果未更新则表示不需要更新
    -     if (!shouldTriggerAutoUpdate(changeEvent, key)) {
    -       continue;
    -     }
     
    -     // 3. update the value
    -     for (SpringValue val : targetValues) {
    -       updateSpringValue(val);
    -     }
    -   }
    - }
    -

    其实原理很简单,就是得了解知道下

    -]]>
    - - Java - Apollo - value - - - Java - Apollo - value - 注解 - environment - - - - Clone Graph Part I - /2014/12/30/Clone-Graph-Part-I/ - problem
    Clone a graph. Input is a Node pointer. Return the Node pointer of the cloned graph.
    -
    -A graph is defined below:
    -struct Node {
    -vector neighbors;
    -}
    - - -

    code

    typedef unordered_map<Node *, Node *> Map;
    - 
    -Node *clone(Node *graph) {
    -    if (!graph) return NULL;
    - 
    -    Map map;
    -    queue<Node *> q;
    -    q.push(graph);
    - 
    -    Node *graphCopy = new Node();
    -    map[graph] = graphCopy;
    - 
    -    while (!q.empty()) {
    -        Node *node = q.front();
    -        q.pop();
    -        int n = node->neighbors.size();
    -        for (int i = 0; i < n; i++) {
    -            Node *neighbor = node->neighbors[i];
    -            // no copy exists
    -            if (map.find(neighbor) == map.end()) {
    -                Node *p = new Node();
    -                map[node]->neighbors.push_back(p);
    -                map[neighbor] = p;
    -                q.push(neighbor);
    -            } else {     // a copy already exists
    -                map[node]->neighbors.push_back(map[neighbor]);
    -            }
    -        }
    -    }
    - 
    -    return graphCopy;
    -}
    -

    anlysis

    using the Breadth-first traversal
    and use a map to save the neighbors not to be duplicated.

    -]]>
    - - leetcode - - - C++ - leetcode - -
    - - Comparator使用小记 - /2020/04/05/Comparator%E4%BD%BF%E7%94%A8%E5%B0%8F%E8%AE%B0/ - 在Java8的stream之前,将对象进行排序的时候,可能需要对象实现Comparable接口,或者自己实现一个Comparator,

    -

    比如这样子

    -

    我的对象是Entity

    -
    public class Entity {
    -
    -    private Long id;
    -
    -    private Long sortValue;
    -
    -    public Long getId() {
    -        return id;
    -    }
    -
    -    public void setId(Long id) {
    -        this.id = id;
    -    }
    -
    -    public Long getSortValue() {
    -        return sortValue;
    -    }
    -
    -    public void setSortValue(Long sortValue) {
    -        this.sortValue = sortValue;
    -    }
    -}
    - -

    Comparator

    -
    public class MyComparator implements Comparator {
    -    @Override
    -    public int compare(Object o1, Object o2) {
    -        Entity e1 = (Entity) o1;
    -        Entity e2 = (Entity) o2;
    -        if (e1.getSortValue() < e2.getSortValue()) {
    -            return -1;
    -        } else if (e1.getSortValue().equals(e2.getSortValue())) {
    -            return 0;
    -        } else {
    -            return 1;
    -        }
    -    }
    -}
    - -

    比较代码

    -
    private static MyComparator myComparator = new MyComparator();
    -
    -    public static void main(String[] args) {
    -        List<Entity> list = new ArrayList<Entity>();
    -        Entity e1 = new Entity();
    -        e1.setId(1L);
    -        e1.setSortValue(1L);
    -        list.add(e1);
    -        Entity e2 = new Entity();
    -        e2.setId(2L);
    -        e2.setSortValue(null);
    -        list.add(e2);
    -        Collections.sort(list, myComparator);
    - -

    看到这里的e2的排序值是null,在Comparator中如果要正常运行的话,就得判空之类的,这里有两点需要,一个是不想写这个MyComparator,然后也没那么好排除掉list里排序值,那么有什么办法能解决这种问题呢,应该说java的这方面真的是很强大

    -

    -

    看一下nullsFirst的实现

    -
    final static class NullComparator<T> implements Comparator<T>, Serializable {
    -        private static final long serialVersionUID = -7569533591570686392L;
    -        private final boolean nullFirst;
    -        // if null, non-null Ts are considered equal
    -        private final Comparator<T> real;
    -
    -        @SuppressWarnings("unchecked")
    -        NullComparator(boolean nullFirst, Comparator<? super T> real) {
    -            this.nullFirst = nullFirst;
    -            this.real = (Comparator<T>) real;
    -        }
    -
    -        @Override
    -        public int compare(T a, T b) {
    -            if (a == null) {
    -                return (b == null) ? 0 : (nullFirst ? -1 : 1);
    -            } else if (b == null) {
    -                return nullFirst ? 1: -1;
    -            } else {
    -                return (real == null) ? 0 : real.compare(a, b);
    -            }
    -        }
    - -

    核心代码就是下面这段,其实就是帮我们把前面要做的事情做掉了,是不是挺方便的,小记一下哈

    -]]>
    - - Java - 集合 - - - Java - Stream - Comparator - 排序 - sort - nullsfirst - -
    - - Disruptor 系列二 - /2022/02/27/Disruptor-%E7%B3%BB%E5%88%97%E4%BA%8C/ - 这里开始慢慢深入的讲一下 disruptor,首先是 lock free , 相比于前面介绍的两个阻塞队列,
    disruptor 本身是不直接使用锁的,因为本身的设计是单个线程去生产,通过 cas 来维护头指针,
    不直接维护尾指针,这样就减少了锁的使用,提升了性能;第二个是这次介绍的重点,
    减少 false sharing 的情况,也就是常说的 伪共享 问题,那么什么叫 伪共享 呢,
    这里要扯到一些 cpu 缓存的知识,

    譬如我在用的这个笔记本

    这里就可能看到 L2 Cache 就是针对每个核的

    这里可以看到现代 CPU 的结构里,分为三级缓存,越靠近 cpu 的速度越快,存储容量越小,
    而 L1 跟 L2 是 CPU 核专属的每个核都有自己的 L1 和 L2 的,其中 L1 还分为数据和指令,
    像我上面的图中显示的 L1 Cache 只有 64KB 大小,其中数据 32KB,指令 32KB,
    而 L2 则有 256KB,L3 有 4MB,其中的 Line Size 是我们这里比较重要的一个值,
    CPU 其实会就近地从 Cache 中读取数据,碰到 Cache Miss 就再往下一级 Cache 读取,
    每次读取是按照缓存行 Cache Line 读取,并且也遵循了“就近原则”,
    也就是相近的数据有可能也会马上被读取,所以以行的形式读取,然而这也造成了 false sharing
    因为类似于 ArrayBlockingQueue,需要有 takeIndex , putIndex , count , 因为在同一个类中,
    很有可能存在于同一个 Cache Line 中,但是这几个值会被不同的线程修改,
    导致从 Cache 取出来以后立马就会被失效,所谓的就近原则也就没用了,
    因为需要反复地标记 dirty 脏位,然后把 Cache 刷掉,就造成了false sharing这种情况
    而在 disruptor 中则使用了填充的方式,让我的头指针能够不产生false sharing

    -
    class LhsPadding
    -{
    -    protected long p1, p2, p3, p4, p5, p6, p7;
    -}
    -
    -class Value extends LhsPadding
    -{
    -    protected volatile long value;
    -}
    -
    -class RhsPadding extends Value
    -{
    -    protected long p9, p10, p11, p12, p13, p14, p15;
    -}
    +    // 取值为上面的1、-1、-2、-3,或者0(以后会讲到)
    +    // 这么理解,暂时只需要知道如果这个值 大于0 代表此线程取消了等待,
    +    //    ps: 半天抢不到锁,不抢了,ReentrantLock是可以指定timeouot的。。。
    +    volatile int waitStatus;
    +    // 前驱节点的引用
    +    volatile Node prev;
    +    // 后继节点的引用
    +    volatile Node next;
    +    // 这个就是线程本尊
    +    volatile Thread thread;
     
    -/**
    - * <p>Concurrent sequence class used for tracking the progress of
    - * the ring buffer and event processors.  Support a number
    - * of concurrent operations including CAS and order writes.
    - *
    - * <p>Also attempts to be more efficient with regards to false
    - * sharing by adding padding around the volatile field.
    - */
    -public class Sequence extends RhsPadding
    -{
    -

    通过代码可以看到,sequence 中其实真正有意义的是 value 字段,因为需要在多线程环境下可见也
    使用了volatile 关键字,而 LhsPaddingRhsPadding 分别在value 前后填充了各
    7 个 long 型的变量,long 型的变量在 Java 中是占用 8 bytes,这样就相当于不管怎么样,
    value 都会单独使用一个缓存行,使得其不会产生 false sharing 的问题。

    +}
    +

    其实可以主要关注这个 waitStatus 因为这个是后面的节点给前面的节点设置的,等于-1 的时候代表后面有节点等待,需要去唤醒,
    这里使用了一个变种的 CLH 队列实现,CLH 队列相关内容可以查看这篇 自旋锁、排队自旋锁、MCS锁、CLH锁

    ]]>
    - Java + java - Java - Disruptor + java + aqs
    @@ -1308,35 +1028,322 @@ Node *clone(Node *graph) { - Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥? - /2020/08/22/Filter-Intercepter-Aop-%E5%95%A5-%E5%95%A5-%E5%95%A5-%E8%BF%99%E4%BA%9B%E9%83%BD%E6%98%AF%E5%95%A5/ - 本来是想取个像现在那些公众号转了又转的文章标题,”面试官再问你xxxxx,就把这篇文章甩给他看”这种标题,但是觉得实在太 low 了,还是用一部我比较喜欢的电影里的一句台词,《人在囧途》里王宝强对着那张老板给他的欠条,看不懂字时候说的那句,这些都是些啥(第四声)
    当我刚开始面 Java 的时候,其实我真的没注意这方面的东西,实话说就是不知道这些是啥,开发中用过 Interceptor和 Aop,了解 aop 的实现原理,但是不知道 Java web 中的 Filter 是怎么回事,知道 dubbo 的 filter,就这样,所以被问到了的确是回答不出来,可能就觉得这个渣渣,这么简单的都不会,所以还是花点时间来看看这个是个啥,为了避免我口吐芬芳,还是耐下性子来简单说下这几个东西
    首先是 servlet,怎么去解释这个呢,因为之前是 PHPer,所以比较喜欢用它来举例子,在普通的 PHP 的 web 应用中一般有几部分组成,接受 HTTP 请求的是前置的 nginx 或者 apache,但是这俩玩意都是只能处理静态的请求,远古时代 PHP 和 HTML 混编是通过 apache 的 php module,跟后来 nginx 使用 php-fpm 其实道理类似,就是把请求中需要 PHP 处理的转发给 PHP,在 Java 中呢,是有个比较牛叉的叫 Tomcat 的,它可以把请求转成 servlet,而 servlet 其实就是一种实现了特定接口的 Java 代码,

    -
    
    -package javax.servlet;
    +    Clone Graph Part I
    +    /2014/12/30/Clone-Graph-Part-I/
    +    problem
    Clone a graph. Input is a Node pointer. Return the Node pointer of the cloned graph.
     
    -import java.io.IOException;
    +A graph is defined below:
    +struct Node {
    +vector neighbors;
    +}
    -/** - * Defines methods that all servlets must implement. - * - * <p> - * A servlet is a small Java program that runs within a Web server. Servlets - * receive and respond to requests from Web clients, usually across HTTP, the - * HyperText Transfer Protocol. - * - * <p> - * To implement this interface, you can write a generic servlet that extends - * <code>javax.servlet.GenericServlet</code> or an HTTP servlet that extends - * <code>javax.servlet.http.HttpServlet</code>. - * - * <p> - * This interface defines methods to initialize a servlet, to service requests, - * and to remove a servlet from the server. These are known as life-cycle - * methods and are called in the following sequence: - * <ol> - * <li>The servlet is constructed, then initialized with the <code>init</code> - * method. - * <li>Any calls from clients to the <code>service</code> method are handled. + +

    code

    typedef unordered_map<Node *, Node *> Map;
    + 
    +Node *clone(Node *graph) {
    +    if (!graph) return NULL;
    + 
    +    Map map;
    +    queue<Node *> q;
    +    q.push(graph);
    + 
    +    Node *graphCopy = new Node();
    +    map[graph] = graphCopy;
    + 
    +    while (!q.empty()) {
    +        Node *node = q.front();
    +        q.pop();
    +        int n = node->neighbors.size();
    +        for (int i = 0; i < n; i++) {
    +            Node *neighbor = node->neighbors[i];
    +            // no copy exists
    +            if (map.find(neighbor) == map.end()) {
    +                Node *p = new Node();
    +                map[node]->neighbors.push_back(p);
    +                map[neighbor] = p;
    +                q.push(neighbor);
    +            } else {     // a copy already exists
    +                map[node]->neighbors.push_back(map[neighbor]);
    +            }
    +        }
    +    }
    + 
    +    return graphCopy;
    +}
    +

    anlysis

    using the Breadth-first traversal
    and use a map to save the neighbors not to be duplicated.

    +]]>
    + + leetcode + + + C++ + leetcode + + + + Comparator使用小记 + /2020/04/05/Comparator%E4%BD%BF%E7%94%A8%E5%B0%8F%E8%AE%B0/ + 在Java8的stream之前,将对象进行排序的时候,可能需要对象实现Comparable接口,或者自己实现一个Comparator,

    +

    比如这样子

    +

    我的对象是Entity

    +
    public class Entity {
    +
    +    private Long id;
    +
    +    private Long sortValue;
    +
    +    public Long getId() {
    +        return id;
    +    }
    +
    +    public void setId(Long id) {
    +        this.id = id;
    +    }
    +
    +    public Long getSortValue() {
    +        return sortValue;
    +    }
    +
    +    public void setSortValue(Long sortValue) {
    +        this.sortValue = sortValue;
    +    }
    +}
    + +

    Comparator

    +
    public class MyComparator implements Comparator {
    +    @Override
    +    public int compare(Object o1, Object o2) {
    +        Entity e1 = (Entity) o1;
    +        Entity e2 = (Entity) o2;
    +        if (e1.getSortValue() < e2.getSortValue()) {
    +            return -1;
    +        } else if (e1.getSortValue().equals(e2.getSortValue())) {
    +            return 0;
    +        } else {
    +            return 1;
    +        }
    +    }
    +}
    + +

    比较代码

    +
    private static MyComparator myComparator = new MyComparator();
    +
    +    public static void main(String[] args) {
    +        List<Entity> list = new ArrayList<Entity>();
    +        Entity e1 = new Entity();
    +        e1.setId(1L);
    +        e1.setSortValue(1L);
    +        list.add(e1);
    +        Entity e2 = new Entity();
    +        e2.setId(2L);
    +        e2.setSortValue(null);
    +        list.add(e2);
    +        Collections.sort(list, myComparator);
    + +

    看到这里的e2的排序值是null,在Comparator中如果要正常运行的话,就得判空之类的,这里有两点需要,一个是不想写这个MyComparator,然后也没那么好排除掉list里排序值,那么有什么办法能解决这种问题呢,应该说java的这方面真的是很强大

    +

    +

    看一下nullsFirst的实现

    +
    final static class NullComparator<T> implements Comparator<T>, Serializable {
    +        private static final long serialVersionUID = -7569533591570686392L;
    +        private final boolean nullFirst;
    +        // if null, non-null Ts are considered equal
    +        private final Comparator<T> real;
    +
    +        @SuppressWarnings("unchecked")
    +        NullComparator(boolean nullFirst, Comparator<? super T> real) {
    +            this.nullFirst = nullFirst;
    +            this.real = (Comparator<T>) real;
    +        }
    +
    +        @Override
    +        public int compare(T a, T b) {
    +            if (a == null) {
    +                return (b == null) ? 0 : (nullFirst ? -1 : 1);
    +            } else if (b == null) {
    +                return nullFirst ? 1: -1;
    +            } else {
    +                return (real == null) ? 0 : real.compare(a, b);
    +            }
    +        }
    + +

    核心代码就是下面这段,其实就是帮我们把前面要做的事情做掉了,是不是挺方便的,小记一下哈

    +]]>
    + + Java + 集合 + + + Java + Stream + Comparator + 排序 + sort + nullsfirst + +
    + + Apollo 的 value 注解是怎么自动更新的 + /2020/11/01/Apollo-%E7%9A%84-value-%E6%B3%A8%E8%A7%A3%E6%98%AF%E6%80%8E%E4%B9%88%E8%87%AA%E5%8A%A8%E6%9B%B4%E6%96%B0%E7%9A%84/ + 在前司和目前公司,用的配置中心都是使用的 Apollo,经过了业界验证,比较强大的配置管理系统,特别是在0.10 后开始支持对使用 value 注解的配置值进行自动更新,今天刚好有个同学问到我,就顺便写篇文章记录下,其实也是借助于 spring 强大的 bean 生命周期管理,可以实现BeanPostProcessor接口,使用postProcessBeforeInitialization方法,来对bean 内部的属性和方法进行判断,是否有 value 注解,如果有就是将它注册到一个 map 中,可以看到这个方法com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor#processField

    +
    @Override
    +  protected void processField(Object bean, String beanName, Field field) {
    +    // register @Value on field
    +    Value value = field.getAnnotation(Value.class);
    +    if (value == null) {
    +      return;
    +    }
    +    Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
    +
    +    if (keys.isEmpty()) {
    +      return;
    +    }
    +
    +    for (String key : keys) {
    +      SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
    +      springValueRegistry.register(beanFactory, key, springValue);
    +      logger.debug("Monitoring {}", springValue);
    +    }
    +  }
    +

    然后我们看下这个springValueRegistry是啥玩意

    +
    public class SpringValueRegistry {
    +  private static final long CLEAN_INTERVAL_IN_SECONDS = 5;
    +  private final Map<BeanFactory, Multimap<String, SpringValue>> registry = Maps.newConcurrentMap();
    +  private final AtomicBoolean initialized = new AtomicBoolean(false);
    +  private final Object LOCK = new Object();
    +
    +  public void register(BeanFactory beanFactory, String key, SpringValue springValue) {
    +    if (!registry.containsKey(beanFactory)) {
    +      synchronized (LOCK) {
    +        if (!registry.containsKey(beanFactory)) {
    +          registry.put(beanFactory, LinkedListMultimap.<String, SpringValue>create());
    +        }
    +      }
    +    }
    +
    +    registry.get(beanFactory).put(key, springValue);
    +
    +    // lazy initialize
    +    if (initialized.compareAndSet(false, true)) {
    +      initialize();
    +    }
    +  }
    +

    这类其实就是个 map 来存放 springvalue,然后有com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener来监听更新操作,当有变更时

    +
    @Override
    + public void onChange(ConfigChangeEvent changeEvent) {
    +   Set<String> keys = changeEvent.changedKeys();
    +   if (CollectionUtils.isEmpty(keys)) {
    +     return;
    +   }
    +   for (String key : keys) {
    +     // 1. check whether the changed key is relevant
    +     Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
    +     if (targetValues == null || targetValues.isEmpty()) {
    +       continue;
    +     }
    +
    +     // 2. check whether the value is really changed or not (since spring property sources have hierarchies)
    +     // 这里其实有一点比较绕,是因为 Apollo 里的 namespace 划分,会出现 key 相同,但是 namespace 不同的情况,所以会有个优先级存在,所以需要去校验 environment 里面的是否已经更新,如果未更新则表示不需要更新
    +     if (!shouldTriggerAutoUpdate(changeEvent, key)) {
    +       continue;
    +     }
    +
    +     // 3. update the value
    +     for (SpringValue val : targetValues) {
    +       updateSpringValue(val);
    +     }
    +   }
    + }
    +

    其实原理很简单,就是得了解知道下

    +]]>
    + + Java + Apollo + value + + + Java + Apollo + value + 注解 + environment + +
    + + 34_Search_for_a_Range + /2016/08/14/34-Search-for-a-Range/ + question

    34. Search for a Range

    Original Page

    +

    Given a sorted array of integers, find the starting and ending position of a given target value.

    +

    Your algorithm’s runtime complexity must be in the order of O(log n).

    +

    If the target is not found in the array, return [-1, -1].

    +

    For example,
    Given [5, 7, 7, 8, 8, 10] and target value 8,
    return [3, 4].

    +

    analysis

    一开始就想到了二分查找,但是原来做二分查找的时候一般都是找到确定的那个数就完成了,
    这里的情况比较特殊,需要找到整个区间,所以需要两遍查找,并且一个是找到小于target
    的最大索引,一个是找到大于target的最大索引,代码参考leetcode discuss,这位仁
    兄也做了详细的分析解释。

    +

    code

    class Solution {
    +public:
    +    vector<int> searchRange(vector<int>& nums, int target) {
    +        vector<int> ret(2, -1);
    +        int i = 0, j = nums.size() - 1;
    +        int mid;
    +        while(i < j){
    +            mid = (i + j) / 2;
    +            if(nums[mid] < target) i = mid + 1;
    +            else j = mid;
    +        }
    +        if(nums[i] != target) return ret;
    +        else {
    +            ret[0] = i;
    +            if((i+1) < (nums.size() - 1) && nums[i+1] > target){
    +                ret[1] = i;
    +                return ret;
    +            }
    +        }   //一点小优化
    +        j = nums.size() - 1;
    +        while(i < j){
    +            mid = (i + j) / 2 + 1;
    +            if(nums[mid] > target) j = mid - 1;
    +            else i = mid;
    +        }
    +        ret[1] = j;
    +        return ret;
    +    }
    +};
    ]]>
    + + leetcode + + + leetcode + c++ + +
    + + Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥? + /2020/08/22/Filter-Intercepter-Aop-%E5%95%A5-%E5%95%A5-%E5%95%A5-%E8%BF%99%E4%BA%9B%E9%83%BD%E6%98%AF%E5%95%A5/ + 本来是想取个像现在那些公众号转了又转的文章标题,”面试官再问你xxxxx,就把这篇文章甩给他看”这种标题,但是觉得实在太 low 了,还是用一部我比较喜欢的电影里的一句台词,《人在囧途》里王宝强对着那张老板给他的欠条,看不懂字时候说的那句,这些都是些啥(第四声)
    当我刚开始面 Java 的时候,其实我真的没注意这方面的东西,实话说就是不知道这些是啥,开发中用过 Interceptor和 Aop,了解 aop 的实现原理,但是不知道 Java web 中的 Filter 是怎么回事,知道 dubbo 的 filter,就这样,所以被问到了的确是回答不出来,可能就觉得这个渣渣,这么简单的都不会,所以还是花点时间来看看这个是个啥,为了避免我口吐芬芳,还是耐下性子来简单说下这几个东西
    首先是 servlet,怎么去解释这个呢,因为之前是 PHPer,所以比较喜欢用它来举例子,在普通的 PHP 的 web 应用中一般有几部分组成,接受 HTTP 请求的是前置的 nginx 或者 apache,但是这俩玩意都是只能处理静态的请求,远古时代 PHP 和 HTML 混编是通过 apache 的 php module,跟后来 nginx 使用 php-fpm 其实道理类似,就是把请求中需要 PHP 处理的转发给 PHP,在 Java 中呢,是有个比较牛叉的叫 Tomcat 的,它可以把请求转成 servlet,而 servlet 其实就是一种实现了特定接口的 Java 代码,

    +
    
    +package javax.servlet;
    +
    +import java.io.IOException;
    +
    +/**
    + * Defines methods that all servlets must implement.
    + *
    + * <p>
    + * A servlet is a small Java program that runs within a Web server. Servlets
    + * receive and respond to requests from Web clients, usually across HTTP, the
    + * HyperText Transfer Protocol.
    + *
    + * <p>
    + * To implement this interface, you can write a generic servlet that extends
    + * <code>javax.servlet.GenericServlet</code> or an HTTP servlet that extends
    + * <code>javax.servlet.http.HttpServlet</code>.
    + *
    + * <p>
    + * This interface defines methods to initialize a servlet, to service requests,
    + * and to remove a servlet from the server. These are known as life-cycle
    + * methods and are called in the following sequence:
    + * <ol>
    + * <li>The servlet is constructed, then initialized with the <code>init</code>
    + * method.
    + * <li>Any calls from clients to the <code>service</code> method are handled.
      * <li>The servlet is taken out of service, then destroyed with the
      * <code>destroy</code> method, then garbage collected and finalized.
      * </ol>
    @@ -1719,81 +1726,231 @@ Node *clone(Node *graph) {
             return true;
         }
     
    -    @Override
    -    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    -        System.out.println("postHandle test");
    -    }
    -}
    -@Aspect
    -@Component
    -public class DemoAspect {
    +    @Override
    +    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    +        System.out.println("postHandle test");
    +    }
    +}
    +@Aspect
    +@Component
    +public class DemoAspect {
    +
    +    @Pointcut("execution( public * com.nicksxs.springbootdemo.demo.DemoController.*())")
    +    public void point() {
    +
    +    }
    +
    +    @Before("point()")
    +    public void doBefore(){
    +        System.out.println("==doBefore==");
    +    }
    +
    +    @After("point()")
    +    public void doAfter(){
    +        System.out.println("==doAfter==");
    +    }
    +}
    +@RestController
    +public class DemoController {
    +
    +    @RequestMapping("/hello")
    +    @ResponseBody
    +    public String hello() {
    +        return "hello world";
    +    }
    +}
    +

    好了,请求一下,看看 stdout,

    搞定完事儿~

    +]]>
    + + Java + Filter + Interceptor - AOP + Spring + Servlet + Interceptor + AOP + + + Java + Filter + Interceptor + AOP + Spring + Tomcat + Servlet + Web + +
    + + G1收集器概述 + /2020/02/09/G1%E6%94%B6%E9%9B%86%E5%99%A8%E6%A6%82%E8%BF%B0/ + G1: The Garbage-First Collector, 垃圾回收优先的垃圾回收器,目标是用户多核 cpu 和大内存的机器,最大的特点就是可预测的停顿时间,官方给出的介绍是提供一个用户在大的堆内存情况下一个低延迟表现的解决方案,通常是 6GB 及以上的堆大小,有低于 0.5 秒稳定的可预测的停顿时间。

    +

    这里主要介绍这个比较新的垃圾回收器,在 G1 之前的垃圾回收器都是基于如下图的内存结构分布,有新生代,老年代和永久代(jdk8 之前),然后G1 往前的那些垃圾回收器都有个分代,比如 serial,parallel 等,一般有个应用的组合,最初的 serial 和 serial old,因为新生代和老年代的收集方式不太一样,新生代主要是标记复制,所以有 eden 跟两个 survival区,老年代一般用标记整理方式,而 G1 对这个不太一样。

    看一下 G1 的内存分布

    可以看到这有很大的不同,G1 通过将内存分成大小相等的 region,每个region是存在于一个连续的虚拟内存范围,对于某个 region 来说其角色是类似于原来的收集器的Eden、Survivor、Old Generation,这个具体在代码层面

    +
    // We encode the value of the heap region type so the generation can be
    + // determined quickly. The tag is split into two parts:
    + //
    + //   major type (young, old, humongous, archive)           : top N-1 bits
    + //   minor type (eden / survivor, starts / cont hum, etc.) : bottom 1 bit
    + //
    + // If there's need to increase the number of minor types in the
    + // future, we'll have to increase the size of the latter and hence
    + // decrease the size of the former.
    + //
    + // 00000 0 [ 0] Free
    + //
    + // 00001 0 [ 2] Young Mask
    + // 00001 0 [ 2] Eden
    + // 00001 1 [ 3] Survivor
    + //
    + // 00010 0 [ 4] Humongous Mask
    + // 00100 0 [ 8] Pinned Mask
    + // 00110 0 [12] Starts Humongous
    + // 00110 1 [13] Continues Humongous
    + //
    + // 01000 0 [16] Old Mask
    + //
    + // 10000 0 [32] Archive Mask
    + // 11100 0 [56] Open Archive
    + // 11100 1 [57] Closed Archive
    + //
    + typedef enum {
    +   FreeTag               = 0,
    +
    +   YoungMask             = 2,
    +   EdenTag               = YoungMask,
    +   SurvTag               = YoungMask + 1,
    +
    +   HumongousMask         = 4,
    +   PinnedMask            = 8,
    +   StartsHumongousTag    = HumongousMask | PinnedMask,
    +   ContinuesHumongousTag = HumongousMask | PinnedMask + 1,
    +
    +   OldMask               = 16,
    +   OldTag                = OldMask,
    +
    +   // Archive regions are regions with immutable content (i.e. not reclaimed, and
    +   // not allocated into during regular operation). They differ in the kind of references
    +   // allowed for the contained objects:
    +   // - Closed archive regions form a separate self-contained (closed) object graph
    +   // within the set of all of these regions. No references outside of closed
    +   // archive regions are allowed.
    +   // - Open archive regions have no restrictions on the references of their objects.
    +   // Objects within these regions are allowed to have references to objects
    +   // contained in any other kind of regions.
    +   ArchiveMask           = 32,
    +   OpenArchiveTag        = ArchiveMask | PinnedMask | OldMask,
    +   ClosedArchiveTag      = ArchiveMask | PinnedMask | OldMask + 1
    + } Tag;
    + +

    hotspot/share/gc/g1/heapRegionType.hpp

    +

    当执行垃圾收集时,G1以类似于CMS收集器的方式运行。 G1执行并发全局标记阶段,以确定整个堆中对象的存活性。标记阶段完成后,G1知道哪些region是基本空的。它首先收集这些region,通常会产生大量的可用空间。这就是为什么这种垃圾收集方法称为“垃圾优先”的原因。顾名思义,G1将其收集和压缩活动集中在可能充满可回收对象(即垃圾)的堆区域。 G1使用暂停预测模型来满足用户定义的暂停时间目标,并根据指定的暂停时间目标选择要收集的区域数。

    +

    由G1标识为可回收的区域是使用撤离的方式(Evacuation)。 G1将对象从堆的一个或多个区域复制到堆上的单个区域,并在此过程中压缩并释放内存。撤离是在多处理器上并行执行的,以减少暂停时间并增加吞吐量。因此,对于每次垃圾收集,G1都在用户定义的暂停时间内连续工作以减少碎片。这是优于前面两种方法的。 CMS(并发标记扫描)垃圾收集器不进行压缩。 ParallelOld垃圾回收仅执行整个堆压缩,这导致相当长的暂停时间。

    +

    需要重点注意的是,G1不是实时收集器。它很有可能达到设定的暂停时间目标,但并非绝对确定。 G1根据先前收集的数据,估算在用户指定的目标时间内可以收集多少个区域。因此,收集器具有收集区域成本的合理准确的模型,并且收集器使用此模型来确定要收集哪些和多少个区域,同时保持在暂停时间目标之内。

    +

    注意:G1同时具有并发(与应用程序线程一起运行,例如优化,标记,清理)和并行(多线程,例如stw)阶段。Full GC仍然是单线程的,但是如果正确调优,您的应用程序应该可以避免Full GC。

    +

    在前面那篇中在代码层面简单的了解了这个可预测时间的过程,这也是 G1 的一大特点。

    +]]>
    + + Java + JVM + GC + C++ + + + Java + JVM + C++ + G1 + GC + Garbage-First Collector + +
    + + Disruptor 系列一 + /2022/02/13/Disruptor-%E7%B3%BB%E5%88%97%E4%B8%80/ + 很久之前就听说过这个框架,不过之前有点跟消息队列混起来,这个也是种队列,但不是跟 rocketmq,nsq 那种一样的,而是在进程内部提供队列服务的,偏向于取代ArrayBlockingQueue,因为这个阻塞队列是使用了锁来控制阻塞,关于并发其实有一些通用的最佳实践,就是用锁,即使是 JDK 提供的锁,也是比较耗资源的,当然这是跟不加锁的对比,同样是锁,JDK 的实现还是性能比较优秀的。常见的阻塞队列中例如 ArrayBlockingQueueLinkedBlockingQueue 都有锁的身影的存在,区别在于 ArrayBlockingQueue 是一把锁,后者是两把锁,不过重点不在几把锁,这里其实是两个问题,一个是所谓的 lock free, 对于一个单生产者的 disruptor 来说,因为写入是只有一个线程的,是可以不用加锁,多生产者的时候使用的是 cas 来获取对应的写入坑位,另一个是解决“伪共享”问题,后面可以详细点分析,先介绍下使用
    首先是数据源

    +
    public class LongEvent {
    +    private long value;
    +
    +    public void set(long value) {
    +        this.value = value;
    +    }
    +
    +    public long getValue() {
    +        return value;
    +    }
    +
    +    public void setValue(long value) {
    +        this.value = value;
    +    }
    +}
    +

    事件生产

    +
    public class LongEventFactory implements EventFactory<LongEvent>
    +{
    +    public LongEvent newInstance()
    +    {
    +        return new LongEvent();
    +    }
    +}
    +

    事件处理器

    +
    public class LongEventHandler implements EventHandler<LongEvent> {
    +
    +    // event 事件,
    +    // sequence 当前的序列 
    +    // 是否当前批次最后一个数据
    +    public void onEvent(LongEvent event, long sequence, boolean endOfBatch)
    +    {
    +        String str = String.format("long event : %s l:%s b:%s", event.getValue(), sequence, endOfBatch);
    +        System.out.println(str);
    +    }
    +}
    +
    +

    主方法代码

    +
    package disruptor;
    +
    +import com.lmax.disruptor.RingBuffer;
    +import com.lmax.disruptor.dsl.Disruptor;
    +import com.lmax.disruptor.util.DaemonThreadFactory;
    +
    +import java.nio.ByteBuffer;
     
    -    @Pointcut("execution( public * com.nicksxs.springbootdemo.demo.DemoController.*())")
    -    public void point() {
    +public class LongEventMain
    +{
    +    public static void main(String[] args) throws Exception
    +    {
    +        // 这个需要是 2 的幂次,这样在定位的时候只需要位移操作,也能减少各种计算操作
    +        int bufferSize = 1024; 
     
    -    }
    +        Disruptor<LongEvent> disruptor = 
    +                new Disruptor<>(LongEvent::new, bufferSize, DaemonThreadFactory.INSTANCE);
     
    -    @Before("point()")
    -    public void doBefore(){
    -        System.out.println("==doBefore==");
    -    }
    +        // 类似于注册处理器
    +        disruptor.handleEventsWith(new LongEventHandler());
    +        // 或者直接用 lambda
    +        disruptor.handleEventsWith((event, sequence, endOfBatch) ->
    +                System.out.println("Event: " + event));
    +        // 启动我们的 disruptor
    +        disruptor.start(); 
     
    -    @After("point()")
    -    public void doAfter(){
    -        System.out.println("==doAfter==");
    -    }
    -}
    -@RestController
    -public class DemoController {
     
    -    @RequestMapping("/hello")
    -    @ResponseBody
    -    public String hello() {
    -        return "hello world";
    +        RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer(); 
    +        ByteBuffer bb = ByteBuffer.allocate(8);
    +        for (long l = 0; true; l++)
    +        {
    +            bb.putLong(0, l);
    +            // 生产事件
    +            ringBuffer.publishEvent((event, sequence, buffer) -> event.set(buffer.getLong(0)), bb);
    +            Thread.sleep(1000);
    +        }
         }
    -}
    -

    好了,请求一下,看看 stdout,

    搞定完事儿~

    -]]>
    - - Java - Filter - Interceptor - AOP - Spring - Servlet - Interceptor - AOP - - - Java - Filter - Interceptor - AOP - Spring - Tomcat - Servlet - Web - -
    - - Dubbo 使用的几个记忆点 - /2022/04/02/Dubbo-%E4%BD%BF%E7%94%A8%E7%9A%84%E5%87%A0%E4%B8%AA%E8%AE%B0%E5%BF%86%E7%82%B9/ - 因为后台使用的 dubbo 作为 rpc 框架,并且会有一些日常使用情景有一些小的技巧,在这里做下记录作笔记用

    -

    dubbo 只拉取不注册

    <dubbo:registry address="zookeeper://127.0.0.1:2181" register="false" />
    -

    就是只要 register="false" 就可以了,这样比如我们在开发环境想运行服务,但又不想让开发环境正常的请求调用到本地来,当然这不是唯一的方式,通过 dubbo 2.7 以上的 tag 路由也可以实现或者自行改造拉取和注册服务的逻辑,因为注册到注册中心的其实是一串带参数的 url,还是比较方便改造的。相反的就是只注册,不拉取

    -

    dubbo 只注册不拉取

    <dubbo:registry address="zookeeper://127.0.0.1:2181" subscribe="false" />
    -

    这个使用场景就是如果我这个服务只作为 provider,没有任何调用其他的服务,其实就可以这么设置

    -

    权重配置

    <dubbo:provider loadbalance="random" weight="50"/>
    -

    首先这是在使用了随机的负载均衡策略的时候可以进行配置,并且是对于多个 provider 的情况下,这样其实也可以部分解决上面的只拉取不注册的问题,我把自己的权重调成 0 或者很低

    +}
    +

    运行下可以看到运行结果

    这里其实就只是最简单的使用,生产者只有一个,然后也不是批量的。

    ]]>
    Java - Dubbo Java - Dubbo - RPC - 负载均衡 + Disruptor
    @@ -2393,174 +2550,13 @@ Node *clone(Node *graph) { Java JVM - GC - C++ - - - Java - JVM - C++ - - - - Disruptor 系列一 - /2022/02/13/Disruptor-%E7%B3%BB%E5%88%97%E4%B8%80/ - 很久之前就听说过这个框架,不过之前有点跟消息队列混起来,这个也是种队列,但不是跟 rocketmq,nsq 那种一样的,而是在进程内部提供队列服务的,偏向于取代ArrayBlockingQueue,因为这个阻塞队列是使用了锁来控制阻塞,关于并发其实有一些通用的最佳实践,就是用锁,即使是 JDK 提供的锁,也是比较耗资源的,当然这是跟不加锁的对比,同样是锁,JDK 的实现还是性能比较优秀的。常见的阻塞队列中例如 ArrayBlockingQueueLinkedBlockingQueue 都有锁的身影的存在,区别在于 ArrayBlockingQueue 是一把锁,后者是两把锁,不过重点不在几把锁,这里其实是两个问题,一个是所谓的 lock free, 对于一个单生产者的 disruptor 来说,因为写入是只有一个线程的,是可以不用加锁,多生产者的时候使用的是 cas 来获取对应的写入坑位,另一个是解决“伪共享”问题,后面可以详细点分析,先介绍下使用
    首先是数据源

    -
    public class LongEvent {
    -    private long value;
    -
    -    public void set(long value) {
    -        this.value = value;
    -    }
    -
    -    public long getValue() {
    -        return value;
    -    }
    -
    -    public void setValue(long value) {
    -        this.value = value;
    -    }
    -}
    -

    事件生产

    -
    public class LongEventFactory implements EventFactory<LongEvent>
    -{
    -    public LongEvent newInstance()
    -    {
    -        return new LongEvent();
    -    }
    -}
    -

    事件处理器

    -
    public class LongEventHandler implements EventHandler<LongEvent> {
    -
    -    // event 事件,
    -    // sequence 当前的序列 
    -    // 是否当前批次最后一个数据
    -    public void onEvent(LongEvent event, long sequence, boolean endOfBatch)
    -    {
    -        String str = String.format("long event : %s l:%s b:%s", event.getValue(), sequence, endOfBatch);
    -        System.out.println(str);
    -    }
    -}
    -
    -

    主方法代码

    -
    package disruptor;
    -
    -import com.lmax.disruptor.RingBuffer;
    -import com.lmax.disruptor.dsl.Disruptor;
    -import com.lmax.disruptor.util.DaemonThreadFactory;
    -
    -import java.nio.ByteBuffer;
    -
    -public class LongEventMain
    -{
    -    public static void main(String[] args) throws Exception
    -    {
    -        // 这个需要是 2 的幂次,这样在定位的时候只需要位移操作,也能减少各种计算操作
    -        int bufferSize = 1024; 
    -
    -        Disruptor<LongEvent> disruptor = 
    -                new Disruptor<>(LongEvent::new, bufferSize, DaemonThreadFactory.INSTANCE);
    -
    -        // 类似于注册处理器
    -        disruptor.handleEventsWith(new LongEventHandler());
    -        // 或者直接用 lambda
    -        disruptor.handleEventsWith((event, sequence, endOfBatch) ->
    -                System.out.println("Event: " + event));
    -        // 启动我们的 disruptor
    -        disruptor.start(); 
    -
    -
    -        RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer(); 
    -        ByteBuffer bb = ByteBuffer.allocate(8);
    -        for (long l = 0; true; l++)
    -        {
    -            bb.putLong(0, l);
    -            // 生产事件
    -            ringBuffer.publishEvent((event, sequence, buffer) -> event.set(buffer.getLong(0)), bb);
    -            Thread.sleep(1000);
    -        }
    -    }
    -}
    -

    运行下可以看到运行结果

    这里其实就只是最简单的使用,生产者只有一个,然后也不是批量的。

    -]]>
    - - Java - - - Java - Disruptor - -
    - - Leetcode 021 合并两个有序链表 ( Merge Two Sorted Lists ) 题解分析 - /2021/10/07/Leetcode-021-%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8-Merge-Two-Sorted-Lists-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 题目介绍

    Merge two sorted linked lists and return it as a sorted list. The list should be made by splicing together the nodes of the first two lists.

    -

    将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

    -

    示例 1

    -
    -

    输入:l1 = [1,2,4], l2 = [1,3,4]
    输出:[1,1,2,3,4,4]

    -
    -

    示例 2

    -

    输入: l1 = [], l2 = []
    输出: []

    -
    -

    示例 3

    -

    输入: l1 = [], l2 = [0]
    输出: [0]

    -
    -

    简要分析

    这题是 Easy 的,看着也挺简单,两个链表进行合并,就是比较下大小,可能将就点的话最好就在两个链表中原地合并

    -

    题解代码

    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    -        // 下面两个if判断了入参的边界,如果其一为null,直接返回另一个就可以了
    -        if (l1 == null) {
    -            return l2;
    -        }
    -        if (l2 == null) {
    -            return l1;
    -        }
    -        // new 一个合并后的头结点
    -        ListNode merged = new ListNode();
    -        // 这个是当前节点
    -        ListNode current = merged;
    -        // 一开始给这个while加了l1和l2不全为null的条件,后面想了下不需要
    -        // 因为内部前两个if就是跳出条件
    -        while (true) {
    -            if (l1 == null) {
    -                // 这里其实跟开头类似,只不过这里需要将l2剩余部分接到merged链表后面
    -                // 所以不能是直接current = l2,这样就是把后面的直接丢了
    -                current.val = l2.val;
    -                current.next = l2.next;
    -                break;
    -            }
    -            if (l2 == null) {
    -                current.val = l1.val;
    -                current.next = l1.next;
    -                break;
    -            }
    -            // 这里是两个链表都不为空的时候,就比较下大小
    -            if (l1.val < l2.val) {
    -                current.val = l1.val;
    -                l1 = l1.next;
    -            } else {
    -                current.val = l2.val;
    -                l2 = l2.next;
    -            }
    -            // 这里是new个新的,其实也可以放在循环头上
    -            current.next = new ListNode();
    -            current = current.next;
    -        }
    -        current = null;
    -        // 返回这个头结点
    -        return merged;
    -    }
    - -

    结果

    -]]>
    - - Java - leetcode + GC + C++ - leetcode - java - 题解 + Java + JVM + C++
    @@ -2639,42 +2635,25 @@ Output: 0 - Leetcode 053 最大子序和 ( Maximum Subarray ) 题解分析 - /2021/11/28/Leetcode-053-%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%92%8C-Maximum-Subarray-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 题目介绍

    Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

    -

    A subarray is a contiguous part of an array.

    -

    示例

    Example 1:

    -
    -

    Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
    Output: 6
    Explanation: [4,-1,2,1] has the largest sum = 6.

    -
    -

    Example 2:

    -
    -

    Input: nums = [1]
    Output: 1

    -
    -

    Example 3:

    -
    -

    Input: nums = [5,4,-1,7,8]
    Output: 23

    -
    -

    说起来这个题其实非常有渊源,大学数据结构的第一个题就是这个,而最佳的算法就是传说中的 online 算法,就是遍历一次就完了,最基本的做法就是记下来所有的连续子数组,然后求出最大的那个。

    -

    代码

    public int maxSubArray(int[] nums) {
    -        int max = nums[0];
    -        int sum = nums[0];
    -        for (int i = 1; i < nums.length; i++) {
    -            // 这里最重要的就是这一行了,其实就是如果前面的 sum 是小于 0 的,那么就不需要前面的 sum,反正加上了还不如不加大
    -            sum = Math.max(nums[i], sum + nums[i]);
    -            // max 是用来承载最大值的
    -            max = Math.max(max, sum);
    -        }
    -        return max;
    -    }
    ]]>
    + Dubbo 使用的几个记忆点 + /2022/04/02/Dubbo-%E4%BD%BF%E7%94%A8%E7%9A%84%E5%87%A0%E4%B8%AA%E8%AE%B0%E5%BF%86%E7%82%B9/ + 因为后台使用的 dubbo 作为 rpc 框架,并且会有一些日常使用情景有一些小的技巧,在这里做下记录作笔记用

    +

    dubbo 只拉取不注册

    <dubbo:registry address="zookeeper://127.0.0.1:2181" register="false" />
    +

    就是只要 register="false" 就可以了,这样比如我们在开发环境想运行服务,但又不想让开发环境正常的请求调用到本地来,当然这不是唯一的方式,通过 dubbo 2.7 以上的 tag 路由也可以实现或者自行改造拉取和注册服务的逻辑,因为注册到注册中心的其实是一串带参数的 url,还是比较方便改造的。相反的就是只注册,不拉取

    +

    dubbo 只注册不拉取

    <dubbo:registry address="zookeeper://127.0.0.1:2181" subscribe="false" />
    +

    这个使用场景就是如果我这个服务只作为 provider,没有任何调用其他的服务,其实就可以这么设置

    +

    权重配置

    <dubbo:provider loadbalance="random" weight="50"/>
    +

    首先这是在使用了随机的负载均衡策略的时候可以进行配置,并且是对于多个 provider 的情况下,这样其实也可以部分解决上面的只拉取不注册的问题,我把自己的权重调成 0 或者很低

    +]]>
    Java - leetcode + Dubbo - leetcode - java - 题解 + Java + Dubbo + RPC + 负载均衡
    @@ -2722,10 +2701,10 @@ Output: 0 @@ -2785,9 +2764,9 @@ inorder = [9,3,15,20,7] - Leetcode 1115 交替打印 FooBar ( Print FooBar Alternately *Medium* ) 题解分析 - /2022/05/01/Leetcode-1115-%E4%BA%A4%E6%9B%BF%E6%89%93%E5%8D%B0-FooBar-Print-FooBar-Alternately-Medium-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 无聊想去 roll 一题就看到了有并发题,就找到了这题,其实一眼看我的想法也是用信号量,但是用 condition 应该也是可以处理的,不过这类问题好像本地有点难调,因为它好像是抽取代码执行的,跟直观的逻辑比较不一样
    Suppose you are given the following code:

    -
    class FooBar {
    -  public void foo() {
    -    for (int i = 0; i < n; i++) {
    -      print("foo");
    -    }
    -  }
    -
    -  public void bar() {
    -    for (int i = 0; i < n; i++) {
    -      print("bar");
    -    }
    -  }
    -}
    -

    The same instance of FooBar will be passed to two different threads:

    -
      -
    • thread A will call foo(), while
    • -
    • thread B will call bar().
      Modify the given program to output "foobar" n times.
    • -
    -

    示例

    Example 1:

    -

    Input: n = 1
    Output: “foobar”
    Explanation: There are two threads being fired asynchronously. One of them calls foo(), while the other calls bar().
    “foobar” is being output 1 time.

    + Leetcode 053 最大子序和 ( Maximum Subarray ) 题解分析 + /2021/11/28/Leetcode-053-%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%92%8C-Maximum-Subarray-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 题目介绍

    Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

    +

    A subarray is a contiguous part of an array.

    +

    示例

    Example 1:

    +
    +

    Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
    Output: 6
    Explanation: [4,-1,2,1] has the largest sum = 6.

    -

    Example 2:

    -

    Input: n = 2
    Output: “foobarfoobar”
    Explanation: “foobar” is being output 2 times.

    +

    Example 2:

    +
    +

    Input: nums = [1]
    Output: 1

    -

    题解

    简析

    其实用信号量是很直观的,就是让打印 foo 的线程先拥有信号量,打印后就等待,给 bar 信号量 + 1,然后 bar 线程运行打印消耗 bar 信号量,再给 foo 信号量 + 1

    -

    code

    class FooBar {
    -    
    -    private final Semaphore foo = new Semaphore(1);
    -    private final Semaphore bar = new Semaphore(0);
    -    private int n;
    -
    -    public FooBar(int n) {
    -        this.n = n;
    -    }
    -
    -    public void foo(Runnable printFoo) throws InterruptedException {
    -        
    -        for (int i = 0; i < n; i++) {
    -            foo.acquire();
    -        	// printFoo.run() outputs "foo". Do not change or remove this line.
    -        	printFoo.run();
    -            bar.release();
    -        }
    -    }
    -
    -    public void bar(Runnable printBar) throws InterruptedException {
    -        
    -        for (int i = 0; i < n; i++) {
    -            bar.acquire();
    -            // printBar.run() outputs "bar". Do not change or remove this line.
    -        	printBar.run();
    -            foo.release();
    +

    Example 3:

    +
    +

    Input: nums = [5,4,-1,7,8]
    Output: 23

    +
    +

    说起来这个题其实非常有渊源,大学数据结构的第一个题就是这个,而最佳的算法就是传说中的 online 算法,就是遍历一次就完了,最基本的做法就是记下来所有的连续子数组,然后求出最大的那个。

    +

    代码

    public int maxSubArray(int[] nums) {
    +        int max = nums[0];
    +        int sum = nums[0];
    +        for (int i = 1; i < nums.length; i++) {
    +            // 这里最重要的就是这一行了,其实就是如果前面的 sum 是小于 0 的,那么就不需要前面的 sum,反正加上了还不如不加大
    +            sum = Math.max(nums[i], sum + nums[i]);
    +            // max 是用来承载最大值的
    +            max = Math.max(max, sum);
             }
    -    }
    -}
    -]]> + return max; + }
    ]]> Java leetcode @@ -2863,7 +2811,6 @@ inorder = [9,3,15,20,7]
    +

    总结

    可能缺少这种思维,做的还是比较少,所以没法一下子反应过来,需要锻炼,我的第一反应是两重遍历,不过那样复杂度就高了,这里应该是只有 O(N) 的复杂度。

    +]]> + + Java + leetcode + Linked List + java + Linked List + + + leetcode + java + 题解 + Linked List + + + + Leetcode 234 回文链表(Palindrome Linked List) 题解分析 + /2020/11/15/Leetcode-234-%E5%9B%9E%E6%96%87%E8%81%94%E8%A1%A8-Palindrome-Linked-List-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 题目介绍

    Given a singly linked list, determine if it is a palindrome.
    给定一个单向链表,判断是否是回文链表

    +

    例一 Example 1:

    Input: 1->2
    Output: false

    +

    例二 Example 2:

    Input: 1->2->2->1
    Output: true

    +

    挑战下自己

    Follow up:
    Could you do it in O(n) time and O(1) space?

    +

    简要分析

    首先这是个单向链表,如果是双向的就可以一个从头到尾,一个从尾到头,显然那样就没啥意思了,然后想过要不找到中点,然后用一个栈,把前一半塞进栈里,但是这种其实也比较麻烦,比如长度是奇偶数,然后如何找到中点,这倒是可以借助于双指针,还是比较麻烦,再想一想,回文链表,就跟最开始的一样,链表只有单向的,我用个栈不就可以逆向了么,先把链表整个塞进栈里,然后在一个个 pop 出来跟链表从头开始比较,全对上了就是回文了

    +
    /**
    + * Definition for singly-linked list.
    + * public class ListNode {
    + *     int val;
    + *     ListNode next;
    + *     ListNode() {}
    + *     ListNode(int val) { this.val = val; }
    + *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
    + * }
    + */
    +class Solution {
    +    public boolean isPalindrome(ListNode head) {
    +        if (head == null) {
    +            return true;
    +        }
    +        ListNode tail = head;
    +        LinkedList<Integer> stack = new LinkedList<>();
    +        // 这里就是一个循环,将所有元素依次压入栈
    +        while (tail != null) {
    +            stack.push(tail.val);
    +            tail = tail.next;
    +        }
    +        // 在逐个 pop 出来,其实这个出来的顺序就等于链表从尾到头遍历,同时跟链表从头到尾遍历进行逐对对比
    +        while (!stack.isEmpty()) {
    +            if (stack.peekFirst() == head.val) {
    +                stack.pollFirst();
    +                head = head.next;
                 } else {
    -                tailA = tailA.next;
    -                tailB = tailB.next;
    +                return false;
                 }
             }
    -        return null;
    -    }
    -

    总结

    可能缺少这种思维,做的还是比较少,所以没法一下子反应过来,需要锻炼,我的第一反应是两重遍历,不过那样复杂度就高了,这里应该是只有 O(N) 的复杂度。

    -]]>
    + return true; + } +}
    ]]> Java leetcode @@ -3185,62 +3260,6 @@ Output: [8,9,9,9,0,0,0,1]
    -

    示例 1:

    -

    输入:nums1 = [1,2,2,1], nums2 = [2,2]
    输出:[2]

    + Leetcode 4 寻找两个正序数组的中位数 ( Median of Two Sorted Arrays *Hard* ) 题解分析 + /2022/03/27/Leetcode-4-%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0-Median-of-Two-Sorted-Arrays-Hard-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 题目介绍

    给定两个大小分别为 mn 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数

    +

    算法的时间复杂度应该为 O(log (m+n))

    +

    示例 1:

    +

    输入:nums1 = [1,3], nums2 = [2]
    输出:2.00000
    解释:合并数组 = [1,2,3] ,中位数 2

    -
    -

    示例 2:

    -

    输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
    输出:[9,4]
    解释:[4,9] 也是可通过的 - 

    +

    示例 2:

    +

    输入:nums1 = [1,2], nums2 = [3,4]
    输出:2.50000
    解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

    -

    提示:

      -
    • 1 <= nums1.length, nums2.length <= 1000
    • -
    • 0 <= nums1[i], nums2[i] <= 1000
    • -
    -

    分析与题解

    两个数组的交集,最简单就是两层循环了把两个都存在的找出来,不过还有个要去重的问题,稍微思考下可以使用集合 set 来处理,先把一个数组全丢进去,再对比另外一个,如果出现在第一个集合里就丢进一个新的集合,最后转换成数组,这次我稍微取了个巧,因为看到了提示里的条件,两个数组中的元素都是不大于 1000 的,所以就搞了个 1000 长度的数组,如果在第一个数组出现,就在对应的下标设置成 1,如果在第二个数组也出现了就加 1,

    -

    code

    public int[] intersection(int[] nums1, int[] nums2) {
    -    // 大小是 1000 的数组,如果没有提示的条件就没法这么做
    -    // define a array which size is 1000, and can not be done like this without the condition in notice
    -        int[] inter = new int[1000];
    -        int[] outer;
    -        int m = 0;
    -        for (int j : nums1) {
    -            //  这里得是设置成 1,因为有可能 nums1 就出现了重复元素,如果直接++会造成结果重复
    -            // need to be set 1, cause element in nums1 can be duplicated
    -            inter[j] = 1;
    -        }
    -        for (int j : nums2) {
    -            if (inter[j] > 0) {
    -                // 这里可以直接+1,因为后面判断只需要判断大于 1
    -                // just plus 1, cause we can judge with condition that larger than  1
    -                inter[j] += 1;
    -            }
    +

    分析与题解

    这个题也是我随机出来的,之前都是随机到 easy 的,而且是序号这么靠前的,然后翻一下,之前应该是用 C++做过的,具体的方法其实可以从他的算法时间复杂度要求看出来,大概率是要二分法这种,后面就结合代码来讲了

    +
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    +        int n1 = nums1.length;
    +        int n2 = nums2.length;
    +        if (n1 > n2) {
    +            return findMedianSortedArrays(nums2, nums1);
             }
    -        for (int i = 0; i < inter.length; i++) {
    -            // 统计下元素数量
    -            // count distinct elements
    -            if (inter[i] > 1) {
    -                m++;
    +
    +        // 找到两个数组的中点下标
    +        int k = (n1 + n2 + 1 ) / 2;
    +        // 使用一个类似于二分法的查找方法
    +        // 起始值就是 num1 的头跟尾
    +        int left = 0;
    +        int right = n1;
    +        while (left < right) {
    +            // m1 表示我取的是 nums1 的中点,即二分法的方式
    +            int m1 = left + (right - left) / 2;
    +            // *** 这里是重点,因为这个问题也可以转换成找成 n1 + n2 那么多个数中的前 (n1 + n2 + 1) / 2 个
    +            // *** 因为两个数组都是排好序的,那么我从 num1 中取了 m1 个,从 num2 中就是去 k - m1 个
    +            // *** 但是不知道取出来大小是否正好是整体排序的第 (n1 + n2 + 1) / 2 个,所以需要二分法上下对比
    +            int m2 = k - m1;
    +            // 如果 nums1[m1] 小,那我在第一个数组 nums1 的二分查找就要把左端点改成前一次的中点 + 1 (不然就进死循环了
    +            if (nums1[m1] < nums2[m2 - 1]) {
    +                left = m1 + 1;
    +            } else {
    +                right = m1;
                 }
             }
    -        // initial a array of size m
    -        outer = new int[m];
    -        m = 0;
    -        for (int i = 0; i < inter.length; i++) {
    -            if (inter[i] > 1) {
    -                // add to outer
    -                outer[m++] = i;
    -            }
    +
    +        // 因为对比后其实我们只是拿到了一个位置,具体哪个是第 k 个就需要继续判断
    +        int m1 = left;
    +        int m2 = k - left;
    +        // 如果 m1 或者 m2 有小于等于 0 的,那这个值可以先抛弃
    +        // m1 如果等于 0,就是 num1[0] 都比 nums2 中所有值都要大
    +        // m2 等于 0 的话 刚好相反
    +        // 可以这么推断,当其中一个是 0 的时候那么另一个 mx 值肯定是> 0 的,那么就是取的对应的这个下标的值
    +        int c1 = Math.max( m1 <= 0 ? Integer.MIN_VALUE : nums1[m1 - 1] , m2 <= 0 ?  Integer.MIN_VALUE : nums2[m2 - 1]);
    +        // 如果两个数组的元素数量和是奇数,那就直接可以返回了,因为 m1 + m2 就是 k, 如果是一个数组,那这个元素其实就是 nums[k - 1]
    +        // 如果 m1 或者 m2 是 0,那另一个就是 k,取 mx - 1的下标就等于是 k - 1
    +        // 如果都不是 0,那就是取的了 nums1[m1 - 1] 与 nums2[m2 - 1]中的较大者,如果取得是后者,那么也就是 m1 + m2 - 1 的下标就是 k - 1
    +        if ((n1 + n2) % 2 == 1) {
    +            return c1;
             }
    -        return outer;
    -    }
    ]]> + // 如果是偶数个,那还要取两个数组后面的较小者,然后求平均值 + int c2 = Math.min(m1 >= n1 ? Integer.MAX_VALUE : nums1[m1], m2 >= n2 ? Integer.MAX_VALUE : nums2[m2]); + return (c1 + c2) / 2.0; + }
    +

    前面考虑的方法还是比较繁琐,考虑了两个数组的各种交叉情况,后面这个参考了一些网上的解法,代码比较简洁,但是可能不容易一下子就搞明白,所以配合了比较多的注释。

    +]]> Java leetcode @@ -3421,7 +3445,7 @@ Output: 0
    +]]> + + leetcode + + + leetcode + c++ + + + + two sum + /2015/01/14/Two-Sum/ + problem

    Given an array of integers, find two numbers such that they add up to a specific target number.

    +

    The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.

    + +

    You may assume that each input would have exactly one solution.

    +

    Input: numbers={2, 7, 11, 15}, target=9
    Output: index1=1, index2=2

    +

    code

    struct Node
    +{
    +    int num, pos;
    +};
    +bool cmp(Node a, Node b)
    +{
    +    return a.num < b.num;
    +}
    +class Solution {
    +public:
    +    vector<int> twoSum(vector<int> &numbers, int target) {
    +        // Start typing your C/C++ solution below
    +        // DO NOT write int main() function
    +        vector<int> result;
    +        vector<Node> array;
    +        for (int i = 0; i < numbers.size(); i++)
    +        {
    +            Node temp;
    +            temp.num = numbers[i];
    +            temp.pos = i;
    +            array.push_back(temp);
    +        }
    +
    +        sort(array.begin(), array.end(), cmp);
    +        for (int i = 0, j = array.size() - 1; i != j;)
    +        {
    +            int sum = array[i].num + array[j].num;
    +            if (sum == target)
    +            {
    +                if (array[i].pos < array[j].pos)
    +                {
    +                    result.push_back(array[i].pos + 1);
    +                    result.push_back(array[j].pos + 1);
    +                } else
    +                {
    +                    result.push_back(array[j].pos + 1);
    +                    result.push_back(array[i].pos + 1);
    +                }
    +                break;
    +            } else if (sum < target)
    +            {
    +                i++;
    +            } else if (sum > target)
    +            {
    +                j--;
    +            }
    +        }
    +        return result;
    +    }
    +};
    + +

    Analysis

    sort the array, then test from head and end, until catch the right answer

    ]]>
    leetcode @@ -4026,77 +4144,6 @@ public: cluster
    - - Leetcode 4 寻找两个正序数组的中位数 ( Median of Two Sorted Arrays *Hard* ) 题解分析 - /2022/03/27/Leetcode-4-%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0-Median-of-Two-Sorted-Arrays-Hard-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 题目介绍

    给定两个大小分别为 mn 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数

    -

    算法的时间复杂度应该为 O(log (m+n))

    -

    示例 1:

    -

    输入:nums1 = [1,3], nums2 = [2]
    输出:2.00000
    解释:合并数组 = [1,2,3] ,中位数 2

    -
    -

    示例 2:

    -

    输入:nums1 = [1,2], nums2 = [3,4]
    输出:2.50000
    解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

    -
    -

    分析与题解

    这个题也是我随机出来的,之前都是随机到 easy 的,而且是序号这么靠前的,然后翻一下,之前应该是用 C++做过的,具体的方法其实可以从他的算法时间复杂度要求看出来,大概率是要二分法这种,后面就结合代码来讲了

    -
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    -        int n1 = nums1.length;
    -        int n2 = nums2.length;
    -        if (n1 > n2) {
    -            return findMedianSortedArrays(nums2, nums1);
    -        }
    -
    -        // 找到两个数组的中点下标
    -        int k = (n1 + n2 + 1 ) / 2;
    -        // 使用一个类似于二分法的查找方法
    -        // 起始值就是 num1 的头跟尾
    -        int left = 0;
    -        int right = n1;
    -        while (left < right) {
    -            // m1 表示我取的是 nums1 的中点,即二分法的方式
    -            int m1 = left + (right - left) / 2;
    -            // *** 这里是重点,因为这个问题也可以转换成找成 n1 + n2 那么多个数中的前 (n1 + n2 + 1) / 2 个
    -            // *** 因为两个数组都是排好序的,那么我从 num1 中取了 m1 个,从 num2 中就是去 k - m1 个
    -            // *** 但是不知道取出来大小是否正好是整体排序的第 (n1 + n2 + 1) / 2 个,所以需要二分法上下对比
    -            int m2 = k - m1;
    -            // 如果 nums1[m1] 小,那我在第一个数组 nums1 的二分查找就要把左端点改成前一次的中点 + 1 (不然就进死循环了
    -            if (nums1[m1] < nums2[m2 - 1]) {
    -                left = m1 + 1;
    -            } else {
    -                right = m1;
    -            }
    -        }
    -
    -        // 因为对比后其实我们只是拿到了一个位置,具体哪个是第 k 个就需要继续判断
    -        int m1 = left;
    -        int m2 = k - left;
    -        // 如果 m1 或者 m2 有小于等于 0 的,那这个值可以先抛弃
    -        // m1 如果等于 0,就是 num1[0] 都比 nums2 中所有值都要大
    -        // m2 等于 0 的话 刚好相反
    -        // 可以这么推断,当其中一个是 0 的时候那么另一个 mx 值肯定是> 0 的,那么就是取的对应的这个下标的值
    -        int c1 = Math.max( m1 <= 0 ? Integer.MIN_VALUE : nums1[m1 - 1] , m2 <= 0 ?  Integer.MIN_VALUE : nums2[m2 - 1]);
    -        // 如果两个数组的元素数量和是奇数,那就直接可以返回了,因为 m1 + m2 就是 k, 如果是一个数组,那这个元素其实就是 nums[k - 1]
    -        // 如果 m1 或者 m2 是 0,那另一个就是 k,取 mx - 1的下标就等于是 k - 1
    -        // 如果都不是 0,那就是取的了 nums1[m1 - 1] 与 nums2[m2 - 1]中的较大者,如果取得是后者,那么也就是 m1 + m2 - 1 的下标就是 k - 1
    -        if ((n1 + n2) % 2 == 1) {
    -            return c1;
    -        }
    -        // 如果是偶数个,那还要取两个数组后面的较小者,然后求平均值
    -        int c2 = Math.min(m1 >= n1 ? Integer.MAX_VALUE : nums1[m1], m2 >= n2 ? Integer.MAX_VALUE : nums2[m2]);
    -        return (c1 + c2) / 2.0;
    -    }
    -

    前面考虑的方法还是比较繁琐,考虑了两个数组的各种交叉情况,后面这个参考了一些网上的解法,代码比较简洁,但是可能不容易一下子就搞明白,所以配合了比较多的注释。

    -]]>
    - - Java - leetcode - - - leetcode - java - 题解 - Median of Two Sorted Arrays - -
    binary-watch /2016/09/29/binary-watch/ @@ -4852,51 +4899,6 @@ public class DynamicSqlSource implements SqlSource { 缓存 - - nginx 日志小记 - /2022/04/17/nginx-%E6%97%A5%E5%BF%97%E5%B0%8F%E8%AE%B0/ - nginx 默认的日志有特定的格式,我们也可以自定义,

    -

    默认的格式是预定义的 combined

    -
    log_format combined '$remote_addr - $remote_user [$time_local] '
    -                    '"$request" $status $body_bytes_sent '
    -                    '"$http_referer" "$http_user_agent"';
    - -

    配置的日志可以使用这个默认的,如果满足需求的话

    -
    Syntax:	access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
    -        access_log off;
    -Default: access_log logs/access.log combined;
    -Context: http, server, location, if in location, limit_except
    - -

    而如果需要额外的一些配置的话可以自己定义 log_format ,比如我想要给日志里加上请求时间,那就可以自己定义一个 log_format 比如添加下

    -
    $request_time
    -request processing time in seconds with a milliseconds resolution;   
    -time elapsed between the first bytes were read from the client and the log write after the last bytes were sent to the client
    - -
    log_format combined_extend '$remote_addr - $remote_user [$time_local] '
    -                    '"$request" $status $body_bytes_sent '
    -                    '"$http_referer" "$http_user_agent" "$request_time"';
    - -

    然后其他的比如还有 gzip 压缩,可以设置压缩级别,flush 刷盘时间还有根据条件控制

    -

    这里的条件控制简单看了下还比较厉害

    -

    比如我想对2xx 跟 3xx 的访问不记录日志

    -
    map $status $loggable {
    -    ~^[23]  0;
    -    default 1;
    -}
    -
    -access_log /path/to/access.log combined if=$loggable;
    - -

    $loggable 是 0 或者空时表示 if 条件为否,上面的默认就是 1,只有当请求状态 status 是 2xx 或 3xx 时才是 0,代表不用记录,有了这个特性就可以更灵活地配置日志

    -

    文章主要参考了 nginx 的 log 模块的文档

    -]]>
    - - nginx - - - nginx - 日志 - -
    openresty /2019/06/18/openresty/ @@ -4955,11 +4957,50 @@ location ~* ]]> - nginx + nginx + + + openresty + nginx + + + + Redis_分布式锁 + /2019/12/10/Redis-Part-1/ + 今天看了一下 redis 分布式锁 redlock 的实现,简单记录下,

    +

    加锁

    原先我对 redis 锁的概念就是加锁使用 setnx,解锁使用 lua 脚本,但是 setnx 具体是啥,lua 脚本是啥不是很清楚
    首先简单思考下这个问题,首先为啥不是先 get 一下 key 存不存在,然后再 set 一个 key value,因为加锁这个操作我们是要保证两点,一个是不能中途被打断,也就是说要原子性,如果是先 get 一下 key,如果不存在再 set 值的话,那就不是原子操作了;第二个是可不可以直接 set 值呢,显然不行,锁要保证唯一性,有且只能有一个线程或者其他应用单位获得该锁,正好 setnx 给了我们这种原子命令

    +

    然后是 setnx 的键和值分别是啥,键比较容易想到是要锁住的资源,比如 user_id, 这里有个我自己之前比较容易陷进去的误区,但是这个误区后
    面再说,这里其实是把user_id 作为要锁住的资源,在我获得锁的时候别的线程不允许操作,以此保证业务的正确性,不会被多个线程同时修改,确定了键,再来看看值是啥,其实原先我认为值是啥都没关系,我只要锁住了,光键就够我用了,但是考虑下多个线程的问题,如果我这个线程加了锁,然后我因为 gc 停顿等原因卡死了,这个时候redis 的锁或者说就是 redis 的缓存已经过期了,这时候另一个线程获得锁成功,然后我这个线程又活过来了,然后我就仍然认为我拿着锁,我去对数据进行修改或者释放锁,是不是就出现问题了,所以是不是我们还需要一个东西来区分这个锁是哪个线程加的,所以我们可以将值设置成为一个线程独有识别的值,至少在相对长的一段时间内不会重复。

    +

    上面其实还有两个问题,一个是当 gc 超时时,我这个线程如何知道我手里的锁已经过期了,一种方法是我在加好锁之后就维护一个超时时间,这里其实还有个问题,不过跟第二个问题相关,就一起说了,就是设置超时时间,有些对于不是锁的 redis 缓存操作可以是先设置好值,然后在设置过期时间,那么这就又有上面说到的不是原子性的问题,那么就需要在同一条指令里把超时时间也设置了,幸好 redis 提供了这种支持

    +
    SET resource_name my_random_value NX PX 30000
    +

    这里借鉴一下解释下,resource_name就是 key,代表要锁住的东西,my_random_value就是识别我这个线程的,NX代表只有在不存在的时候才设置,然后PX 30000表示超时时间是 30秒自动过期

    +

    PS:记录下我原先有的一个误区,是不是要用 key 来区分加锁的线程,这样只有一个用处,就是自身线程可以识别是否是自己加的锁,但是最大的问题是别的线程不知道,其实这个用户的出发点是我在担心前面提过的一个问题,就是当 gc 停顿后,我要去判断当前的这个锁是否是我加的,还有就是当释放锁的时候,如果保证不会错误释放了其他线程加的锁,但是这样附带很多其他问题,最大的就是其他线程怎么知道能不能加这个锁。

    +

    解锁

    当线程在锁过期之前就处理完了业务逻辑,那就可以提前释放这个锁,那么提前释放要怎么操作,直接del key显然是不行的,因为这样就是我前面想用线程随机值加资源名作为锁的初衷,我不能去释放别的线程加的锁,那么我要怎么办呢,先 get 一下看是不是我的?那又变成非原子的操作了,幸好redis 也考虑到了这个问题,给了lua 脚本来操作这种

    +
    if redis.call("get",KEYS[1]) == ARGV[1] then
    +    return redis.call("del",KEYS[1])
    +else
    +    return 0
    +end
    +

    这里的KEYS[1]就是前面加锁的resource_name,ARGV[1]就是线程的随机值my_random_value

    +

    多节点

    前面说的其实是单节点 redis 作为分布式锁的情况,那么当我们的 redis 有多节点的情况呢,如果多节点下处于加锁或者解锁或者锁有效情况下
    redis 的某个节点宕掉了怎么办,这里就有一些需要思考的地方,是否单独搞一个单节点的 redis作为分布式锁专用的,但是如果这个单节点的挂了呢,还有就是成本问题,所以我们需要一个多节点的分布式锁方案
    这里就引出了开头说到的redlock,这个可是 redis的作者写的, 他的加锁过程是分以下几步去做这个事情

    +
      +
    • 获取当前时间(毫秒数)。
    • +
    • 按顺序依次向N个Redis节点执行获取锁的操作。这个获取操作跟前面基于单Redis节点的获取锁的过程相同,包含随机字符串my_random_value,也包含过期时间(比如PX 30000,即锁的有效时间)。为了保证在某个Redis节点不可用的时候算法能够继续运行,这个获取锁的操作还有一个超时时间(time out),它要远小于锁的有效时间(几十毫秒量级)。客户端在向某个Redis节点获取锁失败以后,应该立即尝试下一个Redis节点。这里的失败,应该包含任何类型的失败,比如该Redis节点不可用,或者该Redis节点上的锁已经被其它客户端持有(注:Redlock原文中这里只提到了Redis节点不可用的情况,但也应该包含其它的失败情况)。
    • +
    • 计算整个获取锁的过程总共消耗了多长时间,计算方法是用当前时间减去第1步记录的时间。如果客户端从大多数Redis节点(>= N/2+1)成功获取到了锁,并且获取锁总共消耗的时间没有超过锁的有效时间(lock validity time),那么这时客户端才认为最终获取锁成功;否则,认为最终获取锁失败。
    • +
    • 如果最终获取锁成功了,那么这个锁的有效时间应该重新计算,它等于最初的锁的有效时间减去第3步计算出来的获取锁消耗的时间。
    • +
    • 如果最终获取锁失败了(可能由于获取到锁的Redis节点个数少于N/2+1,或者整个获取锁的过程消耗的时间超过了锁的最初有效时间),那么客户端应该立即向所有Redis节点发起释放锁的操作(即前面介绍的Redis Lua脚本)。
      释放锁的过程比较简单:客户端向所有Redis节点发起释放锁的操作,不管这些节点当时在获取锁的时候成功与否。这里为什么要向所有的节点发送释放锁的操作呢,这里是因为有部分的节点的失败原因可能是加锁时阻塞,加锁成功的结果没有及时返回,所以为了防止这种情况还是需要向所有发起这个释放锁的操作。
      初步记录就先到这。
    • +
    +]]>
    + + Redis + Distributed Lock + C + Redis - nginx - openresty + C + Redis + Distributed Lock + 分布式锁
    @@ -5069,18 +5110,18 @@ Fatal error: Abstract function abst1::abstra1() cannot contain body in php on li rabbitmq 介绍

    接触了一下rabbitmq,原来在选型的时候是在rabbitmq跟kafka之间做选择,网上搜了一下之后发现kafka的优势在于吞吐量,而rabbitmq相对注重可靠性,因为应用在im上,需要保证消息不能丢失所以就暂时选定rabbitmq,
    Message Queue的需求由来已久,80年代最早在金融交易中,高盛等公司采用Teknekron公司的产品,当时的Message queuing软件叫做:the information bus(TIB)。 TIB被电信和通讯公司采用,路透社收购了Teknekron公司。之后,IBM开发了MQSeries,微软开发了Microsoft Message Queue(MSMQ)。这些商业MQ供应商的问题是厂商锁定,价格高昂。2001年,Java Message queuing试图解决锁定和交互性的问题,但对应用来说反而更加麻烦了。
    RabbitMQ采用Erlang语言开发。Erlang语言由Ericson设计,专门为开发concurrent和distribution系统的一种语言,在电信领域使用广泛。OTP(Open Telecom Platform)作为Erlang语言的一部分,包含了很多基于Erlang开发的中间件/库/工具,如mnesia/SASL,极大方便了Erlang应用的开发。OTP就类似于Python语言中众多的module,用户借助这些module可以很方便的开发应用。
    于是2004年,摩根大通和iMatrix开始着手Advanced Message Queuing Protocol (AMQP)开放标准的开发。2006年,AMQP规范发布。2007年,Rabbit技术公司基于AMQP标准开发的RabbitMQ 1.0 发布。所有主要的编程语言均有与代理接口通讯的客户端库。

    简单的使用经验

    通俗的理解

    这里介绍下其中的一些概念,connection表示和队列服务器的连接,一般情况下是tcp连接, channel表示通道,可以在一个连接上建立多个通道,这里主要是节省了tcp连接握手的成本,exchange可以理解成一个路由器,将消息推送给对应的队列queue,其实是像一个订阅的模式。

    集群经验

    rabbitmqctl stop这个是关闭rabbitmq,在搭建集群时候先关闭服务,然后使用rabbitmq-server -detached静默启动,这时候使用rabbitmqctl cluster_status查看集群状态,因为还没将节点加入集群,所以只能看到类似

    -
    Cluster status of node rabbit@rabbit1 ...
    -[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
    - {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]}]
    -...done.
    +
    Cluster status of node rabbit@rabbit1 ...
    +[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
    + {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]}]
    +...done.

    然后就可以把当前节点加入集群,

    -
    rabbit2$ rabbitmqctl stop_app #这个stop_app与stop的区别是前者停的是rabbitmq应用,保留erlang节点,
    -                              #后者是停止了rabbitmq和erlang节点
    -Stopping node rabbit@rabbit2 ...done.
    -rabbit2$ rabbitmqctl join_cluster rabbit@rabbit1 #这里可以用--ram指定将当前节点作为内存节点加入集群
    -Clustering node rabbit@rabbit2 with [rabbit@rabbit1] ...done.
    +
    rabbit2$ rabbitmqctl stop_app #这个stop_app与stop的区别是前者停的是rabbitmq应用,保留erlang节点,
    +                              #后者是停止了rabbitmq和erlang节点
    +Stopping node rabbit@rabbit2 ...done.
    +rabbit2$ rabbitmqctl join_cluster rabbit@rabbit1 #这里可以用--ram指定将当前节点作为内存节点加入集群
    +Clustering node rabbit@rabbit2 with [rabbit@rabbit1] ...done.
     rabbit2$ rabbitmqctl start_app
    -Starting node rabbit@rabbit2 ...done.
    +Starting node rabbit@rabbit2 ...done.

    其他可以参考官方文档

    一些坑

    消息丢失

    这里碰到过一个坑,对于使用exchange来做消息路由的,会有一个情况,就是在routing_key没被订阅的时候,会将该条找不到路由对应的queue的消息丢掉What happens if we break our contract and send a message with one or four words, like "orange" or "quick.orange.male.rabbit"? Well, these messages won't match any bindings and will be lost.对应链接,而当使用空的exchange时,会保留消息,当出现消费者的时候就可以将收到之前生产者所推送的消息对应链接,这里就是用了空的exchange。

    集群搭建

    集群搭建的时候有个erlang vm生成的random cookie,这个是用来做集群之间认证的,相同的cookie才能连接,但是如果通过vim打开复制后在其他几点新建文件写入会多一个换行,导致集群建立是报错,所以这里最好使用scp等传输命令直接传输cookie文件,同时要注意下cookie的文件权限。
    另外在集群搭建的时候如果更改过hostname,那么要把rabbitmq的数据库删除,否则启动后会马上挂掉

    @@ -5270,6 +5311,68 @@ int zslRandomLevel(void) { return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL; }

    当随机值跟0xFFFF进行与操作小于ZSKIPLIST_P * 0xFFFF时才会增大 level 的值,因此保持了一个相对递减的概率
    可以简单分析下,当 random() 的值小于 0xFFFF 的 1/4,才会 level + 1,就意味着当有 1 - 1/4也就是3/4的概率是直接跳出,所以一层的概率是3/4,也就是 1-P,二层的概率是 P*(1-P),三层的概率是 P² * (1-P) 依次递推。

    +]]>
    + + Redis + 数据结构 + C + 源码 + Redis + + + redis + 数据结构 + 源码 + +
    + + redis数据结构介绍五-第五部分 对象 + /2020/01/20/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%BB%8B%E7%BB%8D%E4%BA%94/ + 前面说了这么些数据结构,其实大家对于 redis 最初的印象应该就是个 key-value 的缓存,类似于 memcache,redis 其实也是个 key-value,key 还是一样的字符串,或者说就是用 redis 自己的动态字符串实现,但是 value 其实就是前面说的那些数据结构,差不多快说完了,还有个 quicklist 后面还有一篇,这里先介绍下 redis 对于这些不同类型的 value 是怎么实现的,首先看下 redisObject 的源码头文件

    +
    /* The actual Redis Object */
    +#define OBJ_STRING 0    /* String object. */
    +#define OBJ_LIST 1      /* List object. */
    +#define OBJ_SET 2       /* Set object. */
    +#define OBJ_ZSET 3      /* Sorted set object. */
    +#define OBJ_HASH 4      /* Hash object. */
    +/*
    + * Objects encoding. Some kind of objects like Strings and Hashes can be
    + * internally represented in multiple ways. The 'encoding' field of the object
    + * is set to one of this fields for this object. */
    +#define OBJ_ENCODING_RAW 0     /* Raw representation */
    +#define OBJ_ENCODING_INT 1     /* Encoded as integer */
    +#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
    +#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
    +#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
    +#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
    +#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
    +#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
    +#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
    +#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
    +#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
    +
    +#define LRU_BITS 24
    +#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
    +#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
    +
    +#define OBJ_SHARED_REFCOUNT INT_MAX
    +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;
    +

    主体结构就是这个 redisObject,

    +
      +
    • type: 字段表示对象的类型,它对应的就是 redis 的对外暴露的,或者说用户可以使用的五种类型,OBJ_STRING, OBJ_LIST, OBJ_SET, OBJ_ZSET, OBJ_HASH
    • +
    • encoding: 字段表示这个对象在 redis 内部的编码方式,由OBJ_ENCODING_开头的 11 种
    • +
    • lru: 做LRU替换算法用,占24个bit
    • +
    • refcount: 引用计数。它允许robj对象在某些情况下被共享。
    • +
    • ptr: 指向底层实现数据结构的指针
      当 type 是 OBJ_STRING 时,表示类型是个 string,它的编码方式 encoding 可能有 OBJ_ENCODING_RAW,OBJ_ENCODING_INT,OBJ_ENCODING_EMBSTR 三种
      当 type 是 OBJ_LIST 时,表示类型是 list,它的编码方式 encoding 是 OBJ_ENCODING_QUICKLIST,对于早一些的版本,2.2这种可能还会使用 OBJ_ENCODING_ZIPLIST,OBJ_ENCODING_LINKEDLIST
      当 type 是 OBJ_SET 时,是个集合,但是得看具体元素的类型,有可能使用整数集合,OBJ_ENCODING_INTSET, 如果元素不全是整型或者数量超过一定限制,那么编码就是 OBJ_ENCODING_HT hash table 了
      当 type 是 OBJ_ZSET 时,是个有序集合,它底层有可能使用的是 OBJ_ENCODING_ZIPLIST 或者 OBJ_ENCODING_SKIPLIST
      当 type 是 OBJ_HASH 时,一开始也是 OBJ_ENCODING_ZIPLIST,然后当数据量大于 hash_max_ziplist_entries 时会转成 OBJ_ENCODING_HT
    • +
    ]]>
    Redis @@ -5487,68 +5590,6 @@ REDIS_STATIC void __quicklistInsertNode(quicklist *quicklist, quicklist->len++; }

    前面第一步先根据插入的是头还是尾选择不同的 push 函数,quicklistPushHead 或者 quicklistPushTail,举例分析下从头插入的 quicklistPushHead,先判断当前的 quicklistNode 节点还能不能允许再往 ziplist 里添加元素,如果可以就添加,如果不允许就新建一个 quicklistNode,然后调用 _quicklistInsertNodeBefore 将节点插进去,具体插入quicklist节点的操作类似链表的插入。

    -]]> - - Redis - 数据结构 - C - 源码 - Redis - - - redis - 数据结构 - 源码 - -
    - - redis数据结构介绍五-第五部分 对象 - /2020/01/20/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%BB%8B%E7%BB%8D%E4%BA%94/ - 前面说了这么些数据结构,其实大家对于 redis 最初的印象应该就是个 key-value 的缓存,类似于 memcache,redis 其实也是个 key-value,key 还是一样的字符串,或者说就是用 redis 自己的动态字符串实现,但是 value 其实就是前面说的那些数据结构,差不多快说完了,还有个 quicklist 后面还有一篇,这里先介绍下 redis 对于这些不同类型的 value 是怎么实现的,首先看下 redisObject 的源码头文件

    -
    /* The actual Redis Object */
    -#define OBJ_STRING 0    /* String object. */
    -#define OBJ_LIST 1      /* List object. */
    -#define OBJ_SET 2       /* Set object. */
    -#define OBJ_ZSET 3      /* Sorted set object. */
    -#define OBJ_HASH 4      /* Hash object. */
    -/*
    - * Objects encoding. Some kind of objects like Strings and Hashes can be
    - * internally represented in multiple ways. The 'encoding' field of the object
    - * is set to one of this fields for this object. */
    -#define OBJ_ENCODING_RAW 0     /* Raw representation */
    -#define OBJ_ENCODING_INT 1     /* Encoded as integer */
    -#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
    -#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
    -#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
    -#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
    -#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
    -#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
    -#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
    -#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
    -#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
    -
    -#define LRU_BITS 24
    -#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
    -#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
    -
    -#define OBJ_SHARED_REFCOUNT INT_MAX
    -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;
    -

    主体结构就是这个 redisObject,

    -
      -
    • type: 字段表示对象的类型,它对应的就是 redis 的对外暴露的,或者说用户可以使用的五种类型,OBJ_STRING, OBJ_LIST, OBJ_SET, OBJ_ZSET, OBJ_HASH
    • -
    • encoding: 字段表示这个对象在 redis 内部的编码方式,由OBJ_ENCODING_开头的 11 种
    • -
    • lru: 做LRU替换算法用,占24个bit
    • -
    • refcount: 引用计数。它允许robj对象在某些情况下被共享。
    • -
    • ptr: 指向底层实现数据结构的指针
      当 type 是 OBJ_STRING 时,表示类型是个 string,它的编码方式 encoding 可能有 OBJ_ENCODING_RAW,OBJ_ENCODING_INT,OBJ_ENCODING_EMBSTR 三种
      当 type 是 OBJ_LIST 时,表示类型是 list,它的编码方式 encoding 是 OBJ_ENCODING_QUICKLIST,对于早一些的版本,2.2这种可能还会使用 OBJ_ENCODING_ZIPLIST,OBJ_ENCODING_LINKEDLIST
      当 type 是 OBJ_SET 时,是个集合,但是得看具体元素的类型,有可能使用整数集合,OBJ_ENCODING_INTSET, 如果元素不全是整型或者数量超过一定限制,那么编码就是 OBJ_ENCODING_HT hash table 了
      当 type 是 OBJ_ZSET 时,是个有序集合,它底层有可能使用的是 OBJ_ENCODING_ZIPLIST 或者 OBJ_ENCODING_SKIPLIST
      当 type 是 OBJ_HASH 时,一开始也是 OBJ_ENCODING_ZIPLIST,然后当数据量大于 hash_max_ziplist_entries 时会转成 OBJ_ENCODING_HT
    • -
    ]]>
    Redis @@ -6643,22 +6684,22 @@ uint8_t LFULogIncr(uint8_t counter) {
  • If more than 25% of keys were expired, start again from step 1.
  • 从池子里随机获取20个key,将其中过期的key删掉,如果这其中有超过25%的key已经过期了,那就再来一次,以此保持过期的key不超过25%(左右),并且这个定时策略可以在redis的配置文件

    -
    # Redis calls an internal function to perform many background tasks, like
    -# closing connections of clients in timeout, purging expired keys that are
    -# never requested, and so forth.
    -#
    -# Not all tasks are performed with the same frequency, but Redis checks for
    -# tasks to perform according to the specified "hz" value.
    -#
    -# By default "hz" is set to 10. Raising the value will use more CPU when
    -# Redis is idle, but at the same time will make Redis more responsive when
    -# there are many keys expiring at the same time, and timeouts may be
    -# handled with more precision.
    -#
    -# The range is between 1 and 500, however a value over 100 is usually not
    -# a good idea. Most users should use the default of 10 and raise this up to
    -# 100 only in environments where very low latency is required.
    -hz 10
    +
    # Redis calls an internal function to perform many background tasks, like
    +# closing connections of clients in timeout, purging expired keys that are
    +# never requested, and so forth.
    +#
    +# Not all tasks are performed with the same frequency, but Redis checks for
    +# tasks to perform according to the specified "hz" value.
    +#
    +# By default "hz" is set to 10. Raising the value will use more CPU when
    +# Redis is idle, but at the same time will make Redis more responsive when
    +# there are many keys expiring at the same time, and timeouts may be
    +# handled with more precision.
    +#
    +# The range is between 1 and 500, however a value over 100 is usually not
    +# a good idea. Most users should use the default of 10 and raise this up to
    +# 100 only in environments where very low latency is required.
    +hz 10

    可以配置这个hz的值,代表的含义是每秒的执行次数,默认是10,其实也用了hz的普遍含义。有兴趣可以看看之前写的一篇文章redis系列介绍七-过期策略

    ]]> @@ -6735,6 +6776,43 @@ hz 10
    rust学习笔记-所有权二 /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/ @@ -6776,72 +6854,33 @@ hz 10

    这个例子在书里是会报错的,因为同时存在一个以上的可变引用,但是在我运行的版本里前面这段没有报错,只有当我真的要去更改的时候

    fn main() {
    -    let mut s1 = String::from("hello");
    -    let mut r1 = &mut s1;
    -    let mut r2 = &mut s1;
    -    change(&mut r1);
    -    change(&mut r2);
    -}
    -
    -
    -fn change(s: &mut String) {
    -    s.push_str(", world");
    -}
    -


    这里可能就是具体版本在实现上的一个差异,我用的 rustc 是 1.44.0 版本
    其实上面的主要是由 rust 想要避免这类多重可变更导致的异常问题,总结下就是三个点

    -
      -
    • 两个或两个以上的指针同时同时访问同一空间
    • -
    • 其中至少有一个指针会想空间中写入数据
    • -
    • 没有同步数据访问的机制
      并且我们不能在拥有不可变引用的情况下创建可变引用

      悬垂引用

      还有一点需要注意的就是悬垂引用
      fn main() {
      -    let reference_to_nothing = dangle();
      -}
      -
      -fn dangle() -> &String {
      -    let s = String::from("hello");
      -    &s
      -}
      -这里可以看到其实在 dangle函数返回后,这里的 s 理论上就离开了作用域,但是由于返回了 s 的引用,在 main 函数中就会拿着这个引用,就会出现如下错误

      总结

      最后总结下
    • -
    • 在任何一个段给定的时间里,你要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用。
    • -
    • 引用总是有效的。
    • -
    -]]> - - 语言 - Rust - - - Rust - 所有权 - 内存分布 - 新语言 - 可变引用 - 不可变引用 - -
    - - rust学习笔记-所有权一 - /2021/04/18/rust%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/ - 最近在看 《rust 权威指南》,还是难度比较大的,它里面的一些概念跟之前的用过的都有比较大的差别
    比起有 gc 的虚拟机语言,跟像 C 和 C++这种主动释放内存的,rust 有他的独特点,主要是有三条

    -
      -
    • Rust中的每一个值都有一个对应的变量作为它的所有者。
    • -
    • 在同一时间内,值有且只有一个所有者。
    • -
    • 当所有者离开自己的作用域时,它持有的值就会被释放掉。

      这里有两个重点:
    • -
    • s 在进入作用域后才变得有效
    • -
    • 它会保持自己的有效性直到自己离开作用域为止
    • -
    -

    然后看个案例

    -
    let x = 5;
    -let y = x;
    -

    这个其实有两种,一般可以认为比较多实现的会使用 copy on write 之类的,先让两个都指向同一个快 5 的存储,在发生变更后开始正式拷贝,但是涉及到内存处理的便利性,对于这类简单类型,可以直接拷贝
    但是对于非基础类型

    -
    let s1 = String::from("hello");
    -let s2 = s1;
    +    let mut s1 = String::from("hello");
    +    let mut r1 = &mut s1;
    +    let mut r2 = &mut s1;
    +    change(&mut r1);
    +    change(&mut r2);
    +}
     
    -println!("{}, world!", s1);
    -

    有可能认为有两种内存分布可能
    先看下 string 的内存结构

    第一种可能是

    第二种是

    我们来尝试编译下

    发现有这个错误,其实在 rust 中let y = x这个行为的实质是移动,在赋值给 y 之后 x 就无效了

    这样子就不会造成脱离作用域时,对同一块内存区域的二次释放,如果需要复制,可以使用 clone 方法

    -
    let s1 = String::from("hello");
    -let s2 = s1.clone();
     
    -println!("s1 = {}, s2 = {}", s1, s2);
    -

    这里其实会有点疑惑,为什么前面的x, y 的行为跟 s1, s2 的不一样,其实主要是基本类型和 string 这类的不定大小的类型的内存分配方式不同,x, y这类整型可以直接确定大小,可以直接在栈上分配,而像 string 和其他的变体结构体,其大小都是不能在编译时确定,所以需要在堆上进行分配

    +fn change(s: &mut String) { + s.push_str(", world"); +} +


    这里可能就是具体版本在实现上的一个差异,我用的 rustc 是 1.44.0 版本
    其实上面的主要是由 rust 想要避免这类多重可变更导致的异常问题,总结下就是三个点

    +
      +
    • 两个或两个以上的指针同时同时访问同一空间
    • +
    • 其中至少有一个指针会想空间中写入数据
    • +
    • 没有同步数据访问的机制
      并且我们不能在拥有不可变引用的情况下创建可变引用

      悬垂引用

      还有一点需要注意的就是悬垂引用
      fn main() {
      +    let reference_to_nothing = dangle();
      +}
      +
      +fn dangle() -> &String {
      +    let s = String::from("hello");
      +    &s
      +}
      +这里可以看到其实在 dangle函数返回后,这里的 s 理论上就离开了作用域,但是由于返回了 s 的引用,在 main 函数中就会拿着这个引用,就会出现如下错误

      总结

      最后总结下
    • +
    • 在任何一个段给定的时间里,你要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用。
    • +
    • 引用总是有效的。
    • +
    ]]>
    语言 @@ -6852,6 +6891,8 @@ hz 10
    spring event 介绍 /2022/01/30/spring-event-%E4%BB%8B%E7%BB%8D/ @@ -6978,35 +7048,6 @@ for _ in data: Spring Event - - summary-ranges-228 - /2016/10/12/summary-ranges-228/ - problem

    Given a sorted integer array without duplicates, return the summary of its ranges.

    -

    For example, given [0,1,2,4,5,7], return ["0->2","4->5","7"].

    -

    题解

    每一个区间的起点nums[i]加上j是否等于nums[i+j]
    参考

    -

    Code

    class Solution {
    -public:
    -    vector<string> summaryRanges(vector<int>& nums) {
    -        int i = 0, j = 1, n;
    -        vector<string> res;
    -        n = nums.size();
    -        while(i < n){
    -            j = 1;
    -            while(j < n && nums[i+j] - nums[i] == j) j++;
    -            res.push_back(j <= 1 ? to_string(nums[i]) : to_string(nums[i]) + "->" + to_string(nums[i + j - 1]));
    -            i += j;
    -        }
    -        return res;
    -    }
    -};
    ]]>
    - - leetcode - - - leetcode - c++ - -
    swoole-websocket-test /2016/07/13/swoole-websocket-test/ @@ -7170,19 +7211,6 @@ user3: jvm - - 上次的其他 外行聊国足 - /2022/03/06/%E4%B8%8A%E6%AC%A1%E7%9A%84%E5%85%B6%E4%BB%96-%E5%A4%96%E8%A1%8C%E8%81%8A%E5%9B%BD%E8%B6%B3/ - 上次本来想在换车牌后面聊下这个话题,为啥要聊这个话题呢,也很简单,在地铁上看到一对猜测是情侣或者比较关系好的男女同学在聊,因为是这位男同学是大学学的工科,然后自己爱好设计绘画相关的,可能还以此赚了点钱,在地铁上讨论男的要不要好好努力把大学课程完成好,大致的观点是没必要,本来就不适合,这一段我就不说了,恋爱人的嘴,信你个鬼。
    后面男的说在家里又跟他爹吵了关于男足的,估计是那次输了越南,实话说我不是个足球迷,对各方面技术相关也不熟,只是对包括这个人的解释和网上一些观点的看法,纯主观,这次地铁上这位说的大概意思是足球这个训练什么的很难的,要想赢越南也很难的,不是我们能嘴炮的;在网上看到一个赞同数很多的一个回答,说什么中国是个体育弱国,但是由于有一些乒乓球,跳水等小众项目比较厉害,让民众给误解了,首先我先来反驳下这个偷换概念的观点,第一所谓的体育弱国,跟我们觉得足球不应该这么差没半毛钱关系,因为体育弱国,我们的足球本来就不是顶尖的,也并不是去跟顶尖的球队去争,以足球为例,跟巴西,阿根廷,英国,德国,西班牙,意大利,法国这些足球强国,去比较,我相信没有一个足球迷会这么去做对比,因为我们足球历史最高排名是 1998 年的 37 名,最差是 100 名,把能数出来的强队都数完,估计都还不会到 37,所以根本没有跟强队去做对比,第二体育弱国,我们的体育投入是在逐年降低吗,我们是因战乱没法好好训练踢球?还是这帮傻逼就不争气,前面也说了我们足球世界排名最高 37,最低 100,那么前阵子我们输的越南是第几,目前我们的排名 77 名,越南 92 名,看明白了么,轮排名我们都不至于输越南,然后就是这个排名,这也是我想回应那位地铁上的兄弟,我觉得除了造核弹这种高精尖技术,绝大部分包含足球这类运动,遵循类二八原则,比如满分是 100 分,从 80 提到 90 分或者 90 分提到 100 分非常难,30 分提到 40 分,50 分提到 60 分我觉得都是可以凭后天努力达成的,基本不受天赋限制,这里可以以篮球来类比下,相对足球的确篮球没有那么火,或者行业市值没法比,但是也算是相对大众了,中国在篮球方面相对比较好一点,在 08 年奥运会冲进过八强,那也不是唯一的巅峰,但是我说这个其实是想说明两方面的事情,第一,像篮球一样,状态是有起起伏伏,排名也会变动,但是我觉得至少能维持一个相对稳定的总体排名和持平或者上升的趋势,这恰恰是我们这种所谓的“体育弱国”应该走的路线,第二就是去支持我的类二八原则的,可以看到我们的篮球这两年也很垃圾,排名跌到 29 了,那问题我觉得跟足球是一样的,就是不能脚踏实地,如斯科拉说的,中国篮球太缺少竞争,打得好不好都是这些人打,打输了还是照样拿钱,相对足球,篮球的技术我还是懂一些的,对比 08 年的中国男篮,的确像姚明跟王治郅这样的天赋型+努力型球员少了以后竞争力下降在所难免,但是去对比下基本功,传球,投篮,罚球稳定性,也完全不是一个水平的,这些就是我说的,可以通过努力训练拿 80 分的,只要拿到 80 分,甚至只要拿到 60 分,我觉得应该就还算对得起球迷了,就像 NBA 里球队也会有核心球员的更替,战绩起起伏伏,但是基本功这东西,防守积极性,我觉得不随核心球员的变化而变化,就像姚明这样的天赋,其实他应该还有一些先天缺陷,大脚趾较长等,但是他从 CBA 到 NBA,在 NBA 适应并且打成顶尖中锋,离不开刻苦训练,任何的成功都不是纯天赋的,必须要付出足够的努力。
    说回足球,如果像前面那么洗地(体育弱国),那能给我维持住一个稳定的排名我也能接受,问题是我们的经济物质资源比 2000 年前应该有了质的变化,身体素质也越来越好,即使是体育弱国,这么继续走下坡路,半死不活的,不觉得是打了自己的脸么。足球也需要基本功,基本的体能,力量这些,看看现在这些国足运动员的体型,对比下女足,说实话,如果男足这些运动员都练得不错的体脂率,耐力等,成绩即使不好,也不会比现在更差。
    纯主观吐槽,勿喷。

    -]]>
    - - 生活 - 运动 - - - 生活 - -
    介绍一下 RocketMQ /2020/06/21/%E4%BB%8B%E7%BB%8D%E4%B8%80%E4%B8%8B-RocketMQ/ @@ -7252,6 +7280,51 @@ user3: 中间件 + + nginx 日志小记 + /2022/04/17/nginx-%E6%97%A5%E5%BF%97%E5%B0%8F%E8%AE%B0/ + nginx 默认的日志有特定的格式,我们也可以自定义,

    +

    默认的格式是预定义的 combined

    +
    log_format combined '$remote_addr - $remote_user [$time_local] '
    +                    '"$request" $status $body_bytes_sent '
    +                    '"$http_referer" "$http_user_agent"';
    + +

    配置的日志可以使用这个默认的,如果满足需求的话

    +
    Syntax:	access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
    +        access_log off;
    +Default: access_log logs/access.log combined;
    +Context: http, server, location, if in location, limit_except
    + +

    而如果需要额外的一些配置的话可以自己定义 log_format ,比如我想要给日志里加上请求时间,那就可以自己定义一个 log_format 比如添加下

    +
    $request_time
    +request processing time in seconds with a milliseconds resolution;   
    +time elapsed between the first bytes were read from the client and the log write after the last bytes were sent to the client
    + +
    log_format combined_extend '$remote_addr - $remote_user [$time_local] '
    +                    '"$request" $status $body_bytes_sent '
    +                    '"$http_referer" "$http_user_agent" "$request_time"';
    + +

    然后其他的比如还有 gzip 压缩,可以设置压缩级别,flush 刷盘时间还有根据条件控制

    +

    这里的条件控制简单看了下还比较厉害

    +

    比如我想对2xx 跟 3xx 的访问不记录日志

    +
    map $status $loggable {
    +    ~^[23]  0;
    +    default 1;
    +}
    +
    +access_log /path/to/access.log combined if=$loggable;
    + +

    $loggable 是 0 或者空时表示 if 条件为否,上面的默认就是 1,只有当请求状态 status 是 2xx 或 3xx 时才是 0,代表不用记录,有了这个特性就可以更灵活地配置日志

    +

    文章主要参考了 nginx 的 log 模块的文档

    +]]>
    + + nginx + + + nginx + 日志 + +
    介绍下最近比较实用的端口转发 /2021/11/14/%E4%BB%8B%E7%BB%8D%E4%B8%8B%E6%9C%80%E8%BF%91%E6%AF%94%E8%BE%83%E5%AE%9E%E7%94%A8%E7%9A%84%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91/ @@ -7518,16 +7591,16 @@ user3: - 屯菜惊魂记 - /2022/04/24/%E5%B1%AF%E8%8F%9C%E6%83%8A%E9%AD%82%E8%AE%B0/ - 因某国际大都市的给力表现,昨儿旁边行政区启动应急响应,同事早上就在群里说要去超市买菜了,到了超市人还特别多,由于来的就是我们经常去的那家超市,一方面为了安全,另一方面是怕已经抢不到了,就去了另一家比较远的超市,开车怕没车位就骑了小电驴,还下着小雨,结果到了超市差不多 12 点多,超市里出来的人都是推着一整车一整车的物资,有些比较像我,整箱的泡面,好几提纸巾,还有各种吃的,都是整箱整箱的,进了超市发现结账包括自助结账的都排很长的队,到了蔬菜货架附近,差点哼起那首歌“空空如也~”,新鲜蔬菜基本已被抢空,只剩下一些卖相不太好的土豆番薯之类的,也算是意料之外情理之中了,本来以为这家超市稍微离封控区远一些会空一点,结果就是所谓的某大都市封控了等物资,杭州市是屯了物资等封控,新鲜蔬菜没了我们也只能买点其他的,神奇的是水果基本都在,可能困难时期水果不算必需品了?还是水果基本人人都已经储备了很多,不太能理解,虽然水果还在,但是称重的地方也还有好多人排队,我们采取了并行策略,LD 在那排队,遥控指挥我去拿其他物资,拿了点碱水面,黑米,那黑米的时候还闹了个乌龙,因为前面就是散装鸡蛋的堆货的地方,结果我们以为是在那后面排队,结果称重那个在那散步了,我们还在那排队,看到后面排队,那几个挑的人也该提醒下吧,几个鸡蛋挑了半天,看看人家大妈,直接拿了四盘,看了下牛奶货架也比较空,不过还有致优跟优倍,不过不算很实惠,本来想买,只是后来赶着去结账,就给忘了,称好了黑米去看了下肉,结果肉也没了,都在买猪蹄,我们也不太爱吃猪蹄,就买了点鸡胸肉,整体看起来我们买的东西真的有点格格不入,不买泡面(因为 LD 不让买了),也不屯啥米和鸡蛋,其实鸡蛋已经买了,米也买了,其他的本身冰箱小也放不下太多东西,我是觉得还可能在屯一点这那的,LD 觉得太多了,基本的米面油有了,其他调味品什么也有了。后面就是排队结账,我去排的时候刚好前面一个小伙子跟大妈在争执,大妈说我们差不多时间来的,你要排前面就前面,小伙子有点不高兴,觉得她就是插队,哈哈,平时一般这种剧情都是发生在我身上的,这会看着前面的吵起来还是很开心的,终于有跟我一样较真的人了,有时候总觉得我是个很纠结,很较真的人,但是我现在慢慢认可了这种较真,如果没有人指出来这种是插队行为,是不对的,就会有越来越多的人觉得是可以随意插队的,正确的事应该要坚持,很多情况大家总是觉得多一事不如少一事,鸡毛蒜皮的没什么好计较的,正是这种想法,那么多人才不管任何规则,反而搞得像遵守规则都是傻 X 似的。回到屯物资,后面结账排到队了也没来得及买原来想买的花生牛奶什么的,毕竟那么多人排着队,回家后因为没有蔬菜,结果就只能吃干菜汤和饭了

    + 上次的其他 外行聊国足 + /2022/03/06/%E4%B8%8A%E6%AC%A1%E7%9A%84%E5%85%B6%E4%BB%96-%E5%A4%96%E8%A1%8C%E8%81%8A%E5%9B%BD%E8%B6%B3/ + 上次本来想在换车牌后面聊下这个话题,为啥要聊这个话题呢,也很简单,在地铁上看到一对猜测是情侣或者比较关系好的男女同学在聊,因为是这位男同学是大学学的工科,然后自己爱好设计绘画相关的,可能还以此赚了点钱,在地铁上讨论男的要不要好好努力把大学课程完成好,大致的观点是没必要,本来就不适合,这一段我就不说了,恋爱人的嘴,信你个鬼。
    后面男的说在家里又跟他爹吵了关于男足的,估计是那次输了越南,实话说我不是个足球迷,对各方面技术相关也不熟,只是对包括这个人的解释和网上一些观点的看法,纯主观,这次地铁上这位说的大概意思是足球这个训练什么的很难的,要想赢越南也很难的,不是我们能嘴炮的;在网上看到一个赞同数很多的一个回答,说什么中国是个体育弱国,但是由于有一些乒乓球,跳水等小众项目比较厉害,让民众给误解了,首先我先来反驳下这个偷换概念的观点,第一所谓的体育弱国,跟我们觉得足球不应该这么差没半毛钱关系,因为体育弱国,我们的足球本来就不是顶尖的,也并不是去跟顶尖的球队去争,以足球为例,跟巴西,阿根廷,英国,德国,西班牙,意大利,法国这些足球强国,去比较,我相信没有一个足球迷会这么去做对比,因为我们足球历史最高排名是 1998 年的 37 名,最差是 100 名,把能数出来的强队都数完,估计都还不会到 37,所以根本没有跟强队去做对比,第二体育弱国,我们的体育投入是在逐年降低吗,我们是因战乱没法好好训练踢球?还是这帮傻逼就不争气,前面也说了我们足球世界排名最高 37,最低 100,那么前阵子我们输的越南是第几,目前我们的排名 77 名,越南 92 名,看明白了么,轮排名我们都不至于输越南,然后就是这个排名,这也是我想回应那位地铁上的兄弟,我觉得除了造核弹这种高精尖技术,绝大部分包含足球这类运动,遵循类二八原则,比如满分是 100 分,从 80 提到 90 分或者 90 分提到 100 分非常难,30 分提到 40 分,50 分提到 60 分我觉得都是可以凭后天努力达成的,基本不受天赋限制,这里可以以篮球来类比下,相对足球的确篮球没有那么火,或者行业市值没法比,但是也算是相对大众了,中国在篮球方面相对比较好一点,在 08 年奥运会冲进过八强,那也不是唯一的巅峰,但是我说这个其实是想说明两方面的事情,第一,像篮球一样,状态是有起起伏伏,排名也会变动,但是我觉得至少能维持一个相对稳定的总体排名和持平或者上升的趋势,这恰恰是我们这种所谓的“体育弱国”应该走的路线,第二就是去支持我的类二八原则的,可以看到我们的篮球这两年也很垃圾,排名跌到 29 了,那问题我觉得跟足球是一样的,就是不能脚踏实地,如斯科拉说的,中国篮球太缺少竞争,打得好不好都是这些人打,打输了还是照样拿钱,相对足球,篮球的技术我还是懂一些的,对比 08 年的中国男篮,的确像姚明跟王治郅这样的天赋型+努力型球员少了以后竞争力下降在所难免,但是去对比下基本功,传球,投篮,罚球稳定性,也完全不是一个水平的,这些就是我说的,可以通过努力训练拿 80 分的,只要拿到 80 分,甚至只要拿到 60 分,我觉得应该就还算对得起球迷了,就像 NBA 里球队也会有核心球员的更替,战绩起起伏伏,但是基本功这东西,防守积极性,我觉得不随核心球员的变化而变化,就像姚明这样的天赋,其实他应该还有一些先天缺陷,大脚趾较长等,但是他从 CBA 到 NBA,在 NBA 适应并且打成顶尖中锋,离不开刻苦训练,任何的成功都不是纯天赋的,必须要付出足够的努力。
    说回足球,如果像前面那么洗地(体育弱国),那能给我维持住一个稳定的排名我也能接受,问题是我们的经济物质资源比 2000 年前应该有了质的变化,身体素质也越来越好,即使是体育弱国,这么继续走下坡路,半死不活的,不觉得是打了自己的脸么。足球也需要基本功,基本的体能,力量这些,看看现在这些国足运动员的体型,对比下女足,说实话,如果男足这些运动员都练得不错的体脂率,耐力等,成绩即使不好,也不会比现在更差。
    纯主观吐槽,勿喷。

    ]]>
    生活 + 运动 生活 - 囤物资
    @@ -7547,105 +7620,83 @@ user3: - 给小电驴上牌 - /2022/03/20/%E7%BB%99%E5%B0%8F%E7%94%B5%E9%A9%B4%E4%B8%8A%E7%89%8C/ - 三八节活动的时候下决心买了个小电驴,主要是上下班路上现在通勤条件越来越恶劣了,之前都是觉得坐公交就行了,实际路程就比较短,但是现在或者说大概是年前那两个月差不多就开始了,基本是堵一路,个人感觉是天目山路那边在修地铁,而且蚂蚁的几个空间都在那,上班的时间点都差不多,前一个修地铁感觉挺久了,机动车保有量也越来越多,总体是古墩路就越来越堵,还有个原因就是早上上班的点共享单车都被骑走了,有时候整整走一路都没一辆,有时候孤零零地有一辆基本都是破的;走路其实也是一种选择,但是因为要赶着上班,走得太慢就要很久,可能要 45 分钟这样,走得比较快就一身汗挺难受的。所以考虑自行车和电动车,这里还有一点就是不管是乘公交还是骑共享单车,其实都要从楼下走出去蛮远,公司回来也是,也就是这种通勤方式在准备阶段就花了比较多时间,比如总的从下班到到家的时间是半小时,可能在骑共享单车和公交车上的时间都不到十分钟,就比较难受。觉得这种比例太浪费时间,如果能有这种比较点对点的方式,估计能省时省力不少,前面说的骑共享单车的方式其实在之前是比较可行的,但是后来越来越少车,基本都是每周的前几天,周一到周三都是没有车,走路到公司再冷的天都是走出一身的汗,下雨天就更难受,本来下雨天应该是优先选择坐公交,但是一般下雨天堵车会更严重,而且车子到我上车的那个站,下雨天就挤得不行,总体说下来感觉事情都不打,但是几年下来,还是会挺不爽的。

    -

    电驴看的比较草率,主要是考虑续航,然后锂电池外加 48v 和 24AH,这样一般来讲还是价格比较高的,只是原来没预料到这个限速,以为现在的车子都比较快,但是现在的新国标车子都是 25km/h 的限速,然后 15km/h 都是会要提醒,虽然说有一些特殊的解除限速的方法,但是解了也就 35km/h ,差距不是特别大,而且现在的车子都是比较小,也不太能载东西,特别是上下班路程也不远的情况下,其实不是那么需要速度,就像我朋友说的,可能骑车的时间还不如等红绿灯多,所以就还好,也不打算解除限速,只是品牌上也仔细看,后来选了绿源,目前大部分还是雅迪,爱玛,台羚,绿源,小牛等,路上看的话还是雅迪比较多,不过价格也比较贵一点,还有就是小牛了,是比较新兴的品牌,手机 App 什么的做得比较好,而且也比较贵,最后以相对比较便宜的价格买了个锂电 48V24AH 的小车子,后来发现还是有点不方便的点就是没有比较大的筐,也不好装,这样就是下雨天雨衣什么的比较不方便放。

    -

    聊回来主题上牌这个事情,这个事情也是颇费心力,提车的时候店里的让我跟他早上一起去,但是因为不确定时间,也比较远就没跟着去,因为我是线上买的,线下自提,线下的店可能没啥利润可以拿,就不肯帮忙代上牌,朋友说在线下店里买是可以代上的,自己上牌过程也比较曲折,一开始是头盔没到,然后是等开发票,主要的东西就是需要骑着车子去车管所,不能只自己去,然后需要预约,附近比较近的都是提前一周就预约完了号了,要提前在支付宝上进行预约,比较空的就是店里推荐的景区大队,但是随之而来就是比较蛋疼的,这个景区大队太远了,看下骑车距离有十几公里,所以就有点拖延症,但是总归要上的,不然一直不能开是白买了,上牌的材料主要是车辆合格证,发票,然后车子上的浙品码,在车架上和电池上,然后车架号什么的都要跟合格证上完全对应,整体车子要跟合格证上一毛一样,如果有额外的反光镜,后面副座都需要拆掉,脚踏板要装上,到了那其实还比较顺利,就是十几公里外加那天比较冷,吹得头疼。

    -]]>
    - - 生活 - - - 生活 - -
    - - 搬运两个 StackOverflow 上的 Mysql 编码相关的问题解答 - /2022/01/16/%E6%90%AC%E8%BF%90%E4%B8%A4%E4%B8%AA-StackOverflow-%E4%B8%8A%E7%9A%84-Mysql-%E7%BC%96%E7%A0%81%E7%9B%B8%E5%85%B3%E7%9A%84%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94/ - Mysql 字符编码和排序规则

    这个一直是属于一知半解的状态,知道 utf8 跟 utf8mb4 的区别主要是能不能支持 emoji,但是具体后面配置的排序规则是用来干嘛,或者有什么区别,应该使用哪个,所以在 stackoverflow 上找了下,有两个比较不错的解答,就搬过来并且配合机翻做了点修改

    -

    原文

    For those people still arriving at this question in 2020 or later, there are newer options that may be better than both of these. For example, utf8mb4_0900_ai_ci.

    -

    All these collations are for the UTF-8 character encoding. The differences are in how text is sorted and compared.

    -

    _unicode_ci and _general_ci are two different sets of rules for sorting and comparing text according to the way we expect. Newer versions of MySQL introduce new sets of rules, too, such as _0900_ai_ci for equivalent rules based on Unicode 9.0 - and with no equivalent _general_ci variant. People reading this now should probably use one of these newer collations instead of either _unicode_ci or _general_ci. The description of those older collations below is provided for interest only.

    -

    MySQL is currently transitioning away from an older, flawed UTF-8 implementation. For now, you need to use utf8mb4 instead of utf8 for the character encoding part, to ensure you are getting the fixed version. The flawed version remains for backward compatibility, though it is being deprecated.

    -

    Key differences

    -

    utf8mb4_unicode_ci is based on the official Unicode rules for universal sorting and comparison, which sorts accurately in a wide range of languages.

    -

    utf8mb4_general_ci is a simplified set of sorting rules which aims to do as well as it can while taking many short-cuts designed to improve speed. It does not follow the Unicode rules and will result in undesirable sorting or comparison in some situations, such as when using particular languages or characters.

    -

    On modern servers, this performance boost will be all but negligible. It was devised in a time when servers had a tiny fraction of the CPU performance of today’s computers.

    -

    Benefits of utf8mb4_unicode_ci over utf8mb4_general_ci

    -

    utf8mb4_unicode_ci, which uses the Unicode rules for sorting and comparison, employs a fairly complex algorithm for correct sorting in a wide range of languages and when using a wide range of special characters. These rules need to take into account language-specific conventions; not everybody sorts their characters in what we would call ‘alphabetical order’.

    -

    As far as Latin (ie “European”) languages go, there is not much difference between the Unicode sorting and the simplified utf8mb4_general_cisorting in MySQL, but there are still a few differences:

    -

    For examples, the Unicode collation sorts “ß” like “ss”, and “Œ” like “OE” as people using those characters would normally want, whereas utf8mb4_general_cisorts them as single characters (presumably like “s” and “e” respectively).

    -

    Some Unicode characters are defined as ignorable, which means they shouldn’t count toward the sort order and the comparison should move on to the next character instead. utf8mb4_unicode_cihandles these properly.

    -

    In non-latin languages, such as Asian languages or languages with different alphabets, there may be a lot more differences between Unicode sorting and the simplified utf8mb4_general_cisorting. The suitability of utf8mb4_general_ciwill depend heavily on the language used. For some languages, it’ll be quite inadequate.

    -

    What should you use?

    -

    There is almost certainly no reason to use utf8mb4_general_cianymore, as we have left behind the point where CPU speed is low enough that the performance difference would be important. Your database will almost certainly be limited by other bottlenecks than this.

    -

    In the past, some people recommended to use utf8mb4_general_ciexcept when accurate sorting was going to be important enough to justify the performance cost. Today, that performance cost has all but disappeared, and developers are treating internationalization more seriously.

    -

    There’s an argument to be made that if speed is more important to you than accuracy, you may as well not do any sorting at all. It’s trivial to make an algorithm faster if you do not need it to be accurate. So, utf8mb4_general_ciis a compromise that’s probably not needed for speed reasons and probably also not suitable for accuracy reasons.

    -

    One other thing I’ll add is that even if you know your application only supports the English language, it may still need to deal with people’s names, which can often contain characters used in other languages in which it is just as important to sort correctly. Using the Unicode rules for everything helps add peace of mind that the very smart Unicode people have worked very hard to make sorting work properly.

    -

    What the parts mean

    -

    Firstly, ci is for case-insensitive sorting and comparison. This means it’s suitable for textual data, and case is not important. The other types of collation are cs (case-sensitive) for textual data where case is important, and bin, for where the encoding needs to match, bit for bit, which is suitable for fields which are really encoded binary data (including, for example, Base64). Case-sensitive sorting leads to some weird results and case-sensitive comparison can result in duplicate values differing only in letter case, so case-sensitive collations are falling out of favor for textual data - if case is significant to you, then otherwise ignorable punctuation and so on is probably also significant, and a binary collation might be more appropriate.

    -

    Next, unicode or general refers to the specific sorting and comparison rules - in particular, the way text is normalized or compared. There are many different sets of rules for the utf8mb4 character encoding, with unicode and general being two that attempt to work well in all possible languages rather than one specific one. The differences between these two sets of rules are the subject of this answer. Note that unicode uses rules from Unicode 4.0. Recent versions of MySQL add the rulesets unicode_520 using rules from Unicode 5.2, and 0900 (dropping the “unicode_” part) using rules from Unicode 9.0.

    -

    And lastly, utf8mb4 is of course the character encoding used internally. In this answer I’m talking only about Unicode based encodings.

    -

    翻译

    对于那些在 2020 年或之后仍会遇到这个问题的人,有可能比这两个更好的新选项。例如,utf8mb4_0900_ai_ci

    -

    所有这些排序规则都用于 UTF-8 字符编码。不同之处在于文本的排序和比较方式。

    -

    _unicode_ci_general_ci是两组不同的规则,用于按照我们期望的方式对文本进行排序和比较。较新版本的 MySQL 也引入了新的规则集,例如 _0900_ai_ci用于基于 Unicode 9.0 的等效规则 - 并且没有等效的 _general_ci变体。现在阅读本文的人可能应该使用这些较新的排序规则之一,而不是 _unicode_ci_general_ci。下面对那些较旧的排序规则的描述仅供参考。

    -

    MySQL 目前正在从旧的、有缺陷的 UTF-8 实现过渡。现在,您需要使用 utf8mb4 而不是 utf8作为字符编码部分,以确保您获得的是固定版本。有缺陷的版本仍然是为了向后兼容,尽管它已被弃用。

    -

    主要区别

    -

    utf8mb4_unicode_ci基于官方 Unicode 规则进行通用排序和比较,可在多种语言中准确排序。

    -

    utf8mb4_general_ci是一组简化的排序规则,旨在尽其所能,同时采用许多旨在提高速度的捷径。它不遵循 Unicode 规则,并且在某些情况下会导致不希望的排序或比较,例如在使用特定语言或字符时。

    -

    在现代服务器上,这种性能提升几乎可以忽略不计。它是在服务器的 CPU 性能只有当今计算机的一小部分时设计的。

    -

    utf8mb4_unicode_ci 相对于 utf8mb4_general_ci的优势

    -

    utf8mb4_unicode_ci使用 Unicode 规则进行排序和比较,采用相当复杂的算法在多种语言中以及在使用多种特殊字符时进行正确排序。这些规则需要考虑特定语言的约定;不是每个人都按照我们所说的“字母顺序”对他们的字符进行排序。

    -

    就拉丁语(即“欧洲”)语言而言,Unicode 排序和 MySQL 中简化的 utf8mb4_general_ci排序没有太大区别,但仍有一些区别:

    -

    例如,Unicode 排序规则将“ß”排序为“ss”,将“Œ”排序为“OE”,因为使用这些字符的人通常需要这些字符,而 utf8mb4_general_ci将它们排序为单个字符(大概分别像“s”和“e” )。

    -

    一些 Unicode 字符被定义为可忽略,这意味着它们不应该计入排序顺序,并且比较应该转到下一个字符。 utf8mb4_unicode_ci正确处理这些。

    -

    在非拉丁语言中,例如亚洲语言或具有不同字母的语言,Unicode 排序和简化的 utf8mb4_general_ci排序之间可能存在更多差异。 utf8mb4_general_ci的适用性在很大程度上取决于所使用的语言。对于某些语言,这将是非常不充分的。

    -

    你应该用什么?

    -

    几乎可以肯定没有理由再使用 utf8mb4_general_ci,因为我们已经将 CPU 速度低到会严重影响性能表现的时代远抛在脑后了。您的数据库几乎肯定会受到除此之外的其他瓶颈的限制。

    -

    过去,有些人建议使用 utf8mb4_general_ci,除非准确排序足够重要以证明性能成本是合理的。如今,这种性能成本几乎消失了,开发人员正在更加认真地对待国际化。

    -

    有一个论点是,如果速度对您来说比准确性更重要,那么您可能根本不进行任何排序。如果您不需要准确的算法,那么使算法更快是微不足道的。因此,utf8mb4_general_ci是一种折衷方案,出于速度原因可能不需要,也可能出于准确性原因也不适合。

    -

    我要补充的另一件事是,即使您知道您的应用程序仅支持英语,它可能仍需要处理人名,这些人名通常包含其他语言中使用的字符,在这些语言中正确排序同样重要.对所有事情都使用 Unicode 规则有助于让您更加安心,因为非常聪明的 Unicode 人员已经非常努力地工作以使排序正常工作。

    -

    其余各个部分是什么意思

    -

    首先, ci 用于不区分大小写的排序和比较。这意味着它适用于文本数据,大小写并不重要。其他类型的排序规则是 cs(区分大小写),用于区分大小写的文本数据,以及 bin,用于编码需要匹配的地方,逐位匹配,适用于真正编码二进制数据的字段(包括,用于例如,Base64)。区分大小写的排序会导致一些奇怪的结果,区分大小写的比较可能会导致重复值仅在字母大小写上有所不同,因此区分大小写的排序规则对文本数据不受欢迎 - 如果大小写对您很重要,那么标点符号就可以忽略等等可能也很重要,二进制排序规则可能更合适。

    -

    接下来,unicode 或general 指的是具体的排序和比较规则——特别是文本被规范化或比较的方式。 utf8mb4 字符编码有许多不同的规则集,其中 unicode 和 general 是两种,它们试图在所有可能的语言中都很好地工作,而不是在一种特定的语言中。这两组规则之间的差异是此答案的主题。请注意,unicode 使用 Unicode 4.0 中的规则。 MySQL 的最新版本使用 Unicode 5.2 的规则添加规则集 unicode_520,使用 Unicode 9.0 的规则添加 0900(删除“unicode_”部分)。

    -

    最后,utf8mb4 当然是内部使用的字符编码。在这个答案中,我只谈论基于 Unicode 的编码。

    -

    utf8 和 utf8mb4 编码有什么区别

    原文

    UTF-8is a variable-length encoding. In the case of UTF-8, this means that storing one code point requires one to four bytes. However, MySQL’s encoding called “utf8” (alias of “utf8mb3”) only stores a maximum of three bytes per code point.

    -

    So the character set “utf8”/“utf8mb3” cannot store all Unicode code points: it only supports the range 0x000 to 0xFFFF, which is called the “Basic Multilingual Plane“. See also Comparison of Unicode encodings.

    -

    This is what (a previous version of the same page at)the MySQL documentationhas to say about it:

    -
    -

    The character set named utf8[/utf8mb3] uses a maximum of three bytes per character and contains only BMP characters. As of MySQL 5.5.3, the utf8mb4 character set uses a maximum of four bytes per character supports supplemental characters:

    -
      -
    • For a BMP character, utf8[/utf8mb3] and utf8mb4 have identical storage characteristics: same code values, same encoding, same length.
    • -
    • For a supplementary character, utf8[/utf8mb3] cannot store the character at all, while utf8mb4 requires four bytes to store it. Since utf8[/utf8mb3] cannot store the character at all, you do not have any supplementary characters in utf8[/utf8mb3] columns and you need not worry about converting characters or losing data when upgrading utf8[/utf8mb3] data from older versions of MySQL.
    • -
    + Leetcode 349 两个数组的交集 ( Intersection of Two Arrays *Easy* ) 题解分析 + /2022/03/07/Leetcode-349-%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86-Intersection-of-Two-Arrays-Easy-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 题目介绍

    给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

    +

     

    +

    示例

    +

    示例 1:

    +

    输入:nums1 = [1,2,2,1], nums2 = [2,2]
    输出:[2]

    -

    So if you want your column to support storing characters lying outside the BMP (and you usually want to), such as emoji, use “utf8mb4”. See also What are the most common non-BMP Unicode characters in actual use?.

    -

    译文

    UTF-8 是一种可变长度编码。对于 UTF-8,这意味着存储一个代码点需要一到四个字节。但是,MySQL 的编码称为“utf8”(“utf8mb3”的别名)每个代码点最多只能存储三个字节。

    -

    所以字符集“utf8”/“utf8mb3”不能存储所有的Unicode码位:它只支持0x000到0xFFFF的范围,被称为“基本多语言平面”。另请参阅 Unicode 编码比较

    -

    这就是(同一页面的先前版本)MySQL 文档 不得不说的:

    -

    名为 utf8[/utf8mb3] 的字符集每个字符最多使用三个字节,并且仅包含 BMP 字符。从 MySQL 5.5.3 开始,utf8mb4 字符集每个字符最多使用四个字节,支持补充字符:

    -
      -
    • 对于 BMP 字符,utf8[/utf8mb3] 和 utf8mb4 具有相同的存储特性:相同的代码值、相同的编码、相同的长度。
    • -
    • 对于补充字符,utf8[/utf8mb3] 根本无法存储该字符,而 utf8mb4 需要四个字节来存储它。由于 utf8[/utf8mb3] 根本无法存储字符,因此您在 utf8[/utf8mb3] 列中没有任何补充字符,您不必担心从旧版本升级 utf8[/utf8mb3] 数据时转换字符或丢失数据mysql。
    • -
    +

    示例 2:

    +

    输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
    输出:[9,4]
    解释:[4,9] 也是可通过的 + 

    -

    因此,如果您希望您的列支持存储位于 BMP 之外的字符(并且您通常希望这样做),例如 emoji,请使用“utf8mb4”。另请参阅

    -

    实际使用中最常见的非 BMP Unicode 字符是什么?

    +

    提示:

      +
    • 1 <= nums1.length, nums2.length <= 1000
    • +
    • 0 <= nums1[i], nums2[i] <= 1000
    • +
    +

    分析与题解

    两个数组的交集,最简单就是两层循环了把两个都存在的找出来,不过还有个要去重的问题,稍微思考下可以使用集合 set 来处理,先把一个数组全丢进去,再对比另外一个,如果出现在第一个集合里就丢进一个新的集合,最后转换成数组,这次我稍微取了个巧,因为看到了提示里的条件,两个数组中的元素都是不大于 1000 的,所以就搞了个 1000 长度的数组,如果在第一个数组出现,就在对应的下标设置成 1,如果在第二个数组也出现了就加 1,

    +

    code

    public int[] intersection(int[] nums1, int[] nums2) {
    +    // 大小是 1000 的数组,如果没有提示的条件就没法这么做
    +    // define a array which size is 1000, and can not be done like this without the condition in notice
    +        int[] inter = new int[1000];
    +        int[] outer;
    +        int m = 0;
    +        for (int j : nums1) {
    +            //  这里得是设置成 1,因为有可能 nums1 就出现了重复元素,如果直接++会造成结果重复
    +            // need to be set 1, cause element in nums1 can be duplicated
    +            inter[j] = 1;
    +        }
    +        for (int j : nums2) {
    +            if (inter[j] > 0) {
    +                // 这里可以直接+1,因为后面判断只需要判断大于 1
    +                // just plus 1, cause we can judge with condition that larger than  1
    +                inter[j] += 1;
    +            }
    +        }
    +        for (int i = 0; i < inter.length; i++) {
    +            // 统计下元素数量
    +            // count distinct elements
    +            if (inter[i] > 1) {
    +                m++;
    +            }
    +        }
    +        // initial a array of size m
    +        outer = new int[m];
    +        m = 0;
    +        for (int i = 0; i < inter.length; i++) {
    +            if (inter[i] > 1) {
    +                // add to outer
    +                outer[m++] = i;
    +            }
    +        }
    +        return outer;
    +    }
    ]]>
    + + Java + leetcode + + + leetcode + java + 题解 + Intersection of Two Arrays + + + + 给小电驴上牌 + /2022/03/20/%E7%BB%99%E5%B0%8F%E7%94%B5%E9%A9%B4%E4%B8%8A%E7%89%8C/ + 三八节活动的时候下决心买了个小电驴,主要是上下班路上现在通勤条件越来越恶劣了,之前都是觉得坐公交就行了,实际路程就比较短,但是现在或者说大概是年前那两个月差不多就开始了,基本是堵一路,个人感觉是天目山路那边在修地铁,而且蚂蚁的几个空间都在那,上班的时间点都差不多,前一个修地铁感觉挺久了,机动车保有量也越来越多,总体是古墩路就越来越堵,还有个原因就是早上上班的点共享单车都被骑走了,有时候整整走一路都没一辆,有时候孤零零地有一辆基本都是破的;走路其实也是一种选择,但是因为要赶着上班,走得太慢就要很久,可能要 45 分钟这样,走得比较快就一身汗挺难受的。所以考虑自行车和电动车,这里还有一点就是不管是乘公交还是骑共享单车,其实都要从楼下走出去蛮远,公司回来也是,也就是这种通勤方式在准备阶段就花了比较多时间,比如总的从下班到到家的时间是半小时,可能在骑共享单车和公交车上的时间都不到十分钟,就比较难受。觉得这种比例太浪费时间,如果能有这种比较点对点的方式,估计能省时省力不少,前面说的骑共享单车的方式其实在之前是比较可行的,但是后来越来越少车,基本都是每周的前几天,周一到周三都是没有车,走路到公司再冷的天都是走出一身的汗,下雨天就更难受,本来下雨天应该是优先选择坐公交,但是一般下雨天堵车会更严重,而且车子到我上车的那个站,下雨天就挤得不行,总体说下来感觉事情都不打,但是几年下来,还是会挺不爽的。

    +

    电驴看的比较草率,主要是考虑续航,然后锂电池外加 48v 和 24AH,这样一般来讲还是价格比较高的,只是原来没预料到这个限速,以为现在的车子都比较快,但是现在的新国标车子都是 25km/h 的限速,然后 15km/h 都是会要提醒,虽然说有一些特殊的解除限速的方法,但是解了也就 35km/h ,差距不是特别大,而且现在的车子都是比较小,也不太能载东西,特别是上下班路程也不远的情况下,其实不是那么需要速度,就像我朋友说的,可能骑车的时间还不如等红绿灯多,所以就还好,也不打算解除限速,只是品牌上也仔细看,后来选了绿源,目前大部分还是雅迪,爱玛,台羚,绿源,小牛等,路上看的话还是雅迪比较多,不过价格也比较贵一点,还有就是小牛了,是比较新兴的品牌,手机 App 什么的做得比较好,而且也比较贵,最后以相对比较便宜的价格买了个锂电 48V24AH 的小车子,后来发现还是有点不方便的点就是没有比较大的筐,也不好装,这样就是下雨天雨衣什么的比较不方便放。

    +

    聊回来主题上牌这个事情,这个事情也是颇费心力,提车的时候店里的让我跟他早上一起去,但是因为不确定时间,也比较远就没跟着去,因为我是线上买的,线下自提,线下的店可能没啥利润可以拿,就不肯帮忙代上牌,朋友说在线下店里买是可以代上的,自己上牌过程也比较曲折,一开始是头盔没到,然后是等开发票,主要的东西就是需要骑着车子去车管所,不能只自己去,然后需要预约,附近比较近的都是提前一周就预约完了号了,要提前在支付宝上进行预约,比较空的就是店里推荐的景区大队,但是随之而来就是比较蛋疼的,这个景区大队太远了,看下骑车距离有十几公里,所以就有点拖延症,但是总归要上的,不然一直不能开是白买了,上牌的材料主要是车辆合格证,发票,然后车子上的浙品码,在车架上和电池上,然后车架号什么的都要跟合格证上完全对应,整体车子要跟合格证上一毛一样,如果有额外的反光镜,后面副座都需要拆掉,脚踏板要装上,到了那其实还比较顺利,就是十几公里外加那天比较冷,吹得头疼。

    ]]>
    - Mysql + 生活 - mysql - 字符集 - 编码 - utf8 - utf8mb4 - utf8mb4_0900_ai_ci - utf8mb4_unicode_ci - utf8mb4_general_ci + 生活
    @@ -9041,78 +9092,226 @@ user3: return response; } - response.setCode(ResponseCode.TOPIC_NOT_EXIST); - response.setRemark("No topic route info in name server for the topic: " + requestHeader.getTopic() - + FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL)); - return response; - } - -

    首先调用org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#pickupTopicRouteDataorg.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#topicQueueTable获取到org.apache.rocketmq.common.protocol.route.QueueData这里面存了 brokerName,再通过org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#brokerAddrTable里获取到 broker 的地址信息等,然后再获取 orderMessage 的配置。

    -

    简要分析了下 RocketMQ 的 NameServer 的代码,比较粗粒度。

    -]]> - - MQ - RocketMQ - 消息队列 - RocketMQ - 中间件 - RocketMQ - - - MQ - 消息队列 - RocketMQ - 削峰填谷 - 中间件 - 源码解析 - NameServer - -
    - - 聊一下 RocketMQ 的消息存储之 MMAP - /2021/09/04/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8/ - 这是个很大的话题了,可能会分成两部分说,第一部分就是所谓的零拷贝 ( zero-copy ),这一块其实也不新鲜,我对零拷贝的概念主要来自这篇文章,个人感觉写得非常好,在 rocketmq 中,最大的一块存储就是消息存储,也就是 CommitLog ,当然还有 ConsumeQueue 和 IndexFile,以及其他一些文件,CommitLog 的存储是以一个 1G 大小的文件作为存储单位,写完了就再建一个,那么如何提高这 1G 文件的读写效率呢,就是 mmap,传统意义的读写文件,read,write 都需要由系统调用,来回地在用户态跟内核态进行拷贝切换,

    -
    read(file, tmp_buf, len);
    -write(socket, tmp_buf, len);
    - + response.setCode(ResponseCode.TOPIC_NOT_EXIST); + response.setRemark("No topic route info in name server for the topic: " + requestHeader.getTopic() + + FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL)); + return response; + } + +

    首先调用org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#pickupTopicRouteDataorg.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#topicQueueTable获取到org.apache.rocketmq.common.protocol.route.QueueData这里面存了 brokerName,再通过org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#brokerAddrTable里获取到 broker 的地址信息等,然后再获取 orderMessage 的配置。

    +

    简要分析了下 RocketMQ 的 NameServer 的代码,比较粗粒度。

    +]]>
    + + MQ + RocketMQ + 消息队列 + RocketMQ + 中间件 + RocketMQ + + + MQ + 消息队列 + RocketMQ + 削峰填谷 + 中间件 + 源码解析 + NameServer + +
    + + 聊一下 RocketMQ 的消息存储之 MMAP + /2021/09/04/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8/ + 这是个很大的话题了,可能会分成两部分说,第一部分就是所谓的零拷贝 ( zero-copy ),这一块其实也不新鲜,我对零拷贝的概念主要来自这篇文章,个人感觉写得非常好,在 rocketmq 中,最大的一块存储就是消息存储,也就是 CommitLog ,当然还有 ConsumeQueue 和 IndexFile,以及其他一些文件,CommitLog 的存储是以一个 1G 大小的文件作为存储单位,写完了就再建一个,那么如何提高这 1G 文件的读写效率呢,就是 mmap,传统意义的读写文件,read,write 都需要由系统调用,来回地在用户态跟内核态进行拷贝切换,

    +
    read(file, tmp_buf, len);
    +write(socket, tmp_buf, len);
    + + + +

    vms95Z

    +

    如上面的图显示的,要在用户态跟内核态进行切换,数据还需要在内核缓冲跟用户缓冲之间拷贝多次,

    +
    +
      +
    1. 第一步是调用 read,需要在用户态切换成内核态,DMA模块从磁盘中读取文件,并存储在内核缓冲区,相当于是第一次复制
    2. +
    3. 数据从内核缓冲区被拷贝到用户缓冲区,read 调用返回,伴随着内核态又切换成用户态,完成了第二次复制
    4. +
    5. 然后是write 写入,这里也会伴随着用户态跟内核态的切换,数据从用户缓冲区被复制到内核空间缓冲区,完成了第三次复制,这次有点不一样的是数据不是在内核缓冲区了,会复制到 socket buffer 中。
    6. +
    7. write 系统调用返回,又切换回了用户态,然后数据由 DMA 拷贝到协议引擎。
    8. +
    +
    +

    如此就能看出其实默认的读写操作代价是非常大的,而在 rocketmq 等高性能中间件中都有使用的零拷贝技术,其中 rocketmq 使用的是 mmap

    +

    mmap

    mmap基于 OS 的 mmap 的内存映射技术,通过MMU 映射文件,将文件直接映射到用户态的内存地址,使得对文件的操作不再是 write/read,而转化为直接对内存地址的操作,使随机读写文件和读写内存相似的速度。

    +
    +

    mmap 把文件映射到用户空间里的虚拟内存,省去了从内核缓冲区复制到用户空间的过程,文件中的位置在虚拟内存中有了对应的地址,可以像操作内存一样操作这个文件,这样的文件读写文件方式少了数据从内核缓存到用户空间的拷贝,效率很高。

    +
    +
    tmp_buf = mmap(file, len);
    +write(socket, tmp_buf, len);
    + +

    I68mFx

    +
    +

    第一步:mmap系统调用使得文件内容被DMA引擎复制到内核缓冲区。然后该缓冲区与用户进程共享,在内核和用户内存空间之间不进行任何拷贝。

    +

    第二步:写系统调用使得内核将数据从原来的内核缓冲区复制到与套接字相关的内核缓冲区。

    +

    第三步:第三次拷贝发生在DMA引擎将数据从内核套接字缓冲区传递给协议引擎时。

    +

    通过使用mmap而不是read,我们将内核需要拷贝的数据量减少了一半。当大量的数据被传输时,这将有很好的效果。然而,这种改进并不是没有代价的;在使用mmap+write方法时,有一些隐藏的陷阱。例如当你对一个文件进行内存映射,然后在另一个进程截断同一文件时调用写。你的写系统调用将被总线错误信号SIGBUS打断,因为你执行了一个错误的内存访问。该信号的默认行为是杀死进程并dumpcore–这对网络服务器来说不是最理想的操作。

    +

    有两种方法可以解决这个问题。

    +

    第一种方法是为SIGBUS信号安装一个信号处理程序,然后在处理程序中简单地调用返回。通过这样做,写系统调用会返回它在被打断之前所写的字节数,并将errno设置为成功。让我指出,这将是一个糟糕的解决方案,一个治标不治本的解决方案。因为SIGBUS预示着进程出了严重的问题,所以不鼓励使用这种解决方案。

    +

    第二个解决方案涉及内核的文件租赁(在Windows中称为 “机会锁”)。这是解决这个问题的正确方法。通过在文件描述符上使用租赁,你与内核在一个特定的文件上达成租约。然后你可以向内核请求一个读/写租约。当另一个进程试图截断你正在传输的文件时,内核会向你发送一个实时信号,即RT_SIGNAL_LEASE信号。它告诉你内核即将终止你对该文件的写或读租约。在你的程序访问一个无效的地址和被SIGBUS信号杀死之前,你的写调用会被打断了。写入调用的返回值是中断前写入的字节数,errno将被设置为成功。下面是一些示例代码,显示了如何从内核中获得租约。

    +
    if(fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
    +    perror("kernel lease set signal");
    +    return -1;
    +}
    +/* l_type can be F_RDLCK F_WRLCK */
    +if(fcntl(fd, F_SETLEASE, l_type)){
    +    perror("kernel lease set type");
    +    return -1;
    +}
    +]]>
    + + MQ + RocketMQ + 消息队列 + + + MQ + 消息队列 + RocketMQ + +
    + + 聊一下 RocketMQ 的消息存储三 + /2021/10/03/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E4%B8%89/ + ConsumeQueue 其实是定位到一个 topic 下的消息在 CommitLog 下的偏移量,它也是固定大小的

    +
    // ConsumeQueue file size,default is 30W
    +private int mapedFileSizeConsumeQueue = 300000 * ConsumeQueue.CQ_STORE_UNIT_SIZE;
    +
    +public static final int CQ_STORE_UNIT_SIZE = 20;
    + +

    所以文件大小是5.7M 左右

    +

    5udpag

    +

    ConsumeQueue 的构建是通过org.apache.rocketmq.store.DefaultMessageStore.ReputMessageService运行后的 doReput 方法,而启动是的 reputFromOffset 则是通过org.apache.rocketmq.store.DefaultMessageStore#start中下面代码设置并启动

    +
    log.info("[SetReputOffset] maxPhysicalPosInLogicQueue={} clMinOffset={} clMaxOffset={} clConfirmedOffset={}",
    +                maxPhysicalPosInLogicQueue, this.commitLog.getMinOffset(), this.commitLog.getMaxOffset(), this.commitLog.getConfirmOffset());
    +            this.reputMessageService.setReputFromOffset(maxPhysicalPosInLogicQueue);
    +            this.reputMessageService.start();
    + +

    看一下 doReput 的逻辑

    +
    private void doReput() {
    +            if (this.reputFromOffset < DefaultMessageStore.this.commitLog.getMinOffset()) {
    +                log.warn("The reputFromOffset={} is smaller than minPyOffset={}, this usually indicate that the dispatch behind too much and the commitlog has expired.",
    +                    this.reputFromOffset, DefaultMessageStore.this.commitLog.getMinOffset());
    +                this.reputFromOffset = DefaultMessageStore.this.commitLog.getMinOffset();
    +            }
    +            for (boolean doNext = true; this.isCommitLogAvailable() && doNext; ) {
    +
    +                if (DefaultMessageStore.this.getMessageStoreConfig().isDuplicationEnable()
    +                    && this.reputFromOffset >= DefaultMessageStore.this.getConfirmOffset()) {
    +                    break;
    +                }
    +
    +              // 根据偏移量获取消息
    +                SelectMappedBufferResult result = DefaultMessageStore.this.commitLog.getData(reputFromOffset);
    +                if (result != null) {
    +                    try {
    +                        this.reputFromOffset = result.getStartOffset();
    +
    +                        for (int readSize = 0; readSize < result.getSize() && doNext; ) {
    +                          // 消息校验和转换
    +                            DispatchRequest dispatchRequest =
    +                                DefaultMessageStore.this.commitLog.checkMessageAndReturnSize(result.getByteBuffer(), false, false);
    +                            int size = dispatchRequest.getBufferSize() == -1 ? dispatchRequest.getMsgSize() : dispatchRequest.getBufferSize();
    +
    +                            if (dispatchRequest.isSuccess()) {
    +                                if (size > 0) {
    +                                  // 进行分发处理,包括 ConsumeQueue 和 IndexFile
    +                                    DefaultMessageStore.this.doDispatch(dispatchRequest);
    +
    +                                    if (BrokerRole.SLAVE != DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole()
    +                                        && DefaultMessageStore.this.brokerConfig.isLongPollingEnable()) {
    +                                        DefaultMessageStore.this.messageArrivingListener.arriving(dispatchRequest.getTopic(),
    +                                            dispatchRequest.getQueueId(), dispatchRequest.getConsumeQueueOffset() + 1,
    +                                            dispatchRequest.getTagsCode(), dispatchRequest.getStoreTimestamp(),
    +                                            dispatchRequest.getBitMap(), dispatchRequest.getPropertiesMap());
    +                                    }
    +
    +                                    this.reputFromOffset += size;
    +                                    readSize += size;
    +                                    if (DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) {
    +                                        DefaultMessageStore.this.storeStatsService
    +                                            .getSinglePutMessageTopicTimesTotal(dispatchRequest.getTopic()).incrementAndGet();
    +                                        DefaultMessageStore.this.storeStatsService
    +                                            .getSinglePutMessageTopicSizeTotal(dispatchRequest.getTopic())
    +                                            .addAndGet(dispatchRequest.getMsgSize());
    +                                    }
    +                                } else if (size == 0) {
    +                                    this.reputFromOffset = DefaultMessageStore.this.commitLog.rollNextFile(this.reputFromOffset);
    +                                    readSize = result.getSize();
    +                                }
    +                            } else if (!dispatchRequest.isSuccess()) {
    +
    +                                if (size > 0) {
    +                                    log.error("[BUG]read total count not equals msg total size. reputFromOffset={}", reputFromOffset);
    +                                    this.reputFromOffset += size;
    +                                } else {
    +                                    doNext = false;
    +                                    // If user open the dledger pattern or the broker is master node,
    +                                    // it will not ignore the exception and fix the reputFromOffset variable
    +                                    if (DefaultMessageStore.this.getMessageStoreConfig().isEnableDLegerCommitLog() ||
    +                                        DefaultMessageStore.this.brokerConfig.getBrokerId() == MixAll.MASTER_ID) {
    +                                        log.error("[BUG]dispatch message to consume queue error, COMMITLOG OFFSET: {}",
    +                                            this.reputFromOffset);
    +                                        this.reputFromOffset += result.getSize() - readSize;
    +                                    }
    +                                }
    +                            }
    +                        }
    +                    } finally {
    +                        result.release();
    +                    }
    +                } else {
    +                    doNext = false;
    +                }
    +            }
    +        }
    + +

    分发的逻辑看到这

    +
        class CommitLogDispatcherBuildConsumeQueue implements CommitLogDispatcher {
    +
    +        @Override
    +        public void dispatch(DispatchRequest request) {
    +            final int tranType = MessageSysFlag.getTransactionValue(request.getSysFlag());
    +            switch (tranType) {
    +                case MessageSysFlag.TRANSACTION_NOT_TYPE:
    +                case MessageSysFlag.TRANSACTION_COMMIT_TYPE:
    +                    DefaultMessageStore.this.putMessagePositionInfo(request);
    +                    break;
    +                case MessageSysFlag.TRANSACTION_PREPARED_TYPE:
    +                case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE:
    +                    break;
    +            }
    +        }
    +    }
    +public void putMessagePositionInfo(DispatchRequest dispatchRequest) {
    +        ConsumeQueue cq = this.findConsumeQueue(dispatchRequest.getTopic(), dispatchRequest.getQueueId());
    +        cq.putMessagePositionInfoWrapper(dispatchRequest);
    +    }
    + +

    真正存储的是在这

    +
    private boolean putMessagePositionInfo(final long offset, final int size, final long tagsCode,
    +    final long cqOffset) {
     
    +    if (offset + size <= this.maxPhysicOffset) {
    +        log.warn("Maybe try to build consume queue repeatedly maxPhysicOffset={} phyOffset={}", maxPhysicOffset, offset);
    +        return true;
    +    }
     
    -

    vms95Z

    -

    如上面的图显示的,要在用户态跟内核态进行切换,数据还需要在内核缓冲跟用户缓冲之间拷贝多次,

    -
    -
      -
    1. 第一步是调用 read,需要在用户态切换成内核态,DMA模块从磁盘中读取文件,并存储在内核缓冲区,相当于是第一次复制
    2. -
    3. 数据从内核缓冲区被拷贝到用户缓冲区,read 调用返回,伴随着内核态又切换成用户态,完成了第二次复制
    4. -
    5. 然后是write 写入,这里也会伴随着用户态跟内核态的切换,数据从用户缓冲区被复制到内核空间缓冲区,完成了第三次复制,这次有点不一样的是数据不是在内核缓冲区了,会复制到 socket buffer 中。
    6. -
    7. write 系统调用返回,又切换回了用户态,然后数据由 DMA 拷贝到协议引擎。
    8. -
    -
    -

    如此就能看出其实默认的读写操作代价是非常大的,而在 rocketmq 等高性能中间件中都有使用的零拷贝技术,其中 rocketmq 使用的是 mmap

    -

    mmap

    mmap基于 OS 的 mmap 的内存映射技术,通过MMU 映射文件,将文件直接映射到用户态的内存地址,使得对文件的操作不再是 write/read,而转化为直接对内存地址的操作,使随机读写文件和读写内存相似的速度。

    -
    -

    mmap 把文件映射到用户空间里的虚拟内存,省去了从内核缓冲区复制到用户空间的过程,文件中的位置在虚拟内存中有了对应的地址,可以像操作内存一样操作这个文件,这样的文件读写文件方式少了数据从内核缓存到用户空间的拷贝,效率很高。

    -
    -
    tmp_buf = mmap(file, len);
    -write(socket, tmp_buf, len);
    + this.byteBufferIndex.flip(); + this.byteBufferIndex.limit(CQ_STORE_UNIT_SIZE); + this.byteBufferIndex.putLong(offset); + this.byteBufferIndex.putInt(size); + this.byteBufferIndex.putLong(tagsCode);
    -

    I68mFx

    -
    -

    第一步:mmap系统调用使得文件内容被DMA引擎复制到内核缓冲区。然后该缓冲区与用户进程共享,在内核和用户内存空间之间不进行任何拷贝。

    -

    第二步:写系统调用使得内核将数据从原来的内核缓冲区复制到与套接字相关的内核缓冲区。

    -

    第三步:第三次拷贝发生在DMA引擎将数据从内核套接字缓冲区传递给协议引擎时。

    -

    通过使用mmap而不是read,我们将内核需要拷贝的数据量减少了一半。当大量的数据被传输时,这将有很好的效果。然而,这种改进并不是没有代价的;在使用mmap+write方法时,有一些隐藏的陷阱。例如当你对一个文件进行内存映射,然后在另一个进程截断同一文件时调用写。你的写系统调用将被总线错误信号SIGBUS打断,因为你执行了一个错误的内存访问。该信号的默认行为是杀死进程并dumpcore–这对网络服务器来说不是最理想的操作。

    -

    有两种方法可以解决这个问题。

    -

    第一种方法是为SIGBUS信号安装一个信号处理程序,然后在处理程序中简单地调用返回。通过这样做,写系统调用会返回它在被打断之前所写的字节数,并将errno设置为成功。让我指出,这将是一个糟糕的解决方案,一个治标不治本的解决方案。因为SIGBUS预示着进程出了严重的问题,所以不鼓励使用这种解决方案。

    -

    第二个解决方案涉及内核的文件租赁(在Windows中称为 “机会锁”)。这是解决这个问题的正确方法。通过在文件描述符上使用租赁,你与内核在一个特定的文件上达成租约。然后你可以向内核请求一个读/写租约。当另一个进程试图截断你正在传输的文件时,内核会向你发送一个实时信号,即RT_SIGNAL_LEASE信号。它告诉你内核即将终止你对该文件的写或读租约。在你的程序访问一个无效的地址和被SIGBUS信号杀死之前,你的写调用会被打断了。写入调用的返回值是中断前写入的字节数,errno将被设置为成功。下面是一些示例代码,显示了如何从内核中获得租约。

    -
    if(fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
    -    perror("kernel lease set signal");
    -    return -1;
    -}
    -/* l_type can be F_RDLCK F_WRLCK */
    -if(fcntl(fd, F_SETLEASE, l_type)){
    -    perror("kernel lease set type");
    -    return -1;
    -}
    +

    这里也可以看到 ConsumeQueue 的存储格式,

    +

    AA6Tve

    +

    偏移量,消息大小,跟 tag 的 hashCode

    ]]>
    MQ @@ -9432,216 +9631,68 @@ user3: if (req.getMappedFile() == null) { long beginTime = System.currentTimeMillis(); - - MappedFile mappedFile; - if (messageStore.getMessageStoreConfig().isTransientStorePoolEnable()) { - try { - // 通过 transientStorePool 创建 - mappedFile = ServiceLoader.load(MappedFile.class).iterator().next(); - mappedFile.init(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool()); - } catch (RuntimeException e) { - log.warn("Use default implementation."); - // 默认创建 - mappedFile = new MappedFile(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool()); - } - } else { - // 默认创建 - mappedFile = new MappedFile(req.getFilePath(), req.getFileSize()); - } - - long eclipseTime = UtilAll.computeEclipseTimeMilliseconds(beginTime); - if (eclipseTime > 10) { - int queueSize = this.requestQueue.size(); - log.warn("create mappedFile spent time(ms) " + eclipseTime + " queue size " + queueSize - + " " + req.getFilePath() + " " + req.getFileSize()); - } - - // pre write mappedFile - if (mappedFile.getFileSize() >= this.messageStore.getMessageStoreConfig() - .getMapedFileSizeCommitLog() - && - this.messageStore.getMessageStoreConfig().isWarmMapedFileEnable()) { - mappedFile.warmMappedFile(this.messageStore.getMessageStoreConfig().getFlushDiskType(), - this.messageStore.getMessageStoreConfig().getFlushLeastPagesWhenWarmMapedFile()); - } - - req.setMappedFile(mappedFile); - this.hasException = false; - isSuccess = true; - } - } catch (InterruptedException e) { - log.warn(this.getServiceName() + " interrupted, possibly by shutdown."); - this.hasException = true; - return false; - } catch (IOException e) { - log.warn(this.getServiceName() + " service has exception. ", e); - this.hasException = true; - if (null != req) { - requestQueue.offer(req); - try { - Thread.sleep(1); - } catch (InterruptedException ignored) { - } - } - } finally { - if (req != null && isSuccess) - // 通知前面等待的 - req.getCountDownLatch().countDown(); - } - return true; - } - - - - -]]> - - MQ - RocketMQ - 消息队列 - - - MQ - 消息队列 - RocketMQ - -
    - - 聊一下 RocketMQ 的消息存储三 - /2021/10/03/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E4%B8%89/ - ConsumeQueue 其实是定位到一个 topic 下的消息在 CommitLog 下的偏移量,它也是固定大小的

    -
    // ConsumeQueue file size,default is 30W
    -private int mapedFileSizeConsumeQueue = 300000 * ConsumeQueue.CQ_STORE_UNIT_SIZE;
    -
    -public static final int CQ_STORE_UNIT_SIZE = 20;
    - -

    所以文件大小是5.7M 左右

    -

    5udpag

    -

    ConsumeQueue 的构建是通过org.apache.rocketmq.store.DefaultMessageStore.ReputMessageService运行后的 doReput 方法,而启动是的 reputFromOffset 则是通过org.apache.rocketmq.store.DefaultMessageStore#start中下面代码设置并启动

    -
    log.info("[SetReputOffset] maxPhysicalPosInLogicQueue={} clMinOffset={} clMaxOffset={} clConfirmedOffset={}",
    -                maxPhysicalPosInLogicQueue, this.commitLog.getMinOffset(), this.commitLog.getMaxOffset(), this.commitLog.getConfirmOffset());
    -            this.reputMessageService.setReputFromOffset(maxPhysicalPosInLogicQueue);
    -            this.reputMessageService.start();
    - -

    看一下 doReput 的逻辑

    -
    private void doReput() {
    -            if (this.reputFromOffset < DefaultMessageStore.this.commitLog.getMinOffset()) {
    -                log.warn("The reputFromOffset={} is smaller than minPyOffset={}, this usually indicate that the dispatch behind too much and the commitlog has expired.",
    -                    this.reputFromOffset, DefaultMessageStore.this.commitLog.getMinOffset());
    -                this.reputFromOffset = DefaultMessageStore.this.commitLog.getMinOffset();
    -            }
    -            for (boolean doNext = true; this.isCommitLogAvailable() && doNext; ) {
    -
    -                if (DefaultMessageStore.this.getMessageStoreConfig().isDuplicationEnable()
    -                    && this.reputFromOffset >= DefaultMessageStore.this.getConfirmOffset()) {
    -                    break;
    -                }
    -
    -              // 根据偏移量获取消息
    -                SelectMappedBufferResult result = DefaultMessageStore.this.commitLog.getData(reputFromOffset);
    -                if (result != null) {
    -                    try {
    -                        this.reputFromOffset = result.getStartOffset();
    -
    -                        for (int readSize = 0; readSize < result.getSize() && doNext; ) {
    -                          // 消息校验和转换
    -                            DispatchRequest dispatchRequest =
    -                                DefaultMessageStore.this.commitLog.checkMessageAndReturnSize(result.getByteBuffer(), false, false);
    -                            int size = dispatchRequest.getBufferSize() == -1 ? dispatchRequest.getMsgSize() : dispatchRequest.getBufferSize();
    -
    -                            if (dispatchRequest.isSuccess()) {
    -                                if (size > 0) {
    -                                  // 进行分发处理,包括 ConsumeQueue 和 IndexFile
    -                                    DefaultMessageStore.this.doDispatch(dispatchRequest);
    -
    -                                    if (BrokerRole.SLAVE != DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole()
    -                                        && DefaultMessageStore.this.brokerConfig.isLongPollingEnable()) {
    -                                        DefaultMessageStore.this.messageArrivingListener.arriving(dispatchRequest.getTopic(),
    -                                            dispatchRequest.getQueueId(), dispatchRequest.getConsumeQueueOffset() + 1,
    -                                            dispatchRequest.getTagsCode(), dispatchRequest.getStoreTimestamp(),
    -                                            dispatchRequest.getBitMap(), dispatchRequest.getPropertiesMap());
    -                                    }
    -
    -                                    this.reputFromOffset += size;
    -                                    readSize += size;
    -                                    if (DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) {
    -                                        DefaultMessageStore.this.storeStatsService
    -                                            .getSinglePutMessageTopicTimesTotal(dispatchRequest.getTopic()).incrementAndGet();
    -                                        DefaultMessageStore.this.storeStatsService
    -                                            .getSinglePutMessageTopicSizeTotal(dispatchRequest.getTopic())
    -                                            .addAndGet(dispatchRequest.getMsgSize());
    -                                    }
    -                                } else if (size == 0) {
    -                                    this.reputFromOffset = DefaultMessageStore.this.commitLog.rollNextFile(this.reputFromOffset);
    -                                    readSize = result.getSize();
    -                                }
    -                            } else if (!dispatchRequest.isSuccess()) {
    -
    -                                if (size > 0) {
    -                                    log.error("[BUG]read total count not equals msg total size. reputFromOffset={}", reputFromOffset);
    -                                    this.reputFromOffset += size;
    -                                } else {
    -                                    doNext = false;
    -                                    // If user open the dledger pattern or the broker is master node,
    -                                    // it will not ignore the exception and fix the reputFromOffset variable
    -                                    if (DefaultMessageStore.this.getMessageStoreConfig().isEnableDLegerCommitLog() ||
    -                                        DefaultMessageStore.this.brokerConfig.getBrokerId() == MixAll.MASTER_ID) {
    -                                        log.error("[BUG]dispatch message to consume queue error, COMMITLOG OFFSET: {}",
    -                                            this.reputFromOffset);
    -                                        this.reputFromOffset += result.getSize() - readSize;
    -                                    }
    -                                }
    -                            }
    -                        }
    -                    } finally {
    -                        result.release();
    +
    +                MappedFile mappedFile;
    +                if (messageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
    +                    try {
    +                      // 通过 transientStorePool 创建
    +                        mappedFile = ServiceLoader.load(MappedFile.class).iterator().next();
    +                        mappedFile.init(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool());
    +                    } catch (RuntimeException e) {
    +                        log.warn("Use default implementation.");
    +                      // 默认创建
    +                        mappedFile = new MappedFile(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool());
                         }
                     } else {
    -                    doNext = false;
    +                  // 默认创建
    +                    mappedFile = new MappedFile(req.getFilePath(), req.getFileSize());
                     }
    -            }
    -        }
    -

    分发的逻辑看到这

    -
        class CommitLogDispatcherBuildConsumeQueue implements CommitLogDispatcher {
    +                long eclipseTime = UtilAll.computeEclipseTimeMilliseconds(beginTime);
    +                if (eclipseTime > 10) {
    +                    int queueSize = this.requestQueue.size();
    +                    log.warn("create mappedFile spent time(ms) " + eclipseTime + " queue size " + queueSize
    +                        + " " + req.getFilePath() + " " + req.getFileSize());
    +                }
     
    -        @Override
    -        public void dispatch(DispatchRequest request) {
    -            final int tranType = MessageSysFlag.getTransactionValue(request.getSysFlag());
    -            switch (tranType) {
    -                case MessageSysFlag.TRANSACTION_NOT_TYPE:
    -                case MessageSysFlag.TRANSACTION_COMMIT_TYPE:
    -                    DefaultMessageStore.this.putMessagePositionInfo(request);
    -                    break;
    -                case MessageSysFlag.TRANSACTION_PREPARED_TYPE:
    -                case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE:
    -                    break;
    +                // pre write mappedFile
    +                if (mappedFile.getFileSize() >= this.messageStore.getMessageStoreConfig()
    +                    .getMapedFileSizeCommitLog()
    +                    &&
    +                    this.messageStore.getMessageStoreConfig().isWarmMapedFileEnable()) {
    +                    mappedFile.warmMappedFile(this.messageStore.getMessageStoreConfig().getFlushDiskType(),
    +                        this.messageStore.getMessageStoreConfig().getFlushLeastPagesWhenWarmMapedFile());
    +                }
    +
    +                req.setMappedFile(mappedFile);
    +                this.hasException = false;
    +                isSuccess = true;
    +            }
    +        } catch (InterruptedException e) {
    +            log.warn(this.getServiceName() + " interrupted, possibly by shutdown.");
    +            this.hasException = true;
    +            return false;
    +        } catch (IOException e) {
    +            log.warn(this.getServiceName() + " service has exception. ", e);
    +            this.hasException = true;
    +            if (null != req) {
    +                requestQueue.offer(req);
    +                try {
    +                    Thread.sleep(1);
    +                } catch (InterruptedException ignored) {
    +                }
                 }
    +        } finally {
    +            if (req != null && isSuccess)
    +              // 通知前面等待的
    +                req.getCountDownLatch().countDown();
             }
    -    }
    -public void putMessagePositionInfo(DispatchRequest dispatchRequest) {
    -        ConsumeQueue cq = this.findConsumeQueue(dispatchRequest.getTopic(), dispatchRequest.getQueueId());
    -        cq.putMessagePositionInfoWrapper(dispatchRequest);
    -    }
    + return true; + } -

    真正存储的是在这

    -
    private boolean putMessagePositionInfo(final long offset, final int size, final long tagsCode,
    -    final long cqOffset) {
     
    -    if (offset + size <= this.maxPhysicOffset) {
    -        log.warn("Maybe try to build consume queue repeatedly maxPhysicOffset={} phyOffset={}", maxPhysicOffset, offset);
    -        return true;
    -    }
     
    -    this.byteBufferIndex.flip();
    -    this.byteBufferIndex.limit(CQ_STORE_UNIT_SIZE);
    -    this.byteBufferIndex.putLong(offset);
    -    this.byteBufferIndex.putInt(size);
    -    this.byteBufferIndex.putLong(tagsCode);
    -

    这里也可以看到 ConsumeQueue 的存储格式,

    -

    AA6Tve

    -

    偏移量,消息大小,跟 tag 的 hashCode

    ]]>
    MQ @@ -10008,232 +10059,50 @@ user3: } if (null == status) { - status = ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; - } - - if (ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.hasHook()) { - consumeMessageContext.setStatus(status.toString()); - consumeMessageContext - .setSuccess(ConsumeOrderlyStatus.SUCCESS == status || ConsumeOrderlyStatus.COMMIT == status); - ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.executeHookAfter(consumeMessageContext); - } - - ConsumeMessageOrderlyService.this.getConsumerStatsManager() - .incConsumeRT(ConsumeMessageOrderlyService.this.consumerGroup, messageQueue.getTopic(), consumeRT); - - continueConsume = ConsumeMessageOrderlyService.this.processConsumeResult(msgs, status, context, this); - } else { - continueConsume = false; - } - } - } else { - if (this.processQueue.isDropped()) { - log.warn("the message queue not be able to consume, because it's dropped. {}", this.messageQueue); - return; - } - - ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue, this.processQueue, 100); - } - } - } - -

    获取到锁对象后,使用synchronized尝试申请线程级独占锁。

    -

    如果加锁成功,同一时刻只有一个线程进行消息消费。

    -

    如果加锁失败,会延迟100ms重新尝试向broker端申请锁定messageQueue,锁定成功后重新提交消费请求

    -

    创建消息拉取任务时,消息客户端向broker端申请锁定MessageQueue,使得一个MessageQueue同一个时刻只能被一个消费客户端消费。

    -

    消息消费时,多线程针对同一个消息队列的消费先尝试使用synchronized申请独占锁,加锁成功才能进行消费,使得一个MessageQueue同一个时刻只能被一个消费客户端中一个线程消费。
    这里其实还有很重要的一点是对processQueue的加锁,这里其实是保证了在 rebalance的过程中如果 processQueue 被分配给了另一个 consumer,但是当前已经被我这个 consumer 再消费,但是没提交,就有可能出现被两个消费者消费,所以得进行加锁保证不受 rebalance 影响。

    -]]> - - MQ - RocketMQ - 消息队列 - - - MQ - 消息队列 - RocketMQ - -
    - - 聊一下 SpringBoot 中使用的 cglib 作为动态代理中的一个注意点 - /2021/09/19/%E8%81%8A%E4%B8%80%E4%B8%8B-SpringBoot-%E4%B8%AD%E4%BD%BF%E7%94%A8%E7%9A%84-cglib-%E4%BD%9C%E4%B8%BA%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E4%B8%AD%E7%9A%84%E4%B8%80%E4%B8%AA%E6%B3%A8%E6%84%8F%E7%82%B9/ - 这个话题是由一次组内同学分享引出来的,首先在 springboot 2.x 开始默认使用了 cglib 作为 aop 的实现,这里也稍微讲一下,在一个 1.x 的老项目里,可以看到AopAutoConfiguration 是这样的

    -
    @Configuration
    -@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
    -@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
    -public class AopAutoConfiguration {
    -
    -	@Configuration
    -	@EnableAspectJAutoProxy(proxyTargetClass = false)
    -	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = true)
    -	public static class JdkDynamicAutoProxyConfiguration {
    -	}
    -
    -	@Configuration
    -	@EnableAspectJAutoProxy(proxyTargetClass = true)
    -	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = false)
    -	public static class CglibAutoProxyConfiguration {
    -	}
    -
    -}
    - -

    而在 2.x 中变成了这样

    -
    @Configuration(proxyBeanMethods = false)
    -@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
    -public class AopAutoConfiguration {
    -
    -	@Configuration(proxyBeanMethods = false)
    -	@ConditionalOnClass(Advice.class)
    -	static class AspectJAutoProxyingConfiguration {
    -
    -		@Configuration(proxyBeanMethods = false)
    -		@EnableAspectJAutoProxy(proxyTargetClass = false)
    -		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
    -		static class JdkDynamicAutoProxyConfiguration {
    -
    -		}
    -
    -		@Configuration(proxyBeanMethods = false)
    -		@EnableAspectJAutoProxy(proxyTargetClass = true)
    -		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
    -				matchIfMissing = true)
    -		static class CglibAutoProxyConfiguration {
    -
    -		}
    -
    -	}
    - -

    为何会加载 AopAutoConfiguration 在前面的文章聊聊 SpringBoot 自动装配里已经介绍过,有兴趣的可以看下,可以发现 springboot 在 2.x 版本开始使用 cglib 作为默认的动态代理实现。

    -

    然后就是出现的问题了,代码是这样的,一个简单的基于 springboot 的带有数据库的插入,对插入代码加了事务注解,

    -
    @Mapper
    -public interface StudentMapper {
    -		// 就是插入一条数据
    -    @Insert("insert into student(name, age)" + "values ('nick', '18')")
    -    public Long insert();
    -}
    -
    -@Component
    -public class StudentManager {
    -
    -    @Resource
    -    private StudentMapper studentMapper;
    -    
    -    public Long createStudent() {
    -        return studentMapper.insert();
    -    }
    -}
    -
    -@Component
    -public class StudentServiceImpl implements StudentService {
    -
    -    @Resource
    -    private StudentManager studentManager;
    -
    -    // 自己引用
    -    @Resource
    -    private StudentServiceImpl studentService;
    -
    -    @Override
    -    @Transactional
    -    public Long createStudent() {
    -        Long id = studentManager.createStudent();
    -        Long id2 = studentService.createStudent2();
    -        return 1L;
    -    }
    -
    -    @Transactional
    -    private Long createStudent2() {
    -//        Integer t = Integer.valueOf("aaa");
    -        return studentManager.createStudent();
    -    }
    -}
    - -

    第一个公有方法 createStudent 首先调用了 manager 层的创建方法,然后再通过引入的 studentService 调用了createStudent2,我们先跑一下看看会出现啥情况,果不其然报错了,正是这个报错让我纠结了很久

    -

    EdR7oB

    -

    报了个空指针,而且是在 createStudent2 已经被调用到了,在它的内部,报的 studentManager 是 null,首先 cglib 作为动态代理它是通过继承的方式来实现的,相当于是会在调用目标对象的代理方法时调用 cglib 生成的子类,具体的代理切面逻辑在子类实现,然后在调用目标对象的目标方法,但是继承的方式对于 final 和私有方法其实是没法进行代理的,因为没法继承,所以我最开始的想法是应该通过 studentService 调用 createStudent2 的时候就报错了,也就是不会进入这个方法内部,后面才发现犯了个特别二的错误,继承的方式去调用父类的私有方法,对于 Java 来说是可以调用到的,父类的私有方法并不由子类的InstanceKlass维护,只能通过子类的InstanceKlass找到Java类对应的_super,这样间接地访问。也就是说子类其实是可以访问的,那为啥访问了会报空指针呢,这里报的是studentManager 是空的,可以往依赖注入方面去想,如果忽略依赖注入,我这个studentManager 的确是 null,那是不是就没有被依赖注入呢,但是为啥前面那个可以呢

    -

    这个问题着实查了很久,不废话来看代码

    -
    @Override
    -		protected Object invokeJoinpoint() throws Throwable {
    -			if (this.methodProxy != null) {
    -        // 这里的 target 就是被代理的 bean
    -				return this.methodProxy.invoke(this.target, this.arguments);
    -			}
    -			else {
    -				return super.invokeJoinpoint();
    -			}
    -		}
    - - - -

    这个是org.springframework.aop.framework.CglibAopProxy.CglibMethodInvocation的代码,其实它在这里不是直接调用 super 也就是父类的方法,而是通过 methodProxy 调用 target 目标对象的方法,也就是原始的 studentService bean 的方法,这样子 spring 管理的已经做好依赖注入的 bean 就能正常起作用,否则就会出现上面的问题,因为 cglib 其实是通过继承来实现,通过将调用转移到子类上加入代理逻辑,我们在简单使用的时候会直接 invokeSuper() 调用父类的方法,但是在这里 spring 的场景里需要去支持 spring 的功能逻辑,所以上面的问题就可以开始来解释了,因为 createStudent 是公共方法,cglib 可以对其进行继承代理,但是在执行逻辑的时候其实是通过调用目标对象,也就是 spring 管理的被代理的目标对象的 bean 调用的 createStudent,而对于下面的 createStudent2 方法因为是私有方法,不会走代理逻辑,也就不会有调用回目标对象的逻辑,只是通过继承关系,在子类中没有这个方法,所以会通过子类的InstanceKlass找到这个类对应的_super,然后调用父类的这个私有方法,这里要搞清楚一个点,从这个代理类直接找到其父类然后调用这个私有方法,这个类是由 cglib 生成的,不是被 spring 管理起来经过依赖注入的 bean,所以是没有 studentManager 这个依赖的,也就出现了前面的问题

    -

    而在前面提到的cglib通过methodProxy调用到目标对象,目标对象是在什么时候设置的呢,其实是在bean的生命周期中,org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization这个接口的在bean的初始化过程中,会调用实现了这个接口的方法,

    -
    @Override
    -public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    -	if (bean != null) {
    -		Object cacheKey = getCacheKey(bean.getClass(), beanName);
    -		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    -			return wrapIfNecessary(bean, beanName, cacheKey);
    -		}
    -	}
    -	return bean;
    -}
    + status = ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; + } -

    具体的逻辑在 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary这个方法里

    -
    protected Object getCacheKey(Class<?> beanClass, @Nullable String beanName) {
    -		if (StringUtils.hasLength(beanName)) {
    -			return (FactoryBean.class.isAssignableFrom(beanClass) ?
    -					BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName);
    -		}
    -		else {
    -			return beanClass;
    -		}
    -	}
    +                            if (ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.hasHook()) {
    +                                consumeMessageContext.setStatus(status.toString());
    +                                consumeMessageContext
    +                                    .setSuccess(ConsumeOrderlyStatus.SUCCESS == status || ConsumeOrderlyStatus.COMMIT == status);
    +                                ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.executeHookAfter(consumeMessageContext);
    +                            }
     
    -	/**
    -	 * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
    -	 * @param bean the raw bean instance
    -	 * @param beanName the name of the bean
    -	 * @param cacheKey the cache key for metadata access
    -	 * @return a proxy wrapping the bean, or the raw bean instance as-is
    -	 */
    -	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    -		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
    -			return bean;
    -		}
    -		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
    -			return bean;
    -		}
    -		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
    -			this.advisedBeans.put(cacheKey, Boolean.FALSE);
    -			return bean;
    -		}
    +                            ConsumeMessageOrderlyService.this.getConsumerStatsManager()
    +                                .incConsumeRT(ConsumeMessageOrderlyService.this.consumerGroup, messageQueue.getTopic(), consumeRT);
     
    -		// Create proxy if we have advice.
    -		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    -		if (specificInterceptors != DO_NOT_PROXY) {
    -			this.advisedBeans.put(cacheKey, Boolean.TRUE);
    -			Object proxy = createProxy(
    -					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
    -			this.proxyTypes.put(cacheKey, proxy.getClass());
    -			return proxy;
    -		}
    +                            continueConsume = ConsumeMessageOrderlyService.this.processConsumeResult(msgs, status, context, this);
    +                        } else {
    +                            continueConsume = false;
    +                        }
    +                    }
    +                } else {
    +                    if (this.processQueue.isDropped()) {
    +                        log.warn("the message queue not be able to consume, because it's dropped. {}", this.messageQueue);
    +                        return;
    +                    }
     
    -		this.advisedBeans.put(cacheKey, Boolean.FALSE);
    -		return bean;
    -	}
    + ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue, this.processQueue, 100); + } + } + } -

    然后在 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy 中创建了代理类

    +

    获取到锁对象后,使用synchronized尝试申请线程级独占锁。

    +

    如果加锁成功,同一时刻只有一个线程进行消息消费。

    +

    如果加锁失败,会延迟100ms重新尝试向broker端申请锁定messageQueue,锁定成功后重新提交消费请求

    +

    创建消息拉取任务时,消息客户端向broker端申请锁定MessageQueue,使得一个MessageQueue同一个时刻只能被一个消费客户端消费。

    +

    消息消费时,多线程针对同一个消息队列的消费先尝试使用synchronized申请独占锁,加锁成功才能进行消费,使得一个MessageQueue同一个时刻只能被一个消费客户端中一个线程消费。
    这里其实还有很重要的一点是对processQueue的加锁,这里其实是保证了在 rebalance的过程中如果 processQueue 被分配给了另一个 consumer,但是当前已经被我这个 consumer 再消费,但是没提交,就有可能出现被两个消费者消费,所以得进行加锁保证不受 rebalance 影响。

    ]]>
    - Java - SpringBoot + MQ + RocketMQ + 消息队列 - Java - Spring - SpringBoot - cglib - 事务 + MQ + 消息队列 + RocketMQ
    @@ -10426,11 +10295,193 @@ user3: System.out.println(studentService.queryOne()); System.out.println(studentService.queryAnother()); -} +} + +

    看一下运行结果

    +

    +

    其实这个方法应用场景不止可以用来迁移数据库,还能实现精细化的读写数据源分离之类的,算是做个简单记录和分享。

    +]]> + + Java + SpringBoot + + + Java + Spring + SpringBoot + Druid + 数据源动态切换 + +
    + + 聊一下 SpringBoot 中使用的 cglib 作为动态代理中的一个注意点 + /2021/09/19/%E8%81%8A%E4%B8%80%E4%B8%8B-SpringBoot-%E4%B8%AD%E4%BD%BF%E7%94%A8%E7%9A%84-cglib-%E4%BD%9C%E4%B8%BA%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E4%B8%AD%E7%9A%84%E4%B8%80%E4%B8%AA%E6%B3%A8%E6%84%8F%E7%82%B9/ + 这个话题是由一次组内同学分享引出来的,首先在 springboot 2.x 开始默认使用了 cglib 作为 aop 的实现,这里也稍微讲一下,在一个 1.x 的老项目里,可以看到AopAutoConfiguration 是这样的

    +
    @Configuration
    +@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
    +@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
    +public class AopAutoConfiguration {
    +
    +	@Configuration
    +	@EnableAspectJAutoProxy(proxyTargetClass = false)
    +	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = true)
    +	public static class JdkDynamicAutoProxyConfiguration {
    +	}
    +
    +	@Configuration
    +	@EnableAspectJAutoProxy(proxyTargetClass = true)
    +	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = false)
    +	public static class CglibAutoProxyConfiguration {
    +	}
    +
    +}
    + +

    而在 2.x 中变成了这样

    +
    @Configuration(proxyBeanMethods = false)
    +@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
    +public class AopAutoConfiguration {
    +
    +	@Configuration(proxyBeanMethods = false)
    +	@ConditionalOnClass(Advice.class)
    +	static class AspectJAutoProxyingConfiguration {
    +
    +		@Configuration(proxyBeanMethods = false)
    +		@EnableAspectJAutoProxy(proxyTargetClass = false)
    +		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
    +		static class JdkDynamicAutoProxyConfiguration {
    +
    +		}
    +
    +		@Configuration(proxyBeanMethods = false)
    +		@EnableAspectJAutoProxy(proxyTargetClass = true)
    +		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
    +				matchIfMissing = true)
    +		static class CglibAutoProxyConfiguration {
    +
    +		}
    +
    +	}
    + +

    为何会加载 AopAutoConfiguration 在前面的文章聊聊 SpringBoot 自动装配里已经介绍过,有兴趣的可以看下,可以发现 springboot 在 2.x 版本开始使用 cglib 作为默认的动态代理实现。

    +

    然后就是出现的问题了,代码是这样的,一个简单的基于 springboot 的带有数据库的插入,对插入代码加了事务注解,

    +
    @Mapper
    +public interface StudentMapper {
    +		// 就是插入一条数据
    +    @Insert("insert into student(name, age)" + "values ('nick', '18')")
    +    public Long insert();
    +}
    +
    +@Component
    +public class StudentManager {
    +
    +    @Resource
    +    private StudentMapper studentMapper;
    +    
    +    public Long createStudent() {
    +        return studentMapper.insert();
    +    }
    +}
    +
    +@Component
    +public class StudentServiceImpl implements StudentService {
    +
    +    @Resource
    +    private StudentManager studentManager;
    +
    +    // 自己引用
    +    @Resource
    +    private StudentServiceImpl studentService;
    +
    +    @Override
    +    @Transactional
    +    public Long createStudent() {
    +        Long id = studentManager.createStudent();
    +        Long id2 = studentService.createStudent2();
    +        return 1L;
    +    }
    +
    +    @Transactional
    +    private Long createStudent2() {
    +//        Integer t = Integer.valueOf("aaa");
    +        return studentManager.createStudent();
    +    }
    +}
    + +

    第一个公有方法 createStudent 首先调用了 manager 层的创建方法,然后再通过引入的 studentService 调用了createStudent2,我们先跑一下看看会出现啥情况,果不其然报错了,正是这个报错让我纠结了很久

    +

    EdR7oB

    +

    报了个空指针,而且是在 createStudent2 已经被调用到了,在它的内部,报的 studentManager 是 null,首先 cglib 作为动态代理它是通过继承的方式来实现的,相当于是会在调用目标对象的代理方法时调用 cglib 生成的子类,具体的代理切面逻辑在子类实现,然后在调用目标对象的目标方法,但是继承的方式对于 final 和私有方法其实是没法进行代理的,因为没法继承,所以我最开始的想法是应该通过 studentService 调用 createStudent2 的时候就报错了,也就是不会进入这个方法内部,后面才发现犯了个特别二的错误,继承的方式去调用父类的私有方法,对于 Java 来说是可以调用到的,父类的私有方法并不由子类的InstanceKlass维护,只能通过子类的InstanceKlass找到Java类对应的_super,这样间接地访问。也就是说子类其实是可以访问的,那为啥访问了会报空指针呢,这里报的是studentManager 是空的,可以往依赖注入方面去想,如果忽略依赖注入,我这个studentManager 的确是 null,那是不是就没有被依赖注入呢,但是为啥前面那个可以呢

    +

    这个问题着实查了很久,不废话来看代码

    +
    @Override
    +		protected Object invokeJoinpoint() throws Throwable {
    +			if (this.methodProxy != null) {
    +        // 这里的 target 就是被代理的 bean
    +				return this.methodProxy.invoke(this.target, this.arguments);
    +			}
    +			else {
    +				return super.invokeJoinpoint();
    +			}
    +		}
    + + + +

    这个是org.springframework.aop.framework.CglibAopProxy.CglibMethodInvocation的代码,其实它在这里不是直接调用 super 也就是父类的方法,而是通过 methodProxy 调用 target 目标对象的方法,也就是原始的 studentService bean 的方法,这样子 spring 管理的已经做好依赖注入的 bean 就能正常起作用,否则就会出现上面的问题,因为 cglib 其实是通过继承来实现,通过将调用转移到子类上加入代理逻辑,我们在简单使用的时候会直接 invokeSuper() 调用父类的方法,但是在这里 spring 的场景里需要去支持 spring 的功能逻辑,所以上面的问题就可以开始来解释了,因为 createStudent 是公共方法,cglib 可以对其进行继承代理,但是在执行逻辑的时候其实是通过调用目标对象,也就是 spring 管理的被代理的目标对象的 bean 调用的 createStudent,而对于下面的 createStudent2 方法因为是私有方法,不会走代理逻辑,也就不会有调用回目标对象的逻辑,只是通过继承关系,在子类中没有这个方法,所以会通过子类的InstanceKlass找到这个类对应的_super,然后调用父类的这个私有方法,这里要搞清楚一个点,从这个代理类直接找到其父类然后调用这个私有方法,这个类是由 cglib 生成的,不是被 spring 管理起来经过依赖注入的 bean,所以是没有 studentManager 这个依赖的,也就出现了前面的问题

    +

    而在前面提到的cglib通过methodProxy调用到目标对象,目标对象是在什么时候设置的呢,其实是在bean的生命周期中,org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization这个接口的在bean的初始化过程中,会调用实现了这个接口的方法,

    +
    @Override
    +public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    +	if (bean != null) {
    +		Object cacheKey = getCacheKey(bean.getClass(), beanName);
    +		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    +			return wrapIfNecessary(bean, beanName, cacheKey);
    +		}
    +	}
    +	return bean;
    +}
    + +

    具体的逻辑在 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary这个方法里

    +
    protected Object getCacheKey(Class<?> beanClass, @Nullable String beanName) {
    +		if (StringUtils.hasLength(beanName)) {
    +			return (FactoryBean.class.isAssignableFrom(beanClass) ?
    +					BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName);
    +		}
    +		else {
    +			return beanClass;
    +		}
    +	}
    +
    +	/**
    +	 * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
    +	 * @param bean the raw bean instance
    +	 * @param beanName the name of the bean
    +	 * @param cacheKey the cache key for metadata access
    +	 * @return a proxy wrapping the bean, or the raw bean instance as-is
    +	 */
    +	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    +		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
    +			return bean;
    +		}
    +		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
    +			return bean;
    +		}
    +		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
    +			this.advisedBeans.put(cacheKey, Boolean.FALSE);
    +			return bean;
    +		}
    +
    +		// Create proxy if we have advice.
    +		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    +		if (specificInterceptors != DO_NOT_PROXY) {
    +			this.advisedBeans.put(cacheKey, Boolean.TRUE);
    +			Object proxy = createProxy(
    +					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
    +			this.proxyTypes.put(cacheKey, proxy.getClass());
    +			return proxy;
    +		}
    +
    +		this.advisedBeans.put(cacheKey, Boolean.FALSE);
    +		return bean;
    +	}
    -

    看一下运行结果

    -

    -

    其实这个方法应用场景不止可以用来迁移数据库,还能实现精细化的读写数据源分离之类的,算是做个简单记录和分享。

    +

    然后在 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy 中创建了代理类

    ]]>
    Java @@ -10440,8 +10491,109 @@ user3: Java Spring SpringBoot - Druid - 数据源动态切换 + cglib + 事务 + +
    + + 搬运两个 StackOverflow 上的 Mysql 编码相关的问题解答 + /2022/01/16/%E6%90%AC%E8%BF%90%E4%B8%A4%E4%B8%AA-StackOverflow-%E4%B8%8A%E7%9A%84-Mysql-%E7%BC%96%E7%A0%81%E7%9B%B8%E5%85%B3%E7%9A%84%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94/ + Mysql 字符编码和排序规则

    这个一直是属于一知半解的状态,知道 utf8 跟 utf8mb4 的区别主要是能不能支持 emoji,但是具体后面配置的排序规则是用来干嘛,或者有什么区别,应该使用哪个,所以在 stackoverflow 上找了下,有两个比较不错的解答,就搬过来并且配合机翻做了点修改

    +

    原文

    For those people still arriving at this question in 2020 or later, there are newer options that may be better than both of these. For example, utf8mb4_0900_ai_ci.

    +

    All these collations are for the UTF-8 character encoding. The differences are in how text is sorted and compared.

    +

    _unicode_ci and _general_ci are two different sets of rules for sorting and comparing text according to the way we expect. Newer versions of MySQL introduce new sets of rules, too, such as _0900_ai_ci for equivalent rules based on Unicode 9.0 - and with no equivalent _general_ci variant. People reading this now should probably use one of these newer collations instead of either _unicode_ci or _general_ci. The description of those older collations below is provided for interest only.

    +

    MySQL is currently transitioning away from an older, flawed UTF-8 implementation. For now, you need to use utf8mb4 instead of utf8 for the character encoding part, to ensure you are getting the fixed version. The flawed version remains for backward compatibility, though it is being deprecated.

    +

    Key differences

    +

    utf8mb4_unicode_ci is based on the official Unicode rules for universal sorting and comparison, which sorts accurately in a wide range of languages.

    +

    utf8mb4_general_ci is a simplified set of sorting rules which aims to do as well as it can while taking many short-cuts designed to improve speed. It does not follow the Unicode rules and will result in undesirable sorting or comparison in some situations, such as when using particular languages or characters.

    +

    On modern servers, this performance boost will be all but negligible. It was devised in a time when servers had a tiny fraction of the CPU performance of today’s computers.

    +

    Benefits of utf8mb4_unicode_ci over utf8mb4_general_ci

    +

    utf8mb4_unicode_ci, which uses the Unicode rules for sorting and comparison, employs a fairly complex algorithm for correct sorting in a wide range of languages and when using a wide range of special characters. These rules need to take into account language-specific conventions; not everybody sorts their characters in what we would call ‘alphabetical order’.

    +

    As far as Latin (ie “European”) languages go, there is not much difference between the Unicode sorting and the simplified utf8mb4_general_cisorting in MySQL, but there are still a few differences:

    +

    For examples, the Unicode collation sorts “ß” like “ss”, and “Œ” like “OE” as people using those characters would normally want, whereas utf8mb4_general_cisorts them as single characters (presumably like “s” and “e” respectively).

    +

    Some Unicode characters are defined as ignorable, which means they shouldn’t count toward the sort order and the comparison should move on to the next character instead. utf8mb4_unicode_cihandles these properly.

    +

    In non-latin languages, such as Asian languages or languages with different alphabets, there may be a lot more differences between Unicode sorting and the simplified utf8mb4_general_cisorting. The suitability of utf8mb4_general_ciwill depend heavily on the language used. For some languages, it’ll be quite inadequate.

    +

    What should you use?

    +

    There is almost certainly no reason to use utf8mb4_general_cianymore, as we have left behind the point where CPU speed is low enough that the performance difference would be important. Your database will almost certainly be limited by other bottlenecks than this.

    +

    In the past, some people recommended to use utf8mb4_general_ciexcept when accurate sorting was going to be important enough to justify the performance cost. Today, that performance cost has all but disappeared, and developers are treating internationalization more seriously.

    +

    There’s an argument to be made that if speed is more important to you than accuracy, you may as well not do any sorting at all. It’s trivial to make an algorithm faster if you do not need it to be accurate. So, utf8mb4_general_ciis a compromise that’s probably not needed for speed reasons and probably also not suitable for accuracy reasons.

    +

    One other thing I’ll add is that even if you know your application only supports the English language, it may still need to deal with people’s names, which can often contain characters used in other languages in which it is just as important to sort correctly. Using the Unicode rules for everything helps add peace of mind that the very smart Unicode people have worked very hard to make sorting work properly.

    +

    What the parts mean

    +

    Firstly, ci is for case-insensitive sorting and comparison. This means it’s suitable for textual data, and case is not important. The other types of collation are cs (case-sensitive) for textual data where case is important, and bin, for where the encoding needs to match, bit for bit, which is suitable for fields which are really encoded binary data (including, for example, Base64). Case-sensitive sorting leads to some weird results and case-sensitive comparison can result in duplicate values differing only in letter case, so case-sensitive collations are falling out of favor for textual data - if case is significant to you, then otherwise ignorable punctuation and so on is probably also significant, and a binary collation might be more appropriate.

    +

    Next, unicode or general refers to the specific sorting and comparison rules - in particular, the way text is normalized or compared. There are many different sets of rules for the utf8mb4 character encoding, with unicode and general being two that attempt to work well in all possible languages rather than one specific one. The differences between these two sets of rules are the subject of this answer. Note that unicode uses rules from Unicode 4.0. Recent versions of MySQL add the rulesets unicode_520 using rules from Unicode 5.2, and 0900 (dropping the “unicode_” part) using rules from Unicode 9.0.

    +

    And lastly, utf8mb4 is of course the character encoding used internally. In this answer I’m talking only about Unicode based encodings.

    +

    翻译

    对于那些在 2020 年或之后仍会遇到这个问题的人,有可能比这两个更好的新选项。例如,utf8mb4_0900_ai_ci

    +

    所有这些排序规则都用于 UTF-8 字符编码。不同之处在于文本的排序和比较方式。

    +

    _unicode_ci_general_ci是两组不同的规则,用于按照我们期望的方式对文本进行排序和比较。较新版本的 MySQL 也引入了新的规则集,例如 _0900_ai_ci用于基于 Unicode 9.0 的等效规则 - 并且没有等效的 _general_ci变体。现在阅读本文的人可能应该使用这些较新的排序规则之一,而不是 _unicode_ci_general_ci。下面对那些较旧的排序规则的描述仅供参考。

    +

    MySQL 目前正在从旧的、有缺陷的 UTF-8 实现过渡。现在,您需要使用 utf8mb4 而不是 utf8作为字符编码部分,以确保您获得的是固定版本。有缺陷的版本仍然是为了向后兼容,尽管它已被弃用。

    +

    主要区别

    +

    utf8mb4_unicode_ci基于官方 Unicode 规则进行通用排序和比较,可在多种语言中准确排序。

    +

    utf8mb4_general_ci是一组简化的排序规则,旨在尽其所能,同时采用许多旨在提高速度的捷径。它不遵循 Unicode 规则,并且在某些情况下会导致不希望的排序或比较,例如在使用特定语言或字符时。

    +

    在现代服务器上,这种性能提升几乎可以忽略不计。它是在服务器的 CPU 性能只有当今计算机的一小部分时设计的。

    +

    utf8mb4_unicode_ci 相对于 utf8mb4_general_ci的优势

    +

    utf8mb4_unicode_ci使用 Unicode 规则进行排序和比较,采用相当复杂的算法在多种语言中以及在使用多种特殊字符时进行正确排序。这些规则需要考虑特定语言的约定;不是每个人都按照我们所说的“字母顺序”对他们的字符进行排序。

    +

    就拉丁语(即“欧洲”)语言而言,Unicode 排序和 MySQL 中简化的 utf8mb4_general_ci排序没有太大区别,但仍有一些区别:

    +

    例如,Unicode 排序规则将“ß”排序为“ss”,将“Œ”排序为“OE”,因为使用这些字符的人通常需要这些字符,而 utf8mb4_general_ci将它们排序为单个字符(大概分别像“s”和“e” )。

    +

    一些 Unicode 字符被定义为可忽略,这意味着它们不应该计入排序顺序,并且比较应该转到下一个字符。 utf8mb4_unicode_ci正确处理这些。

    +

    在非拉丁语言中,例如亚洲语言或具有不同字母的语言,Unicode 排序和简化的 utf8mb4_general_ci排序之间可能存在更多差异。 utf8mb4_general_ci的适用性在很大程度上取决于所使用的语言。对于某些语言,这将是非常不充分的。

    +

    你应该用什么?

    +

    几乎可以肯定没有理由再使用 utf8mb4_general_ci,因为我们已经将 CPU 速度低到会严重影响性能表现的时代远抛在脑后了。您的数据库几乎肯定会受到除此之外的其他瓶颈的限制。

    +

    过去,有些人建议使用 utf8mb4_general_ci,除非准确排序足够重要以证明性能成本是合理的。如今,这种性能成本几乎消失了,开发人员正在更加认真地对待国际化。

    +

    有一个论点是,如果速度对您来说比准确性更重要,那么您可能根本不进行任何排序。如果您不需要准确的算法,那么使算法更快是微不足道的。因此,utf8mb4_general_ci是一种折衷方案,出于速度原因可能不需要,也可能出于准确性原因也不适合。

    +

    我要补充的另一件事是,即使您知道您的应用程序仅支持英语,它可能仍需要处理人名,这些人名通常包含其他语言中使用的字符,在这些语言中正确排序同样重要.对所有事情都使用 Unicode 规则有助于让您更加安心,因为非常聪明的 Unicode 人员已经非常努力地工作以使排序正常工作。

    +

    其余各个部分是什么意思

    +

    首先, ci 用于不区分大小写的排序和比较。这意味着它适用于文本数据,大小写并不重要。其他类型的排序规则是 cs(区分大小写),用于区分大小写的文本数据,以及 bin,用于编码需要匹配的地方,逐位匹配,适用于真正编码二进制数据的字段(包括,用于例如,Base64)。区分大小写的排序会导致一些奇怪的结果,区分大小写的比较可能会导致重复值仅在字母大小写上有所不同,因此区分大小写的排序规则对文本数据不受欢迎 - 如果大小写对您很重要,那么标点符号就可以忽略等等可能也很重要,二进制排序规则可能更合适。

    +

    接下来,unicode 或general 指的是具体的排序和比较规则——特别是文本被规范化或比较的方式。 utf8mb4 字符编码有许多不同的规则集,其中 unicode 和 general 是两种,它们试图在所有可能的语言中都很好地工作,而不是在一种特定的语言中。这两组规则之间的差异是此答案的主题。请注意,unicode 使用 Unicode 4.0 中的规则。 MySQL 的最新版本使用 Unicode 5.2 的规则添加规则集 unicode_520,使用 Unicode 9.0 的规则添加 0900(删除“unicode_”部分)。

    +

    最后,utf8mb4 当然是内部使用的字符编码。在这个答案中,我只谈论基于 Unicode 的编码。

    +

    utf8 和 utf8mb4 编码有什么区别

    原文

    UTF-8is a variable-length encoding. In the case of UTF-8, this means that storing one code point requires one to four bytes. However, MySQL’s encoding called “utf8” (alias of “utf8mb3”) only stores a maximum of three bytes per code point.

    +

    So the character set “utf8”/“utf8mb3” cannot store all Unicode code points: it only supports the range 0x000 to 0xFFFF, which is called the “Basic Multilingual Plane“. See also Comparison of Unicode encodings.

    +

    This is what (a previous version of the same page at)the MySQL documentationhas to say about it:

    +
    +

    The character set named utf8[/utf8mb3] uses a maximum of three bytes per character and contains only BMP characters. As of MySQL 5.5.3, the utf8mb4 character set uses a maximum of four bytes per character supports supplemental characters:

    +
      +
    • For a BMP character, utf8[/utf8mb3] and utf8mb4 have identical storage characteristics: same code values, same encoding, same length.
    • +
    • For a supplementary character, utf8[/utf8mb3] cannot store the character at all, while utf8mb4 requires four bytes to store it. Since utf8[/utf8mb3] cannot store the character at all, you do not have any supplementary characters in utf8[/utf8mb3] columns and you need not worry about converting characters or losing data when upgrading utf8[/utf8mb3] data from older versions of MySQL.
    • +
    +
    +

    So if you want your column to support storing characters lying outside the BMP (and you usually want to), such as emoji, use “utf8mb4”. See also What are the most common non-BMP Unicode characters in actual use?.

    +

    译文

    UTF-8 是一种可变长度编码。对于 UTF-8,这意味着存储一个代码点需要一到四个字节。但是,MySQL 的编码称为“utf8”(“utf8mb3”的别名)每个代码点最多只能存储三个字节。

    +

    所以字符集“utf8”/“utf8mb3”不能存储所有的Unicode码位:它只支持0x000到0xFFFF的范围,被称为“基本多语言平面”。另请参阅 Unicode 编码比较

    +

    这就是(同一页面的先前版本)MySQL 文档 不得不说的:

    +
    +

    名为 utf8[/utf8mb3] 的字符集每个字符最多使用三个字节,并且仅包含 BMP 字符。从 MySQL 5.5.3 开始,utf8mb4 字符集每个字符最多使用四个字节,支持补充字符:

    +
      +
    • 对于 BMP 字符,utf8[/utf8mb3] 和 utf8mb4 具有相同的存储特性:相同的代码值、相同的编码、相同的长度。
    • +
    • 对于补充字符,utf8[/utf8mb3] 根本无法存储该字符,而 utf8mb4 需要四个字节来存储它。由于 utf8[/utf8mb3] 根本无法存储字符,因此您在 utf8[/utf8mb3] 列中没有任何补充字符,您不必担心从旧版本升级 utf8[/utf8mb3] 数据时转换字符或丢失数据mysql。
    • +
    +
    +

    因此,如果您希望您的列支持存储位于 BMP 之外的字符(并且您通常希望这样做),例如 emoji,请使用“utf8mb4”。另请参阅

    +

    实际使用中最常见的非 BMP Unicode 字符是什么?

    +]]>
    + + Mysql + + + mysql + 字符集 + 编码 + utf8 + utf8mb4 + utf8mb4_0900_ai_ci + utf8mb4_unicode_ci + utf8mb4_general_ci + +
    + + 屯菜惊魂记 + /2022/04/24/%E5%B1%AF%E8%8F%9C%E6%83%8A%E9%AD%82%E8%AE%B0/ + 因某国际大都市的给力表现,昨儿旁边行政区启动应急响应,同事早上就在群里说要去超市买菜了,到了超市人还特别多,由于来的就是我们经常去的那家超市,一方面为了安全,另一方面是怕已经抢不到了,就去了另一家比较远的超市,开车怕没车位就骑了小电驴,还下着小雨,结果到了超市差不多 12 点多,超市里出来的人都是推着一整车一整车的物资,有些比较像我,整箱的泡面,好几提纸巾,还有各种吃的,都是整箱整箱的,进了超市发现结账包括自助结账的都排很长的队,到了蔬菜货架附近,差点哼起那首歌“空空如也~”,新鲜蔬菜基本已被抢空,只剩下一些卖相不太好的土豆番薯之类的,也算是意料之外情理之中了,本来以为这家超市稍微离封控区远一些会空一点,结果就是所谓的某大都市封控了等物资,杭州市是屯了物资等封控,新鲜蔬菜没了我们也只能买点其他的,神奇的是水果基本都在,可能困难时期水果不算必需品了?还是水果基本人人都已经储备了很多,不太能理解,虽然水果还在,但是称重的地方也还有好多人排队,我们采取了并行策略,LD 在那排队,遥控指挥我去拿其他物资,拿了点碱水面,黑米,那黑米的时候还闹了个乌龙,因为前面就是散装鸡蛋的堆货的地方,结果我们以为是在那后面排队,结果称重那个在那散步了,我们还在那排队,看到后面排队,那几个挑的人也该提醒下吧,几个鸡蛋挑了半天,看看人家大妈,直接拿了四盘,看了下牛奶货架也比较空,不过还有致优跟优倍,不过不算很实惠,本来想买,只是后来赶着去结账,就给忘了,称好了黑米去看了下肉,结果肉也没了,都在买猪蹄,我们也不太爱吃猪蹄,就买了点鸡胸肉,整体看起来我们买的东西真的有点格格不入,不买泡面(因为 LD 不让买了),也不屯啥米和鸡蛋,其实鸡蛋已经买了,米也买了,其他的本身冰箱小也放不下太多东西,我是觉得还可能在屯一点这那的,LD 觉得太多了,基本的米面油有了,其他调味品什么也有了。后面就是排队结账,我去排的时候刚好前面一个小伙子跟大妈在争执,大妈说我们差不多时间来的,你要排前面就前面,小伙子有点不高兴,觉得她就是插队,哈哈,平时一般这种剧情都是发生在我身上的,这会看着前面的吵起来还是很开心的,终于有跟我一样较真的人了,有时候总觉得我是个很纠结,很较真的人,但是我现在慢慢认可了这种较真,如果没有人指出来这种是插队行为,是不对的,就会有越来越多的人觉得是可以随意插队的,正确的事应该要坚持,很多情况大家总是觉得多一事不如少一事,鸡毛蒜皮的没什么好计较的,正是这种想法,那么多人才不管任何规则,反而搞得像遵守规则都是傻 X 似的。回到屯物资,后面结账排到队了也没来得及买原来想买的花生牛奶什么的,毕竟那么多人排着队,回家后因为没有蔬菜,结果就只能吃干菜汤和饭了

    +]]>
    + + 生活 + + + 生活 + 囤物资
    @@ -12858,35 +13010,6 @@ result = result CachedThreadPool - - 聊聊 Sharding-Jdbc 的简单原理初篇 - /2021/12/26/%E8%81%8A%E8%81%8A-Sharding-Jdbc-%E7%9A%84%E7%AE%80%E5%8D%95%E5%8E%9F%E7%90%86%E5%88%9D%E7%AF%87/ - 在上一篇 sharding-jdbc 的介绍中其实碰到过一个问题,这里也引出了一个比较有意思的话题
    就是我在执行 query 的时候犯过一个比较难发现的错误,

    -
    ResultSet resultSet = ps.executeQuery(sql);
    -

    实际上应该是

    -
    ResultSet resultSet = ps.executeQuery();
    -

    而这里的差别就是,是否传 sql 这个参数,首先我们要知道这个 ps 是什么,它也是个接口java.sql.PreparedStatement,而真正的实现类是org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement,我们来看下继承关系

    这里可以看到继承关系里有org.apache.shardingsphere.driver.jdbc.unsupported.AbstractUnsupportedOperationPreparedStatement
    那么在我上面的写错的代码里

    -
    @Override
    -public final ResultSet executeQuery(final String sql) throws SQLException {
    -    throw new SQLFeatureNotSupportedException("executeQuery with SQL for PreparedStatement");
    -}
    -

    这个报错一开始让我有点懵,后来点进去了发现是这么个异常,但是我其实一开始是用的更新语句,以为更新不支持,因为平时使用没有深究过,以为是不是需要使用 Mybatis 才可以执行更新,但是理论上也不应该,再往上看原来这些异常是由 sharding-jdbc 包装的,也就是在上面说的AbstractUnsupportedOperationPreparedStatement,这其实也是一种设计思想,本身 jdbc 提供了一系列接口,由各家去支持,包括 mysql,sql server,oracle 等,而正因为这个设计,所以 sharding-jdbc 也可以在此基础上进行设计,我们可以总体地看下 sharding-jdbc 的实现基础

    看了前面ShardingSpherePreparedStatement的继承关系,应该也能猜到这里的几个类都是实现了 jdbc 的基础接口,

    在前一篇的 demo 中的

    -
    Connection conn = dataSource.getConnection();
    -

    其实就获得了org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection#ShardingSphereConnection
    然后获得java.sql.PreparedStatement

    -
    PreparedStatement ps = conn.prepareStatement(sql)
    -

    就是获取了org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement
    然后就是执行

    -
    ResultSet resultSet = ps.executeQuery();
    -

    然后获得结果
    org.apache.shardingsphere.driver.jdbc.core.resultset.ShardingSphereResultSet

    -

    其实像 mybatis 也是基于这样去实现的

    -]]>
    - - Java - - - Java - Sharding-Jdbc - -
    聊聊 mysql 的 MVCC 续篇 /2020/05/02/%E8%81%8A%E8%81%8A-mysql-%E7%9A%84-MVCC-%E7%BB%AD%E7%AF%87/ @@ -12962,6 +13085,115 @@ void ReadView::prepare(trx_id_t id) { 幻读 + + 聊聊 mysql 的 MVCC + /2020/04/26/%E8%81%8A%E8%81%8A-mysql-%E7%9A%84-MVCC/ + 很久以前,有位面试官问到,你知道 mysql 的事务隔离级别吗,“额 O__O …,不太清楚”,完了之后我就去网上找相关的文章,找到了这篇MySQL 四种事务隔离级的说明, 文章写得特别好,看了这个就懂了各个事务隔离级别都是啥,不过看了这个之后多思考一下的话还是会发现问题,这么神奇的事务隔离级别是怎么实现的呢

    +

    其中 innodb 的事务隔离用到了标题里说到的 mvcc,Multiversion concurrency control, 直译过来就是多版本并发控制,先不讲这个究竟是个啥,考虑下如果纯猜测,这个事务隔离级别应该会是怎么样实现呢,愚钝的我想了下,可以在事务开始的时候拷贝一个表,这个可以支持 RR 级别,RC 级别就不支持了,而且要是个非常大的表,想想就不可行

    +

    腆着脸说虽然这个不可行,但是思路是对的,具体实行起来需要做一系列(肥肠多)的改动,首先根据我的理解,其实这个拷贝一个表是变成拷贝一条记录,但是如果有多个事务,那就得拷贝多次,这个问题其实可以借助版本管理系统来解释,在用版本管理系统,git 之类的之前,很原始的可能是开发完一个功能后,就打个压缩包用时间等信息命名,然后如果后面要找回这个就直接用这个压缩包的就行了,后来有了 svn,git 中心式和分布式的版本管理系统,它的一个特点是粒度可以控制到文件和代码行级别,对应的我们的 mysql 事务是不是也可以从一开始预想的表级别细化到行的级别,可能之前很多人都了解过,数据库的一行记录除了我们用户自定义的字段,还有一些额外的字段,去源码data0type.h里捞一下

    +
    /* Precise data types for system columns and the length of those columns;
    +NOTE: the values must run from 0 up in the order given! All codes must
    +be less than 256 */
    +#define DATA_ROW_ID 0     /* row id: a 48-bit integer */
    +#define DATA_ROW_ID_LEN 6 /* stored length for row id */
    +
    +/** Transaction id: 6 bytes */
    +constexpr size_t DATA_TRX_ID = 1;
    +
    +/** Transaction ID type size in bytes. */
    +constexpr size_t DATA_TRX_ID_LEN = 6;
    +
    +/** Rollback data pointer: 7 bytes */
    +constexpr size_t DATA_ROLL_PTR = 2;
    +
    +/** Rollback data pointer type size in bytes. */
    +constexpr size_t DATA_ROLL_PTR_LEN = 7;
    + +

    一个是 DATA_ROW_ID,这个是在数据没指定主键的时候会生成一个隐藏的,如果用户有指定主键就是主键了

    +

    一个是 DATA_TRX_ID,这个表示这条记录的事务 ID

    +

    还有一个是 DATA_ROLL_PTR 指向回滚段的指针

    +

    指向的回滚段其实就是我们常说的 undo log,这里面的具体结构就是个链表,在 mvcc 里会使用到这个,还有就是这个 DATA_TRX_ID,每条记录都记录了这个事务 ID,表示的是这条记录的当前值是被哪个事务修改的,下面就扯回事务了,我们知道 Read Uncommitted, 其实用不到隔离,直接读取当前值就好了,到了 Read Committed 级别,我们要让事务读取到提交过的值,mysql 使用了一个叫 read view 的玩意,它里面有这些值是我们需要注意的,

    +

    m_low_limit_id, 这个是 read view 创建时最大的活跃事务 id

    +

    m_up_limit_id, 这个是 read view 创建时最小的活跃事务 id

    +

    m_ids, 这个是 read view 创建时所有的活跃事务 id 数组

    +

    m_creator_trx_id 这个是当前记录的创建事务 id

    +

    判断事务的可见性主要的逻辑是这样,

    +
      +
    1. 当记录的事务 id 小于最小活跃事务 id,说明是可见的,
    2. +
    3. 如果记录的事务 id 等于当前事务 id,说明是自己的更改,可见
    4. +
    5. 如果记录的事务 id 大于最大的活跃事务 id, 不可见
    6. +
    7. 如果记录的事务 id 介于 m_low_limit_idm_up_limit_id 之间,则要判断它是否在 m_ids 中,如果在,不可见,如果不在,表示已提交,可见
      具体的代码捞一下看看
      /** Check whether the changes by id are visible.
      +  @param[in]	id	transaction id to check against the view
      +  @param[in]	name	table name
      +  @return whether the view sees the modifications of id. */
      +  bool changes_visible(trx_id_t id, const table_name_t &name) const
      +      MY_ATTRIBUTE((warn_unused_result)) {
      +    ut_ad(id > 0);
      +
      +    if (id < m_up_limit_id || id == m_creator_trx_id) {
      +      return (true);
      +    }
      +
      +    check_trx_id_sanity(id, name);
      +
      +    if (id >= m_low_limit_id) {
      +      return (false);
      +
      +    } else if (m_ids.empty()) {
      +      return (true);
      +    }
      +
      +    const ids_t::value_type *p = m_ids.data();
      +
      +    return (!std::binary_search(p, p + m_ids.size(), id));
      +  }
      +剩下来一点是啥呢,就是 Read CommittedRepeated Read 也不一样,那前面说的 read view 都能支持吗,又是怎么支持呢,假如这个 read view 是在事务一开始就创建,那好像能支持的只是 RR 事务隔离级别,其实呢,这是通过创建 read view的时机,对于 RR 级别,就是在事务的第一个 select 语句是创建,对于 RC 级别,是在每个 select 语句执行前都是创建一次,那样就可以保证能读到所有已提交的数据
    8. +
    +]]>
    + + Mysql + C + 数据结构 + 源码 + Mysql + + + mysql + 数据结构 + 源码 + mvcc + read view + +
    + + 聊聊 Sharding-Jdbc 的简单原理初篇 + /2021/12/26/%E8%81%8A%E8%81%8A-Sharding-Jdbc-%E7%9A%84%E7%AE%80%E5%8D%95%E5%8E%9F%E7%90%86%E5%88%9D%E7%AF%87/ + 在上一篇 sharding-jdbc 的介绍中其实碰到过一个问题,这里也引出了一个比较有意思的话题
    就是我在执行 query 的时候犯过一个比较难发现的错误,

    +
    ResultSet resultSet = ps.executeQuery(sql);
    +

    实际上应该是

    +
    ResultSet resultSet = ps.executeQuery();
    +

    而这里的差别就是,是否传 sql 这个参数,首先我们要知道这个 ps 是什么,它也是个接口java.sql.PreparedStatement,而真正的实现类是org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement,我们来看下继承关系

    这里可以看到继承关系里有org.apache.shardingsphere.driver.jdbc.unsupported.AbstractUnsupportedOperationPreparedStatement
    那么在我上面的写错的代码里

    +
    @Override
    +public final ResultSet executeQuery(final String sql) throws SQLException {
    +    throw new SQLFeatureNotSupportedException("executeQuery with SQL for PreparedStatement");
    +}
    +

    这个报错一开始让我有点懵,后来点进去了发现是这么个异常,但是我其实一开始是用的更新语句,以为更新不支持,因为平时使用没有深究过,以为是不是需要使用 Mybatis 才可以执行更新,但是理论上也不应该,再往上看原来这些异常是由 sharding-jdbc 包装的,也就是在上面说的AbstractUnsupportedOperationPreparedStatement,这其实也是一种设计思想,本身 jdbc 提供了一系列接口,由各家去支持,包括 mysql,sql server,oracle 等,而正因为这个设计,所以 sharding-jdbc 也可以在此基础上进行设计,我们可以总体地看下 sharding-jdbc 的实现基础

    看了前面ShardingSpherePreparedStatement的继承关系,应该也能猜到这里的几个类都是实现了 jdbc 的基础接口,

    在前一篇的 demo 中的

    +
    Connection conn = dataSource.getConnection();
    +

    其实就获得了org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection#ShardingSphereConnection
    然后获得java.sql.PreparedStatement

    +
    PreparedStatement ps = conn.prepareStatement(sql)
    +

    就是获取了org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement
    然后就是执行

    +
    ResultSet resultSet = ps.executeQuery();
    +

    然后获得结果
    org.apache.shardingsphere.driver.jdbc.core.resultset.ShardingSphereResultSet

    +

    其实像 mybatis 也是基于这样去实现的

    +]]>
    + + Java + + + Java + Sharding-Jdbc + +
    聊聊 mysql 索引的一些细节 /2020/12/27/%E8%81%8A%E8%81%8A-mysql-%E7%B4%A2%E5%BC%95%E7%9A%84%E4%B8%80%E4%BA%9B%E7%BB%86%E8%8A%82/ @@ -13028,83 +13260,93 @@ $ - 聊聊 mysql 的 MVCC - /2020/04/26/%E8%81%8A%E8%81%8A-mysql-%E7%9A%84-MVCC/ - 很久以前,有位面试官问到,你知道 mysql 的事务隔离级别吗,“额 O__O …,不太清楚”,完了之后我就去网上找相关的文章,找到了这篇MySQL 四种事务隔离级的说明, 文章写得特别好,看了这个就懂了各个事务隔离级别都是啥,不过看了这个之后多思考一下的话还是会发现问题,这么神奇的事务隔离级别是怎么实现的呢

    -

    其中 innodb 的事务隔离用到了标题里说到的 mvcc,Multiversion concurrency control, 直译过来就是多版本并发控制,先不讲这个究竟是个啥,考虑下如果纯猜测,这个事务隔离级别应该会是怎么样实现呢,愚钝的我想了下,可以在事务开始的时候拷贝一个表,这个可以支持 RR 级别,RC 级别就不支持了,而且要是个非常大的表,想想就不可行

    -

    腆着脸说虽然这个不可行,但是思路是对的,具体实行起来需要做一系列(肥肠多)的改动,首先根据我的理解,其实这个拷贝一个表是变成拷贝一条记录,但是如果有多个事务,那就得拷贝多次,这个问题其实可以借助版本管理系统来解释,在用版本管理系统,git 之类的之前,很原始的可能是开发完一个功能后,就打个压缩包用时间等信息命名,然后如果后面要找回这个就直接用这个压缩包的就行了,后来有了 svn,git 中心式和分布式的版本管理系统,它的一个特点是粒度可以控制到文件和代码行级别,对应的我们的 mysql 事务是不是也可以从一开始预想的表级别细化到行的级别,可能之前很多人都了解过,数据库的一行记录除了我们用户自定义的字段,还有一些额外的字段,去源码data0type.h里捞一下

    -
    /* Precise data types for system columns and the length of those columns;
    -NOTE: the values must run from 0 up in the order given! All codes must
    -be less than 256 */
    -#define DATA_ROW_ID 0     /* row id: a 48-bit integer */
    -#define DATA_ROW_ID_LEN 6 /* stored length for row id */
    -
    -/** Transaction id: 6 bytes */
    -constexpr size_t DATA_TRX_ID = 1;
    -
    -/** Transaction ID type size in bytes. */
    -constexpr size_t DATA_TRX_ID_LEN = 6;
    -
    -/** Rollback data pointer: 7 bytes */
    -constexpr size_t DATA_ROLL_PTR = 2;
    -
    -/** Rollback data pointer type size in bytes. */
    -constexpr size_t DATA_ROLL_PTR_LEN = 7;
    + 聊聊 Sharding-Jdbc 分库分表下的分页方案 + /2022/01/09/%E8%81%8A%E8%81%8A-Sharding-Jdbc-%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E4%B8%8B%E7%9A%84%E5%88%86%E9%A1%B5%E6%96%B9%E6%A1%88/ + 前面在聊 Sharding-Jdbc 的时候看到了一篇文章,关于一个分页的查询,一直比较直接的想法就是分库分表下的分页是非常不合理的,一般我们的实操方案都是分表加上 ES 搜索做分页,或者通过合表读写分离的方案,因为对于 sharding-jdbc 如果没有带分表键,查询基本都是只能在所有分表都执行一遍,然后再加上分页,基本上是分页越大后续的查询越耗资源,但是仔细的去想这个细节还是这次,就简单说说
    首先就是我的分表结构

    +
    CREATE TABLE `student_time_0` (
    +  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    +  `user_id` int(11) NOT NULL,
    +  `name` varchar(200) COLLATE utf8_bin DEFAULT NULL,
    +  `age` tinyint(3) unsigned DEFAULT NULL,
    +  `create_time` bigint(20) DEFAULT NULL,
    +  PRIMARY KEY (`id`)
    +) ENGINE=InnoDB AUTO_INCREMENT=674 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    +

    有这样的三个表,student_time_0, student_time_1, student_time_2, 以 user_id 作为分表键,根据表数量取模作为分表依据
    这里先构造点数据,

    +
    insert into student_time (`name`, `user_id`, `age`, `create_time`) values (?, ?, ?, ?)
    +

    主要是为了保证 create_time 唯一比较好说明问题,

    +
    int i = 0;
    +try (
    +        Connection conn = dataSource.getConnection();
    +        PreparedStatement ps = conn.prepareStatement(insertSql)) {
    +    do {
    +        ps.setString(1, localName + new Random().nextInt(100));
    +        ps.setLong(2, 10086L + (new Random().nextInt(100)));
    +        ps.setInt(3, 18);
    +        ps.setLong(4, new Date().getTime());
     
    -

    一个是 DATA_ROW_ID,这个是在数据没指定主键的时候会生成一个隐藏的,如果用户有指定主键就是主键了

    -

    一个是 DATA_TRX_ID,这个表示这条记录的事务 ID

    -

    还有一个是 DATA_ROLL_PTR 指向回滚段的指针

    -

    指向的回滚段其实就是我们常说的 undo log,这里面的具体结构就是个链表,在 mvcc 里会使用到这个,还有就是这个 DATA_TRX_ID,每条记录都记录了这个事务 ID,表示的是这条记录的当前值是被哪个事务修改的,下面就扯回事务了,我们知道 Read Uncommitted, 其实用不到隔离,直接读取当前值就好了,到了 Read Committed 级别,我们要让事务读取到提交过的值,mysql 使用了一个叫 read view 的玩意,它里面有这些值是我们需要注意的,

    -

    m_low_limit_id, 这个是 read view 创建时最大的活跃事务 id

    -

    m_up_limit_id, 这个是 read view 创建时最小的活跃事务 id

    -

    m_ids, 这个是 read view 创建时所有的活跃事务 id 数组

    -

    m_creator_trx_id 这个是当前记录的创建事务 id

    -

    判断事务的可见性主要的逻辑是这样,

    -
      -
    1. 当记录的事务 id 小于最小活跃事务 id,说明是可见的,
    2. -
    3. 如果记录的事务 id 等于当前事务 id,说明是自己的更改,可见
    4. -
    5. 如果记录的事务 id 大于最大的活跃事务 id, 不可见
    6. -
    7. 如果记录的事务 id 介于 m_low_limit_idm_up_limit_id 之间,则要判断它是否在 m_ids 中,如果在,不可见,如果不在,表示已提交,可见
      具体的代码捞一下看看
      /** Check whether the changes by id are visible.
      -  @param[in]	id	transaction id to check against the view
      -  @param[in]	name	table name
      -  @return whether the view sees the modifications of id. */
      -  bool changes_visible(trx_id_t id, const table_name_t &name) const
      -      MY_ATTRIBUTE((warn_unused_result)) {
      -    ut_ad(id > 0);
       
      -    if (id < m_up_limit_id || id == m_creator_trx_id) {
      -      return (true);
      -    }
      +        int result = ps.executeUpdate();
      +        LOGGER.info("current execute result: {}", result);
      +        Thread.sleep(new Random().nextInt(100));
      +        i++;
      +    } while (i <= 2000);
      +

      三个表的数据分别是 673,678,650,说明符合预期了,各个表数据不一样,接下来比如我们想要做一个这样的分页查询

      +
      select * from student_time ORDER BY create_time ASC limit 1000, 5;
      +

      student_time 对于我们使用的 sharding-jdbc 来说当然是逻辑表,首先从一无所知去想这个查询如果我们自己来处理应该是怎么做,
      首先是不是可以每个表都从 333 开始取 5 条数据,类似于下面的查询,然后进行 15 条的合并重排序获取前面的 5 条

      +
      select * from student_time_0 ORDER BY create_time ASC limit 333, 5;
      +select * from student_time_1 ORDER BY create_time ASC limit 333, 5;
      +select * from student_time_2 ORDER BY create_time ASC limit 333, 5;
      +

      忽略前面 limit 差的 1,这个结果除非三个表的分布是绝对的均匀,否则结果肯定会出现一定的偏差,以为每个表的 333 这个位置对于其他表来说都不一定是一样的,这样对于最后整体的结果,就会出现偏差
      因为一直在纠结怎么让这个更直观的表现出来,所以尝试画了个图

      黑色的框代表我从每个表里按排序从 334 到 338 的 5 条数据, 他们在每个表里都是代表了各自正确的排序值,但是对于我们想要的其实是合表后的 1001,1005 这五条,然后我们假设总的排序值位于前 1000 的分布是第 0 个表是 320 条,第 1 个表是 340 条,第 2 个表是 340 条,那么可以明显地看出来我这么查的结果简单合并肯定是不对的。
      那么 sharding-jdbc 是如何保证这个结果的呢,其实就是我在每个表里都查分页偏移量和分页大小那么多的数据,在我这个例子里就是对于 0,1,2 三个分表每个都查 1005 条数据,即使我的数据不平衡到最极端的情况,前 1005 条数据都出在某个分表中,也可以正确获得最后的结果,但是明显的问题就是大分页,数据较多,就会导致非常大的问题,即使如 sharding-jdbc 对于合并排序的优化做得比较好,也还是需要传输那么大量的数据,并且查询也耗时,那么有没有解决方案呢,应该说有两个,或者说主要是想讲后者
      第一个办法是像这种查询,如果业务上不需要进行跳页,而是只给下一页,那么我们就能把前一次的最大偏移量的 create_time 记录下来,下一页就可以拿着这个偏移量进行查询,这个比较简单易懂,就不多说了
      第二个办法是看的58 沈剑的一篇文章,尝试理解讲述一下,
      这个办法的第一步跟前面那个错误的方法或者说不准确的方法一样,先是将分页偏移量平均后在三个表里进行查询

      +
      t0
      +334 10158 nick95  18  1641548941767
      +335 10098 nick11  18  1641548941879
      +336 10167 nick51  18  1641548942089
      +337 10167 nick3 18  1641548942119
      +338 10170 nick57  18  1641548942169
       
      -    check_trx_id_sanity(id, name);
       
      -    if (id >= m_low_limit_id) {
      -      return (false);
      +t1
      +334 10105 nick98  18  1641548939071   最小
      +335 10174 nick94  18  1641548939377
      +336 10129 nick85  18  1641548939442
      +337 10141 nick84  18  1641548939480
      +338 10096 nick74  18  1641548939668
       
      -    } else if (m_ids.empty()) {
      -      return (true);
      -    }
      +t2
      +334 10184 nick11  18  1641548945075
      +335 10109 nick93  18  1641548945382
      +336 10181 nick41  18  1641548945583
      +337 10130 nick80  18  1641548945993
      +338 10184 nick19  18  1641548946294  最大
      +

      然后要做什么呢,其实目标比较明白,因为前面那种方法其实就是我知道了前一页的偏移量,所以可以直接当做条件来进行查询,那这里我也想着拿到这个条件,所以我将第一遍查出来的最小的 create_time 和最大的 create_time 找出来,然后再去三个表里查询,其实主要是最小值,因为我拿着最小值去查以后我就能知道这个最小值在每个表里处在什么位置,

      +
      t0
      +322 10161 nick81  18  1641548939284
      +323 10113 nick16  18  1641548939393
      +324 10110 nick56  18  1641548939577
      +325 10116 nick69  18  1641548939588
      +326 10173 nick51  18  1641548939646
       
      -    const ids_t::value_type *p = m_ids.data();
      +t1
      +334 10105 nick98  18  1641548939071
      +335 10174 nick94  18  1641548939377
      +336 10129 nick85  18  1641548939442
      +337 10141 nick84  18  1641548939480
      +338 10096 nick74  18  1641548939668
       
      -    return (!std::binary_search(p, p + m_ids.size(), id));
      -  }
      -剩下来一点是啥呢,就是 Read CommittedRepeated Read 也不一样,那前面说的 read view 都能支持吗,又是怎么支持呢,假如这个 read view 是在事务一开始就创建,那好像能支持的只是 RR 事务隔离级别,其实呢,这是通过创建 read view的时机,对于 RR 级别,就是在事务的第一个 select 语句是创建,对于 RC 级别,是在每个 select 语句执行前都是创建一次,那样就可以保证能读到所有已提交的数据
    8. -
    +t2 +297 10136 nick28 18 1641548939161 +298 10142 nick68 18 1641548939177 +299 10124 nick41 18 1641548939237 +300 10148 nick87 18 1641548939510 +301 10169 nick23 18 1641548939715
    +

    我只贴了前五条数据,为了方便知道偏移量,每个分表都使用了自增主键,我们可以看到前一次查询的最小值分别在其他两个表里的位置分别是 322-1 和 297-1,那么对于总体来说这个时间应该是在 322 - 1 + 333 + 297 - 1 = 951,那这样子我只要对后面的数据最多每个表查 1000 - 951 + 5 = 54 条数据再进行合并排序就可以获得最终正确的结果。
    这个就是传说中的二次查询法。

    ]]>
    - Mysql - C - 数据结构 - 源码 - Mysql + Java - mysql - 数据结构 - 源码 - mvcc - read view + Java + Sharding-Jdbc
    @@ -13564,93 +13806,56 @@ constexpr size_t DATA_ROLL_PTR_LEN - 聊聊 Sharding-Jdbc 分库分表下的分页方案 - /2022/01/09/%E8%81%8A%E8%81%8A-Sharding-Jdbc-%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E4%B8%8B%E7%9A%84%E5%88%86%E9%A1%B5%E6%96%B9%E6%A1%88/ - 前面在聊 Sharding-Jdbc 的时候看到了一篇文章,关于一个分页的查询,一直比较直接的想法就是分库分表下的分页是非常不合理的,一般我们的实操方案都是分表加上 ES 搜索做分页,或者通过合表读写分离的方案,因为对于 sharding-jdbc 如果没有带分表键,查询基本都是只能在所有分表都执行一遍,然后再加上分页,基本上是分页越大后续的查询越耗资源,但是仔细的去想这个细节还是这次,就简单说说
    首先就是我的分表结构

    -
    CREATE TABLE `student_time_0` (
    -  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    -  `user_id` int(11) NOT NULL,
    -  `name` varchar(200) COLLATE utf8_bin DEFAULT NULL,
    -  `age` tinyint(3) unsigned DEFAULT NULL,
    -  `create_time` bigint(20) DEFAULT NULL,
    -  PRIMARY KEY (`id`)
    -) ENGINE=InnoDB AUTO_INCREMENT=674 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    -

    有这样的三个表,student_time_0, student_time_1, student_time_2, 以 user_id 作为分表键,根据表数量取模作为分表依据
    这里先构造点数据,

    -
    insert into student_time (`name`, `user_id`, `age`, `create_time`) values (?, ?, ?, ?)
    -

    主要是为了保证 create_time 唯一比较好说明问题,

    -
    int i = 0;
    -try (
    -        Connection conn = dataSource.getConnection();
    -        PreparedStatement ps = conn.prepareStatement(insertSql)) {
    -    do {
    -        ps.setString(1, localName + new Random().nextInt(100));
    -        ps.setLong(2, 10086L + (new Random().nextInt(100)));
    -        ps.setInt(3, 18);
    -        ps.setLong(4, new Date().getTime());
    -
    -
    -        int result = ps.executeUpdate();
    -        LOGGER.info("current execute result: {}", result);
    -        Thread.sleep(new Random().nextInt(100));
    -        i++;
    -    } while (i <= 2000);
    -

    三个表的数据分别是 673,678,650,说明符合预期了,各个表数据不一样,接下来比如我们想要做一个这样的分页查询

    -
    select * from student_time ORDER BY create_time ASC limit 1000, 5;
    -

    student_time 对于我们使用的 sharding-jdbc 来说当然是逻辑表,首先从一无所知去想这个查询如果我们自己来处理应该是怎么做,
    首先是不是可以每个表都从 333 开始取 5 条数据,类似于下面的查询,然后进行 15 条的合并重排序获取前面的 5 条

    -
    select * from student_time_0 ORDER BY create_time ASC limit 333, 5;
    -select * from student_time_1 ORDER BY create_time ASC limit 333, 5;
    -select * from student_time_2 ORDER BY create_time ASC limit 333, 5;
    -

    忽略前面 limit 差的 1,这个结果除非三个表的分布是绝对的均匀,否则结果肯定会出现一定的偏差,以为每个表的 333 这个位置对于其他表来说都不一定是一样的,这样对于最后整体的结果,就会出现偏差
    因为一直在纠结怎么让这个更直观的表现出来,所以尝试画了个图

    黑色的框代表我从每个表里按排序从 334 到 338 的 5 条数据, 他们在每个表里都是代表了各自正确的排序值,但是对于我们想要的其实是合表后的 1001,1005 这五条,然后我们假设总的排序值位于前 1000 的分布是第 0 个表是 320 条,第 1 个表是 340 条,第 2 个表是 340 条,那么可以明显地看出来我这么查的结果简单合并肯定是不对的。
    那么 sharding-jdbc 是如何保证这个结果的呢,其实就是我在每个表里都查分页偏移量和分页大小那么多的数据,在我这个例子里就是对于 0,1,2 三个分表每个都查 1005 条数据,即使我的数据不平衡到最极端的情况,前 1005 条数据都出在某个分表中,也可以正确获得最后的结果,但是明显的问题就是大分页,数据较多,就会导致非常大的问题,即使如 sharding-jdbc 对于合并排序的优化做得比较好,也还是需要传输那么大量的数据,并且查询也耗时,那么有没有解决方案呢,应该说有两个,或者说主要是想讲后者
    第一个办法是像这种查询,如果业务上不需要进行跳页,而是只给下一页,那么我们就能把前一次的最大偏移量的 create_time 记录下来,下一页就可以拿着这个偏移量进行查询,这个比较简单易懂,就不多说了
    第二个办法是看的58 沈剑的一篇文章,尝试理解讲述一下,
    这个办法的第一步跟前面那个错误的方法或者说不准确的方法一样,先是将分页偏移量平均后在三个表里进行查询

    -
    t0
    -334 10158 nick95  18  1641548941767
    -335 10098 nick11  18  1641548941879
    -336 10167 nick51  18  1641548942089
    -337 10167 nick3 18  1641548942119
    -338 10170 nick57  18  1641548942169
    -
    -
    -t1
    -334 10105 nick98  18  1641548939071   最小
    -335 10174 nick94  18  1641548939377
    -336 10129 nick85  18  1641548939442
    -337 10141 nick84  18  1641548939480
    -338 10096 nick74  18  1641548939668
    -
    -t2
    -334 10184 nick11  18  1641548945075
    -335 10109 nick93  18  1641548945382
    -336 10181 nick41  18  1641548945583
    -337 10130 nick80  18  1641548945993
    -338 10184 nick19  18  1641548946294  最大
    -

    然后要做什么呢,其实目标比较明白,因为前面那种方法其实就是我知道了前一页的偏移量,所以可以直接当做条件来进行查询,那这里我也想着拿到这个条件,所以我将第一遍查出来的最小的 create_time 和最大的 create_time 找出来,然后再去三个表里查询,其实主要是最小值,因为我拿着最小值去查以后我就能知道这个最小值在每个表里处在什么位置,

    -
    t0
    -322 10161 nick81  18  1641548939284
    -323 10113 nick16  18  1641548939393
    -324 10110 nick56  18  1641548939577
    -325 10116 nick69  18  1641548939588
    -326 10173 nick51  18  1641548939646
    +    聊聊一次 brew update 引发的血案
    +    /2020/06/13/%E8%81%8A%E8%81%8A%E4%B8%80%E6%AC%A1-brew-update-%E5%BC%95%E5%8F%91%E7%9A%84%E8%A1%80%E6%A1%88/
    +    熟悉我的人(谁熟悉你啊🙄)知道我以前写过 PHP,虽然现在在工作中没用到了,但是自己的一些小工具还是会用 PHP 来写,但是在 Mac 碰到了一个环境相关的问题,因为我也是个更新狂魔,用了 brew 之后因为 gfw 的原因,如果长时间不更新,有时候要装一个用它装一个软件的话,前置的更新耗时就会让人非常头大,所以我基本会隔天 update 一下,但是这样会带来一个很心烦的问题,就是像这样,因为我是要用一个固定版本的 PHP,如果一直升需要一直配扩展啥的也很麻烦,如果一直升级 PHP 到最新版可能会比较少碰到这个问题

    +
    dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.64.dylib
    +

    这是什么鬼啊,然后我去这个目录下看了下,已经都是libicui18n.67.dylib了,而且它没有把原来的版本保留下来,首先这个是个叫 icu4c是啥玩意,谷歌了一下

    +
    +

    ICU4C是ICU在C/C++平台下的版本, ICU(International Component for Unicode)是基于”IBM公共许可证”的,与开源组织合作研究的, 用于支持软件国际化的开源项目。ICU4C提供了C/C++平台强大的国际化开发能力,软件开发者几乎可以使用ICU4C解决任何国际化的问题,根据各地的风俗和语言习惯,实现对数字、货币、时间、日期、和消息的格式化、解析,对字符串进行大小写转换、整理、搜索和排序等功能,必须一提的是,ICU4C提供了强大的BIDI算法,对阿拉伯语等BIDI语言提供了完善的支持。

    +
    +

    然后首先想到的解决方案就是能不能我使用brew install icu4c@64来重装下原来的版本,发现不行,并木有,之前的做法就只能是去网上把 64 的下载下来,然后放到这个目录,比较麻烦不智能,虽然没抱着希望在谷歌着,不过这次竟然给我找到了一个我认为非常 nice 的解决方案,因为是在 Stack Overflow 找到的,本着写给像我这样的小小白看的,那就稍微翻译一下
    第一步,我们到 brew的目录下

    +
    cd $(brew --prefix)/Homebrew/Library/Taps/homebrew/homebrew-core/Formula
    +

    这个可以理解为是 maven 的 pom 文件,不过有很多不同之处,使用ruby 写的,然后一个文件对应一个组件或者软件,那我们看下有个叫icu4c.rb的文件,
    第二步看看它的提交历史

    +
    git log --follow icu4c.rb
    +

    在 git log 的海洋中寻找,寻找它的(64版本)的身影

    第三步注意这三个红框,Stack Overflow 给出来的答案这一步是找到这个 commit id 直接切出一个新分支

    +
    git checkout -b icu4c-63 e7f0f10dc63b1dc1061d475f1a61d01b70ef2cb7
    +

    其实注意 commit id 旁边的红框,这个是有tag 的,可以直接

    +
    git checkout icu4c-64
    +

    PS: 因为我的问题是出在 64 的问题,Stack Overflow 回答的是 63 的,反正是一样的解决方法
    第四部,切回去之后我们就可以用 brew 提供的基于文件的安装命令来重新装上 64 版本

    +
    brew reinstall ./icu4c.rb
    +

    然后就是第五步,切换版本

    +
    brew switch icu4c 64.2
    +

    最后把分支切回来

    +
    git checkout master
    +

    是不是感觉很厉害的解决方法,大佬还提供了一个更牛的,直接写个 zsh 方法

    +
    # zsh
    +function hiicu64() {
    +  local last_dir=$(pwd)
     
    -t1
    -334 10105 nick98  18  1641548939071
    -335 10174 nick94  18  1641548939377
    -336 10129 nick85  18  1641548939442
    -337 10141 nick84  18  1641548939480
    -338 10096 nick74  18  1641548939668
    +  cd $(brew --prefix)/Homebrew/Library/Taps/homebrew/homebrew-core/Formula
    +  git checkout icu4c-4
    +  brew reinstall ./icu4c.rb
    +  brew switch icu4c 64.2
    +  git checkout master
     
    -t2
    -297 10136 nick28  18  1641548939161
    -298 10142 nick68  18  1641548939177
    -299 10124 nick41  18  1641548939237
    -300 10148 nick87  18  1641548939510
    -301 10169 nick23  18  1641548939715
    -

    我只贴了前五条数据,为了方便知道偏移量,每个分表都使用了自增主键,我们可以看到前一次查询的最小值分别在其他两个表里的位置分别是 322-1 和 297-1,那么对于总体来说这个时间应该是在 322 - 1 + 333 + 297 - 1 = 951,那这样子我只要对后面的数据最多每个表查 1000 - 951 + 5 = 54 条数据再进行合并排序就可以获得最终正确的结果。
    这个就是传说中的二次查询法。

    + cd $last_dir +}
    +

    对应自己的版本改改版本号就可以了,非常好用。

    ]]>
    - Java + Mac + PHP + Homebrew + PHP + icu4c - Java - Sharding-Jdbc + Mac + PHP + Homebrew + icu4c + zsh
    @@ -13772,6 +13977,23 @@ t2 WeakReference + + 聊聊如何识别和意识到日常生活中的各类危险 + /2021/06/06/%E8%81%8A%E8%81%8A%E5%A6%82%E4%BD%95%E8%AF%86%E5%88%AB%E5%92%8C%E6%84%8F%E8%AF%86%E5%88%B0%E6%97%A5%E5%B8%B8%E7%94%9F%E6%B4%BB%E4%B8%AD%E7%9A%84%E5%90%84%E7%B1%BB%E5%8D%B1%E9%99%A9/ + 这篇博客的灵感又是来自于我从绍兴来杭州的路上,在我们进站以后上电梯快到的时候,突然前面不动了,右边我能看到的是有个人的行李箱一时拎不起来,另一边后面看到其实是个小孩子在那哭闹,一位妈妈就在那停着安抚或者可能有点手足无措,其实这一点应该是在几年前慢慢意识到是个非常危险的场景,特别是像绍兴北站这样上去站台是非常长的电梯,因为最近扩建改造,车次减少了很多,所以每一班都有很多人,检票上站台的电梯都是满员运转,试想这种情况,如果刚才那位妈妈再多停留一点时间,很可能就会出现后面的人上不来被挤下去,再严重点就是踩踏事件,但是这类情况很少人真的意识到,非常明显的例子就是很多人拿着比较大比较重的行李箱,不走垂梯,并且在快到的时候没有提前准备好,有可能在玩手机啥的,如果提不动,后面又是挤满人了,就很可能出现前面说的这种情况,并且其实这种是非紧急情况,大多数人都没有心理准备,一旦发生后果可能就会很严重,例如火灾地震疏散大部分人或者说负责引导的都是指示要有序撤离,防止踩踏,但是普通坐个扶梯,一般都不会有这个意识,但是如果这个时间比较长,出现了人员站不住往后倒了,真的会很严重。所以如果自己是带娃的或者带了很重的行李箱的,请提前做好准备,看到前面有人带的,最好也保持一定距离。
    还有比如日常走路,旁边有车子停着的情况,比较基本的看车灯有没有亮着,亮着的是否是倒车灯,这种应该特别注意远离,至少保持距离,不能挨着走,很多人特别是一些老年人,在一些人比较多的路上,往往完全无视旁边这些车的状态,我走我的路,谁敢阻拦我,管他车在那动不动,其实真的非常危险,车子本身有视线死角,再加上司机的驾驶习惯和状态,想去送死跟碰瓷的除外,还有就是有一些车会比较特殊,车子发动着,但是没灯,可能是车子灯坏了或者司机通过什么方式关了灯,这种比较难避开,不过如果车子打着了,一般会有比较大的热量散发,车子刚灭了也会有,反正能远离点尽量远离,从轿车的车前面走过挨着走要比从屁股后面挨着走稍微安全一些,但也最好不要挨着车走。
    最后一点其实是我觉得是我自己比较怕死,一般对来向的车或者从侧面出来的车会做更长的预判距离,特别是电瓶车,一般是不让人的,像送外卖的小哥,的确他们不太容易,但是真的很危险啊,基本就生死看刹车,能刹住就赚了,刹不住就看身子骨扛不扛撞了,只是这里要多说点又要谈到资本的趋利性了,总是想法设法的压榨以获取更多的利益,也不扯远了,能远离就远离吧。

    +]]>
    + + 生活 + + + 生活 + 糟心事 + 扶梯 + 踩踏 + 安全 + 电瓶车 + +
    聊聊厦门旅游的好与不好 /2021/04/11/%E8%81%8A%E8%81%8A%E5%8E%A6%E9%97%A8%E6%97%85%E6%B8%B8%E7%9A%84%E5%A5%BD%E4%B8%8E%E4%B8%8D%E5%A5%BD/ @@ -13802,23 +14024,6 @@ t2 海蛎煎 - - 聊聊如何识别和意识到日常生活中的各类危险 - /2021/06/06/%E8%81%8A%E8%81%8A%E5%A6%82%E4%BD%95%E8%AF%86%E5%88%AB%E5%92%8C%E6%84%8F%E8%AF%86%E5%88%B0%E6%97%A5%E5%B8%B8%E7%94%9F%E6%B4%BB%E4%B8%AD%E7%9A%84%E5%90%84%E7%B1%BB%E5%8D%B1%E9%99%A9/ - 这篇博客的灵感又是来自于我从绍兴来杭州的路上,在我们进站以后上电梯快到的时候,突然前面不动了,右边我能看到的是有个人的行李箱一时拎不起来,另一边后面看到其实是个小孩子在那哭闹,一位妈妈就在那停着安抚或者可能有点手足无措,其实这一点应该是在几年前慢慢意识到是个非常危险的场景,特别是像绍兴北站这样上去站台是非常长的电梯,因为最近扩建改造,车次减少了很多,所以每一班都有很多人,检票上站台的电梯都是满员运转,试想这种情况,如果刚才那位妈妈再多停留一点时间,很可能就会出现后面的人上不来被挤下去,再严重点就是踩踏事件,但是这类情况很少人真的意识到,非常明显的例子就是很多人拿着比较大比较重的行李箱,不走垂梯,并且在快到的时候没有提前准备好,有可能在玩手机啥的,如果提不动,后面又是挤满人了,就很可能出现前面说的这种情况,并且其实这种是非紧急情况,大多数人都没有心理准备,一旦发生后果可能就会很严重,例如火灾地震疏散大部分人或者说负责引导的都是指示要有序撤离,防止踩踏,但是普通坐个扶梯,一般都不会有这个意识,但是如果这个时间比较长,出现了人员站不住往后倒了,真的会很严重。所以如果自己是带娃的或者带了很重的行李箱的,请提前做好准备,看到前面有人带的,最好也保持一定距离。
    还有比如日常走路,旁边有车子停着的情况,比较基本的看车灯有没有亮着,亮着的是否是倒车灯,这种应该特别注意远离,至少保持距离,不能挨着走,很多人特别是一些老年人,在一些人比较多的路上,往往完全无视旁边这些车的状态,我走我的路,谁敢阻拦我,管他车在那动不动,其实真的非常危险,车子本身有视线死角,再加上司机的驾驶习惯和状态,想去送死跟碰瓷的除外,还有就是有一些车会比较特殊,车子发动着,但是没灯,可能是车子灯坏了或者司机通过什么方式关了灯,这种比较难避开,不过如果车子打着了,一般会有比较大的热量散发,车子刚灭了也会有,反正能远离点尽量远离,从轿车的车前面走过挨着走要比从屁股后面挨着走稍微安全一些,但也最好不要挨着车走。
    最后一点其实是我觉得是我自己比较怕死,一般对来向的车或者从侧面出来的车会做更长的预判距离,特别是电瓶车,一般是不让人的,像送外卖的小哥,的确他们不太容易,但是真的很危险啊,基本就生死看刹车,能刹住就赚了,刹不住就看身子骨扛不扛撞了,只是这里要多说点又要谈到资本的趋利性了,总是想法设法的压榨以获取更多的利益,也不扯远了,能远离就远离吧。

    -]]>
    - - 生活 - - - 生活 - 糟心事 - 扶梯 - 踩踏 - 安全 - 电瓶车 - -
    聊聊我刚学会的应用诊断方法 /2020/05/22/%E8%81%8A%E8%81%8A%E6%88%91%E5%88%9A%E5%AD%A6%E4%BC%9A%E7%9A%84%E5%BA%94%E7%94%A8%E8%AF%8A%E6%96%AD%E6%96%B9%E6%B3%95/ @@ -13907,10 +14112,9 @@ t2 - 聊聊最近平淡的生活之《花束般的恋爱》观后感 - /2021/12/31/%E8%81%8A%E8%81%8A%E6%9C%80%E8%BF%91%E5%B9%B3%E6%B7%A1%E7%9A%84%E7%94%9F%E6%B4%BB%E4%B9%8B%E3%80%8A%E8%8A%B1%E6%9D%9F%E8%88%AC%E7%9A%84%E6%81%8B%E7%88%B1%E3%80%8B%E8%A7%82%E5%90%8E%E6%84%9F/ - 周末在领导的提议下看了豆瓣的年度榜单,本来感觉没啥心情看的,看到主演有有村架纯就觉得可以看一下,颜值即正义嘛,男主小麦跟女主小娟(后面简称小麦跟小娟)是两个在一次非常偶然的没赶上地铁末班车事件中相识,这里得说下日本这种通宵营业的店好像挺不错的,看着也挺正常,国内估计只有酒吧之类的可以。晚上去的地方是有点暗暗的,好像也有点类似酒吧,旁边有类似于 dj 那种,然后同桌的还有除了男女主的另外一对男女,也是因为没赶上地铁末班车的,但也是陌生人,然后小麦突然看到了有个非常有名的电影人,小娟竟然也认识,然后旁边那对完全不认识,还在那吹自己看过很多电影,比如《肖申克的救赎》,于是男女主都特别鄙夷地看着他们,然后他们又去了另一个有点像泡澡的地方席地而坐,他们发现了自己的鞋子都是一样的,然后在女的去上厕所的时候,小麦暗恋的学姐也来了,然后小麦就去跟学姐他们一起坐了,小娟回来后有点不开心就说去朋友家睡,幸好小麦看出来了(他竟然看出来了,本来以为应该是没填过恋爱很木讷的),就追出去,然后就去了小麦家,到了家小娟发现小麦家的书柜上的书简直就跟她自己家的一模一样,小麦还给小娟吹了头发,一起吃烤饭团,看电影,第二天送小娟上了公交,还约好了一起看木乃伊展,然而并没有交换联系方式,但是他们还是约上了一起看了木乃伊展,在餐馆就出现了片头那一幕的来源,因为餐馆他们想一起听歌,就用有线耳机一人一个耳朵听,但是旁边就有个大叔说“你们是不是不爱音乐,左右耳朵是不一样的,只有一起听才是真正的音乐”这样的话,然后的剧情有点跳,因为是指他们一直在这家餐馆吃饭,中间有他们一起出去玩情节穿插着,也是在这他们确立了关系,可以说主体就是体现了他们非常的合拍和默契,就像一些影评说的,这部电影是说如何跟百分百合拍的人分手,然后就是正常的恋爱开始啪啪啪,一直腻在床上,也没去就业说明会,后面也有讲了一点小麦带着小娟去认识他的朋友,也把小娟介绍给了他们认识,这里算是个小伏笔,后面他们分手也有这里的人的一些关系,接下去的剧情说实话我是不太喜欢的,如果一部八分的电影只是说恋爱被现实打败的话,我觉得在我这是不合格的,但是事实也是这样,小麦其实是有家里的资助,所以后面还是按自己的喜好给一些机构画点插画,小娟则要出去工作,因为小娟家庭观念也是要让她出去有正经工作,用脚指头想也能知道肯定不顺利,然后就是暂时在一家蛋糕店工作,小麦就每天去接小娟,日子过得甜甜蜜蜜,后面小娟在自己的努力下考了个什么资格证,去了一家医院还是什么做前台行政,这中间当然就有父母来见面吃饭了,他们在开始恋爱不久就同居合租了,然后小娟父母就是来说要让她有个正经工作,对男的说的话就是人生就是责任这类的话,而小麦爸爸算是个导火索,因为小麦家里是做烟花生意的,他爸让他就做烟花生意,因为要回老家,并且小麦也不想做,所以就拒绝了,然后他爸就说不给他每个月五万的资助,这也导致了小麦需要去找工作,这个过程也是很辛苦,本来想要年前找好工作,然后事与愿违,后面有一次小娟被同事吐槽怎么从来不去团建,于是她就去了(我以为会拒绝),正在团建的时候小麦给她电话,说找到工作了,是一个创业物流公司这种,这里剧情就是我觉得比较俗套的,小麦各种被虐,累成狗,但是就像小娟爸爸说的话,人生就是责任,所以一直在坚持,但是这样也导致了跟小娟的交流也越来越少,他们原来最爱的漫画,爱玩的游戏,也只剩小娟一个人看,一个人玩,而正是这个时候,小娟说她辞掉了工作,去做一个不是太靠谱的漫画改造的密室逃脱,然后这里其实有一点后面争议很大的,就是这个工作其实是前面小麦介绍给小娟的那些朋友中一个的女朋友介绍的,而在有个剧情就是小娟有一次在这个密室逃脱的老板怀里醒过来,是在 KTV 那样的场景里,这就有很多人觉得小娟是不是出轨了,我觉得其实不那么重要,因为这个离职的事情已经让一切矛盾都摆在眼前,小麦其实是接受这种需要承担责任的生活,也想着要跟小娟结婚,但是小娟似乎还是想要过着那样理想的生活,做自己想做的事情,看自己爱看的漫画,也要小麦能像以前那样一直那么默契的有着相同的爱好,这里的触发点其实还有个是那个小麦的朋友(也就是他女朋友介绍小娟那个不靠谱工作的)的葬礼上,小麦在参加完葬礼后有挺多想倾诉的,而小娟只是想睡了,这个让小麦第二天起来都不想理小娟,只是这里我不太理解,难道这点闹情绪都不能接受吗,所谓的合拍也只是毫无限制的情况下的合拍吧,真正的生活怎么可能如此理想呢,即使没有物质生活的压力,也会有其他的各种压力和限制,在这之后其实小麦想说的是小娟是不是没有想跟自己继续在一起的想法了,而小娟觉得都不说话了,还怎么结婚呢,后面其实导演搞了个小 trick,突然放了异常婚礼,但是不是男女主的,我并不觉得这个桥段很好,在婚礼里男女主都觉得自己想要跟对方说分手了,但是当他们去了最开始一直去的餐馆的时候,一个算是一个现实映照的就是他们一直坐的位子被占了,可能也是导演想通过这个来说明他们已经回不去了,在餐馆交谈的时候,小麦其实是说他们结婚吧,并没有想前面婚礼上预设地要分手,但是小娟放弃了,不想结婚,因为不想过那样的生活了,而小麦觉得可能生活就是那样,不可能一直保持刚恋爱时候的那种感觉,生活就是责任,人生就意味着责任。

    -

    我的一些观点也在前面说了,恋爱到婚姻,即使物质没问题,经济没问题,也会有各种各样的问题,需要一起去解决,因为结婚就意味着需要相互扶持,而不是各取所需,可能我的要求比较高,后面男女主在分手后还一起住了一段时间,我原来还在想会不会通过这个方式让他们继续去磨合同步,只是我失望了,最后给个打分可能是 5 到 6 分吧,勉强及格,好的影视剧应该源于生活高于生活,这一部可能还比不上生活。

    + 聊聊最近平淡的生活之看《神探狄仁杰》 + /2021/12/19/%E8%81%8A%E8%81%8A%E6%9C%80%E8%BF%91%E5%B9%B3%E6%B7%A1%E7%9A%84%E7%94%9F%E6%B4%BB%E4%B9%8B%E7%9C%8B%E3%80%8A%E7%A5%9E%E6%8E%A2%E7%8B%84%E4%BB%81%E6%9D%B0%E3%80%8B/ + 其实最近看的不止这一部,前面看了《继承者们》,《少年包青天》这些,就一起聊下,其中看《继承者们》算是个人比较喜欢,以前就有这种看剧的习惯,这个跟《一生一世》里任嘉伦说自己看《寻秦记》看了几十遍一样,我看喜欢的剧也基本上会看不止五遍,继承者们是有帅哥美女看,而且印象中剧情也挺甜的,一般情况下最好是已经有点遗忘剧情了,因为我个人觉得看剧分两种,无聊了又心情不太好,可以看些这类轻松又看过的剧,可以不完全专心地看剧,另外有心情专心看的时候,可以看一些需要思考,一些探案类的或者烧脑类。
    最近看了《神探狄仁杰》,因为跟前面看的《少年包青天》都是这类古装探案剧,正好有些感想,《少年包青天》算是儿时阴影,本来是不太会去看的,正好有一次有机会跟 LD 一起看了会就也觉得比较有意思就看了下去,不得不说,以前的这些剧还是很不错的,包括剧情和演员,第一部一共是 40 集,看的过程中也发现了大概是五个案子,平均八集一个案子,整体节奏还是比较慢的,但是基本每个案子其实都是构思得很巧妙,很久以前看过但是现在基本不太记得剧情了,每个案子在前面几集的时候基本都猜不到犯案逻辑,但是在看了狄仁杰之后,发现两部剧也有比较大的差别,少年包青天相对来说逻辑性会更强一些,个人主观觉得推理的严谨性更高,可能剧本打磨上更将就一下,而狄仁杰因为要提现他的个人强项,不比少年包青天中有公孙策一时瑜亮的情节,狄仁杰中分工明确,李元芳是个武力担当,曾泰是捧哏的,相对来说是狄仁杰在案子里从始至终地推进案情,有些甚至有些玄乎,会有一些跳脱跟不合理,有些像是狄仁杰的奇遇,不过这些想法是私人的观点,并不是想要评孰优孰劣;第二个感受是不知道是不是年代关系,特别是少年包青天,每个案件的大 boss 基本都不是个完全的坏人,甚至都是比较情有可原的可怜人,因为一些特殊原因,而好几个都是包拯身边的人,这一点其实跟狄仁杰里第一个使团惊魂案件比较类似,虎敬晖也是个人物形象比较丰满的角色,不是个标签化的淡薄形象,跟金木兰的感情和反叛行动在最后都说明的缘由,而且也有随着跟狄仁杰一起办案被其影响感化,最终为了救狄仁杰而被金木兰所杀,只是这样金木兰这个角色就会有些偏执和符号化,当然剧本肯定不是能面面俱到,这样的剧本已经比现在很多流量剧的好很多了。还想到了前阵子看的《指环王》中的白袍萨鲁曼在剧中也是个比较单薄的角色,这样的电影彪炳影史也没办法把个个人物都设计得完整有血有肉,或者说这本来也是应该有侧重点,当然其实我也不觉得指环王就是绝对的最好的,因为相对来说故事情节的复杂性等真的不如西游记,只是在 86 版之后的各种乱七八糟的翻牌和乱拍已经让这个真正的王者神话故事有点力不从心,这里边有部西游记后传是个人还比较喜欢的,虽然武打动作比较鬼畜,但是剧情基本是无敌的,在西游的架构上衍生出来这么完整丰富的故事,人物角色也都有各自的出彩点。
    说回狄仁杰,在这之前也看过徐克拍的几部狄仁杰的电影版,第一部刘德华拍得相对完成度更高,故事性也可圈可点,后面几部就是剧情拉胯,靠特效拉回来一点分,虽说这个也是所谓的狄仁杰宇宙的构思在里面但是现在来看基本是跟西游那些差不多,完全没有整体性可言,打一枪换一个地方,演员也没有延续性,剧情也是前后跳脱,没什么关联跟承上启下,导致质量层次不一,更不用谈什么狄仁杰宇宙了,不过这个事情也是难说,原因很多,现在资本都是更加趋利的,一些需要更长久时间才能有回报的投资是很难获得资本青睐,所以只能将重心投给选择一些流量明星,而本来应该将资源投给剧本打磨的基本就没了,再深入说也没意义了,社会现状就是这样。
    还有一点感想是,以前的剧里的拍摄环境还是比较惨的,看着一些房子,甚至皇宫都是比较破旧的,地面还是石板这种,想想以前的演员的环境再想想现在的,比如成龙说的,以前他拍剧就是啪摔了,问这条有没有过,过了就直接送医院,而不是现在可能手蹭破点皮就大叫,甚至还有饭圈这些破事。

    ]]>
    生活 @@ -13920,89 +14124,6 @@ t2 看剧
    - - G1收集器概述 - /2020/02/09/G1%E6%94%B6%E9%9B%86%E5%99%A8%E6%A6%82%E8%BF%B0/ - G1: The Garbage-First Collector, 垃圾回收优先的垃圾回收器,目标是用户多核 cpu 和大内存的机器,最大的特点就是可预测的停顿时间,官方给出的介绍是提供一个用户在大的堆内存情况下一个低延迟表现的解决方案,通常是 6GB 及以上的堆大小,有低于 0.5 秒稳定的可预测的停顿时间。

    -

    这里主要介绍这个比较新的垃圾回收器,在 G1 之前的垃圾回收器都是基于如下图的内存结构分布,有新生代,老年代和永久代(jdk8 之前),然后G1 往前的那些垃圾回收器都有个分代,比如 serial,parallel 等,一般有个应用的组合,最初的 serial 和 serial old,因为新生代和老年代的收集方式不太一样,新生代主要是标记复制,所以有 eden 跟两个 survival区,老年代一般用标记整理方式,而 G1 对这个不太一样。

    看一下 G1 的内存分布

    可以看到这有很大的不同,G1 通过将内存分成大小相等的 region,每个region是存在于一个连续的虚拟内存范围,对于某个 region 来说其角色是类似于原来的收集器的Eden、Survivor、Old Generation,这个具体在代码层面

    -
    // We encode the value of the heap region type so the generation can be
    - // determined quickly. The tag is split into two parts:
    - //
    - //   major type (young, old, humongous, archive)           : top N-1 bits
    - //   minor type (eden / survivor, starts / cont hum, etc.) : bottom 1 bit
    - //
    - // If there's need to increase the number of minor types in the
    - // future, we'll have to increase the size of the latter and hence
    - // decrease the size of the former.
    - //
    - // 00000 0 [ 0] Free
    - //
    - // 00001 0 [ 2] Young Mask
    - // 00001 0 [ 2] Eden
    - // 00001 1 [ 3] Survivor
    - //
    - // 00010 0 [ 4] Humongous Mask
    - // 00100 0 [ 8] Pinned Mask
    - // 00110 0 [12] Starts Humongous
    - // 00110 1 [13] Continues Humongous
    - //
    - // 01000 0 [16] Old Mask
    - //
    - // 10000 0 [32] Archive Mask
    - // 11100 0 [56] Open Archive
    - // 11100 1 [57] Closed Archive
    - //
    - typedef enum {
    -   FreeTag               = 0,
    -
    -   YoungMask             = 2,
    -   EdenTag               = YoungMask,
    -   SurvTag               = YoungMask + 1,
    -
    -   HumongousMask         = 4,
    -   PinnedMask            = 8,
    -   StartsHumongousTag    = HumongousMask | PinnedMask,
    -   ContinuesHumongousTag = HumongousMask | PinnedMask + 1,
    -
    -   OldMask               = 16,
    -   OldTag                = OldMask,
    -
    -   // Archive regions are regions with immutable content (i.e. not reclaimed, and
    -   // not allocated into during regular operation). They differ in the kind of references
    -   // allowed for the contained objects:
    -   // - Closed archive regions form a separate self-contained (closed) object graph
    -   // within the set of all of these regions. No references outside of closed
    -   // archive regions are allowed.
    -   // - Open archive regions have no restrictions on the references of their objects.
    -   // Objects within these regions are allowed to have references to objects
    -   // contained in any other kind of regions.
    -   ArchiveMask           = 32,
    -   OpenArchiveTag        = ArchiveMask | PinnedMask | OldMask,
    -   ClosedArchiveTag      = ArchiveMask | PinnedMask | OldMask + 1
    - } Tag;
    - -

    hotspot/share/gc/g1/heapRegionType.hpp

    -

    当执行垃圾收集时,G1以类似于CMS收集器的方式运行。 G1执行并发全局标记阶段,以确定整个堆中对象的存活性。标记阶段完成后,G1知道哪些region是基本空的。它首先收集这些region,通常会产生大量的可用空间。这就是为什么这种垃圾收集方法称为“垃圾优先”的原因。顾名思义,G1将其收集和压缩活动集中在可能充满可回收对象(即垃圾)的堆区域。 G1使用暂停预测模型来满足用户定义的暂停时间目标,并根据指定的暂停时间目标选择要收集的区域数。

    -

    由G1标识为可回收的区域是使用撤离的方式(Evacuation)。 G1将对象从堆的一个或多个区域复制到堆上的单个区域,并在此过程中压缩并释放内存。撤离是在多处理器上并行执行的,以减少暂停时间并增加吞吐量。因此,对于每次垃圾收集,G1都在用户定义的暂停时间内连续工作以减少碎片。这是优于前面两种方法的。 CMS(并发标记扫描)垃圾收集器不进行压缩。 ParallelOld垃圾回收仅执行整个堆压缩,这导致相当长的暂停时间。

    -

    需要重点注意的是,G1不是实时收集器。它很有可能达到设定的暂停时间目标,但并非绝对确定。 G1根据先前收集的数据,估算在用户指定的目标时间内可以收集多少个区域。因此,收集器具有收集区域成本的合理准确的模型,并且收集器使用此模型来确定要收集哪些和多少个区域,同时保持在暂停时间目标之内。

    -

    注意:G1同时具有并发(与应用程序线程一起运行,例如优化,标记,清理)和并行(多线程,例如stw)阶段。Full GC仍然是单线程的,但是如果正确调优,您的应用程序应该可以避免Full GC。

    -

    在前面那篇中在代码层面简单的了解了这个可预测时间的过程,这也是 G1 的一大特点。

    -]]>
    - - Java - JVM - GC - C++ - - - Java - JVM - C++ - G1 - GC - Garbage-First Collector - -
    聊聊最近平淡的生活之看看老剧 /2021/11/21/%E8%81%8A%E8%81%8A%E6%9C%80%E8%BF%91%E5%B9%B3%E6%B7%A1%E7%9A%84%E7%94%9F%E6%B4%BB%E4%B9%8B%E7%9C%8B%E7%9C%8B%E8%80%81%E5%89%A7/ @@ -14029,130 +14150,75 @@ t2

    感悟是,总觉得学计算机的就应该会装系统,会修电脑,之前亲戚还拿着一个完全开不起来的笔记本让我来修,这真的是,我说可以找官方维修的,结果我说我搞不定,她直接觉得是修不好了,直接电脑都懒得拿回去了,后面又一次反复解释了才明白,另外就是 360 全家桶,别说老电脑了,新机器都不太吃得消。

    ]]> - 生活 - - - 生活 - 装电脑 - 老电脑 - 360 全家桶 - 修电脑的 - -
    - - 聊聊这次换车牌及其他 - /2022/02/20/%E8%81%8A%E8%81%8A%E8%BF%99%E6%AC%A1%E6%8D%A2%E8%BD%A6%E7%89%8C%E5%8F%8A%E5%85%B6%E4%BB%96/ - 去年 8 月份运气比较好,摇到了车牌,本来其实应该很早就开始摇的,前面第一次换工作没注意社保断缴了一个月,也是大意失荆州,后面到了 17 年社保满两年了,好像只摇了一次,还是就没摇过,有点忘了,好像是什么原因导致那次也没摇成功,但是后面暂住证就取消了,需要居住证,居住证又要一年及以上的租房合同,并且那会买车以后也不怎么开,住的地方车位还好,但是公司车位一个月要两三千,甚至还是打车上下班比较实惠,所以也没放在心上,后面摇到房以后,也觉得应该准备起来车子,就开始办了居住证,居住证其实还可以用劳动合同,而且办起来也挺快,大概是三四月份开始摇,到 8 月份的某一天收到短信说摇到了,一开始还挺开心,不过心里抱着也不怎么开,也没怎么大放在心上,不过这里有一点就是我把那个照片直接发出去,上面有着我的身份证号,被 LD 说了一顿,以后也应该小心点,但是后面不知道是哪里看了下,说杭州上牌已经需要国六标准的车了,瞬间感觉是空欢喜了,可是有同事说是可以的,我就又打了官方的电话,结果说可以的,要先转籍,然后再做上牌。

    -

    转籍其实是很方便的,在交警 12123 App 上申请就行了,在转籍以后,需要去实地验车,验车的话,在支付宝-杭州交警生活号里进行预约,找就近的车管所就好,需要准备一些东西,首先是行驶证,机动车登记证书,身份证,居住证,还有车上需要准备的东西是要有三脚架和反光背心,反光背心是最近几个月开始要的,问过之前去验车的只需要三脚架就好了,预约好了的话建议是赶上班时间越早越好,不然过去排队时间要很久,而且人多了以后会很乱,各种插队,而且有很多都是汽车销售,一个销售带着一堆车,我们附近那个进去的小路没一会就堵满车,进去需要先排队,然后扫码,接着交资料,这两个都排着队,如果去晚了就要排很久的队,交完资料才是排队等验车,验车就是打开引擎盖,有人会帮忙拓印发动机车架号,然后验车的会各种检查一下,车里面,还有后备箱,建议车内整理干净点,后备箱不要放杂物,检验完了之后,需要把三脚架跟反光背心放在后备箱盖子上,人在旁边拍个照,然后需要把车牌遮住后再拍个车子的照片,再之后就是去把车牌卸了,这个多吐槽下,那边应该是本来那边师傅帮忙卸车牌,结果他就说是教我们拆,虽然也不算难,但是不排除师傅有在偷懒,完了之后就是把旧车牌交回去,然后需要在手机上(警察叔叔 App)提交各种资料,包括身份证,行驶证,机动车登记证书,提交了之后就等寄车牌过来了。

    -

    这里面缺失的一个环节就是选号了,选号杭州有两个方式,一种就是根据交管局定期发布的选号号段,可以自定义拼 20 个号,在手机上的交警 12123 App 上可以三个一组的形式提交,如果有没被选走的,就可以预选到这个了,但是这种就是也需要有一定策略,最新出的号段能选中的概率大一点,然后数字全是 8,6 这种的肯定会一早就被选走,然后如果跟我一样可以提前选下尾号,因为尾号数字影响限号,我比较有可能周五回家,所以得避开 5,0 的,第二种就是 50 选一跟以前新车选号一样,就不介绍了。第一种选中了以后可以在前面交还旧车牌的时候填上等着寄过来了,因为我是第一种选中的,第二种也可以在手机上选,也在可以在交还车牌的时候现场选。

    -

    总体过程其实是 LD 在各种查资料跟帮我跑来跑去,要不是 LD,估计在交管局那边我就懵逼了,各种插队,而且车子开着车子,也不能随便跑,所以建议办这个的时候有个人一起比较好。

    -]]>
    - - 生活 - - - 生活 - 换车牌 - -
    - - 聊聊那些加塞狗 - /2021/01/17/%E8%81%8A%E8%81%8A%E9%82%A3%E4%BA%9B%E5%8A%A0%E5%A1%9E%E7%8B%97/ - 今天真的是被气得不轻,情况是碰到一个有 70 多秒的直行红灯,然后直行就排了很长的队,但是左转车道没车,就有好几辆车占着左转车道,准备往直行车道插队加塞,一般这种加塞的,会挑个不太计较的,如果前面一辆不让的话就再等等,我因为赶着回家,就不想让,结果那辆车几次车头直接往里冲,当时怒气值基本已经蓄满了,我真的是分毫都不想让,如果路上都是让着这种人的,那么这种情况只会越来越严重,我理解的这种心态,就赌你怕麻烦,多一事不如少一事,结果就是每次都能顺利插队加塞,其实延伸到我们社会中的种种实质性的排队或者等同于排队的情况,都已经有这种惯有思维,一方面这种不符合规则,可能在严重程度上容易被很多人所忽视,基本上已经被很多人当成是“合理”行为,另一方面,对于这些“微小”的违规行为,本身管理层面也基本没有想要管的意思,就更多的成为了纵容这些行为的导火索,并且大多数人都是想着如果不让,发生点小剐小蹭的要浪费很多时间精力来处理,甚至会觉得会被别人觉得自己太小气等等,诸多内外成本结合起来,会真的去硬刚的可能少之又少了,这样也就让更多的人觉得这种行为是被默许的,再举个非常小的例子,以我们公司疫情期间的盒饭发放为例,有两个比较“有意思”的事情,第一个就是因为疫情,本来是让排队要间隔一米,但是可能除了我比较怕死会跟前面的人保持点距离基本没别人会不挨着前面的人,甚至我跟我前面的人保持点距离,后面的同学会推着我让我上去;第二个是关于拿饭,这么多人排着队拿饭,然后有部分同学,一个人拿好几份,帮组里其他人的都拿了,有些甚至一个人拿十份,假如这个盒饭发放是说明了可以按部门直接全领了那就没啥问题,但是当时的状况是个人排队领自己的那一份,如果一个同学直接帮着组里十几个人都拿了,后面排队的人是什么感受呢,甚至有些是看到队伍排长了,就找队伍里自己认识的比较靠前的人说你帮我也拿一份,其实作为我这个比较按规矩办事的“愣头青”来说,我是比较不能接受这两件小事里的行为的,再往下说可能就有点偏激了,先说到这~

    -]]>
    - - 生活 - 开车 - - - 生活 - 开车 - 加塞 - 糟心事 - 规则 - -
    - - 聊聊部分公交车的设计bug - /2021/12/05/%E8%81%8A%E8%81%8A%E9%83%A8%E5%88%86%E5%85%AC%E4%BA%A4%E8%BD%A6%E7%9A%84%E8%AE%BE%E8%AE%A1bug/ - 今天惯例坐公交回住的地方,不小心撞了头,原因是我们想坐倒数第二排,然后LD 走在我后面,我就走到最后一排中间等着,但是最后一排是高一截的,等 LD 坐进去以后,我就往前走,结果撞到了车顶的扶手杆子的一端,差点撞昏了去,这里我觉得其实杆子长度应该短一点,不然从最后一排出来,还是有比较大概率因为没注意看而撞到头,特别是没注意看的情况,发力其实会比较大,一头撞上就会像我这样,眼前一黑,又痛得要死。
    还有一点就是座位设计了,先来看个图

    图里大致画了两条线,因为可能是轮胎还是什么原因,后排中间会有那么大的突起,但是看两条红线可以发现,靠近过道的座位边缘跟地面突起的边缘不是一样宽的,这样导致的结果就是坐着的时候有一个脚没地儿搁,要不就得侧着斜着坐,或者就是一个脚悬空,短程的可能还好,路程远一点还是比较难受的,特别是像我现在这样,大腿外侧有点难受的情况,就会更难受。
    虽然说这两个点,基本是屁用没有,但是我也是在自己这个博客说说,也当是个树洞了。

    -]]>
    - - 生活 - - - 生活 - 公交 - 杭州 - -
    - - 记录下 Java Stream 的一些高效操作 - /2022/05/15/%E8%AE%B0%E5%BD%95%E4%B8%8B-Java-Lambda-%E7%9A%84%E4%B8%80%E4%BA%9B%E9%AB%98%E6%95%88%E6%93%8D%E4%BD%9C/ - 我们日常在代码里处理一些集合逻辑的时候用到 Stream 其实还挺多的,普通的取值过滤集合一般都是结合 ide 的提示就能搞定了,但是有些不太常用的就在这记录下,争取后面都更新记录下来。

    -

    自定义 distinctByKey 对结果进行去重

    stream 中自带的 distinct 只能对元素进行去重
    比如下面代码

    -
    public static void main(String[] args) {
    -        List<Integer> list = new ArrayList<>();
    -        list.add(1);
    -        list.add(1);
    -        list.add(2);
    -        list = list.stream().distinct().collect(Collectors.toList());
    -        System.out.println(list);
    -    }
    -

    结果就是去了重的

    -
    [1, 2]
    -

    但是当我的元素是个复杂对象,我想根据对象里的某个元素进行过滤的时候,就需要用到自定义的 distinctByKey 了,比如下面的想对 userId 进行去重

    -
    public static void main(String[] args) {
    -        List<StudentRecord> list = new ArrayList<>();
    -        StudentRecord s1 = new StudentRecord();
    -        s1.setUserId(11L);
    -        s1.setCourseId(100L);
    -        s1.setScore(100);
    -        list.add(s1);
    -        StudentRecord s2 = new StudentRecord();
    -        s2.setUserId(11L);
    -        s2.setCourseId(101L);
    -        s2.setScore(100);
    -        list.add(s2);
    -        StudentRecord s3 = new StudentRecord();
    -        s3.setUserId(12L);
    -        s3.setCourseId(100L);
    -        s3.setScore(100);
    -        list.add(s3);
    -        System.out.println(list.stream().distinct().collect(Collectors.toList()));
    -    }
    -    @Data
    -    static class StudentRecord {
    -        Long id;
    -        Long userId;
    -        Long courseId;
    -        Integer score;
    -    }
    -

    结果就是

    -
    [StudentRecord(id=null, userId=11, courseId=100, score=100), StudentRecord(id=null, userId=11, courseId=101, score=100), StudentRecord(id=null, userId=12, courseId=100, score=100)]
    -

    因为对象都不一样,所以就没法去重了,这里就需要用

    -
    public static <T> Predicate<T> distinctByKey(
    -            Function<? super T, ?> keyExtractor) {
    -
    -        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
    -        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    -    }
    -

    然后就可以用它来去重了

    -
    System.out.println(list.stream().filter(distinctByKey(StudentRecord::getUserId)).collect(Collectors.toList()));
    -

    看下结果

    -
    [StudentRecord(id=null, userId=11, courseId=100, score=100), StudentRecord(id=null, userId=12, courseId=100, score=100)]
    -

    但是说实在的这个功能感觉应该是 stream 默认给实现的

    -

    使用 java.util.stream.Collectors#groupingBy 对 list 进行分组

    这个使用场景还是蛮多的,上面的场景里比如我要对 userId 进行分组,就一行代码就解决了

    -
    System.out.println(list.stream().collect(Collectors.groupingBy(StudentRecord::getUserId)));
    -

    结果

    {11=[StudentRecord(id=null, userId=11, courseId=100, score=100), StudentRecord(id=null, userId=11, courseId=101, score=100)], 12=[StudentRecord(id=null, userId=12, courseId=100, score=100)]}
    -

    很方便的变成了以 userId 作为 key,以相同 userIdStudentRecordList 作为 valuemap 结构

    + 生活 + + + 生活 + 装电脑 + 老电脑 + 360 全家桶 + 修电脑的 + +
    + + 聊聊这次换车牌及其他 + /2022/02/20/%E8%81%8A%E8%81%8A%E8%BF%99%E6%AC%A1%E6%8D%A2%E8%BD%A6%E7%89%8C%E5%8F%8A%E5%85%B6%E4%BB%96/ + 去年 8 月份运气比较好,摇到了车牌,本来其实应该很早就开始摇的,前面第一次换工作没注意社保断缴了一个月,也是大意失荆州,后面到了 17 年社保满两年了,好像只摇了一次,还是就没摇过,有点忘了,好像是什么原因导致那次也没摇成功,但是后面暂住证就取消了,需要居住证,居住证又要一年及以上的租房合同,并且那会买车以后也不怎么开,住的地方车位还好,但是公司车位一个月要两三千,甚至还是打车上下班比较实惠,所以也没放在心上,后面摇到房以后,也觉得应该准备起来车子,就开始办了居住证,居住证其实还可以用劳动合同,而且办起来也挺快,大概是三四月份开始摇,到 8 月份的某一天收到短信说摇到了,一开始还挺开心,不过心里抱着也不怎么开,也没怎么大放在心上,不过这里有一点就是我把那个照片直接发出去,上面有着我的身份证号,被 LD 说了一顿,以后也应该小心点,但是后面不知道是哪里看了下,说杭州上牌已经需要国六标准的车了,瞬间感觉是空欢喜了,可是有同事说是可以的,我就又打了官方的电话,结果说可以的,要先转籍,然后再做上牌。

    +

    转籍其实是很方便的,在交警 12123 App 上申请就行了,在转籍以后,需要去实地验车,验车的话,在支付宝-杭州交警生活号里进行预约,找就近的车管所就好,需要准备一些东西,首先是行驶证,机动车登记证书,身份证,居住证,还有车上需要准备的东西是要有三脚架和反光背心,反光背心是最近几个月开始要的,问过之前去验车的只需要三脚架就好了,预约好了的话建议是赶上班时间越早越好,不然过去排队时间要很久,而且人多了以后会很乱,各种插队,而且有很多都是汽车销售,一个销售带着一堆车,我们附近那个进去的小路没一会就堵满车,进去需要先排队,然后扫码,接着交资料,这两个都排着队,如果去晚了就要排很久的队,交完资料才是排队等验车,验车就是打开引擎盖,有人会帮忙拓印发动机车架号,然后验车的会各种检查一下,车里面,还有后备箱,建议车内整理干净点,后备箱不要放杂物,检验完了之后,需要把三脚架跟反光背心放在后备箱盖子上,人在旁边拍个照,然后需要把车牌遮住后再拍个车子的照片,再之后就是去把车牌卸了,这个多吐槽下,那边应该是本来那边师傅帮忙卸车牌,结果他就说是教我们拆,虽然也不算难,但是不排除师傅有在偷懒,完了之后就是把旧车牌交回去,然后需要在手机上(警察叔叔 App)提交各种资料,包括身份证,行驶证,机动车登记证书,提交了之后就等寄车牌过来了。

    +

    这里面缺失的一个环节就是选号了,选号杭州有两个方式,一种就是根据交管局定期发布的选号号段,可以自定义拼 20 个号,在手机上的交警 12123 App 上可以三个一组的形式提交,如果有没被选走的,就可以预选到这个了,但是这种就是也需要有一定策略,最新出的号段能选中的概率大一点,然后数字全是 8,6 这种的肯定会一早就被选走,然后如果跟我一样可以提前选下尾号,因为尾号数字影响限号,我比较有可能周五回家,所以得避开 5,0 的,第二种就是 50 选一跟以前新车选号一样,就不介绍了。第一种选中了以后可以在前面交还旧车牌的时候填上等着寄过来了,因为我是第一种选中的,第二种也可以在手机上选,也在可以在交还车牌的时候现场选。

    +

    总体过程其实是 LD 在各种查资料跟帮我跑来跑去,要不是 LD,估计在交管局那边我就懵逼了,各种插队,而且车子开着车子,也不能随便跑,所以建议办这个的时候有个人一起比较好。

    ]]>
    - java + 生活 - java - stream + 生活 + 换车牌 + +
    + + 聊聊那些加塞狗 + /2021/01/17/%E8%81%8A%E8%81%8A%E9%82%A3%E4%BA%9B%E5%8A%A0%E5%A1%9E%E7%8B%97/ + 今天真的是被气得不轻,情况是碰到一个有 70 多秒的直行红灯,然后直行就排了很长的队,但是左转车道没车,就有好几辆车占着左转车道,准备往直行车道插队加塞,一般这种加塞的,会挑个不太计较的,如果前面一辆不让的话就再等等,我因为赶着回家,就不想让,结果那辆车几次车头直接往里冲,当时怒气值基本已经蓄满了,我真的是分毫都不想让,如果路上都是让着这种人的,那么这种情况只会越来越严重,我理解的这种心态,就赌你怕麻烦,多一事不如少一事,结果就是每次都能顺利插队加塞,其实延伸到我们社会中的种种实质性的排队或者等同于排队的情况,都已经有这种惯有思维,一方面这种不符合规则,可能在严重程度上容易被很多人所忽视,基本上已经被很多人当成是“合理”行为,另一方面,对于这些“微小”的违规行为,本身管理层面也基本没有想要管的意思,就更多的成为了纵容这些行为的导火索,并且大多数人都是想着如果不让,发生点小剐小蹭的要浪费很多时间精力来处理,甚至会觉得会被别人觉得自己太小气等等,诸多内外成本结合起来,会真的去硬刚的可能少之又少了,这样也就让更多的人觉得这种行为是被默许的,再举个非常小的例子,以我们公司疫情期间的盒饭发放为例,有两个比较“有意思”的事情,第一个就是因为疫情,本来是让排队要间隔一米,但是可能除了我比较怕死会跟前面的人保持点距离基本没别人会不挨着前面的人,甚至我跟我前面的人保持点距离,后面的同学会推着我让我上去;第二个是关于拿饭,这么多人排着队拿饭,然后有部分同学,一个人拿好几份,帮组里其他人的都拿了,有些甚至一个人拿十份,假如这个盒饭发放是说明了可以按部门直接全领了那就没啥问题,但是当时的状况是个人排队领自己的那一份,如果一个同学直接帮着组里十几个人都拿了,后面排队的人是什么感受呢,甚至有些是看到队伍排长了,就找队伍里自己认识的比较靠前的人说你帮我也拿一份,其实作为我这个比较按规矩办事的“愣头青”来说,我是比较不能接受这两件小事里的行为的,再往下说可能就有点偏激了,先说到这~

    +]]>
    + + 生活 + 开车 + + + 生活 + 开车 + 加塞 + 糟心事 + 规则 + +
    + + 聊聊部分公交车的设计bug + /2021/12/05/%E8%81%8A%E8%81%8A%E9%83%A8%E5%88%86%E5%85%AC%E4%BA%A4%E8%BD%A6%E7%9A%84%E8%AE%BE%E8%AE%A1bug/ + 今天惯例坐公交回住的地方,不小心撞了头,原因是我们想坐倒数第二排,然后LD 走在我后面,我就走到最后一排中间等着,但是最后一排是高一截的,等 LD 坐进去以后,我就往前走,结果撞到了车顶的扶手杆子的一端,差点撞昏了去,这里我觉得其实杆子长度应该短一点,不然从最后一排出来,还是有比较大概率因为没注意看而撞到头,特别是没注意看的情况,发力其实会比较大,一头撞上就会像我这样,眼前一黑,又痛得要死。
    还有一点就是座位设计了,先来看个图

    图里大致画了两条线,因为可能是轮胎还是什么原因,后排中间会有那么大的突起,但是看两条红线可以发现,靠近过道的座位边缘跟地面突起的边缘不是一样宽的,这样导致的结果就是坐着的时候有一个脚没地儿搁,要不就得侧着斜着坐,或者就是一个脚悬空,短程的可能还好,路程远一点还是比较难受的,特别是像我现在这样,大腿外侧有点难受的情况,就会更难受。
    虽然说这两个点,基本是屁用没有,但是我也是在自己这个博客说说,也当是个树洞了。

    +]]>
    + + 生活 + + + 生活 + 公交 + 杭州 + +
    + + 聊聊最近平淡的生活之《花束般的恋爱》观后感 + /2021/12/31/%E8%81%8A%E8%81%8A%E6%9C%80%E8%BF%91%E5%B9%B3%E6%B7%A1%E7%9A%84%E7%94%9F%E6%B4%BB%E4%B9%8B%E3%80%8A%E8%8A%B1%E6%9D%9F%E8%88%AC%E7%9A%84%E6%81%8B%E7%88%B1%E3%80%8B%E8%A7%82%E5%90%8E%E6%84%9F/ + 周末在领导的提议下看了豆瓣的年度榜单,本来感觉没啥心情看的,看到主演有有村架纯就觉得可以看一下,颜值即正义嘛,男主小麦跟女主小娟(后面简称小麦跟小娟)是两个在一次非常偶然的没赶上地铁末班车事件中相识,这里得说下日本这种通宵营业的店好像挺不错的,看着也挺正常,国内估计只有酒吧之类的可以。晚上去的地方是有点暗暗的,好像也有点类似酒吧,旁边有类似于 dj 那种,然后同桌的还有除了男女主的另外一对男女,也是因为没赶上地铁末班车的,但也是陌生人,然后小麦突然看到了有个非常有名的电影人,小娟竟然也认识,然后旁边那对完全不认识,还在那吹自己看过很多电影,比如《肖申克的救赎》,于是男女主都特别鄙夷地看着他们,然后他们又去了另一个有点像泡澡的地方席地而坐,他们发现了自己的鞋子都是一样的,然后在女的去上厕所的时候,小麦暗恋的学姐也来了,然后小麦就去跟学姐他们一起坐了,小娟回来后有点不开心就说去朋友家睡,幸好小麦看出来了(他竟然看出来了,本来以为应该是没填过恋爱很木讷的),就追出去,然后就去了小麦家,到了家小娟发现小麦家的书柜上的书简直就跟她自己家的一模一样,小麦还给小娟吹了头发,一起吃烤饭团,看电影,第二天送小娟上了公交,还约好了一起看木乃伊展,然而并没有交换联系方式,但是他们还是约上了一起看了木乃伊展,在餐馆就出现了片头那一幕的来源,因为餐馆他们想一起听歌,就用有线耳机一人一个耳朵听,但是旁边就有个大叔说“你们是不是不爱音乐,左右耳朵是不一样的,只有一起听才是真正的音乐”这样的话,然后的剧情有点跳,因为是指他们一直在这家餐馆吃饭,中间有他们一起出去玩情节穿插着,也是在这他们确立了关系,可以说主体就是体现了他们非常的合拍和默契,就像一些影评说的,这部电影是说如何跟百分百合拍的人分手,然后就是正常的恋爱开始啪啪啪,一直腻在床上,也没去就业说明会,后面也有讲了一点小麦带着小娟去认识他的朋友,也把小娟介绍给了他们认识,这里算是个小伏笔,后面他们分手也有这里的人的一些关系,接下去的剧情说实话我是不太喜欢的,如果一部八分的电影只是说恋爱被现实打败的话,我觉得在我这是不合格的,但是事实也是这样,小麦其实是有家里的资助,所以后面还是按自己的喜好给一些机构画点插画,小娟则要出去工作,因为小娟家庭观念也是要让她出去有正经工作,用脚指头想也能知道肯定不顺利,然后就是暂时在一家蛋糕店工作,小麦就每天去接小娟,日子过得甜甜蜜蜜,后面小娟在自己的努力下考了个什么资格证,去了一家医院还是什么做前台行政,这中间当然就有父母来见面吃饭了,他们在开始恋爱不久就同居合租了,然后小娟父母就是来说要让她有个正经工作,对男的说的话就是人生就是责任这类的话,而小麦爸爸算是个导火索,因为小麦家里是做烟花生意的,他爸让他就做烟花生意,因为要回老家,并且小麦也不想做,所以就拒绝了,然后他爸就说不给他每个月五万的资助,这也导致了小麦需要去找工作,这个过程也是很辛苦,本来想要年前找好工作,然后事与愿违,后面有一次小娟被同事吐槽怎么从来不去团建,于是她就去了(我以为会拒绝),正在团建的时候小麦给她电话,说找到工作了,是一个创业物流公司这种,这里剧情就是我觉得比较俗套的,小麦各种被虐,累成狗,但是就像小娟爸爸说的话,人生就是责任,所以一直在坚持,但是这样也导致了跟小娟的交流也越来越少,他们原来最爱的漫画,爱玩的游戏,也只剩小娟一个人看,一个人玩,而正是这个时候,小娟说她辞掉了工作,去做一个不是太靠谱的漫画改造的密室逃脱,然后这里其实有一点后面争议很大的,就是这个工作其实是前面小麦介绍给小娟的那些朋友中一个的女朋友介绍的,而在有个剧情就是小娟有一次在这个密室逃脱的老板怀里醒过来,是在 KTV 那样的场景里,这就有很多人觉得小娟是不是出轨了,我觉得其实不那么重要,因为这个离职的事情已经让一切矛盾都摆在眼前,小麦其实是接受这种需要承担责任的生活,也想着要跟小娟结婚,但是小娟似乎还是想要过着那样理想的生活,做自己想做的事情,看自己爱看的漫画,也要小麦能像以前那样一直那么默契的有着相同的爱好,这里的触发点其实还有个是那个小麦的朋友(也就是他女朋友介绍小娟那个不靠谱工作的)的葬礼上,小麦在参加完葬礼后有挺多想倾诉的,而小娟只是想睡了,这个让小麦第二天起来都不想理小娟,只是这里我不太理解,难道这点闹情绪都不能接受吗,所谓的合拍也只是毫无限制的情况下的合拍吧,真正的生活怎么可能如此理想呢,即使没有物质生活的压力,也会有其他的各种压力和限制,在这之后其实小麦想说的是小娟是不是没有想跟自己继续在一起的想法了,而小娟觉得都不说话了,还怎么结婚呢,后面其实导演搞了个小 trick,突然放了异常婚礼,但是不是男女主的,我并不觉得这个桥段很好,在婚礼里男女主都觉得自己想要跟对方说分手了,但是当他们去了最开始一直去的餐馆的时候,一个算是一个现实映照的就是他们一直坐的位子被占了,可能也是导演想通过这个来说明他们已经回不去了,在餐馆交谈的时候,小麦其实是说他们结婚吧,并没有想前面婚礼上预设地要分手,但是小娟放弃了,不想结婚,因为不想过那样的生活了,而小麦觉得可能生活就是那样,不可能一直保持刚恋爱时候的那种感觉,生活就是责任,人生就意味着责任。

    +

    我的一些观点也在前面说了,恋爱到婚姻,即使物质没问题,经济没问题,也会有各种各样的问题,需要一起去解决,因为结婚就意味着需要相互扶持,而不是各取所需,可能我的要求比较高,后面男女主在分手后还一起住了一段时间,我原来还在想会不会通过这个方式让他们继续去磨合同步,只是我失望了,最后给个打分可能是 5 到 6 分吧,勉强及格,好的影视剧应该源于生活高于生活,这一部可能还比不上生活。

    +]]>
    + + 生活 + + + 生活 + 看剧
    @@ -14275,72 +14341,72 @@ server.6=192.168< - two sum - /2015/01/14/Two-Sum/ - problem

    Given an array of integers, find two numbers such that they add up to a specific target number.

    -

    The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.

    - -

    You may assume that each input would have exactly one solution.

    -

    Input: numbers={2, 7, 11, 15}, target=9
    Output: index1=1, index2=2

    -

    code

    struct Node
    -{
    -    int num, pos;
    -};
    -bool cmp(Node a, Node b)
    -{
    -    return a.num < b.num;
    -}
    -class Solution {
    -public:
    -    vector<int> twoSum(vector<int> &numbers, int target) {
    -        // Start typing your C/C++ solution below
    -        // DO NOT write int main() function
    -        vector<int> result;
    -        vector<Node> array;
    -        for (int i = 0; i < numbers.size(); i++)
    -        {
    -            Node temp;
    -            temp.num = numbers[i];
    -            temp.pos = i;
    -            array.push_back(temp);
    -        }
    -
    -        sort(array.begin(), array.end(), cmp);
    -        for (int i = 0, j = array.size() - 1; i != j;)
    -        {
    -            int sum = array[i].num + array[j].num;
    -            if (sum == target)
    -            {
    -                if (array[i].pos < array[j].pos)
    -                {
    -                    result.push_back(array[i].pos + 1);
    -                    result.push_back(array[j].pos + 1);
    -                } else
    -                {
    -                    result.push_back(array[j].pos + 1);
    -                    result.push_back(array[i].pos + 1);
    -                }
    -                break;
    -            } else if (sum < target)
    -            {
    -                i++;
    -            } else if (sum > target)
    -            {
    -                j--;
    -            }
    -        }
    -        return result;
    -    }
    -};
    + 记录下 Java Stream 的一些高效操作 + /2022/05/15/%E8%AE%B0%E5%BD%95%E4%B8%8B-Java-Lambda-%E7%9A%84%E4%B8%80%E4%BA%9B%E9%AB%98%E6%95%88%E6%93%8D%E4%BD%9C/ + 我们日常在代码里处理一些集合逻辑的时候用到 Stream 其实还挺多的,普通的取值过滤集合一般都是结合 ide 的提示就能搞定了,但是有些不太常用的就在这记录下,争取后面都更新记录下来。

    +

    自定义 distinctByKey 对结果进行去重

    stream 中自带的 distinct 只能对元素进行去重
    比如下面代码

    +
    public static void main(String[] args) {
    +        List<Integer> list = new ArrayList<>();
    +        list.add(1);
    +        list.add(1);
    +        list.add(2);
    +        list = list.stream().distinct().collect(Collectors.toList());
    +        System.out.println(list);
    +    }
    +

    结果就是去了重的

    +
    [1, 2]
    +

    但是当我的元素是个复杂对象,我想根据对象里的某个元素进行过滤的时候,就需要用到自定义的 distinctByKey 了,比如下面的想对 userId 进行去重

    +
    public static void main(String[] args) {
    +        List<StudentRecord> list = new ArrayList<>();
    +        StudentRecord s1 = new StudentRecord();
    +        s1.setUserId(11L);
    +        s1.setCourseId(100L);
    +        s1.setScore(100);
    +        list.add(s1);
    +        StudentRecord s2 = new StudentRecord();
    +        s2.setUserId(11L);
    +        s2.setCourseId(101L);
    +        s2.setScore(100);
    +        list.add(s2);
    +        StudentRecord s3 = new StudentRecord();
    +        s3.setUserId(12L);
    +        s3.setCourseId(100L);
    +        s3.setScore(100);
    +        list.add(s3);
    +        System.out.println(list.stream().distinct().collect(Collectors.toList()));
    +    }
    +    @Data
    +    static class StudentRecord {
    +        Long id;
    +        Long userId;
    +        Long courseId;
    +        Integer score;
    +    }
    +

    结果就是

    +
    [StudentRecord(id=null, userId=11, courseId=100, score=100), StudentRecord(id=null, userId=11, courseId=101, score=100), StudentRecord(id=null, userId=12, courseId=100, score=100)]
    +

    因为对象都不一样,所以就没法去重了,这里就需要用

    +
    public static <T> Predicate<T> distinctByKey(
    +            Function<? super T, ?> keyExtractor) {
     
    -

    Analysis

    sort the array, then test from head and end, until catch the right answer

    + Map<Object, Boolean> seen = new ConcurrentHashMap<>(); + return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; + }
    +

    然后就可以用它来去重了

    +
    System.out.println(list.stream().filter(distinctByKey(StudentRecord::getUserId)).collect(Collectors.toList()));
    +

    看下结果

    +
    [StudentRecord(id=null, userId=11, courseId=100, score=100), StudentRecord(id=null, userId=12, courseId=100, score=100)]
    +

    但是说实在的这个功能感觉应该是 stream 默认给实现的

    +

    使用 java.util.stream.Collectors#groupingBy 对 list 进行分组

    这个使用场景还是蛮多的,上面的场景里比如我要对 userId 进行分组,就一行代码就解决了

    +
    System.out.println(list.stream().collect(Collectors.groupingBy(StudentRecord::getUserId)));
    +

    结果

    {11=[StudentRecord(id=null, userId=11, courseId=100, score=100), StudentRecord(id=null, userId=11, courseId=101, score=100)], 12=[StudentRecord(id=null, userId=12, courseId=100, score=100)]}
    +

    很方便的变成了以 userId 作为 key,以相同 userIdStudentRecordList 作为 valuemap 结构

    ]]>
    - leetcode + java - leetcode - c++ + java + stream
    @@ -14358,59 +14424,6 @@ public: 生活 - - 聊聊一次 brew update 引发的血案 - /2020/06/13/%E8%81%8A%E8%81%8A%E4%B8%80%E6%AC%A1-brew-update-%E5%BC%95%E5%8F%91%E7%9A%84%E8%A1%80%E6%A1%88/ - 熟悉我的人(谁熟悉你啊🙄)知道我以前写过 PHP,虽然现在在工作中没用到了,但是自己的一些小工具还是会用 PHP 来写,但是在 Mac 碰到了一个环境相关的问题,因为我也是个更新狂魔,用了 brew 之后因为 gfw 的原因,如果长时间不更新,有时候要装一个用它装一个软件的话,前置的更新耗时就会让人非常头大,所以我基本会隔天 update 一下,但是这样会带来一个很心烦的问题,就是像这样,因为我是要用一个固定版本的 PHP,如果一直升需要一直配扩展啥的也很麻烦,如果一直升级 PHP 到最新版可能会比较少碰到这个问题

    -
    dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.64.dylib
    -

    这是什么鬼啊,然后我去这个目录下看了下,已经都是libicui18n.67.dylib了,而且它没有把原来的版本保留下来,首先这个是个叫 icu4c是啥玩意,谷歌了一下

    -
    -

    ICU4C是ICU在C/C++平台下的版本, ICU(International Component for Unicode)是基于”IBM公共许可证”的,与开源组织合作研究的, 用于支持软件国际化的开源项目。ICU4C提供了C/C++平台强大的国际化开发能力,软件开发者几乎可以使用ICU4C解决任何国际化的问题,根据各地的风俗和语言习惯,实现对数字、货币、时间、日期、和消息的格式化、解析,对字符串进行大小写转换、整理、搜索和排序等功能,必须一提的是,ICU4C提供了强大的BIDI算法,对阿拉伯语等BIDI语言提供了完善的支持。

    -
    -

    然后首先想到的解决方案就是能不能我使用brew install icu4c@64来重装下原来的版本,发现不行,并木有,之前的做法就只能是去网上把 64 的下载下来,然后放到这个目录,比较麻烦不智能,虽然没抱着希望在谷歌着,不过这次竟然给我找到了一个我认为非常 nice 的解决方案,因为是在 Stack Overflow 找到的,本着写给像我这样的小小白看的,那就稍微翻译一下
    第一步,我们到 brew的目录下

    -
    cd $(brew --prefix)/Homebrew/Library/Taps/homebrew/homebrew-core/Formula
    -

    这个可以理解为是 maven 的 pom 文件,不过有很多不同之处,使用ruby 写的,然后一个文件对应一个组件或者软件,那我们看下有个叫icu4c.rb的文件,
    第二步看看它的提交历史

    -
    git log --follow icu4c.rb
    -

    在 git log 的海洋中寻找,寻找它的(64版本)的身影

    第三步注意这三个红框,Stack Overflow 给出来的答案这一步是找到这个 commit id 直接切出一个新分支

    -
    git checkout -b icu4c-63 e7f0f10dc63b1dc1061d475f1a61d01b70ef2cb7
    -

    其实注意 commit id 旁边的红框,这个是有tag 的,可以直接

    -
    git checkout icu4c-64
    -

    PS: 因为我的问题是出在 64 的问题,Stack Overflow 回答的是 63 的,反正是一样的解决方法
    第四部,切回去之后我们就可以用 brew 提供的基于文件的安装命令来重新装上 64 版本

    -
    brew reinstall ./icu4c.rb
    -

    然后就是第五步,切换版本

    -
    brew switch icu4c 64.2
    -

    最后把分支切回来

    -
    git checkout master
    -

    是不是感觉很厉害的解决方法,大佬还提供了一个更牛的,直接写个 zsh 方法

    -
    # zsh
    -function hiicu64() {
    -  local last_dir=$(pwd)
    -
    -  cd $(brew --prefix)/Homebrew/Library/Taps/homebrew/homebrew-core/Formula
    -  git checkout icu4c-4
    -  brew reinstall ./icu4c.rb
    -  brew switch icu4c 64.2
    -  git checkout master
    -
    -  cd $last_dir
    -}
    -

    对应自己的版本改改版本号就可以了,非常好用。

    -]]>
    - - Mac - PHP - Homebrew - PHP - icu4c - - - Mac - PHP - Homebrew - icu4c - zsh - -
    难得的大扫除 /2022/04/10/%E9%9A%BE%E5%BE%97%E7%9A%84%E5%A4%A7%E6%89%AB%E9%99%A4/ @@ -14424,17 +14437,4 @@ public: 大扫除 - - 聊聊最近平淡的生活之看《神探狄仁杰》 - /2021/12/19/%E8%81%8A%E8%81%8A%E6%9C%80%E8%BF%91%E5%B9%B3%E6%B7%A1%E7%9A%84%E7%94%9F%E6%B4%BB%E4%B9%8B%E7%9C%8B%E3%80%8A%E7%A5%9E%E6%8E%A2%E7%8B%84%E4%BB%81%E6%9D%B0%E3%80%8B/ - 其实最近看的不止这一部,前面看了《继承者们》,《少年包青天》这些,就一起聊下,其中看《继承者们》算是个人比较喜欢,以前就有这种看剧的习惯,这个跟《一生一世》里任嘉伦说自己看《寻秦记》看了几十遍一样,我看喜欢的剧也基本上会看不止五遍,继承者们是有帅哥美女看,而且印象中剧情也挺甜的,一般情况下最好是已经有点遗忘剧情了,因为我个人觉得看剧分两种,无聊了又心情不太好,可以看些这类轻松又看过的剧,可以不完全专心地看剧,另外有心情专心看的时候,可以看一些需要思考,一些探案类的或者烧脑类。
    最近看了《神探狄仁杰》,因为跟前面看的《少年包青天》都是这类古装探案剧,正好有些感想,《少年包青天》算是儿时阴影,本来是不太会去看的,正好有一次有机会跟 LD 一起看了会就也觉得比较有意思就看了下去,不得不说,以前的这些剧还是很不错的,包括剧情和演员,第一部一共是 40 集,看的过程中也发现了大概是五个案子,平均八集一个案子,整体节奏还是比较慢的,但是基本每个案子其实都是构思得很巧妙,很久以前看过但是现在基本不太记得剧情了,每个案子在前面几集的时候基本都猜不到犯案逻辑,但是在看了狄仁杰之后,发现两部剧也有比较大的差别,少年包青天相对来说逻辑性会更强一些,个人主观觉得推理的严谨性更高,可能剧本打磨上更将就一下,而狄仁杰因为要提现他的个人强项,不比少年包青天中有公孙策一时瑜亮的情节,狄仁杰中分工明确,李元芳是个武力担当,曾泰是捧哏的,相对来说是狄仁杰在案子里从始至终地推进案情,有些甚至有些玄乎,会有一些跳脱跟不合理,有些像是狄仁杰的奇遇,不过这些想法是私人的观点,并不是想要评孰优孰劣;第二个感受是不知道是不是年代关系,特别是少年包青天,每个案件的大 boss 基本都不是个完全的坏人,甚至都是比较情有可原的可怜人,因为一些特殊原因,而好几个都是包拯身边的人,这一点其实跟狄仁杰里第一个使团惊魂案件比较类似,虎敬晖也是个人物形象比较丰满的角色,不是个标签化的淡薄形象,跟金木兰的感情和反叛行动在最后都说明的缘由,而且也有随着跟狄仁杰一起办案被其影响感化,最终为了救狄仁杰而被金木兰所杀,只是这样金木兰这个角色就会有些偏执和符号化,当然剧本肯定不是能面面俱到,这样的剧本已经比现在很多流量剧的好很多了。还想到了前阵子看的《指环王》中的白袍萨鲁曼在剧中也是个比较单薄的角色,这样的电影彪炳影史也没办法把个个人物都设计得完整有血有肉,或者说这本来也是应该有侧重点,当然其实我也不觉得指环王就是绝对的最好的,因为相对来说故事情节的复杂性等真的不如西游记,只是在 86 版之后的各种乱七八糟的翻牌和乱拍已经让这个真正的王者神话故事有点力不从心,这里边有部西游记后传是个人还比较喜欢的,虽然武打动作比较鬼畜,但是剧情基本是无敌的,在西游的架构上衍生出来这么完整丰富的故事,人物角色也都有各自的出彩点。
    说回狄仁杰,在这之前也看过徐克拍的几部狄仁杰的电影版,第一部刘德华拍得相对完成度更高,故事性也可圈可点,后面几部就是剧情拉胯,靠特效拉回来一点分,虽说这个也是所谓的狄仁杰宇宙的构思在里面但是现在来看基本是跟西游那些差不多,完全没有整体性可言,打一枪换一个地方,演员也没有延续性,剧情也是前后跳脱,没什么关联跟承上启下,导致质量层次不一,更不用谈什么狄仁杰宇宙了,不过这个事情也是难说,原因很多,现在资本都是更加趋利的,一些需要更长久时间才能有回报的投资是很难获得资本青睐,所以只能将重心投给选择一些流量明星,而本来应该将资源投给剧本打磨的基本就没了,再深入说也没意义了,社会现状就是这样。
    还有一点感想是,以前的剧里的拍摄环境还是比较惨的,看着一些房子,甚至皇宫都是比较破旧的,地面还是石板这种,想想以前的演员的环境再想想现在的,比如成龙说的,以前他拍剧就是啪摔了,问这条有没有过,过了就直接送医院,而不是现在可能手蹭破点皮就大叫,甚至还有饭圈这些破事。

    -]]>
    - - 生活 - - - 生活 - 看剧 - -
    diff --git a/sitemap.xml b/sitemap.xml index 345e66eb37..e1e20c1db9 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -1516,7 +1516,7 @@ https://nicksxs.me/ - 2022-05-29 + 2022-05-30 daily 1.0 @@ -1524,1953 +1524,1953 @@ https://nicksxs.me/tags/%E7%94%9F%E6%B4%BB/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/2020/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/2021/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%8B%96%E6%9B%B4/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Java/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/JVM/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/C/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/leetcode/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/java/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/tags/%E9%A2%98%E8%A7%A3/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/tags/Linked-List/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Binary-Tree/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/DFS/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E4%BA%8C%E5%8F%89%E6%A0%91/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E8%AF%BB%E5%90%8E%E6%84%9F/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/tags/2019/ - 2022-05-29 + https://nicksxs.me/tags/%E9%A2%98%E8%A7%A3/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/ - 2022-05-29 + https://nicksxs.me/tags/Linked-List/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E6%8A%80%E6%9C%AF/ - 2022-05-29 + https://nicksxs.me/tags/2019/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E8%AF%BB%E4%B9%A6/ - 2022-05-29 + https://nicksxs.me/tags/%E8%AF%BB%E5%90%8E%E6%84%9F/ + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%B9%B6%E5%8F%91/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/j-u-c/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/aqs/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/condition/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/await/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/signal/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/lock/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/unlock/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/c/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/tags/Apollo/ - 2022-05-29 + https://nicksxs.me/tags/%E6%8A%80%E6%9C%AF/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/value/ - 2022-05-29 + https://nicksxs.me/tags/%E8%AF%BB%E4%B9%A6/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E6%B3%A8%E8%A7%A3/ - 2022-05-29 + https://nicksxs.me/tags/%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/environment/ - 2022-05-29 + https://nicksxs.me/tags/c/ + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Stream/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Comparator/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%8E%92%E5%BA%8F/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/sort/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/nullsfirst/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/Disruptor/ - 2022-05-29 + https://nicksxs.me/tags/Apollo/ + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/tags/value/ + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/tags/%E6%B3%A8%E8%A7%A3/ + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/tags/environment/ + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Filter/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Interceptor/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/AOP/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Spring/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Tomcat/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Servlet/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Web/ - 2022-05-29 + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/tags/G1/ + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/tags/GC/ + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/tags/Garbage-First-Collector/ + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/tags/Disruptor/ + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Dubbo/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/RPC/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E9%80%92%E5%BD%92/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Preorder-Traversal/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Inorder-Traversal/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%89%8D%E5%BA%8F/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E4%B8%AD%E5%BA%8F/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/tags/Print-FooBar-Alternately/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/DP/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/stack/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/min-stack/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%9C%80%E5%B0%8F%E6%A0%88/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/leetcode-155/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/linked-list/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Lowest-Common-Ancestor-of-a-Binary-Tree/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/string/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/Intersection-of-Two-Arrays/ - 2022-05-29 + https://nicksxs.me/tags/Median-of-Two-Sorted-Arrays/ + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/dp/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E4%BB%A3%E7%A0%81%E9%A2%98%E8%A7%A3/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Trapping-Rain-Water/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%8E%A5%E9%9B%A8%E6%B0%B4/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Leetcode-42/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Rotate-Image/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E7%9F%A9%E9%98%B5/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/linux/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/grep/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%BD%AC%E4%B9%89/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/mfc/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/tags/Maven/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Remove-Duplicates-from-Sorted-List/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/tags/C/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/Redis/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/tags/Distributed-Lock/ - 2022-05-29 + https://nicksxs.me/tags/Print-FooBar-Alternately/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81/ - 2022-05-29 + https://nicksxs.me/tags/Maven/ + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/hadoop/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/cluster/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/tags/Median-of-Two-Sorted-Arrays/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/docker/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/mysql/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Docker/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/namespace/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/cgroup/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Dockerfile/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/echo/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/uname/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%8F%91%E8%A1%8C%E7%89%88/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Gogs/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Webhook/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%8D%9A%E5%AE%A2%EF%BC%8C%E6%96%87%E7%AB%A0/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Mysql/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Mybatis/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Sql%E6%B3%A8%E5%85%A5/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E7%BC%93%E5%AD%98/ - 2022-05-29 + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/tags/openresty/ + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/nginx/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E6%97%A5%E5%BF%97/ - 2022-05-29 + https://nicksxs.me/tags/C/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/openresty/ - 2022-05-29 + https://nicksxs.me/tags/Redis/ + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/tags/Distributed-Lock/ + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81/ + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/php/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/mq/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/im/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/redis/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%BA%90%E7%A0%81/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%BA%94%E7%94%A8/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Evict/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%BF%87%E6%9C%9F%E7%AD%96%E7%95%A5/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Rust/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%89%80%E6%9C%89%E6%9D%83/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%86%85%E5%AD%98%E5%88%86%E5%B8%83/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%96%B0%E8%AF%AD%E8%A8%80/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%8F%AF%E5%8F%98%E5%BC%95%E7%94%A8/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E4%B8%8D%E5%8F%AF%E5%8F%98%E5%BC%95%E7%94%A8/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%88%87%E7%89%87/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/spark/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/python/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Spring-Event/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/websocket/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/swoole/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/WordPress/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%B0%8F%E6%8A%80%E5%B7%A7/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/gc/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%A0%87%E8%AE%B0%E6%95%B4%E7%90%86/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/jvm/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/MQ/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/RocketMQ/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%89%8A%E5%B3%B0%E5%A1%AB%E8%B0%B7/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6/ - 2022-05-29 + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/tags/%E6%97%A5%E5%BF%97/ + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/ssh/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%90%90%E6%A7%BD/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E7%96%AB%E6%83%85/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E7%BE%8E%E5%9B%BD/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%85%AC%E4%BA%A4%E8%BD%A6/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%8F%A3%E7%BD%A9/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%9D%80%E4%BA%BA%E8%AF%9B%E5%BF%83/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%BC%80%E8%BD%A6/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%8A%A0%E5%A1%9E/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E7%B3%9F%E5%BF%83%E4%BA%8B/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%A7%84%E5%88%99/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%85%AC%E4%BA%A4/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%B7%AF%E6%94%BF%E8%A7%84%E5%88%92/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%9F%BA%E7%A1%80%E8%AE%BE%E6%96%BD/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%9D%AD%E5%B7%9E/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%81%A5%E5%BA%B7%E7%A0%81/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E6%89%93%E5%8D%A1/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%B9%B8%E7%A6%8F%E4%BA%86%E5%90%97/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%B6%B3%E7%90%83/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/git/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/scp/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%BF%90%E5%8A%A8/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%87%8F%E8%82%A5/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%B7%91%E6%AD%A5/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%B9%B2%E6%B4%BB/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%BD%B1%E8%AF%84/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%AF%84%E7%94%9F%E8%99%AB/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E5%9B%A4%E7%89%A9%E8%B5%84/ - 2022-05-29 + https://nicksxs.me/tags/Intersection-of-Two-Arrays/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E5%AD%97%E7%AC%A6%E9%9B%86/ - 2022-05-29 + https://nicksxs.me/tags/DefaultMQPushConsumer/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E7%BC%96%E7%A0%81/ - 2022-05-29 + https://nicksxs.me/tags/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/utf8/ - 2022-05-29 + https://nicksxs.me/tags/NameServer/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/utf8mb4/ - 2022-05-29 + https://nicksxs.me/tags/SpringBoot/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/utf8mb4-0900-ai-ci/ - 2022-05-29 + https://nicksxs.me/tags/Druid/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/utf8mb4-unicode-ci/ - 2022-05-29 + https://nicksxs.me/tags/%E6%95%B0%E6%8D%AE%E6%BA%90%E5%8A%A8%E6%80%81%E5%88%87%E6%8D%A2/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/utf8mb4-general-ci/ - 2022-05-29 + https://nicksxs.me/tags/cglib/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/DefaultMQPushConsumer/ - 2022-05-29 + https://nicksxs.me/tags/%E4%BA%8B%E5%8A%A1/ + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/tags/%E5%AD%97%E7%AC%A6%E9%9B%86/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/ - 2022-05-29 + https://nicksxs.me/tags/%E7%BC%96%E7%A0%81/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/NameServer/ - 2022-05-29 + https://nicksxs.me/tags/utf8/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/SpringBoot/ - 2022-05-29 + https://nicksxs.me/tags/utf8mb4/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/cglib/ - 2022-05-29 + https://nicksxs.me/tags/utf8mb4-0900-ai-ci/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E4%BA%8B%E5%8A%A1/ - 2022-05-29 + https://nicksxs.me/tags/utf8mb4-unicode-ci/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/Druid/ - 2022-05-29 + https://nicksxs.me/tags/utf8mb4-general-ci/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E6%95%B0%E6%8D%AE%E6%BA%90%E5%8A%A8%E6%80%81%E5%88%87%E6%8D%A2/ - 2022-05-29 + https://nicksxs.me/tags/%E5%9B%A4%E7%89%A9%E8%B5%84/ + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E4%B8%9C%E4%BA%AC%E5%A5%A5%E8%BF%90%E4%BC%9A/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E4%B8%BE%E9%87%8D/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%B0%84%E5%87%BB/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E4%B9%92%E4%B9%93%E7%90%83/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%B7%B3%E6%B0%B4/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/SPI/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Adaptive/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%87%AA%E9%80%82%E5%BA%94%E6%8B%93%E5%B1%95/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%AE%B9%E9%94%99%E6%9C%BA%E5%88%B6/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Synchronized/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%81%8F%E5%90%91%E9%94%81/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E9%87%8D%E9%87%8F%E7%BA%A7%E9%94%81/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%87%AA%E6%97%8B/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E7%B1%BB%E5%8A%A0%E8%BD%BD/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%8A%A0%E8%BD%BD/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E9%AA%8C%E8%AF%81/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%87%86%E5%A4%87/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%A7%A3%E6%9E%90/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%88%9D%E5%A7%8B%E5%8C%96/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E9%93%BE%E6%8E%A5/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/JPS/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/JStack/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/JMap/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/top/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Broker/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Sharding-Jdbc/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/ThreadPool/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E7%BA%BF%E7%A8%8B%E6%B1%A0/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/FixedThreadPool/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/LimitedThreadPool/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/EagerThreadPool/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/CachedThreadPool/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/mvcc/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/read-view/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/gap-lock/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/next-key-lock/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%B9%BB%E8%AF%BB/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E7%B4%A2%E5%BC%95/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/is-null/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/is-not-null/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/procedure/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8F/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/bloom-filter/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E4%BA%92%E6%96%A5%E9%94%81/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Design-Patterns/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%8D%95%E4%BE%8B/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/Singleton/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E8%87%AA%E5%8A%A8%E8%A3%85%E9%85%8D/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/AutoConfiguration/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/tags/ThreadLocal/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/tags/%E5%BC%B1%E5%BC%95%E7%94%A8/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/tags/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/WeakReference/ - 2022-05-29 + https://nicksxs.me/tags/Mac/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E6%97%85%E6%B8%B8/ - 2022-05-29 + https://nicksxs.me/tags/PHP/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E5%8E%A6%E9%97%A8/ - 2022-05-29 + https://nicksxs.me/tags/Homebrew/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E4%B8%AD%E5%B1%B1%E8%B7%AF/ - 2022-05-29 + https://nicksxs.me/tags/icu4c/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E5%B1%80%E5%8F%A3%E8%A1%97/ - 2022-05-29 + https://nicksxs.me/tags/zsh/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E9%BC%93%E6%B5%AA%E5%B1%BF/ - 2022-05-29 + https://nicksxs.me/tags/ThreadLocal/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E6%9B%BE%E5%8E%9D%E5%9E%B5/ - 2022-05-29 + https://nicksxs.me/tags/%E5%BC%B1%E5%BC%95%E7%94%A8/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E6%A4%8D%E7%89%A9%E5%9B%AD/ - 2022-05-29 + https://nicksxs.me/tags/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E9%A9%AC%E6%88%8F%E5%9B%A2/ - 2022-05-29 + https://nicksxs.me/tags/WeakReference/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E6%B2%99%E8%8C%B6%E9%9D%A2/ - 2022-05-29 + https://nicksxs.me/tags/%E6%89%B6%E6%A2%AF/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E6%B5%B7%E8%9B%8E%E7%85%8E/ - 2022-05-29 + https://nicksxs.me/tags/%E8%B8%A9%E8%B8%8F/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E6%89%B6%E6%A2%AF/ - 2022-05-29 + https://nicksxs.me/tags/%E5%AE%89%E5%85%A8/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E8%B8%A9%E8%B8%8F/ - 2022-05-29 + https://nicksxs.me/tags/%E7%94%B5%E7%93%B6%E8%BD%A6/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E5%AE%89%E5%85%A8/ - 2022-05-29 + https://nicksxs.me/tags/%E6%97%85%E6%B8%B8/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E7%94%B5%E7%93%B6%E8%BD%A6/ - 2022-05-29 + https://nicksxs.me/tags/%E5%8E%A6%E9%97%A8/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/Thread-dump/ - 2022-05-29 + https://nicksxs.me/tags/%E4%B8%AD%E5%B1%B1%E8%B7%AF/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/ - 2022-05-29 + https://nicksxs.me/tags/%E5%B1%80%E5%8F%A3%E8%A1%97/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4/ - 2022-05-29 + https://nicksxs.me/tags/%E9%BC%93%E6%B5%AA%E5%B1%BF/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E4%B8%89%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4/ - 2022-05-29 + https://nicksxs.me/tags/%E6%9B%BE%E5%8E%9D%E5%9E%B5/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/2PC/ - 2022-05-29 + https://nicksxs.me/tags/%E6%A4%8D%E7%89%A9%E5%9B%AD/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/3PC/ - 2022-05-29 + https://nicksxs.me/tags/%E9%A9%AC%E6%88%8F%E5%9B%A2/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E9%AA%91%E8%BD%A6/ - 2022-05-29 + https://nicksxs.me/tags/%E6%B2%99%E8%8C%B6%E9%9D%A2/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E7%9C%8B%E5%89%A7/ - 2022-05-29 + https://nicksxs.me/tags/%E6%B5%B7%E8%9B%8E%E7%85%8E/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/G1/ - 2022-05-29 + https://nicksxs.me/tags/Thread-dump/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/GC/ - 2022-05-29 + https://nicksxs.me/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/Garbage-First-Collector/ - 2022-05-29 + https://nicksxs.me/tags/%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E8%A3%85%E7%94%B5%E8%84%91/ - 2022-05-29 + https://nicksxs.me/tags/%E4%B8%89%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E8%80%81%E7%94%B5%E8%84%91/ - 2022-05-29 + https://nicksxs.me/tags/2PC/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/360-%E5%85%A8%E5%AE%B6%E6%A1%B6/ - 2022-05-29 + https://nicksxs.me/tags/3PC/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E4%BF%AE%E7%94%B5%E8%84%91%E7%9A%84/ - 2022-05-29 + https://nicksxs.me/tags/%E9%AA%91%E8%BD%A6/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E6%8D%A2%E8%BD%A6%E7%89%8C/ - 2022-05-29 + https://nicksxs.me/tags/%E7%9C%8B%E5%89%A7/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/stream/ - 2022-05-29 + https://nicksxs.me/tags/%E8%A3%85%E7%94%B5%E8%84%91/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/zookeeper/ - 2022-05-29 + https://nicksxs.me/tags/%E8%80%81%E7%94%B5%E8%84%91/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E7%9C%8B%E4%B9%A6/ - 2022-05-29 + https://nicksxs.me/tags/360-%E5%85%A8%E5%AE%B6%E6%A1%B6/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/%E9%AB%98%E9%80%9F/ - 2022-05-29 + https://nicksxs.me/tags/%E4%BF%AE%E7%94%B5%E8%84%91%E7%9A%84/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/Mac/ - 2022-05-29 + https://nicksxs.me/tags/%E6%8D%A2%E8%BD%A6%E7%89%8C/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/PHP/ - 2022-05-29 + https://nicksxs.me/tags/zookeeper/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/Homebrew/ - 2022-05-29 + https://nicksxs.me/tags/%E7%9C%8B%E4%B9%A6/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/icu4c/ - 2022-05-29 + https://nicksxs.me/tags/%E9%AB%98%E9%80%9F/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/tags/zsh/ - 2022-05-29 + https://nicksxs.me/tags/stream/ + 2022-05-30 weekly 0.2 https://nicksxs.me/tags/%E5%A4%A7%E6%89%AB%E9%99%A4/ - 2022-05-29 + 2022-05-30 weekly 0.2 @@ -3479,1022 +3479,1022 @@ https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/JVM/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/leetcode/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/2020/ - 2022-05-29 + https://nicksxs.me/categories/leetcode/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/leetcode/ - 2022-05-29 + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/2020/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/GC/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ - 2022-05-29 + https://nicksxs.me/categories/Binary-Tree/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Linked-List/ - 2022-05-29 + https://nicksxs.me/categories/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E8%AF%BB%E5%90%8E%E6%84%9F/ - 2022-05-29 + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/2019/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Binary-Tree/ - 2022-05-29 + https://nicksxs.me/categories/Linked-List/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/2019/ - 2022-05-29 + https://nicksxs.me/categories/C/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/C/ - 2022-05-29 + https://nicksxs.me/categories/leetcode/java/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/2020/ - 2022-05-29 + https://nicksxs.me/categories/%E8%AF%BB%E5%90%8E%E6%84%9F/ + 2022-05-30 weekly 0.2 - - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/ - 2022-05-29 + + https://nicksxs.me/categories/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/2020/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/%E5%B9%B6%E5%8F%91/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/leetcode/java/ - 2022-05-29 + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/java/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E8%AF%BB%E5%90%8E%E6%84%9F/%E6%9D%91%E4%B8%8A%E6%98%A5%E6%A0%91/ - 2022-05-29 + https://nicksxs.me/categories/Java/%E9%9B%86%E5%90%88/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/Apollo/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Java/%E9%9B%86%E5%90%88/ - 2022-05-29 + https://nicksxs.me/categories/leetcode/java/Binary-Tree/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Java/Dubbo/ - 2022-05-29 + https://nicksxs.me/categories/Filter/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Filter/ - 2022-05-29 + https://nicksxs.me/categories/%E8%AF%BB%E5%90%8E%E6%84%9F/%E6%9D%91%E4%B8%8A%E6%98%A5%E6%A0%91/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/2020/ - 2022-05-29 + https://nicksxs.me/categories/Java/Dubbo/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/2021/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/leetcode/java/Binary-Tree/ - 2022-05-29 + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/2020/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/DP/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/leetcode/java/Linked-List/ - 2022-05-29 + https://nicksxs.me/categories/stack/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/stack/ - 2022-05-29 + https://nicksxs.me/categories/leetcode/java/Linked-List/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/leetcode/Lowest-Common-Ancestor-of-a-Binary-Tree/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/linked-list/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E5%AD%97%E7%AC%A6%E4%B8%B2-online/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Java/Apollo/value/ - 2022-05-29 + https://nicksxs.me/categories/Java/leetcode/Rotate-Image/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Java/leetcode/Rotate-Image/ - 2022-05-29 + https://nicksxs.me/categories/Linux/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Linux/ - 2022-05-29 + https://nicksxs.me/categories/Java/Apollo/value/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Java/Maven/ - 2022-05-29 + https://nicksxs.me/categories/leetcode/java/Binary-Tree/DFS/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Redis/ - 2022-05-29 + https://nicksxs.me/categories/Java/Maven/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Interceptor-AOP/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/data-analysis/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/docker/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Docker/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/leetcode/java/Binary-Tree/DFS/ - 2022-05-29 + https://nicksxs.me/categories/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/ - 2022-05-29 + https://nicksxs.me/categories/Java/Mybatis/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/leetcode/java/DP/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Java/Mybatis/ - 2022-05-29 + https://nicksxs.me/categories/nginx/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/nginx/ - 2022-05-29 + https://nicksxs.me/categories/Redis/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/leetcode/java/stack/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/php/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/redis/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/leetcode/java/linked-list/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/categories/leetcode/java/string/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E8%AF%AD%E8%A8%80/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Java/Spring/ - 2022-05-29 + https://nicksxs.me/categories/leetcode/java/string/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Linux/%E5%91%BD%E4%BB%A4/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E5%B0%8F%E6%8A%80%E5%B7%A7/ - 2022-05-29 + https://nicksxs.me/categories/Java/Spring/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Java/gc/ - 2022-05-29 + https://nicksxs.me/categories/%E5%B0%8F%E6%8A%80%E5%B7%A7/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E8%BF%90%E5%8A%A8/ - 2022-05-29 + https://nicksxs.me/categories/Java/gc/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/MQ/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/categories/Redis/Distributed-Lock/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/ssh/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%90%90%E6%A7%BD/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Spring/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%85%AC%E4%BA%A4/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E8%AF%BB%E5%90%8E%E6%84%9F/%E7%99%BD%E5%B2%A9%E6%9D%BE/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/git/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/shell/ - 2022-05-29 + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E8%BF%90%E5%8A%A8/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Docker/%E4%BB%8B%E7%BB%8D/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%BD%B1%E8%AF%84/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Mysql/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/SpringBoot/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Mybatis/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/Dubbo/RPC/ - 2022-05-29 + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/categories/Redis/Distributed-Lock/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Dubbo-RPC/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/%E7%B1%BB%E5%8A%A0%E8%BD%BD/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Thread-dump/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Dubbo-%E7%BA%BF%E7%A8%8B%E6%B1%A0/ - 2022-05-29 + https://nicksxs.me/categories/Redis/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Redis/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ - 2022-05-29 + https://nicksxs.me/categories/Dubbo-%E7%BA%BF%E7%A8%8B%E6%B1%A0/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Redis/%E5%BA%94%E7%94%A8/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/Design-Patterns/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/SpringBoot/ - 2022-05-29 + https://nicksxs.me/categories/Mac/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E6%97%85%E6%B8%B8/ - 2022-05-29 + https://nicksxs.me/categories/SpringBoot/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/ - 2022-05-29 + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E6%97%85%E6%B8%B8/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%BC%80%E8%BD%A6/ - 2022-05-29 + https://nicksxs.me/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E8%AF%AD%E8%A8%80/Rust/ - 2022-05-29 + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%BC%80%E8%BD%A6/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Mac/ - 2022-05-29 + https://nicksxs.me/categories/%E8%AF%AD%E8%A8%80/Rust/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Rust/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Linux/%E5%91%BD%E4%BB%A4/grep/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/gc/jvm/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/MQ/RocketMQ/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/categories/C/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/ssh/%E6%8A%80%E5%B7%A7/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%90%90%E6%A7%BD/%E7%96%AB%E6%83%85/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Spring/Servlet/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E8%AF%BB%E5%90%8E%E6%84%9F/%E7%99%BD%E5%B2%A9%E6%9D%BE/%E5%B9%B8%E7%A6%8F%E4%BA%86%E5%90%97/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/git/%E5%B0%8F%E6%8A%80%E5%B7%A7/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/shell/%E5%B0%8F%E6%8A%80%E5%B7%A7/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E8%BF%90%E5%8A%A8/%E8%B7%91%E6%AD%A5/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%BD%B1%E8%AF%84/2020/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Linux/%E5%91%BD%E4%BB%A4/echo/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Mysql/Sql%E6%B3%A8%E5%85%A5/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Mybatis/%E7%BC%93%E5%AD%98/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/Dubbo/RPC/SPI/ - 2022-05-29 + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/categories/C/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Dubbo/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Linux/%E5%91%BD%E4%BB%A4/top/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Mysql/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ - 2022-05-29 + https://nicksxs.me/categories/Redis/%E6%BA%90%E7%A0%81/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Redis/%E6%BA%90%E7%A0%81/ - 2022-05-29 + https://nicksxs.me/categories/Mysql/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Mysql/%E7%B4%A2%E5%BC%95/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Redis/%E7%BC%93%E5%AD%98/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Java/Singleton/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4/ - 2022-05-29 + https://nicksxs.me/categories/Mac/PHP/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Mac/PHP/ - 2022-05-29 + https://nicksxs.me/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E5%B0%8F%E6%8A%80%E5%B7%A7/grep/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/ - 2022-05-29 - weekly - 0.2 - - - - https://nicksxs.me/categories/C/Redis/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%90%90%E6%A7%BD/%E7%96%AB%E6%83%85/%E7%BE%8E%E5%9B%BD/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%90%90%E6%A7%BD/%E7%96%AB%E6%83%85/%E5%8F%A3%E7%BD%A9/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Spring/Servlet/Interceptor/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E8%BF%90%E5%8A%A8/%E8%B7%91%E6%AD%A5/%E5%B9%B2%E6%B4%BB/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Docker/%E5%8F%91%E8%A1%8C%E7%89%88%E6%9C%AC/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/MQ/RocketMQ/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Spring/Mybatis/ - 2022-05-29 + 2022-05-30 + weekly + 0.2 + + + + https://nicksxs.me/categories/C/Redis/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Dubbo/SPI/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Dubbo/%E5%AE%B9%E9%94%99%E6%9C%BA%E5%88%B6/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E5%B0%8F%E6%8A%80%E5%B7%A7/top/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E5%B7%A5%E5%85%B7/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Dubbo/%E7%BA%BF%E7%A8%8B%E6%B1%A0/ - 2022-05-29 + https://nicksxs.me/categories/Mysql/%E6%BA%90%E7%A0%81/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Mysql/%E6%BA%90%E7%A0%81/ - 2022-05-29 + https://nicksxs.me/categories/Dubbo/%E7%BA%BF%E7%A8%8B%E6%B1%A0/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/C/Mysql/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%BC%93%E5%AD%98/ - 2022-05-29 + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/%E4%B8%89%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4/ - 2022-05-29 + https://nicksxs.me/categories/Mac/Homebrew/ + 2022-05-30 weekly 0.2 - https://nicksxs.me/categories/Mac/Homebrew/ - 2022-05-29 + https://nicksxs.me/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/%E4%B8%89%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4/ + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E5%B0%8F%E6%8A%80%E5%B7%A7/grep/%E6%9F%A5%E6%97%A5%E5%BF%97/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/RocketMQ/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Spring/Servlet/Interceptor/AOP/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E5%B0%8F%E6%8A%80%E5%B7%A7/top/%E6%8E%92%E5%BA%8F/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Dubbo/%E7%BA%BF%E7%A8%8B%E6%B1%A0/ThreadPool/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%BC%93%E5%AD%98/%E7%A9%BF%E9%80%8F/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/PHP/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/Dubbo/SPI/Adaptive/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%BC%93%E5%AD%98/%E7%A9%BF%E9%80%8F/%E5%87%BB%E7%A9%BF/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/PHP/icu4c/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/RocketMQ/ - 2022-05-29 + 2022-05-30 weekly 0.2 https://nicksxs.me/categories/%E7%BC%93%E5%AD%98/%E7%A9%BF%E9%80%8F/%E5%87%BB%E7%A9%BF/%E9%9B%AA%E5%B4%A9/ - 2022-05-29 + 2022-05-30 weekly 0.2 diff --git a/tags/GC/index.html b/tags/GC/index.html index 9447f42d44..6bca68bb58 100644 --- a/tags/GC/index.html +++ b/tags/GC/index.html @@ -1 +1 @@ -标签: GC | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    GC 标签

    2020
    \ No newline at end of file +标签: gc | Nicksxs's Blog

    Nicksxs's Blog

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

    0%
    \ No newline at end of file