-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
59 lines (28 loc) · 14.8 KB
/
search.xml
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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Linux TCP超时重传机制介绍</title>
<link href="/2020/07/26/tcp-retry/"/>
<url>/2020/07/26/tcp-retry/</url>
<content type="html"><![CDATA[<h1 id="Linux-TCP超时重传机制介绍"><a href="#Linux-TCP超时重传机制介绍" class="headerlink" title="Linux TCP超时重传机制介绍"></a>Linux TCP超时重传机制介绍</h1><p>背景:在工作当中遇到了下面的场景:</p><ul><li>建立的tcp连接,在弱网环境下,经常莫名其妙的被server端给reset掉</li></ul><p>排查了非常久,最终确定和<strong><em>Linux TCP超时重传</em></strong>有关,记录一下最近学习的Linux TCP超时机制;</p><h2 id="RFC-TCP-超时重传机制整理"><a href="#RFC-TCP-超时重传机制整理" class="headerlink" title="RFC TCP 超时重传机制整理"></a>RFC TCP 超时重传机制整理</h2><p>和网络相关的所有操作,还是需要通过RFC文档来学习;</p><h3 id="TCP超时事件驱动"><a href="#TCP超时事件驱动" class="headerlink" title="TCP超时事件驱动"></a>TCP超时事件驱动</h3><p>关于TCP事件驱动中,对超时的处理逻辑见<a href="#refer-anchor"><sup>1</sup></a>,其中提到了下面三种超时事件:</p><ul><li>User Timeout:超时后清空队列,切换到Close状态;</li><li>Retransmission Timeout:超时后则发起重传;</li><li>Time-Wait Timeout:切换到Close状态<br>其中关于User Timeout和Retransmission Timeout的详情参考2.2节和2.3节<h3 id="User-Timeout"><a href="#User-Timeout" class="headerlink" title="User Timeout"></a>User Timeout</h3>关于User Timeout设置详情见附录<a href="#refer-anchor"><sup>2</sup></a>,总结如下: <blockquote><p>The TCP user timeout controls how long transmitted data may remain unacknowledged before a connection is forcefully closed.</p></blockquote></li></ul><p><em>Note:从实际抓包效果来看,Linux实现User Timeout的方式和RFC文档中描述的相去甚远;</em></p><h3 id="多次重试失败后管理方式"><a href="#多次重试失败后管理方式" class="headerlink" title="多次重试失败后管理方式"></a>多次重试失败后管理方式</h3><p>针对TCP某个数据包多次重试情况下,建议的TCP采用的管理方式见附录<a href="#refer-anchor"><sup>3</sup></a>,总结如下:</p><ul><li>提供R1和R2两个参数(可以对应次数或者时间)来表示不同的阈值,对应不同的操作逻辑;</li><li>当重试次数/耗时超过R1后,则触发IP层的死网关探测(dead gateway diagnosis);</li><li>当重试次数/耗时超过R2后,则关闭当前TCP连接;</li><li>应用程序必须为每个连接设置一个R2;</li><li>关于TCP建连过程也可以参考R1/R2参数,部分处理细节存在差异; <h3 id="超时重试计时器管理方式"><a href="#超时重试计时器管理方式" class="headerlink" title="超时重试计时器管理方式"></a>超时重试计时器管理方式</h3>针对超时和重试,TCP是采用计时器的方式实现。实际的执行流程可以总结为下面几步:</li><li>新建tcp连接,会启动一个RTO定时器;</li><li>每次发送一个包(包括重传包),如果没有启动定时器,则启动一个当前RTO的定时器,直到超时;</li><li>当所有发出的包都被确认之后,关闭当前的定时器;</li><li>如果一个确认新数据包的ACK到达之后,则重置当前的定时器;</li><li>当发送的包中,<em>存在未被确认的数据</em>,在定时器超时后:</li><li>重传最早未被确认的包;</li><li>设置RTO <- RTO * 2 (“back off the timer”)</li><li>按照当前RTO值重置定时器; </li><li>当超时次数超过上面的R1或者R2之后,则认为TCP连接失败,主动发起断开操作</li></ul><p>Note:Linux针对定时器的实现,除RTO值设置存在少许差异外,其他基本遵循上面的流程;</p><p>针对TCP传输过程中,超时重试定时器的管理方式见附录<a href="#refer-anchor"><sup>4</sup></a>,总结一下定时器的要点如下:</p><ul><li>RTO(retransmission timeout)计算:</li><li>在得到RTT之前,将RTO设置成1s;</li><li>当第一个RTT计算出来之后,需要按照: <figure class="highlight plain"><table><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></pre></td><td class="code"><pre><span class="line">SRTT <- R</span><br><span class="line">RTTVAR <- R/2</span><br><span class="line">RTO <- SRTT + max (G, K*RTTVAR) where K = 4 </span><br><span class="line">当计算得到后续的RTT R’,则按照:</span><br><span class="line">RTTVAR <- (1 - beta) * RTTVAR + beta * |SRTT - R'|</span><br><span class="line">SRTT <- (1 - alpha) * SRTT + alpha * R'</span><br><span class="line">RTO <- SRTT + max (G, K*RTTVAR)</span><br></pre></td></tr></table></figure></li><li>当RTO不足1S的时候,RTO应该被设置成1S</li></ul><h2 id="Linux-TCP-超时机制介绍"><a href="#Linux-TCP-超时机制介绍" class="headerlink" title="Linux TCP 超时机制介绍"></a>Linux TCP 超时机制介绍</h2><p>Linux中关于TCP的超时重试机制可以总结成下面两点:</p><ul><li>介绍Linux中关于超时判断的逻辑;</li><li>介绍关于Linux TCP重传定时器的逻辑;</li></ul><p>备注:主要参考的是三系内核</p><h3 id="Linux中关于超时判断的逻辑"><a href="#Linux中关于超时判断的逻辑" class="headerlink" title="Linux中关于超时判断的逻辑"></a>Linux中关于超时判断的逻辑</h3><h4 id="关键系统参数:"><a href="#关键系统参数:" class="headerlink" title="关键系统参数:"></a>关键系统参数:</h4><ul><li>net.ipv4.tcp_retries1,作用参考上文介绍的R1参数;</li><li>net.ipv4.tcp_retries2,作用参考上文介绍的R2参数;</li><li>TCP_USER_TIMEOUT,作用参考上文中介绍的User Timeout参数; <h4 id="重传逻辑:"><a href="#重传逻辑:" class="headerlink" title="重传逻辑:"></a>重传逻辑:</h4></li><li>数据包的第一次重传,不论超时时间多久(TCP都不会认为超时),TCP都会进行一次重传;</li><li>经过一次重传的包,如果再次进入重传的逻辑,则会走到判断是否超时的逻辑:</li><li>如果判断超时,则会发送RST包关闭当前的TCP连接;</li><li>如果未超时,则会重传当前的数据包;</li><li>超时判断逻辑:</li><li>未设置TCP_USER_TIMEOUT:</li><li>根据tcp_retries1计算超时,用于判断是否更新路由;</li><li>根据tcp_retries2计算超时,用于判断是否关闭TCP连接;<br>超时计算逻辑:</li><li>timeout = ((2 << boundary) - 1) * rto_base;<br>设置了TCP_USER_TIME:</li><li>将TCP_USER_TIME的值作为retries1和retries2中计算的超时时间;<br>Note:在未设置TCP_USER_TIME情况下,在retries2=5的情况下,计算的超时时间基本上10S以上;</li></ul><h3 id="Linux-TCP重传定时器的逻辑"><a href="#Linux-TCP重传定时器的逻辑" class="headerlink" title="Linux TCP重传定时器的逻辑"></a>Linux TCP重传定时器的逻辑</h3><p>Linux中TCP重传定时器的逻辑基本上和RFC描述的一致,部分细节存在差异:</p><ul><li>新建tcp连接,会开启若干个定时器,其中icsk_retransmit_timer处理超时重传;</li><li>重传定时器回调函数为上文中超时函数函数;<br>重传行为:</li><li>在包发送经过RTO后,如果没有收到ACK,则认为超时,走到判断是否超时的逻辑当中;</li><li>如果没有超时,则重传当前数据包,同时更新RTO后重置定时器;</li><li>如果超时,则直接关闭TCP连接;<br>Note:Linux对RTO不足1S的情况,并没有强制按照1S对齐;<h2 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h2><div id="refer-anchor"></div> </li></ul><ol><li><a href="https://tools.ietf.org/html/rfc793#section-3.9" target="_blank" rel="noopener">rfc793</a></li><li><a href="https://tools.ietf.org/html/rfc5482" target="_blank" rel="noopener">rfc5482</a></li><li><a href="https://tools.ietf.org/html/rfc1122#page-100" target="_blank" rel="noopener">rfc1122</a></li><li><a href="https://tools.ietf.org/html/rfc6298" target="_blank" rel="noopener">rfc6298</a></li></ol>]]></content>
<categories>
<category> 网络基础 </category>
</categories>
<tags>
<tag> tcp </tag>
</tags>
</entry>
<entry>
<title>go benchmark</title>
<link href="/2020/07/26/go%20benchmark/"/>
<url>/2020/07/26/go%20benchmark/</url>
<content type="html"><![CDATA[<h1 id="go-benchmark"><a href="#go-benchmark" class="headerlink" title="go benchmark"></a>go benchmark</h1><h2 id="benchmark是什么"><a href="#benchmark是什么" class="headerlink" title="benchmark是什么"></a>benchmark是什么</h2><p>这里就先理解是做性能测试;</p><h2 id="主要应用场景"><a href="#主要应用场景" class="headerlink" title="主要应用场景"></a>主要应用场景</h2><p>当你优化了一个函数,但是你不知道这个函数和之前函数的性能比起来到底怎么样,这个时候benchmark就能派上用场了;</p><h2 id="go-benchmark示例"><a href="#go-benchmark示例" class="headerlink" title="go benchmark示例"></a>go benchmark示例</h2><h3 id="运行指令"><a href="#运行指令" class="headerlink" title="运行指令"></a>运行指令</h3><p><code>go test -benchmem -bench .</code></p><h3 id="指令输出"><a href="#指令输出" class="headerlink" title="指令输出"></a>指令输出</h3><figure class="highlight plain"><table><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></pre></td><td class="code"><pre><span class="line">goos: linux</span><br><span class="line">goarch: amd64</span><br><span class="line">pkg: github.com/bfenetworks/bfe/go_benchmark</span><br><span class="line">BenchmarkDecodeStdLoad-40 1 1641440167 ns/op 255783000 B/op 5338128 allocs/op</span><br><span class="line">#对应的函数 本次benchmark中运行了几次 运行的耗时 运行中消耗的内存 运行中内存分配次数</span><br></pre></td></tr></table></figure><h2 id="demo"><a href="#demo" class="headerlink" title="demo"></a>demo</h2><h3 id="正常运行"><a href="#正常运行" class="headerlink" title="正常运行"></a>正常运行</h3><p>这里面是正常运行benchmark,其中<code>ReportAllocs</code>会将操作当中的内存分配也一起给出;效果等同于运行的时候给出<code>-benchmem</code>;</p><figure class="highlight golang"><table><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkDecodeStdLoad</span><span class="params">(b *testing.B)</span></span> {</span><br><span class="line">b.ReportAllocs()</span><br><span class="line">bal := bfe_balance.NewBalTable(<span class="literal">nil</span>)</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i < b.N; i++ {</span><br><span class="line">bal.BalTableConfLoad(<span class="string">"gslb.data"</span>, <span class="string">"cluster_table.data"</span>)</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="重置计数器"><a href="#重置计数器" class="headerlink" title="重置计数器"></a>重置计数器</h3><p>有一些特别耗时的setup操作,我们需要在setup完成之后再记录时间,这个时候就可以使用<code>ResetTimer</code>;</p><figure class="highlight golang"><table><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkDecodeIterReloadLoad</span><span class="params">(b *testing.B)</span></span> {</span><br><span class="line">b.ReportAllocs()</span><br><span class="line">bal := bfe_balance.NewBalTable(<span class="literal">nil</span>)</span><br><span class="line">gslbConfs, backendConfs, _ := bal.BalTableConfLoad(<span class="string">"gslb.data"</span>, <span class="string">"cluster_table.data"</span>)</span><br><span class="line">b.ResetTimer()</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i < b.N; i++ {</span><br><span class="line">bal.BalTableReload(gslbConfs, backendConfs)</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="并行运行"><a href="#并行运行" class="headerlink" title="并行运行"></a>并行运行</h3><p>针对验证并行执行下的效果,可以使用<code>RunParallel</code>;</p><figure class="highlight plain"><table><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></pre></td><td class="code"><pre><span class="line">func BenchmarkParallelDecodeIterLoad(b *testing.B) {</span><br><span class="line">b.ReportAllocs()</span><br><span class="line">b.RunParallel(func(pb *testing.PB) {</span><br><span class="line">for pb.Next() {</span><br><span class="line">bal := bfe_balance.NewBalTable(nil)</span><br><span class="line">bal.BalTableConfLoadNew("gslb.data", "cluster_table.data")</span><br><span class="line">}</span><br><span class="line">})</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> golang </category>
</categories>
<tags>
<tag> golang </tag>
<tag> benchmark </tag>
</tags>
</entry>
</search>