-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
129 lines (66 loc) · 101 KB
/
atom.xml
File metadata and controls
129 lines (66 loc) · 101 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>不眠之夜の博客</title>
<subtitle>不眠之夜</subtitle>
<link href="https://blog.restlessnight.cn/atom.xml" rel="self"/>
<link href="https://blog.restlessnight.cn/"/>
<updated>2025-04-10T06:31:04.927Z</updated>
<id>https://blog.restlessnight.cn/</id>
<author>
<name>victor.smile</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>龟兔赛跑:快慢指针法详解(Floyd's Tortoise and Hare Algorithm)</title>
<link href="https://blog.restlessnight.cn/2025/04/08/gui-tu-sai-pao-kuai-man-zhi-zhen-fa-xiang-jie-floyd-s-tortoise-and-hare-algorithm/"/>
<id>https://blog.restlessnight.cn/2025/04/08/gui-tu-sai-pao-kuai-man-zhi-zhen-fa-xiang-jie-floyd-s-tortoise-and-hare-algorithm/</id>
<published>2025-04-08T12:01:25.000Z</published>
<updated>2025-04-10T06:31:04.927Z</updated>
<content type="html"><![CDATA[<hr><h1 id="龟兔赛跑:快慢指针法详解(Floyd’s-Tortoise-and-Hare-Algorithm)"><a href="#龟兔赛跑:快慢指针法详解(Floyd’s-Tortoise-and-Hare-Algorithm)" class="headerlink" title="龟兔赛跑:快慢指针法详解(Floyd’s Tortoise and Hare Algorithm)"></a>龟兔赛跑:快慢指针法详解(Floyd’s Tortoise and Hare Algorithm)</h1><h2 id="📌-简介"><a href="#📌-简介" class="headerlink" title="📌 简介"></a>📌 简介</h2><p>快慢指针法(Floyd’s Tortoise and Hare Algorithm)是一种优雅而高效的算法技巧,广泛应用于链表、数组等线性数据结构的问题求解。它以“快慢”两个指针的相对速度差为核心,通过巧妙的移动策略解决诸如 <strong>环形检测</strong>、<strong>中点查找</strong>、<strong>重复元素定位</strong> 等问题。该方法由 Robert W. Floyd 提出,因其形象地将快慢指针比喻为“乌龟与兔子”而得名。</p><p>常用于判断链表是否有环、找中点、定位环起点或数组中重复元素等问题。</p><hr><h2 id="🚀-基本原理"><a href="#🚀-基本原理" class="headerlink" title="🚀 基本原理"></a>🚀 基本原理</h2><p>🧠 快慢指针法的核心在于利用两个指针以不同速度遍历数据结构:</p><ul><li><strong>慢指针(slow pointer,乌龟)</strong>:每次移动 1 步;</li><li><strong>快指针(fast pointer,兔子)</strong>:每次移动 2 步。</li></ul><p>🧠 根据问题的特性:</p><ul><li>如果数据结构中存在环,快慢指针最终会在环内相遇;</li><li>如果不存在环,快指针会率先到达结构的末尾(例如链表的 <code>null</code> 或数组边界)。</li></ul><p>🧠 Floyd 判圈算法(也称“乌龟与兔子算法”)通过快慢指针解决此问题,分为两个阶段:</p><ul><li><strong>检测环并找到相遇点</strong>:使用快慢指针,快指针每次走 2 步,慢指针每次走 1 步,若存在环,两者会在环内相遇。</li><li><strong>定位环入口</strong>:从链表头部和相遇点同时以慢速(每次 1 步)前进,两者相遇的节点即为环入口。</li></ul><h3 id="🧠-详细解释与数学证明"><a href="#🧠-详细解释与数学证明" class="headerlink" title="🧠 详细解释与数学证明"></a>🧠 详细解释与数学证明</h3><ul><li>L :环外部分的长度(从链表头到环入口的步数)。</li><li>C :环的长度(环内节点总数)。</li><li>k :慢指针在环内走的步数(相遇时)。</li><li>相遇点:快慢指针第一次相遇的位置。</li><li>环入口:链表进入环的第一个节点。</li></ul><h3 id="❓-为什么能相遇?(数学推导)"><a href="#❓-为什么能相遇?(数学推导)" class="headerlink" title="❓ 为什么能相遇?(数学推导)"></a>❓ 为什么能相遇?(数学推导)</h3><ul><li>初始时,快指针和慢指针都从链表头部开始。</li><li>慢指针速度为 1,设其到达环入口时走了 L 步,此时位置为环入口。</li><li>快指针速度为 2,此时快指针走了 2L步,可能已在环内绕了几圈。</li><li>相遇时:<ul><li>慢指针总步数:L+k(环外 L 步 + 环内 k步)。</li><li>快指针总步数:2(L+k) (速度是慢指针的两倍)。</li></ul></li><li>由于快指针在环内可能多绕了几圈,设其绕了 n 圈(n≥0),则:<br>2(L+k)=L+k+nC<br>化简:<br>L+k=nC<br>这表明,快慢指针相遇时,慢指针在环内走了 k 步,快指针在环内走了 k+nC步,相遇点距离环入口 k步(环内位置为 k mod C )。</li></ul><h3 id="❓-为什么能在环入口相遇?"><a href="#❓-为什么能在环入口相遇?" class="headerlink" title="❓ 为什么能在环入口相遇?"></a>❓ 为什么能在环入口相遇?</h3><ul><li>相遇后,将一个指针(记为 ptr1)重置到链表头部,另一个指针(记为 ptr2)留在相遇点。</li><li>两者以相同速度(每次 1 步)前进:<ul><li>ptr1 从头走 L步到达环入口。</li><li>ptr2 从相遇点(距环入口 k 步)走:<ul><li>相遇点到环入口的距离为 C−k(环内逆向距离)。</li><li>但从相遇点继续向前走 L 步:L=nC−k ptr2 从相遇点走 L 步,相当于在环内走 nC−k 步后回到环入口(因为 nC 为整圈)。</li></ul></li></ul></li><li>因此,ptr1 和 ptr2 会在环入口相遇。</li></ul><h3 id="📝-图示说明(文字描述)"><a href="#📝-图示说明(文字描述)" class="headerlink" title="📝 图示说明(文字描述)"></a>📝 图示说明(文字描述)</h3><p>假设链表为:A -> B -> C -> D -> E -> C(环入口为 C)。</p><ul><li>环外:A -> B(L=2)。</li><li>环内:C -> D -> E -> C(C=3)。</li><li>快慢指针:<ul><li>慢指针:A -> B -> C -> D(走 3 步)。</li><li>快指针:A -> C -> E -> D(走 6 步),在 D 相遇。</li></ul></li><li>相遇点 D 距环入口 C 为 1 步(k=1)。</li><li>L+k=2+1=3=1⋅C。</li><li>ptr1 从 A 走 2 步到 C,ptr2 从 D 走 2 步(D -> E -> C),在 C 相遇。</li></ul><hr><h2 id="👣-快慢指针法的一般流程(简洁版)"><a href="#👣-快慢指针法的一般流程(简洁版)" class="headerlink" title="👣 快慢指针法的一般流程(简洁版)"></a>👣 快慢指针法的一般流程(简洁版)</h2><ol><li>初始化快慢指针(一般都指向起点);</li><li>快指针每次移动两步,慢指针每次移动一步;</li><li>依据场景判断终止条件(如是否相遇、是否到达终点等);</li><li>若需要定位特定位置(如环起点),可引入第三指针从头与慢指针同步推进。</li></ol><hr><h2 id="🧪-常见应用及深入解析"><a href="#🧪-常见应用及深入解析" class="headerlink" title="🧪 常见应用及深入解析"></a>🧪 常见应用及深入解析</h2><h3 id="1-判断链表是否有环"><a href="#1-判断链表是否有环" class="headerlink" title="1. 判断链表是否有环"></a>1. 判断链表是否有环</h3><p><strong>场景描述:</strong> 我们在链表中可能会遇到环形结构的问题,例如,链表中的某个节点指向之前的某个节点,形成一个环。这种情况会导致传统的遍历方法进入无限循环,而快慢指针能够有效地检测环的存在。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">hasCycle</span><span class="params">(ListNode head)</span> {</span><br><span class="line"> <span class="keyword">if</span> (head == <span class="literal">null</span> || head.next == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">slow</span> <span class="operator">=</span> head;</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">fast</span> <span class="operator">=</span> head;</span><br><span class="line"> <span class="keyword">while</span> (fast != <span class="literal">null</span> && fast.next != <span class="literal">null</span>) {</span><br><span class="line"> slow = slow.next;</span><br><span class="line"> fast = fast.next.next;</span><br><span class="line"> <span class="keyword">if</span> (slow == fast) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="2-找到环的起始节点"><a href="#2-找到环的起始节点" class="headerlink" title="2. 找到环的起始节点"></a>2. 找到环的起始节点</h3><p><strong>场景描述:</strong> 当链表中存在环时,除了判断环的存在,我们可能还需要定位环的起始节点。利用快慢指针相遇的特性,我们可以通过从头和相遇点同时开始遍历来找到环的起点。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ListNode <span class="title function_">detectCycle</span><span class="params">(ListNode head)</span> {</span><br><span class="line"> <span class="keyword">if</span> (head == <span class="literal">null</span> || head.next == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">slow</span> <span class="operator">=</span> head, fast = head;</span><br><span class="line"> <span class="keyword">while</span> (fast != <span class="literal">null</span> && fast.next != <span class="literal">null</span>) {</span><br><span class="line"> slow = slow.next;</span><br><span class="line"> fast = fast.next.next;</span><br><span class="line"> <span class="keyword">if</span> (slow == fast) {</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">ptr</span> <span class="operator">=</span> head;</span><br><span class="line"> <span class="keyword">while</span> (ptr != slow) {</span><br><span class="line"> ptr = ptr.next;</span><br><span class="line"> slow = slow.next;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ptr;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="3-寻找链表中点"><a href="#3-寻找链表中点" class="headerlink" title="3. 寻找链表中点"></a>3. 寻找链表中点</h3><p><strong>场景描述:</strong> 在链表中,我们常常需要找到链表的中间节点。通过使用快慢指针,快指针以两倍的速度前进,慢指针则每次走一步,当快指针到达末尾时,慢指针恰好位于链表的中点。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ListNode <span class="title function_">findMiddle</span><span class="params">(ListNode head)</span> {</span><br><span class="line"> <span class="keyword">if</span> (head == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">slow</span> <span class="operator">=</span> head;</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">fast</span> <span class="operator">=</span> head;</span><br><span class="line"> <span class="keyword">while</span> (fast != <span class="literal">null</span> && fast.next != <span class="literal">null</span>) {</span><br><span class="line"> slow = slow.next;</span><br><span class="line"> fast = fast.next.next;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> slow;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="4-数组中查找重复元素"><a href="#4-数组中查找重复元素" class="headerlink" title="4. 数组中查找重复元素"></a>4. 数组中查找重复元素</h3><p><strong>场景描述:</strong> 在数组中,我们可能需要检测是否有重复的元素。通过将数组看作一个链表,我们可以利用快慢指针来有效地找到重复元素。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findDuplicate</span><span class="params">(<span class="type">int</span>[] nums)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">slow</span> <span class="operator">=</span> nums[<span class="number">0</span>];</span><br><span class="line"> <span class="type">int</span> <span class="variable">fast</span> <span class="operator">=</span> nums[<span class="number">0</span>];</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> slow = nums[slow];</span><br><span class="line"> fast = nums[nums[fast]];</span><br><span class="line"> } <span class="keyword">while</span> (slow != fast);</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> <span class="variable">ptr1</span> <span class="operator">=</span> nums[<span class="number">0</span>];</span><br><span class="line"> <span class="type">int</span> <span class="variable">ptr2</span> <span class="operator">=</span> slow;</span><br><span class="line"> <span class="keyword">while</span> (ptr1 != ptr2) {</span><br><span class="line"> ptr1 = nums[ptr1];</span><br><span class="line"> ptr2 = nums[ptr2];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ptr1;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>说明:</p><ul><li>在使用这种算法时,数组的元素 <strong>必须</strong> 在有效的索引范围内,即 <code>nums[i]</code> 必须满足 <code>0 <= nums[i] < 数组长度</code>。</li><li>如果数组中的元素超出了有效索引范围,那么程序会抛出 <code>ArrayIndexOutOfBoundsException</code> 错误。<br>这个限制是快慢指针法在数组中应用时的一个重要前提,确保可以通过元素值来安全地访问数组中的其他元素。</li></ul><h3 id="5-查找链表中倒数第-N-个节点(固定差快慢指针)"><a href="#5-查找链表中倒数第-N-个节点(固定差快慢指针)" class="headerlink" title="5. 查找链表中倒数第 N 个节点(固定差快慢指针)"></a>5. 查找链表中倒数第 N 个节点(固定差快慢指针)</h3><p><strong>场景描述:</strong> 在链表中,我们可能需要找到倒数第 N 个节点。通过让快指针先走 N 步,再让慢指针和快指针同时前进,直到快指针到达链表末尾,慢指针即为倒数第 N 个节点。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ListNode <span class="title function_">findNthFromEnd</span><span class="params">(ListNode head, <span class="type">int</span> n)</span> {</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">fast</span> <span class="operator">=</span> head;</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">slow</span> <span class="operator">=</span> head;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// fast 先走 n 步</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> <span class="keyword">if</span> (fast == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> fast = fast.next;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// fast 和 slow 同步前进</span></span><br><span class="line"> <span class="keyword">while</span> (fast != <span class="literal">null</span>) {</span><br><span class="line"> fast = fast.next;</span><br><span class="line"> slow = slow.next;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> slow;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><strong>说明:</strong>虽然两个指针速度相同,但由于存在固定的“距离差”,当快指针走到末尾时,慢指针恰好在目标节点。可以视为快慢指针的 <strong>滞后型变体</strong>。</p><h3 id="6-判断链表是否为回文结构"><a href="#6-判断链表是否为回文结构" class="headerlink" title="6. 判断链表是否为回文结构"></a>6. 判断链表是否为回文结构</h3><p><strong>场景描述:</strong> 判断一个链表是否为回文链表,意味着要检查链表的前半部分和后半部分是否一致。通过使用快慢指针找到链表中点,再反转后半部分并与前半部分进行比较,可以有效地解决这个问题。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isPalindrome</span><span class="params">(ListNode head)</span> {</span><br><span class="line"> <span class="keyword">if</span> (head == <span class="literal">null</span> || head.next == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 找中点</span></span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">slow</span> <span class="operator">=</span> head, fast = head;</span><br><span class="line"> <span class="keyword">while</span> (fast != <span class="literal">null</span> && fast.next != <span class="literal">null</span>) {</span><br><span class="line"> slow = slow.next;</span><br><span class="line"> fast = fast.next.next;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 反转后半部分</span></span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">prev</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">while</span> (slow != <span class="literal">null</span>) {</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">next</span> <span class="operator">=</span> slow.next;</span><br><span class="line"> slow.next = prev;</span><br><span class="line"> prev = slow;</span><br><span class="line"> slow = next;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 比较两部分</span></span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">left</span> <span class="operator">=</span> head, right = prev;</span><br><span class="line"> <span class="keyword">while</span> (right != <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (left.val != right.val) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> left = left.next;</span><br><span class="line"> right = right.next;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="7-找到两个链表的相交节点"><a href="#7-找到两个链表的相交节点" class="headerlink" title="7. 找到两个链表的相交节点"></a>7. 找到两个链表的相交节点</h3><p><strong>场景描述:</strong> 在两个链表中,可能有一部分节点是共享的。使用快慢指针的技巧,我们可以找到两个链表相交的第一个节点。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ListNode <span class="title function_">getIntersectionNode</span><span class="params">(ListNode headA, ListNode headB)</span> {</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">a</span> <span class="operator">=</span> headA, b = headB;</span><br><span class="line"> <span class="keyword">while</span> (a != b) {</span><br><span class="line"> a = (a == <span class="literal">null</span>) ? headB : a.next;</span><br><span class="line"> b = (b == <span class="literal">null</span>) ? headA : b.next;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> a;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><hr><h2 id="📈-时间与空间复杂度分析"><a href="#📈-时间与空间复杂度分析" class="headerlink" title="📈 时间与空间复杂度分析"></a>📈 时间与空间复杂度分析</h2><table><thead><tr><th>操作</th><th>时间复杂度</th><th>空间复杂度</th><th>备注</th></tr></thead><tbody><tr><td>判断是否有环</td><td>O(n)</td><td>O(1)</td><td>最多遍历所有节点</td></tr><tr><td>寻找中点</td><td>O(n)</td><td>O(1)</td><td>快指针走完链表</td></tr><tr><td>找环起点</td><td>O(n)</td><td>O(1)</td><td>相遇后最多再走 L 步</td></tr><tr><td>找重复数</td><td>O(n)</td><td>O(1)</td><td>数组模拟链表,线性时间收敛</td></tr><tr><td>倒数第 N 个</td><td>O(n)</td><td>O(1)</td><td>两次遍历压缩成一个固定差双指针</td></tr><tr><td>回文链表</td><td>O(n)</td><td>O(1)</td><td>快慢指针+链表反转+比较</td></tr><tr><td>找相交节点</td><td>O(n)</td><td>O(1)</td><td>指针同步抵消长度差</td></tr></tbody></table><hr><h2 id="✅-使用建议与注意事项"><a href="#✅-使用建议与注意事项" class="headerlink" title="✅ 使用建议与注意事项"></a>✅ 使用建议与注意事项</h2><ul><li>避免空指针访问。</li><li>明确快慢速度关系,步长不能随意变。</li><li>数组转链表时注意索引越界问题。</li></ul><hr><h2 id="🌍-实际应用场景"><a href="#🌍-实际应用场景" class="headerlink" title="🌍 实际应用场景"></a>🌍 实际应用场景</h2><ul><li>检测链表/图结构是否存在环;</li><li>操作系统中检测死锁;</li><li>查找循环依赖;</li><li>数组类问题的环判断(如找重复数)。</li></ul><hr><h2 id="📎-快慢指针的非典型变种与对比算法"><a href="#📎-快慢指针的非典型变种与对比算法" class="headerlink" title="📎 快慢指针的非典型变种与对比算法"></a>📎 快慢指针的非典型变种与对比算法</h2><p>虽然“快慢指针”一词常用于速度不同的双指针同步推进,但并非所有使用双指针的算法都属于快慢指针法。<br>以下是一种常见的“窗口内趋势检测”方法,看起来像双指针,实则与快慢指针的核心思想不同:</p><h3 id="案例:局部趋势检测"><a href="#案例:局部趋势检测" class="headerlink" title="案例:局部趋势检测"></a>案例:局部趋势检测</h3><p>数据是按时间排序的,例如一年中每个月的数据,或一个月中每天的数据。<br><strong>问题描述:</strong></p><blockquote><p>在任意一个点,判断其后 5 天以内是否有超过 ±30% 的涨幅或跌幅。</p></blockquote><p><strong>思路:</strong></p><ul><li>外层指针遍历每个时间点 <code>i</code>;</li><li>内层在 <code>i+1</code> 到 <code>i+5</code> 范围内查找;</li><li>如果 <code>(data[j] - data[i]) / data[i]</code> 的绝对值大于 30%,记录该趋势。</li></ul><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">hasStrongTrend</span><span class="params">(<span class="type">double</span>[] data)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> data.length;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i + <span class="number">1</span>; j <= i + <span class="number">5</span> && j < n; j++) {</span><br><span class="line"> <span class="type">double</span> <span class="variable">ratio</span> <span class="operator">=</span> (data[j] - data[i]) / data[i];</span><br><span class="line"> <span class="keyword">if</span> (Math.abs(ratio) >= <span class="number">0.3</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><strong>分析:</strong></p><ul><li>❌ 不属于典型快慢指针:没有速度差,没有相遇判定;</li><li>✅ 更接近滑动窗口 / 固定范围扫描;</li><li>✅ 可归类为“窗口内双指针比较法”或“局部趋势检测”。</li></ul><p>这类算法是快慢指针的 <strong>并行指针结构的一种衍生对比形式</strong>,常用于时间序列分析、金融涨跌监测、局部波动检测等。</p><hr><h2 id="📚-拓展阅读"><a href="#📚-拓展阅读" class="headerlink" title="📚 拓展阅读"></a>📚 拓展阅读</h2><ul><li><a href="https://en.wikipedia.org/wiki/Cycle_detection">Floyd 判圈算法 - Wikipedia</a></li><li>LeetCode 141, 142, 287, 876 等经典题目</li></ul><hr><p>快慢指针法是一种空间效率极高的“动态双指针法”,也是刷题、面试、系统开发中极具实战价值的算法技巧。熟练掌握它,将帮助你轻松解决一类复杂的问题!</p><hr>]]></content>
<summary type="html"><hr>
<h1 id="龟兔赛跑:快慢指针法详解(Floyd’s-Tortoise-and-Hare-Algorithm)"><a href="#龟兔赛跑:快慢指针法详解(Floyd’s-Tortoise-and-Hare-Algorithm)" class="headerli</summary>
<category term="算法" scheme="https://blog.restlessnight.cn/categories/%E7%AE%97%E6%B3%95/"/>
<category term="算法" scheme="https://blog.restlessnight.cn/tags/%E7%AE%97%E6%B3%95/"/>
<category term="Algorithm" scheme="https://blog.restlessnight.cn/tags/Algorithm/"/>
<category term="指针" scheme="https://blog.restlessnight.cn/tags/%E6%8C%87%E9%92%88/"/>
<category term="快慢指针" scheme="https://blog.restlessnight.cn/tags/%E5%BF%AB%E6%85%A2%E6%8C%87%E9%92%88/"/>
<category term="LeetCode" scheme="https://blog.restlessnight.cn/tags/LeetCode/"/>
</entry>
<entry>
<title>时光错位:一次MySQL主从同步延迟的排查与优化</title>
<link href="https://blog.restlessnight.cn/2025/04/03/shi-guang-cuo-wei-yi-ci-mysql-zhu-cong-tong-bu-yan-chi-de-pai-cha-yu-you-hua/"/>
<id>https://blog.restlessnight.cn/2025/04/03/shi-guang-cuo-wei-yi-ci-mysql-zhu-cong-tong-bu-yan-chi-de-pai-cha-yu-you-hua/</id>
<published>2025-04-03T09:06:53.000Z</published>
<updated>2025-04-09T02:49:35.972Z</updated>
<content type="html"><![CDATA[<h2 id="时光错位:一次MySQL主从同步延迟的排查与优化"><a href="#时光错位:一次MySQL主从同步延迟的排查与优化" class="headerlink" title="时光错位:一次MySQL主从同步延迟的排查与优化"></a>时光错位:一次MySQL主从同步延迟的排查与优化</h2><h2 id="问题出现背景"><a href="#问题出现背景" class="headerlink" title="问题出现背景"></a>问题出现背景</h2><p>在生产环境中,执行 <code>ALTER TABLE</code> 语句向某张大表新增字段后,主从同步出现严重延迟。从库 <code>SHOW SLAVE STATUS</code> 显示 <code>Seconds_Behind_Master</code> 迅速增长,从正常的几秒飙升至数小时甚至上万秒,同时从库查询开始频繁超时,业务访问受阻。<code>SHOW PROCESSLIST</code> 发现大量查询处于 <code>Waiting for table metadata lock</code> 或 <code>Waiting for table flush</code> 状态,导致连接数剧增,最终触及 <code>max_connections</code> 限制,影响整个系统的稳定性。</p><h2 id="问题解析"><a href="#问题解析" class="headerlink" title="问题解析"></a><strong>问题解析</strong></h2><h3 id="1-查询从库状态"><a href="#1-查询从库状态" class="headerlink" title="1. 查询从库状态"></a><strong>1. 查询从库状态</strong></h3><p>SHOW SLAVE STATUS\G</p><p>具体指标查看:<a href="#%E5%85%B3%E9%94%AE%E5%AD%97%E6%AE%B5%E8%A7%A3%E6%9E%90"><strong>关键字段解析</strong></a></p><h3 id="2-结果发现"><a href="#2-结果发现" class="headerlink" title="2. 结果发现"></a><strong>2. 结果发现</strong></h3><p>Slave_SQL_Running_State 值为: <code>Waiting for table metadata lock</code></p><ul><li>正常值:<code>Reading event from the relay log</code></li></ul><p><strong>Slave_IO_State</strong></p><ul><li>正常值:<code>Waiting for master to send event</code>(等待主库发送事件)</li></ul><p>Slave_IO_Running:</p><ul><li>正常值:<code>Yes</code>(等待主库发送事件)</li></ul><p>发现 Slave_SQL_Running_State 值异常,并没有出现Duplicate column、Unknown、Can’t 、Eorr 等能看出是错误的地方。</p><h3 id="3-针对Waiting-for-table-metadata-lock-排查分析"><a href="#3-针对Waiting-for-table-metadata-lock-排查分析" class="headerlink" title="3. 针对Waiting for table metadata lock 排查分析"></a><strong>3. 针对<code>Waiting for table metadata lock</code> 排查分析</strong></h3><p><strong>可能的原因分析</strong></p><ol><li>主库执行了 DDL 语句<ul><li><code>ALTER TABLE</code>、<code>CREATE INDEX</code>、<code>DROP TABLE</code> 等操作可能会导致锁等待。</li><li>如果从库正在执行 SQL,而主库又修改了表结构,从库可能会等待 metadata lock 释放。</li></ul></li><li>主库长时间未提交事务<ul><li>事务未提交,导致从库 SQL 线程等待执行,阻塞后续操作。</li></ul></li><li>从库上有并发查询占用了表<ul><li>从库上有查询正在使用表,而 <code>SQL_THREAD</code> 需要修改表结构,导致等待。</li></ul></li><li>使用了 <code>LOCK TABLES</code><ul><li>如果主库或者从库有 <code>LOCK TABLES</code>,可能会阻止复制线程获取 metadata lock。</li></ul></li></ol><p>通过现象分析 主库执行DDL 语句但并未阻塞,主库正常访问,而且从库的DDL并未执行,说明DDL语句已经阻塞了,排除1,2可能性,查询并未锁表现象,排除4,最大可能就是3</p><h3 id="4-查询未提交的事务"><a href="#4-查询未提交的事务" class="headerlink" title="4. 查询未提交的事务"></a><strong>4. 查询未提交的事务</strong></h3><p><strong>通show processlist</strong> 查到不少等待锁,但这些锁的时间都比较短 明显是DDL阻塞之后的查询也被阻塞</p><p><img src="/../image/image-17440951411051.png" alt="image"></p><p>执行以下 SQL 语句,检查 <code>INFORMATION_SCHEMA.INNODB_TRX</code> 表,定位未提交的事务:</p><figure class="highlight sql"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line"> trx_id,</span><br><span class="line"> trx_mysql_thread_id,</span><br><span class="line"> trx_query,</span><br><span class="line"> trx_state,</span><br><span class="line"> trx_wait_started,</span><br><span class="line"> TIMESTAMPDIFF(<span class="keyword">SECOND</span>, trx_wait_started, NOW()) <span class="keyword">AS</span> wait_time_seconds</span><br><span class="line"><span class="keyword">FROM</span></span><br><span class="line"> INFORMATION_SCHEMA.INNODB_TRX;</span><br><span class="line">或</span><br><span class="line"> <span class="keyword">SELECT</span> <span class="operator">*</span></span><br><span class="line"> <span class="keyword">FROM</span></span><br><span class="line"> information_schema.INNODB_TRX</span><br><span class="line"> <span class="keyword">WHERE</span> STATE<span class="operator">=</span><span class="string">'ACTIVE'</span>;</span><br></pre></td></tr></tbody></table></figure><p>查询结果显示存在 5 个未提交的事务,其中部分事务已运行超过 8 天,并且执行时间仍在持续增加。</p><h3 id="5-关联-SHOW-PROCESSLIST-进行确认"><a href="#5-关联-SHOW-PROCESSLIST-进行确认" class="headerlink" title="5. 关联 SHOW PROCESSLIST 进行确认"></a><strong>5. 关联 <code>SHOW PROCESSLIST</code> 进行确认</strong></h3><p>通过 <code>trx_mysql_thread_id</code> 关联 <code>SHOW PROCESSLIST</code> 进一步确认事务详情,发现一个 ID 为 <code>19138566</code> 的线程,状态如下:</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Id User Host db Command Time State Info</span><br><span class="line">19138566 readuser 10.11.11.11:50369 ioscar_customersystem_customerbasic Query 648905 Creating <span class="built_in">sort</span> index SELECT concat(a.id) AS <span class="string">'案件ID'</span>, a.customer_name AS <span class="string">'客户'</span>, ac.account_age AS <span class="string">'账龄段'</span>,</span><br></pre></td></tr></tbody></table></figure><p>该查询已运行 <strong>648905 秒(约 8 天)</strong>,且执行时间仍在增长,表明该事务可能已进入死锁或长时间未提交,严重影响 MySQL 性能和主从同步。</p><h3 id="6-终止异常事务"><a href="#6-终止异常事务" class="headerlink" title="6. 终止异常事务"></a><strong>6. 终止异常事务</strong></h3><p>由于该事务占用资源并可能阻塞主从同步,立即执行 <code>KILL</code> 命令终止该事务:</p><figure class="highlight sql"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">KILL <span class="number">19138566</span>;</span><br></pre></td></tr></tbody></table></figure><h3 id="7-观察从库状态变化"><a href="#7-观察从库状态变化" class="headerlink" title="7. 观察从库状态变化"></a><strong>7. 观察从库状态变化</strong></h3><p>执行 <code>SHOW SLAVE STATUS;</code> 查看主从同步状态,发现 <code>Slave_SQL_Running_State</code> 由 <strong>Waiting for Master to send event</strong> 变更为 <strong>altering table</strong>,说明从库正在执行 <code>ALTER TABLE</code> 语句,即之前被阻塞的 DDL 语句开始恢复执行。</p><p>随着 <code>ALTER TABLE</code> 事件完成,<code>Slave_SQL_Running_State</code> 逐步变更为:</p><ul><li><code>Waiting for Slave Worker queue</code></li><li><code>Reading event from the relay log</code></li></ul><p>此时,说明从库已恢复正常同步,并正在继续处理 relay log。</p><h3 id="8-监控主从延迟恢复情况"><a href="#8-监控主从延迟恢复情况" class="headerlink" title="8. 监控主从延迟恢复情况"></a><strong>8. 监控主从延迟恢复情况</strong></h3><p>查询 <code>SHOW SLAVE STATUS;</code>,关注 <code>Seconds_Behind_Master</code>(主从延迟时间):</p><ul><li>事务终止前,<code>Seconds_Behind_Master</code> 超过 <strong>9W 秒(约 25 小时)</strong></li><li>终止事务后,<code>Seconds_Behind_Master</code> 开始逐步缩小</li><li>经过数小时观察,主从延迟最终缩小至 <strong>1 秒以内</strong></li><li>验证主从数据,确保数据一致性</li></ul><h3 id="9-结论"><a href="#9-结论" class="headerlink" title="9. 结论"></a><strong>9. 结论</strong></h3><p>本次 MySQL 主从延迟的根因是 <strong>长期未提交的事务阻塞了主库的 binlog 生成,从而影响从库的同步</strong>。通过以下步骤成功修复问题:</p><ol><li>查询未提交事务,定位长期运行 SQL</li><li>通过 <code>SHOW PROCESSLIST</code> 确认事务状态</li><li><code>KILL</code> 关键阻塞事务</li><li>观察 <code>SHOW SLAVE STATUS</code> 变化,验证 <code>Slave_SQL_Running_State</code> 状态</li><li>监控 <code>Seconds_Behind_Master</code> 缩小至 1 秒,确保主从同步恢复</li></ol><p>至此,主从同步恢复正常,问题解决。</p><h2 id="问题回顾"><a href="#问题回顾" class="headerlink" title="问题回顾"></a>问题回顾</h2><p>后续分析事故产生原理</p><p>主要从是 <a href="#MySQL%E5%85%B1%E4%BA%AB%E9%94%81%E5%92%8C%E7%8B%AC%E5%8D%A0%E9%94%81">MySQL共享锁和独占锁</a> 分析</p><p><strong>发生 <code>Waiting for table metadata lock</code> 的原因</strong></p><p>当 <code>ALTER TABLE</code> 需要 <code>X 锁</code>,但表上有正在执行的 <code>SELECT</code> 时,<code>ALTER TABLE</code> 会<strong>进入等待状态</strong>。这时 <strong>新的查询(SELECT)也会被阻塞</strong>,即使它只是想获取 S 锁!</p><p><strong>📌 详细的锁定过程</strong></p><ol><li><code>SELECT</code> 语句执行,获取 **<code>S 锁</code>**(共享 metadata lock)。</li><li>你运行 <code>ALTER TABLE</code>,它需要 <strong><code>X 锁</code><strong>(独占 metadata lock),所以它必须</strong>等待</strong>所有 <code>S 锁</code> 释放。</li><li><strong><code>ALTER TABLE</code> 在等待时,新来的 <code>SELECT</code> 也会被阻塞!</strong><ul><li>因为 MySQL <strong>会保证所有等待中的事务按顺序执行</strong>。</li><li><code>ALTER TABLE</code> 必须先执行完,新的 <code>SELECT</code> 才能继续。</li></ul></li></ol><h2 id="关键字段解析"><a href="#关键字段解析" class="headerlink" title="关键字段解析"></a><strong>关键字段解析</strong></h2><p><code>SHOW SLAVE STATUS\G</code></p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line">mysql> show slave status\G</span><br><span class="line">*************************** 1. row ***************************</span><br><span class="line"> Slave_IO_State: Waiting <span class="keyword">for</span> master to send event --IO thread的状态</span><br><span class="line"> Master_Host: 10.10.10.10 -- 主库的地址 </span><br><span class="line"> Master_User: repl -- 用于连接主库复制账号(这个账号是在主库上创建)</span><br><span class="line"> Master_Port: 3300 -- 主库的端口</span><br><span class="line"> Connect_Retry: 10 -- 连接重试之间的秒数(默认 60)</span><br><span class="line"> Master_Log_File: mysql-bin.005395 -- I/O 线程当前正在读取的主库的二进制日志文件名称。</span><br><span class="line"> Read_Master_Log_Pos: 684976832 -- I/O 线程已读取的当前主库二进制日志文件中的位点</span><br><span class="line"> Relay_Log_File: dd-relay.000063 -- SQL线程正在读取和执行的中继日志名称</span><br><span class="line"> Relay_Log_Pos: 684953253 -- SQL线程正在读取和执行的当前中继日志的位点</span><br><span class="line"> Relay_Master_Log_File: mysql-bin.005395 -- SQL 线程执行的最新事件 对应在主库上的二进制日志文件名称。</span><br><span class="line"> Slave_IO_Running: Yes -- IO线程是否已启动并已成功连接到主库</span><br><span class="line"> Slave_SQL_Running: Yes -- SQL线程是否启动。</span><br><span class="line"> Replicate_Do_DB: -- 需要复制的DB</span><br><span class="line"> Replicate_Ignore_DB: -- 复制忽略的DB</span><br><span class="line"> Replicate_Do_Table: -- 需要复制的表</span><br><span class="line"> Replicate_Ignore_Table: -- 复制忽略的表</span><br><span class="line"> Replicate_Wild_Do_Table: -- 用于指定需要复制的数据库表,支持通配符(wildcard)的形式</span><br><span class="line"> Replicate_Wild_Ignore_Table: -- 用于指定需要忽略(不复制)的数据库表,同样支持通配符的形式。</span><br><span class="line"> Last_Errno: 0 -- Last_SQL_Errno的别名</span><br><span class="line"> Last_Error: -- Last_SQL_Error的别名</span><br><span class="line"> Skip_Counter: 0 -- 系统变sql_slave_skip_counter 的当前值 (从库跳过的SQL数量)</span><br><span class="line"> Exec_Master_Log_Pos: 684953080 -- SQL线程已经读取和执行过的中继日志 对应在主库二进制日志文件的位点</span><br><span class="line"> Relay_Log_Space: 684977292 -- 所有现有中继日志文件的总大小。</span><br><span class="line"> Until_Condition: None -- start slave 中制定 <span class="keyword">until</span> 语句</span><br><span class="line"> Until_Log_File: -- start slave 中制定 <span class="keyword">until</span> 语句</span><br><span class="line"> Until_Log_Pos: 0 -- start slave 中制定 <span class="keyword">until</span> 语句</span><br><span class="line"> Master_SSL_Allowed: No -- 是否允许与源的 SSL 连接</span><br><span class="line"> Master_SSL_CA_File: -- 指定用于验证主服务器证书的证书颁发机构(CA)文件的路径</span><br><span class="line"> Master_SSL_CA_Path: -- 指定用于验证主服务器证书的证书颁发机构(CA)路径的路径</span><br><span class="line"> Master_SSL_Cert: -- 指定从服务器的 SSL 证书文件的路径</span><br><span class="line"> Master_SSL_Cipher: -- 指定在 SSL 通信中使用的密码套件</span><br><span class="line"> Master_SSL_Key: -- 指定从服务器的 SSL 私钥文件的路径</span><br><span class="line"> Seconds_Behind_Master: 0 -- 主从延迟</span><br><span class="line">Master_SSL_Verify_Server_Cert: No -- 表示是否验证主服务器的 SSL 证书。</span><br><span class="line"> Last_IO_Errno: 0 -- 导致IO线程停止的最近一次的错误码,Errno :0 表示表示没有错误</span><br><span class="line"> Last_IO_Error: -- 导致IO线程停止的最近的错误信息 。Erro为空表示没有错误</span><br><span class="line"> Last_SQL_Errno: 0 -- 导致SQL线程停止的最近的错误码。Errno :0 表示没有错误</span><br><span class="line"> Last_SQL_Error: -- 导致SQL线程停止的错误信息,Erro为空表示没有错误</span><br><span class="line"> Replicate_Ignore_Server_Ids: -- 忽略复制的主库的server_id</span><br><span class="line"> Master_Server_Id: 181323300 -- 主库的参数server_id的值</span><br><span class="line"> Master_UUID: 127ef593-1826-11eb-8a97-6c92bf7d39de -- 主库参数server_uuid的值</span><br><span class="line"> Master_Info_File: mysql.slave_master_info -- 在从库上存储主库信息的文件或表</span><br><span class="line"> SQL_Delay: 0 -- 从库延迟主库多少秒</span><br><span class="line"> SQL_Remaining_Delay: NULL -- 当Slave_SQL_Running_State为 时 Waiting <span class="keyword">until</span> MASTER_DELAY seconds after master executed event,该字段包含剩余延迟秒数。其他时候,该字段为 NULL。</span><br><span class="line"> Slave_SQL_Running_State: Slave has <span class="built_in">read</span> all relay <span class="built_in">log</span>; waiting <span class="keyword">for</span> more updates -- SQL线程的运行状态</span><br><span class="line"> Master_Retry_Count: 86400 -- 在连接丢失的情况下,从库可以尝试重新连接到主库的次数。</span><br><span class="line"> Master_Bind: --</span><br><span class="line"> Last_IO_Error_Timestamp: -- 最近的I/O 线程发生错误的时间 格式YYMMDD hh:mm:ss</span><br><span class="line"> Last_SQL_Error_Timestamp: -- 最近的SQL 线程发生错误的时间 格式YYMMDD hh:mm:ss</span><br><span class="line"> Master_SSL_Crl: -- 指定撤销列表 (CRL) 文件的路径,该文件包含已被撤销的 SSL 证书列表</span><br><span class="line"> Master_SSL_Crlpath: -- 指定撤销列表 (CRL) 文件的路径,该文件包含已被撤销的 SSL 证书列表</span><br><span class="line"> Retrieved_Gtid_Set: 127ef593-1826-11eb-8a97-6c92bf7d39de:330411-2764671 -- 从库已经接收到的GTID的集合(I/O线程),如果GTID模式没有开启则为空。这个值是现在存在或者已经存在在relay <span class="built_in">log</span>中的GTID集合</span><br><span class="line"> Executed_Gtid_Set: 127ef593-1826-11eb-8a97-6c92bf7d39de:1-2764671,</span><br><span class="line">3133d0b5-8d65-11e7-9f2e-c88d83a9846a:1-12697883,</span><br><span class="line">657b7d6b-8d60-11e7-b85f-6c92bf4e09e6:1-1661102840 -- 已经被写进binlog的GTID的集合(SQL线程),这个值和 系统参数 gtid_executed 相同。也和在该实例上执行 show master status 中的Executed_Gtid_Set 值相同</span><br><span class="line"> Auto_Position: 1 -- 如果正在使用自动定位1;否则为 0。</span><br><span class="line"> Replicate_Rewrite_DB: -- 用于指定需要在主从复制过程中进行数据库名重写的规则。</span><br><span class="line"> Channel_Name: -- 正在显示的复制通道</span><br><span class="line"> Master_TLS_Version: -- 源上使用的 TLS 版本</span><br></pre></td></tr></tbody></table></figure><p>执行后,可能会看到如下重要字段:</p><ul><li><code>SHOW SLAVE STATUS\G</code> 主要用于监控从库同步状态。</li><li><code>Slave_IO_Running</code> 和 <code>Slave_SQL_Running</code> 应该都是 <code>Yes</code>。</li><li><code>Seconds_Behind_Master</code> 应该尽量接近 <code>0</code>。</li><li>如果有错误,可以 <code>STOP SLAVE</code>,检查 <code>Last_Error</code>,必要时重新设置主从关系</li></ul><p><code>Slave_IO_Running</code> 字段用于指示 MySQL 复制中 I/O 线程的运行状态,常见的值包括:</p><table><thead><tr><th><strong>值</strong></th><th><strong>含义</strong></th></tr></thead><tbody><tr><td><code>Yes</code></td><td>I/O 线程正在运行,正常从主库读取 binlog</td></tr><tr><td><code>No</code></td><td>I/O 线程未运行,可能由于错误或手动停止</td></tr><tr><td><code>Connecting</code></td><td>I/O 线程正在尝试连接主库,但尚未成功</td></tr></tbody></table><h3 id="常见情况解析"><a href="#常见情况解析" class="headerlink" title="常见情况解析"></a><strong>常见情况解析</strong></h3><ol><li><strong>正常状态</strong><ul><li><code>Slave_IO_Running: Yes</code></li><li>说明从库成功连接到主库,并在持续接收 binlog。</li></ul></li><li><strong>连接失败</strong><ul><li><code>Slave_IO_Running: Connecting</code></li><li>说明从库正在尝试连接主库,但尚未成功,可能是:<ul><li>主库地址 (<code>Master_Host</code>) 配置错误</li><li>主库未开启 <code>binlog</code></li><li>端口 (<code>Master_Port</code>) 未开放</li><li>账户或密码错误 (<code>Master_User</code> / <code>Master_Password</code>)</li></ul></li></ul></li><li><strong>I/O 线程停止</strong><ul><li><code>Slave_IO_Running: No</code></li><li>可能原因:<ul><li>手动执行了 <code>STOP SLAVE</code></li><li>网络问题导致连接断开</li><li>认证失败(账号或权限问题)</li><li><code>Last_IO_Error</code> 字段中可能会有详细错误信息</li></ul></li></ul></li></ol><p><code>Slave_IO_State</code> 是 <code>SHOW SLAVE STATUS\G</code> 输出中的一个字段,它描述了 MySQL 复制中 <strong>I/O 线程当前的状态</strong>,表示它正在执行的操作。</p><h3 id="常见-Slave-IO-State-值及含义"><a href="#常见-Slave-IO-State-值及含义" class="headerlink" title="常见 Slave_IO_State 值及含义"></a><strong>常见 <code>Slave_IO_State</code> 值及含义</strong></h3><table><thead><tr><th><code>Slave_IO_State</code> 值</th><th>说明</th></tr></thead><tbody><tr><td><strong>Waiting for master to send event</strong></td><td>从库已经成功连接主库,并在等待主库发送 binlog(正常状态)。</td></tr><tr><td><strong>Connecting to master</strong></td><td>正在尝试连接主库,但连接尚未建立,可能是主库未启动或网络问题。</td></tr><tr><td><strong>Waiting for the slave SQL thread to free relay log</strong></td><td>SQL 线程执行过慢,I/O 线程等待 SQL 线程处理 relay log。</td></tr><tr><td><strong>Waiting for master update</strong></td><td>说明主库上没有新的 binlog 事件,I/O 线程在等待更新(通常无问题)。</td></tr><tr><td><strong>Reconnecting after a failed binlog dump request</strong></td><td>从库尝试重新连接主库,可能是由于网络问题或主库重启导致连接断开。</td></tr><tr><td><strong>Waiting for master connection</strong></td><td>复制未正常启动,可能 <code>START SLAVE</code> 尚未执行,或者连接参数错误。</td></tr><tr><td><strong>Queueing master event to the relay log</strong></td><td>I/O 线程正在将主库 binlog 事件写入 relay log(正常状态)。</td></tr><tr><td><strong>Waiting to reconnect after a failed master event read</strong></td><td>从库读取主库 binlog 失败,正在等待重连。可能是主库关闭、网络异常等原因。</td></tr><tr><td><strong>Waiting for master to send event (after aborting replication due to an error)</strong></td><td>发生复制错误,导致 I/O 线程终止,可能需要手动修复并重启 <code>START SLAVE</code>。</td></tr></tbody></table><h3 id="title-时光错位:一次MySQL主从同步延迟的排查与优化"><a href="#title-时光错位:一次MySQL主从同步延迟的排查与优化" class="headerlink" title="title: 时光错位:一次MySQL主从同步延迟的排查与优化"></a>title: 时光错位:一次MySQL主从同步延迟的排查与优化</h3><h3 id="title-时光错位:一次MySQL主从同步延迟的排查与优化-1"><a href="#title-时光错位:一次MySQL主从同步延迟的排查与优化-1" class="headerlink" title="title: 时光错位:一次MySQL主从同步延迟的排查与优化"></a>title: 时光错位:一次MySQL主从同步延迟的排查与优化</h3><h3 id="Slave-SQL-Running-State-解析"><a href="#Slave-SQL-Running-State-解析" class="headerlink" title="Slave_SQL_Running_State 解析"></a><strong><code>Slave_SQL_Running_State</code> 解析</strong></h3><p><code>Slave_SQL_Running_State</code> 主要描述 <strong>从库 SQL 线程的当前状态</strong>,可以帮助判断从库是否正常执行 <strong>binlog 事件</strong>。</p><hr><h3 id="📌-常见状态解析"><a href="#📌-常见状态解析" class="headerlink" title="📌 常见状态解析"></a><strong>📌 常见状态解析</strong></h3><p>执行 <code>SHOW SLAVE STATUS\G</code> 可能会看到以下 <code>Slave_SQL_Running_State</code>:</p><table><thead><tr><th>状态</th><th>说明</th><th>解决方案(如果有问题)</th></tr></thead><tbody><tr><td><strong>Reading event from the relay log</strong></td><td>SQL 线程正在从 relay log 读取 binlog 事件,并准备执行</td><td><strong>正常状态</strong>,无需处理</td></tr><tr><td><strong>Waiting for dependent transaction to commit</strong></td><td>等待前一个事务提交(事务串行化导致等待)</td><td><strong>正常状态</strong>,但如果卡住太久,检查 <code>SHOW PROCESSLIST;</code></td></tr><tr><td><strong>Waiting for master to send event</strong></td><td>从库 I/O 线程在等待主库发送 binlog 事件</td><td><strong>正常状态</strong>,但如果长时间无进展,检查 <code>Slave_IO_Running</code></td></tr><tr><td><strong>Slave has read all relay log; waiting for more updates</strong></td><td>从库 SQL 线程已经执行完 relay log,等待新的数据</td><td><strong>正常状态</strong></td></tr><tr><td><strong>Waiting for table metadata lock</strong></td><td>被 <strong>metadata lock</strong> 阻塞,导致 SQL 线程无法继续</td><td>参见 本文 <strong>解决方案</strong></td></tr><tr><td><strong>Waiting for an event from Coordinator</strong></td><td>适用于多线程复制(MTS),SQL 线程在等待事件分配</td><td><strong>正常状态</strong>,但如果卡住,检查 <code>SHOW PROCESSLIST;</code></td></tr><tr><td><strong>Error ‘…’ on query. Default database: ‘…’. Query: ‘…’</strong></td><td>复制 SQL 线程遇到错误,可能导致复制停止</td><td>检查 <code>Last_SQL_Error</code> 并修复错误</td></tr><tr><td><strong>NULL(空值)</strong></td><td>SQL 线程未运行(可能已停止)</td><td><code>START SLAVE SQL_THREAD;</code> 重新启动</td></tr></tbody></table><h2 id="MySQL共享锁和独占锁"><a href="#MySQL共享锁和独占锁" class="headerlink" title="MySQL共享锁和独占锁"></a>MySQL<strong>共享锁</strong>和独占锁</h2><p>在 MySQL 数据库中,锁机制是确保数据一致性和完整性的重要手段,主要分为<strong>共享锁</strong>(Shared Lock,简称 S 锁)和<strong>独占锁</strong>(Exclusive Lock,简称 X 锁)。</p><h3 id="共享锁(S-锁)"><a href="#共享锁(S-锁)" class="headerlink" title="共享锁(S 锁)"></a>共享锁(S 锁)</h3><p>共享锁允许多个事务同时读取同一数据,而不会相互阻塞。当一个事务对数据加上共享锁后,其他事务也可以对该数据加共享锁,但不能加独占锁。这意味着在持有共享锁的情况下,数据只能被读取,不能被修改。</p><p><strong>应用场景:</strong></p><p>当需要读取某条记录并希望防止其他事务对其进行修改时,可以使用共享锁。在 MySQL 中,可以通过在 <code>SELECT</code> 语句后添加 <code>LOCK IN SHARE MODE</code> 来实现:</p><figure class="highlight sql"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">START</span> TRANSACTION;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> your_table <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span> LOCK <span class="keyword">IN</span> SHARE MODE;</span><br><span class="line"><span class="comment">-- 其他操作</span></span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></tbody></table></figure><p>上述语句会对 <code>your_table</code> 中满足条件的记录加上共享锁,直到事务提交或回滚后释放。</p><h3 id="独占锁(X-锁)"><a href="#独占锁(X-锁)" class="headerlink" title="独占锁(X 锁)"></a>独占锁(X 锁)</h3><p>独占锁又称为排他锁或写锁,在同一时刻只允许一个事务对数据进行修改。当一个事务对数据加上独占锁后,其他事务既不能加共享锁,也不能加独占锁,必须等待锁的释放。这确保了数据的修改操作是互斥的,防止了并发修改导致的数据不一致问题。</p><p><strong>应用场景:</strong></p><p>当需要更新或删除某条记录,并希望在操作完成前防止其他事务对其进行读取或修改时,可以使用独占锁。在 MySQL 中,<code>UPDATE</code>、<code>DELETE</code> 等操作会自动对涉及的记录加独占锁。如果需要在 <code>SELECT</code> 查询时手动加独占锁,可以使用 <code>FOR UPDATE</code>:</p><figure class="highlight sql"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">START</span> TRANSACTION;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> your_table <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span> <span class="keyword">FOR</span> <span class="keyword">UPDATE</span>;</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></tbody></table></figure><p>上述语句会对 <code>your_table</code> 中满足条件的记录加上独占锁,直到事务提交或回滚后释放。</p><h3 id="共享锁与独占锁的兼容性"><a href="#共享锁与独占锁的兼容性" class="headerlink" title="共享锁与独占锁的兼容性"></a>共享锁与独占锁的兼容性</h3><p>共享锁和独占锁之间的兼容性如下:</p><ul><li><strong>共享锁 vs. 共享锁</strong>:兼容,多个事务可以同时持有共享锁。</li><li><strong>共享锁 vs. 独占锁</strong>:不兼容,若一个事务持有共享锁,其他事务不能获取独占锁,反之亦然。</li><li><strong>独占锁 vs. 独占锁</strong>:不兼容,一个事务持有独占锁时,其他事务不能获取独占锁。</li></ul><h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><ul><li><strong>默认读取行为</strong>:在 MySQL 的 InnoDB 存储引擎中,普通的 <code>SELECT</code> 语句默认使用<strong>快照读</strong>,不会加任何锁,而是通过多版本并发控制(MVCC)机制读取数据的快照。只有在显式使用 <code>LOCK IN SHARE MODE</code> 或 <code>FOR UPDATE</code> 时,才会对读取的数据加锁。</li><li><strong>死锁与性能</strong>:过度使用锁可能导致死锁或性能下降,因此应根据具体需求合理使用锁机制,避免不必要的锁争用。</li></ul><p>通过正确理解和使用共享锁与独占锁,可以有效控制并发事务对数据的访问,确保数据的正确性和一致性。</p>]]></content>
<summary type="html"><h2 id="时光错位:一次MySQL主从同步延迟的排查与优化"><a href="#时光错位:一次MySQL主从同步延迟的排查与优化" class="headerlink" title="时光错位:一次MySQL主从同步延迟的排查与优化"></a>时光错位:一次MySQL主从</summary>
<category term="MySQL" scheme="https://blog.restlessnight.cn/categories/MySQL/"/>
<category term="MySQL" scheme="https://blog.restlessnight.cn/tags/MySQL/"/>
<category term="主从同步" scheme="https://blog.restlessnight.cn/tags/%E4%B8%BB%E4%BB%8E%E5%90%8C%E6%AD%A5/"/>
</entry>
<entry>
<title>隐匿之符:一次关于负号的雪花追溯</title>
<link href="https://blog.restlessnight.cn/2025/04/03/yin-ni-zhi-fu-yi-ci-guan-yu-fu-hao-de-xue-hua-zhui-su/"/>
<id>https://blog.restlessnight.cn/2025/04/03/yin-ni-zhi-fu-yi-ci-guan-yu-fu-hao-de-xue-hua-zhui-su/</id>
<published>2025-04-03T09:06:53.000Z</published>
<updated>2025-04-16T09:44:28.676Z</updated>
<content type="html"><![CDATA[<blockquote><p><em>在 64 位的世界里,最左边的那一位,决定了一切。</em></p></blockquote><h1 id="一、背景"><a href="#一、背景" class="headerlink" title="一、背景"></a><strong>一、背景</strong></h1><p>在测试环境中,发现数据库中的 ID 值出现负数现象,第一反应是 ID 超过 long 类型能表示的最大值,导致数据转成负值。</p><p>Java 中 long 类型的最大值为 2^63 - 1 = 9223372036854775807,超过该值后会转成负值。</p><h1 id="二、实际环境分析数据"><a href="#二、实际环境分析数据" class="headerlink" title="二、实际环境分析数据"></a><strong>二、实际环境分析数据</strong></h1><p>生产环境数据分析:</p><ul><li>按最近3个月 ID 增长趋势预估:预测 5.5 个月后会增长至 long 最大值</li><li>按最近16个月 ID 增长趋势预估:预测仅2+个月后会增长至 long 最大值</li></ul><p>可见 ID 增长速度有加快趋势,如果未采取处理举措,将很快遇到全量 ID 转成负值问题。</p><h1 id="三、负-ID-对系统的影响"><a href="#三、负-ID-对系统的影响" class="headerlink" title="三、负 ID 对系统的影响"></a><strong>三、负 ID 对系统的影响</strong></h1><h2 id="1-数据库影响"><a href="#1-数据库影响" class="headerlink" title="1. 数据库影响"></a><strong>1. 数据库影响</strong></h2><ul><li><strong>性能问题</strong>:ID 递增类值用作主键时,如果达到负值,会导致类 B+ 树结构非最后端插入,触发频繁的页分裂,降低性能</li><li><strong>排序问题</strong>:负 ID 会导致排序结果异常,尤其是用 ID 进行排序的场景</li></ul><h2 id="2-业务影响"><a href="#2-业务影响" class="headerlink" title="2. 业务影响"></a><strong>2. 业务影响</strong></h2><ul><li><strong>分页查询崩溃</strong>:一些场景依赖 ID 递增,实现分页(如每次按 ID 条件查询大于上次最大 ID ),不支持负值时会导致查询失效或重复数据</li></ul><h1 id="四、解决方案"><a href="#四、解决方案" class="headerlink" title="四、解决方案"></a><strong>四、解决方案</strong></h1><h2 id="业务层面(代码侵入)"><a href="#业务层面(代码侵入)" class="headerlink" title="业务层面(代码侵入)"></a>业务层面(代码侵入)</h2><p>id 转成BigInteger 然后数据库用无符号bigint</p><h2 id="百度id-uid-generator-方向"><a href="#百度id-uid-generator-方向" class="headerlink" title="百度id uid generator 方向"></a>百度id uid generator 方向</h2><h3 id="默认配置"><a href="#默认配置" class="headerlink" title="默认配置"></a>默认配置</h3><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="type">int</span> <span class="variable">timeBits</span> <span class="operator">=</span> <span class="number">28</span>;</span><br><span class="line"><span class="keyword">protected</span> <span class="type">int</span> <span class="variable">workerBits</span> <span class="operator">=</span> <span class="number">22</span>;</span><br><span class="line"><span class="keyword">protected</span> <span class="type">int</span> <span class="variable">seqBits</span> <span class="operator">=</span> <span class="number">13</span>;</span><br></pre></td></tr></tbody></table></figure><h3 id="配置解析"><a href="#配置解析" class="headerlink" title="配置解析"></a>配置解析</h3><table><thead><tr><th>timeBits (28)</th><th>workerBits (22)</th><th>seqBits (13)</th></tr></thead><tbody><tr><td>时间戳(秒)</td><td>机器号(固定为29)</td><td>每秒序列号</td></tr></tbody></table><p>UID = (deltaSeconds << (22 + 13)) | (workerId << 13) | sequence;</p><ol><li><p>序列号(sequence)</p><p>2^13 - 1 = 8191</p><ul><li>最多能表示:<strong>8192 个唯一值</strong></li><li>范围是:<strong>0 ~ 8191(共 8192 个)</strong></li><li>所以在十进制里:最后最多就是 <strong>8191</strong></li></ul></li><li><p>机器号(workerId)</p><p>2^22 - 1 = 4,194,304</p><ul><li>最多能表示:4,194,303<strong>个唯一值</strong></li><li>范围是:<strong>0 ~</strong> 4,194,303<strong>(共</strong> 4,194,304<strong>个)</strong></li><li>所以在十进制里:最后最多就是 4,194,304</li></ul></li><li><p>时间(deltaSeconds )</p><p>2^28 - 1 = 268,435,455 秒</p><ul><li>秒:268,435,455 秒</li><li>分钟:≈ 4,473,924 分钟</li><li>小时:≈ 74,565 小时</li><li>天数:≈ 3,107 天</li><li>年数:≈ <strong>8.5 年</strong></li></ul><p>你从设置的起始时间(Epoch)开始,最多能支撑 <strong>8.5 年内的 UID 生成</strong>,</p></li></ol><h3 id="问题原因"><a href="#问题原因" class="headerlink" title="问题原因"></a>问题原因</h3><p>Epoch默认时间 :2016-05-20</p><p>2016+8.5年 2025年之后会超变成负数</p><h3 id="处理方案"><a href="#处理方案" class="headerlink" title="处理方案"></a>处理方案</h3><p><strong>目标 :</strong></p><ol><li><p>保证id 不重复</p></li><li><p>尽量保证id 递增趋势</p><p>当前id :9090949948782127403L</p><p>0111111000101001100010101101000000000000000000111010100100101011(64位)</p><p>0 标志位</p><p>1111110001010011000101011010 → 264581466 (时间差秒)</p><p>0000000000000000011101 → 29 (机器号)</p><p>0100100101011 → 2347 (序列号)</p></li></ol><p><strong>处理方法 : 调整uid 各组成位置位数</strong></p><ol><li><p>首先保证id 不重复可以通过修改机器号,不使用之前的机器id 生成uid 可以保证id 不与之前重复,即使时间戳一样</p></li><li><p>将时间差位置扩充以便能使用的时间更长,减少机器号位置</p><p>30位 ≈ 31, 557,600 秒 ≈ 34.0年 机器号:20位=1,048,576个</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="type">int</span> <span class="variable">timeBits</span> <span class="operator">=</span> <span class="number">30</span>;</span><br><span class="line"><span class="keyword">protected</span> <span class="type">int</span> <span class="variable">workerBits</span> <span class="operator">=</span> <span class="number">20</span>;</span><br><span class="line"><span class="keyword">protected</span> <span class="type">int</span> <span class="variable">seqBits</span> <span class="operator">=</span> <span class="number">13</span>;</span><br></pre></td></tr></tbody></table></figure><p>UID = (deltaSeconds << (20 + 13)) | (workerId << 13) | sequence;</p></li><li><p>只将时间戳位置 扩充30位,如果时间差还是当前时间差,那时间戳位置向后移动两位,生成的id 必然小于之前id,观察目前2进制时间差前6位都是1 如果想生成的id大于之前id,需要保证 标识位0 后 7位都是1 ,这样 时间戳位置就需要扩充至37位 ,前 7位为1 保证大于之前id 但需要每次算时间差 + 上前7为是1后30位为0的37位2进制数(1111111000000000000000000000000000000 =34091302913),但同时机器位缩小至13位 只能表示8192台机器号 (如果机器号不够用可以折中,适当缩小时间差位数,但可能生成id 会小于之前id 位数缩短越多交叉越多)</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="type">int</span> <span class="variable">timeBits</span> <span class="operator">=</span> <span class="number">37</span>;</span><br><span class="line"><span class="keyword">protected</span> <span class="type">int</span> <span class="variable">workerBits</span> <span class="operator">=</span> <span class="number">13</span>;</span><br><span class="line"><span class="keyword">protected</span> <span class="type">int</span> <span class="variable">seqBits</span> <span class="operator">=</span> <span class="number">13</span>;</span><br></pre></td></tr></tbody></table></figure><p>UID = (mask << 30+13 + 13) | (deltaSeconds << (13 + 13)) | (workerId << 13) | sequence;</p></li></ol><h3 id="处理流程及源码修改"><a href="#处理流程及源码修改" class="headerlink" title="处理流程及源码修改"></a>处理流程及源码修改</h3><p>公式可能不好理,解通俗解释:</p><p>28位二进制时间差 + 22位二进制机器号 + 13位 二进制序列号</p><p>举例:</p><ul><li>时间 2025-04-16 00:00:00 时间戳 1744732800 秒</li><li>Epoch默认时间 :2016-05-20 00:00:00 时间戳 1463673600秒</li><li>时间差 281059200 = 1744732800 - 1463673600</li><li>机器号比如 现在是 29 序列号1024</li><li>id = 二进制(时间差 281059200) + 二进制(29)+ 二进制(1024) 不够需要补全对应位置的位数</li><li>10000110000001001111110000000 + 0000000000000000011101 + 0010000000000</li><li>整体转long 无符号 9657120577919624088 有符号 -8789623495789927528、</li><li>为啥有符号是负的 ,这是因为 28位时间差 最多表示 268,435,455秒 但 281,059,200 时间差明显超过了 ,再转二进制 就是1开头的29位二进制数 10000110000001001111110000000 这样 开头的标志位 0 就被时间差的第29位占用了,标志位是1 转 long 就是负数</li><li>通过调整时间位,让时间位够用 ,不占用标志位 就能 使long值为正数</li><li>调整后 30位二进制时间差 + 20位二进制机器号 + 13位 二进制序列号</li><li>二进制(时间差 281059200) + 二进制(29)+ 二进制(1024) 不够需要补全对应位置的位</li><li>010000110000001001111110000000 + 00000000000000011101 + 0010000000000</li><li>整体转long 值为 2414280144480085222 比目前id 小可能会有一系列问题</li><li>那 如何保证生成的id 大呢解析目前id 时间戳位置 前6位都是1 保证比之前id大 需要二进制时间戳前7位都是1 此时生成的id 比 之前的大 暂时称 1 的这几位数是 mask 位 减少 机器号位置增加mark位</li><li>此时 id = 7位二进制 (mask )+ 30位二进制时间差 + 13位二进制机器号 + 13位 二进制序列号</li><li>1111111 + 010000110000001001111110000000 + 0000000011101 + 0010000000000</li><li>整体转long 值为 9170176006445836224</li><li>达到不重复,不为负数,比之前id大的目的</li></ul><p>代码调整后增加mask 位(标红位置为修改源码)</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Copyright (c) 2017 Baidu, Inc. All Rights Reserve.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Licensed under the Apache License, Version 2.0 (the "License");</span></span><br><span class="line"><span class="comment"> * you may not use this file except in compliance with the License.</span></span><br><span class="line"><span class="comment"> * You may obtain a copy of the License at</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <http://www.apache.org/licenses/LICENSE-2.0></span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Unless required by applicable law or agreed to in writing, software</span></span><br><span class="line"><span class="comment"> * distributed under the License is distributed on an "AS IS" BASIS,</span></span><br><span class="line"><span class="comment"> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span></span><br><span class="line"><span class="comment"> * See the License for the specific language governing permissions and</span></span><br><span class="line"><span class="comment"> * limitations under the License.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">package</span> com.baidu.fsg.uid;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.commons.lang.builder.ToStringBuilder;</span><br><span class="line"><span class="keyword">import</span> org.apache.commons.lang.builder.ToStringStyle;</span><br><span class="line"><span class="keyword">import</span> org.springframework.util.Assert;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Allocate 64 bits for the UID(long)<br></span></span><br><span class="line"><span class="comment"> * sign (fixed 1bit) -> deltaSecond -> workerId -> sequence(within the same second)</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> yutianbao</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BitsAllocator</span> {</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Total 64 bits</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">TOTAL_BITS</span> <span class="operator">=</span> <span class="number">1</span> << <span class="number">6</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Bits for [sign-> mark -> second-> workId-> sequence]</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> <span class="variable">signBits</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> timestampBits;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> workerIdBits;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> sequenceBits;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Max value for workId & sequence</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span> maxDeltaSeconds;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span> maxWorkerId;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span> maxSequence;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Shift for timestamp & workerId</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> timestampShift;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> workerIdShift;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//面具位 7位为1 保证大于之前id 但需要每次算时间差 + 上前7为是1后30位为0的37位2进制数(1111111000000000000000000000000000000 =136365211648</span></span><br><span class="line"> <span class="comment">// mask = (1L << maskLen) -1 面具值 = 1111111</span></span><br><span class="line"> <span class="comment">//mask << maskShift = 1111111000000000000000000000000000000</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">maskLen</span> <span class="operator">=</span> <span class="number">7</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span> mask ;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> maskShift;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Constructor with timestampBits, workerIdBits, sequenceBits<br></span></span><br><span class="line"><span class="comment"> * The highest bit used for sign, so <code>63</code> bits for timestampBits, workerIdBits, sequenceBits</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">BitsAllocator</span><span class="params">(<span class="type">int</span> timestampBits, <span class="type">int</span> workerIdBits, <span class="type">int</span> sequenceBits)</span> {</span><br><span class="line"> <span class="comment">// make sure allocated 64 bits</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">allocateTotalBits</span> <span class="operator">=</span> signBits + timestampBits + workerIdBits + sequenceBits;</span><br><span class="line"> Assert.isTrue(allocateTotalBits == TOTAL_BITS, <span class="string">"allocate not enough 64 bits"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// initialize bits</span></span><br><span class="line"> <span class="built_in">this</span>.timestampBits = timestampBits;</span><br><span class="line"> <span class="built_in">this</span>.workerIdBits = workerIdBits;</span><br><span class="line"> <span class="built_in">this</span>.sequenceBits = sequenceBits;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// initialize max value</span></span><br><span class="line"> <span class="built_in">this</span>.maxDeltaSeconds = ~(-<span class="number">1L</span> << timestampBits);</span><br><span class="line"> <span class="built_in">this</span>.maxWorkerId = ~(-<span class="number">1L</span> << workerIdBits);</span><br><span class="line"> <span class="built_in">this</span>.maxSequence = ~(-<span class="number">1L</span> << sequenceBits);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// initialize shift</span></span><br><span class="line"> <span class="built_in">this</span>.timestampShift = workerIdBits + sequenceBits;</span><br><span class="line"> <span class="built_in">this</span>.workerIdShift = sequenceBits;</span><br><span class="line"> <span class="comment">//面具参数相关值</span></span><br><span class="line"> <span class="built_in">this</span>.mask = (<span class="number">1L</span> << maskLen) -<span class="number">1</span> ;</span><br><span class="line"> <span class="built_in">this</span>.maskShift = timestampShift + timestampBits - maskLen;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Allocate bits for UID according to delta seconds & workerId & sequence<br></span></span><br><span class="line"><span class="comment"> * <b>Note that: </b>The highest bit will always be 0 for sign</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> deltaSeconds</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> workerId</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> sequence</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">allocate</span><span class="params">(<span class="type">long</span> deltaSeconds, <span class="type">long</span> workerId, <span class="type">long</span> sequence)</span> {</span><br><span class="line"> <span class="keyword">return</span> (mask << maskShift) | (deltaSeconds << timestampShift) | (workerId << workerIdShift) | sequence;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Getters</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getSignBits</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> signBits;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getTimestampBits</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> timestampBits;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getWorkerIdBits</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> workerIdBits;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getSequenceBits</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> sequenceBits;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getMaxDeltaSeconds</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> maxDeltaSeconds;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getMaxWorkerId</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> maxWorkerId;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getMaxSequence</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> maxSequence;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getTimestampShift</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> timestampShift;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getWorkerIdShift</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> workerIdShift;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> ToStringBuilder.reflectionToString(<span class="built_in">this</span>, ToStringStyle.SHORT_PREFIX_STYLE);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>备注:</p><ol><li>修改之后如果时间位不够用生成 的id 可能重复,修改之前位数不够用先是负数后可能重复</li><li>修改之后如果代码涉及到id 解析的 需要修改对应解析方法</li></ol>]]></content>
<summary type="html"><blockquote>
<p><em>在 64 位的世界里,最左边的那一位,决定了一切。</em></p>
</blockquote>
<h1 id="一、背景"><a href="#一、背景" class="headerlink" title="一、背景"></a><stro</summary>
<category term="雪花算法" scheme="https://blog.restlessnight.cn/categories/%E9%9B%AA%E8%8A%B1%E7%AE%97%E6%B3%95/"/>
<category term="算法" scheme="https://blog.restlessnight.cn/tags/%E7%AE%97%E6%B3%95/"/>
<category term="雪花算法" scheme="https://blog.restlessnight.cn/tags/%E9%9B%AA%E8%8A%B1%E7%AE%97%E6%B3%95/"/>
</entry>
<entry>
<title>Hello World</title>
<link href="https://blog.restlessnight.cn/2025/04/03/hello-world/"/>
<id>https://blog.restlessnight.cn/2025/04/03/hello-world/</id>
<published>2025-04-03T08:53:27.389Z</published>
<updated>2025-04-07T06:13:23.646Z</updated>
<content type="html"><![CDATA[<p>Welcome to <a href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p><h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></tbody></table></figure><p>More info: <a href="https://hexo.io/docs/writing.html">Writing</a></p><h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></tbody></table></figure><p>More info: <a href="https://hexo.io/docs/server.html">Server</a></p><h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></tbody></table></figure><p>More info: <a href="https://hexo.io/docs/generating.html">Generating</a></p><h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></tbody></table></figure><p>More info: <a href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>]]></content>
<summary type="html"><p>Welcome to <a href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/">documentation</a> for</summary>
<category term="hello" scheme="https://blog.restlessnight.cn/categories/hello/"/>
</entry>
</feed>