<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>诡异的鱼塘</title>
  
  <subtitle>一 个 浅 鱼 塘</subtitle>
  <link href="https://blog.archetto.moe/atom.xml" rel="self"/>
  
  <link href="https://blog.archetto.moe/"/>
  <updated>2024-11-08T09:46:39.639Z</updated>
  <id>https://blog.archetto.moe/</id>
  
  <author>
    <name>Oswin Wu</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>部署PaddleOCR到阿里云函数计算</title>
    <link href="https://blog.archetto.moe/2024/10/29/Linux/PaddleOCR-Over_AliyunFC/"/>
    <id>https://blog.archetto.moe/2024/10/29/Linux/PaddleOCR-Over_AliyunFC/</id>
    <published>2024-10-29T22:20:14.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="Background"><a href="#Background" class="headerlink" title="Background"></a>Background</h2><p>因为某些需要，需要在阿里云函数计算(Function Compute)平台部署PaddleOCR。</p><h2 id="OverView"><a href="#OverView" class="headerlink" title="OverView"></a>OverView</h2><p>阿里云函数计算平台，是事件驱动的全托管计算服务，优点就是无需管理基础设置，只需要编写并上传代码或者镜像，即可完成部署。不负任何责任的推测，底层八成就是docker或者containerd,从上传docker镜像和<a href="https://help.aliyun.com/zh/functioncompute/fc-3-0/use-cases/image-usage-notes-1">文档中的描述</a>可以看出来。</p><h2 id="CPU-版本部署"><a href="#CPU-版本部署" class="headerlink" title="CPU 版本部署"></a>CPU 版本部署</h2><p>其实对于paddleocr来说，CPU版本的部署，主要问题出现在依赖的安装上。</p><h3 id="使用pip处理依赖"><a href="#使用pip处理依赖" class="headerlink" title="使用pip处理依赖"></a>使用pip处理依赖</h3><p>我们可以直接创建一个Web函数（python 3.10），然后使用WebIDE打开，并在WebIDE的终端中使用pip命令进行一些依赖的安装。<br>首先我们可以尝试安装一些简单的库，比如requests，然后尝试在WebIDE中进行debug。此时，我们可以看到WebIDE左侧的文件管理中看到已经安装上的依赖，点击部署后，类似git commit 的操作，将代码上传到函数计算平台（<strong>包括安装好的依赖</strong>）。<br>这一步可以说是顺风顺水，WebIDE的体验十分舒畅，可以说真的和vscode的体验差不多<del>就是vscode嘛</del>。<br>接下来我们开始干活，安装paddlepaddle和paddleocr，并且开始编写我们的业务代码。<br>当完成依赖安装，同时写完业务之后，点击部署代码后，部署的速度不仅超级慢，WebIDE下面还会多出一堆python依赖，最后当网页提示部署完成，运行时会报错，提示缺少依赖(git commit不完了呀)。<br>而当你十分不解&#x2F;不小心刷新了网页，WebIDE还会贴心地提示你IDE代码包加载失败。<br><img src="https://blog.archetto.moe/images/Linux/WebIDE-Loading-Failed.png" alt="在线 IDE 代码包尚未加载成功" loading="lazy"><br>至此使用pip处理paddlepaddle的依赖可以说是完全失败。<br>接下来，我们尝试使用FC平台定义的自定义层来解决这个问题。</p><h3 id="使用并构建自定义层"><a href="#使用并构建自定义层" class="headerlink" title="使用并构建自定义层"></a>使用并构建自定义层</h3><p><img src="https://blog.archetto.moe/images/Linux/FC-Custom-Layer.png" alt="自定义层" loading="lazy"><br>点击红框，就可以给当前函数添加自定义层。<br>其实这个自定义层和docker的layer是大差不差的概念，在FC平台创建自定义层，可以认为是某种程度上可视化模板化地编写Dockerfile中的某一层，添加自定义层就是将这个layer挂载到函数上。<br><img src="https://blog.archetto.moe/images/Linux/Create-Custom-Layer.png" alt="创建自定义层" loading="lazy"><br><del>常威你还说不会武功</del><br>构建完之后直接添加为自定义层就可以正常使用了。</p><h2 id="GPU-版本部署"><a href="#GPU-版本部署" class="headerlink" title="GPU 版本部署"></a>GPU 版本部署</h2><p>GPU实例的部署就要折磨人一些了。</p><h3 id="选择地区"><a href="#选择地区" class="headerlink" title="选择地区"></a>选择地区</h3><p>目前（2024-10-29）阿里云函数计算GPU实例只在<a href="https://help.aliyun.com/zh/functioncompute/fc-3-0/product-overview/supported-regions">几个地区可用</a>。<br>笔者选择的是在华东1（杭州）的区域。</p><h3 id="GPU显卡型号的选择"><a href="#GPU显卡型号的选择" class="headerlink" title="GPU显卡型号的选择"></a>GPU显卡型号的选择</h3><p>在笔者多次的尝试下，发现只有Tesla显卡的资源比较富足，不会出现资源耗尽&#x2F;资源不足的情况。Ada和Ampere显卡的资源比较少，在使用时会出现资源不足的情况。</p><h3 id="底层镜像的选择"><a href="#底层镜像的选择" class="headerlink" title="底层镜像的选择"></a>底层镜像的选择</h3><p>像文章开头说的一样，只要是能在NVIDIA Container Runtime中运行的镜像都可以。但是出于镜像大小的问题（主要是push到阿里云ACR镜像仓库的时候，30G镜像顶不住），我们可以直接使用阿里云提供的基础镜像。在本次部署中我们使用的是<code>registry.cn-hangzhou.aliyuncs.com/serverless_devs/paddlepaddle:22.12-py3</code>的镜像，这个镜像已经内置了paddlepaddle。<br>理论上我们只需要安装paddleocr即可，但是实际上可能会出现以下几种问题：<br><img src="https://blog.archetto.moe/images/Linux/FC-Error-1.png" alt="FC-Error-1" loading="lazy"><br><img src="https://blog.archetto.moe/images/Linux/FC-Error-2.png" alt="FC-Error-2" loading="lazy"><br>碰到以上情况，请调整paddlepaddle-gpu的版本，升级之后即可解决(可能是paddleocr兼容性的问题，要相信云服务器的CPU真的有avx指令集)。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Linux" scheme="https://blog.archetto.moe/categories/Linux/"/>
    
    
    <category term="paddlepaddle" scheme="https://blog.archetto.moe/tags/paddlepaddle/"/>
    
    <category term="paddleocr" scheme="https://blog.archetto.moe/tags/paddleocr/"/>
    
    <category term="founction-compute" scheme="https://blog.archetto.moe/tags/founction-compute/"/>
    
    <category term="aliyun" scheme="https://blog.archetto.moe/tags/aliyun/"/>
    
  </entry>
  
  <entry>
    <title>Linux Ops Tips</title>
    <link href="https://blog.archetto.moe/2023/07/11/Linux/Linux-Ops-Tips/"/>
    <id>https://blog.archetto.moe/2023/07/11/Linux/Linux-Ops-Tips/</id>
    <published>2023-07-11T20:55:00.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><p>这个帖子是一些使用Linux（Based on Debian）时的小技巧集合，不定期更新。有些内容可能其他Linux Distro也可以用。</p><h2 id="裸机安装相关"><a href="#裸机安装相关" class="headerlink" title="裸机安装相关"></a>裸机安装相关</h2><h3 id="移除不使用的kernel"><a href="#移除不使用的kernel" class="headerlink" title="移除不使用的kernel"></a>移除不使用的kernel</h3><p>查看当前kernel</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">uname</span> -a</span><br></pre></td></tr></table></figure><p>以下是一个示例</p><figure class="highlight plaintext"><table><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">root@debian:~# uname -a</span><br><span class="line">Linux debian 6.1.0-10-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.37-1 (2023-07-03) x86_64 GNU/Linux</span><br></pre></td></tr></table></figure><p>可以发现目前正在使用的时6.1.37-1的kernel，随后查看所有的kernel</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dpkg --list | grep linux-image</span><br></pre></td></tr></table></figure><p>以下是一个示例</p><figure class="highlight plaintext"><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">root@debian:~# dpkg --list | grep linux-image</span><br><span class="line">rc  linux-image-5.10.0-23-amd64  5.10.179-1                     amd64        Linux 5.10 for 64-bit PCs (signed)</span><br><span class="line">ii  linux-image-5.10.0-8-amd64   5.10.46-5                      amd64        Linux 5.10 for 64-bit PCs (signed)</span><br><span class="line">ii  linux-image-6.1.0-10-amd64   6.1.37-1                       amd64        Linux 6.1 for 64-bit PCs (signed)</span><br><span class="line">ii  linux-image-amd64            6.1.37-1                       amd64        Linux for 64-bit PCs (meta-package)</span><br></pre></td></tr></table></figure><p>那么linux-image-5.10.0-23-amd64和linux-image-5.10.0-8-amd64是未被使用可以卸载的kernel,<br>直接执行<code>apt remove</code>即可，如下</p><figure class="highlight plaintext"><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><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></pre></td><td class="code"><pre><span class="line">root@debian:~# apt remove linux-image-5.10.0*</span><br><span class="line">Reading package lists... Done</span><br><span class="line">Building dependency tree... Done</span><br><span class="line">Reading state information... Done</span><br><span class="line">Note, selecting &#x27;linux-image-5.10.0-23-amd64&#x27; for glob &#x27;linux-image-5.10.0*&#x27;</span><br><span class="line">Note, selecting &#x27;linux-image-5.10.0-8-amd64-unsigned&#x27; for glob &#x27;linux-image-5.10.0*&#x27;</span><br><span class="line">Note, selecting &#x27;linux-image-5.10.0-8-amd64&#x27; for glob &#x27;linux-image-5.10.0*&#x27;</span><br><span class="line">Note, selecting &#x27;linux-image-5.10.0-23-amd64-unsigned&#x27; for glob &#x27;linux-image-5.10.0*&#x27;</span><br><span class="line">Package &#x27;linux-image-5.10.0-23-amd64-unsigned&#x27; is not installed, so not removed</span><br><span class="line">Package &#x27;linux-image-5.10.0-8-amd64-unsigned&#x27; is not installed, so not removed</span><br><span class="line">Package &#x27;linux-image-5.10.0-23-amd64&#x27; is not installed, so not removed</span><br><span class="line">The following packages will be REMOVED:</span><br><span class="line">  linux-image-5.10.0-8-amd64</span><br><span class="line">0 upgraded, 0 newly installed, 1 to remove and 0 not upgraded.</span><br><span class="line">After this operation, 302 MB disk space will be freed.</span><br><span class="line">Do you want to continue? [Y/n] y</span><br><span class="line">(Reading database ... 36690 files and directories currently installed.)</span><br><span class="line">Removing linux-image-5.10.0-8-amd64 (5.10.46-5) ...</span><br><span class="line">I: /vmlinuz.old is now a symlink to boot/vmlinuz-6.1.0-10-amd64</span><br><span class="line">I: /initrd.img.old is now a symlink to boot/initrd.img-6.1.0-10-amd64</span><br><span class="line">/etc/kernel/postrm.d/initramfs-tools:</span><br><span class="line">update-initramfs: Deleting /boot/initrd.img-5.10.0-8-amd64</span><br><span class="line">/etc/kernel/postrm.d/zz-update-grub:</span><br><span class="line">Generating grub configuration file ...</span><br><span class="line">Found linux image: /boot/vmlinuz-6.1.0-10-amd64</span><br><span class="line">Found initrd image: /boot/initrd.img-6.1.0-10-amd64</span><br><span class="line">Warning: os-prober will not be executed to detect other bootable partitions.</span><br><span class="line">Systems on them will not be added to the GRUB boot configuration.</span><br><span class="line">Check GRUB_DISABLE_OS_PROBER documentation entry.</span><br><span class="line">done</span><br></pre></td></tr></table></figure><h3 id="安装Docker"><a href="#安装Docker" class="headerlink" title="安装Docker"></a>安装Docker</h3><p>替换了download.docker.com成mirrors.ustc.edu.cn&#x2F;docker-ce加速下载</p><figure class="highlight bash"><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><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"><span class="built_in">sudo</span> apt-get install ca-certificates curl gnupg -y</span><br><span class="line"><span class="built_in">sudo</span> install -m 0755 -d /etc/apt/keyrings</span><br><span class="line">curl -fsSL https://download.docker.com/linux/debian/gpg | <span class="built_in">sudo</span> gpg --dearmor -o /etc/apt/keyrings/docker.gpg</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chmod</span> a+r /etc/apt/keyrings/docker.gpg</span><br><span class="line"><span class="built_in">echo</span> \</span><br><span class="line">  <span class="string">&quot;deb [arch=&quot;</span>$(dpkg --print-architecture)<span class="string">&quot; signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.ustc.edu.cn/docker-ce/linux/debian \</span></span><br><span class="line"><span class="string">  &quot;</span>$(. /etc/os-release &amp;&amp; <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$VERSION_CODENAME</span>&quot;</span>)<span class="string">&quot; stable&quot;</span> | \</span><br><span class="line">  <span class="built_in">sudo</span> <span class="built_in">tee</span> /etc/apt/sources.list.d/docker.list &gt; /dev/null</span><br><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"><span class="built_in">sudo</span> apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y</span><br></pre></td></tr></table></figure><p>不需要额外安装，直接<code>docker compose</code>就可使用docker-compose<br>随后安装一个ctop用来查看docker容器的资源使用情况</p><figure class="highlight bash"><table><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"><span class="built_in">sudo</span> wget https://github.com/bcicen/ctop/releases/download/v0.7.7/ctop-0.7.7-linux-amd64 -O /usr/local/bin/ctop</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chmod</span> +x /usr/local/bin/ctop</span><br></pre></td></tr></table></figure><p>下载比较慢就是用ghproxy加速一下，即：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> wget https://ghproxy.com/https://github.com/bcicen/ctop/releases/download/v0.7.7/ctop-0.7.7-linux-amd64 -O /usr/local/bin/ctop</span><br></pre></td></tr></table></figure><h2 id="Docker-相关"><a href="#Docker-相关" class="headerlink" title="Docker 相关"></a>Docker 相关</h2><h3 id="Grafana-Prometheus-Node-Exporter"><a href="#Grafana-Prometheus-Node-Exporter" class="headerlink" title="Grafana + Prometheus + Node Exporter"></a>Grafana + Prometheus + Node Exporter</h3><figure class="highlight yaml"><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><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></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&#x27;3.8&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">monitoring:</span></span><br><span class="line">    <span class="attr">driver:</span> <span class="string">bridge</span></span><br><span class="line"></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">prometheus_data:</span> </span><br><span class="line">    <span class="attr">driver:</span> <span class="string">local</span></span><br><span class="line">    <span class="attr">driver_opts:</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">none</span></span><br><span class="line">      <span class="attr">o:</span> <span class="string">bind</span></span><br><span class="line">      <span class="attr">device:</span> <span class="string">/opt/prometheus</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">grafana_data:</span></span><br><span class="line">    <span class="attr">driver:</span> <span class="string">local</span></span><br><span class="line">    <span class="attr">driver_opts:</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">none</span></span><br><span class="line">      <span class="attr">o:</span> <span class="string">bind</span></span><br><span class="line">      <span class="attr">device:</span> <span class="string">/opt/grafana</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">grafana:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">grafana/grafana-oss:latest</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">grafana</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">grafana_data/data:/var/lib/grafana</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">grafana_data/provisioning:/etc/grafana/provisioning</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">GF_SECURITY_ADMIN_USER=$&#123;ADMIN_USER&#125;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">GF_SECURITY_ADMIN_PASSWORD=$&#123;ADMIN_PASSWORD&#125;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">GF_USERS_ALLOW_SIGN_UP=false</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;33281:3000&quot;</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">monitoring</span></span><br><span class="line"></span><br><span class="line"><span class="attr">local_exporter:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">prom/node-exporter:latest</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">node-exporter</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/proc:/host/proc:ro</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/sys:/host/sys:ro</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/:/rootfs:ro</span></span><br><span class="line">    <span class="attr">command:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;--path.procfs=/host/proc&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;--path.rootfs=/rootfs&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;--path.sysfs=/host/sys&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)&#x27;</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;9100:9100&quot;</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">monitoring</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">prometheus:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">prom/prometheus:latest</span></span><br><span class="line">    <span class="attr">user:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">prometheus</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">prometheus_data/prometheus.yml:/etc/prometheus/prometheus.yml</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">prometheus_data/data:/prometheus</span></span><br><span class="line">    <span class="attr">command:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;--config.file=/etc/prometheus/prometheus.yml&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;--storage.tsdb.path=/prometheus&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;--web.console.libraries=/etc/prometheus/console_libraries&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;--web.console.templates=/etc/prometheus/consoles&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;--storage.tsdb.retention.time=168h&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;--web.enable-lifecycle&#x27;</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;19090:9090&quot;</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">monitoring</span></span><br></pre></td></tr></table></figure><p>同目录下创建以下两个文件，修改user_here及password_here<br>.env</p><figure class="highlight plaintext"><table><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">ADMIN_USER=user_here</span><br><span class="line">ADMIN_PASSWORD=password_here</span><br></pre></td></tr></table></figure><p>prometheus.yml</p><figure class="highlight yaml"><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><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="attr">global:</span></span><br><span class="line">  <span class="attr">scrape_interval:</span>     <span class="string">15s</span></span><br><span class="line">  <span class="attr">external_labels:</span></span><br><span class="line">      <span class="attr">monitor:</span> <span class="string">&#x27;docker-host-alpha&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">scrape_configs:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">&#x27;node_exporter&#x27;</span></span><br><span class="line">    <span class="attr">scrape_interval:</span> <span class="string">5s</span></span><br><span class="line">    <span class="attr">static_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">targets:</span> [<span class="string">&#x27;node-exporter:9100&#x27;</span>]</span><br><span class="line"></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">&#x27;prometheus&#x27;</span></span><br><span class="line">    <span class="attr">scrape_interval:</span> <span class="string">10s</span></span><br><span class="line">    <span class="attr">static_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">targets:</span> [<span class="string">&#x27;localhost:9090&#x27;</span>]</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="一些常用命令"><a href="#一些常用命令" class="headerlink" title="一些常用命令"></a>一些常用命令</h2><h3 id="ss"><a href="#ss" class="headerlink" title="ss"></a>ss</h3><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 统计当前连接</span></span><br><span class="line">ss -s</span><br><span class="line"><span class="comment"># 查看TCP和UDP的连接及正在监听的sockets</span></span><br><span class="line">ss -nultp</span><br></pre></td></tr></table></figure><h3 id="一些组合使用的命令"><a href="#一些组合使用的命令" class="headerlink" title="一些组合使用的命令"></a>一些组合使用的命令</h3><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 统计文件个数</span></span><br><span class="line"><span class="built_in">ls</span> | <span class="built_in">wc</span> -l</span><br><span class="line"><span class="comment"># 显示以:分割的第一段, 以下示例将会显示test_field_1</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;test_field_1:test_field_2&quot;</span>| <span class="built_in">cut</span> -d: -f1</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Linux" scheme="https://blog.archetto.moe/categories/Linux/"/>
    
    
    <category term="debian" scheme="https://blog.archetto.moe/tags/debian/"/>
    
  </entry>
  
  <entry>
    <title>defer、goto以及sync.Mutex</title>
    <link href="https://blog.archetto.moe/2022/08/17/Golang/defer%E3%80%81goto%E4%BB%A5%E5%8F%8Async.Mutex/"/>
    <id>https://blog.archetto.moe/2022/08/17/Golang/defer%E3%80%81goto%E4%BB%A5%E5%8F%8Async.Mutex/</id>
    <published>2022-08-17T23:21:00.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近写着写着遇到些稀奇古怪的问题，想着与defer、GOTO以及锁有关，于是整理了一下。</p><h2 id="切入正题"><a href="#切入正题" class="headerlink" title="切入正题"></a>切入正题</h2><p>首先来看看本文的主角们</p><h3 id="十分好用的defer"><a href="#十分好用的defer" class="headerlink" title="十分好用的defer"></a>十分好用的defer</h3><figure class="highlight go"><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><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    doSomething()</span><br><span class="line">    <span class="keyword">defer</span> finishDoSomething()   </span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Something has been done&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">doSomething</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Do something&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">finishDoSomething</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Finish do something&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这是一个defer的用例，很简单，就是在<code>doSomething</code>结束之后，再执行<code>finishDOsomething</code>（<code>println</code>输出到<code>stderr</code>,<code>fmt.Println</code>输出到<code>stdout</code>,<del>我绝对不是为了偷懒少打点才用的<code>println</code></del>）,以上代码的输出如下：</p><figure class="highlight plaintext"><table><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">Do something</span><br><span class="line">Something has been done</span><br><span class="line">Finish do somethin</span><br></pre></td></tr></table></figure><p>没有任何问题，十分正常且符合预期的输出。</p><h3 id="掺和一下goto，顺便加了点料"><a href="#掺和一下goto，顺便加了点料" class="headerlink" title="掺和一下goto，顺便加了点料"></a>掺和一下goto，顺便加了点料</h3><p>现在笔者想在<code>doSomething</code>结束后，从头来过，再来一遍，为了便于区分，顺便加了点其他料：</p><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    restart := <span class="literal">true</span></span><br><span class="line">    runningTimes := <span class="number">1</span></span><br><span class="line">INIT:</span><br><span class="line">    doSomething(runningTimes)</span><br><span class="line">    <span class="keyword">defer</span> finishDoSomething(runningTimes)</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Something has been done &quot;</span>, runningTimes, <span class="string">&quot; time&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> restart &#123;</span><br><span class="line">        <span class="built_in">println</span>(<span class="string">&quot;Star Over&quot;</span>)</span><br><span class="line">        restart = <span class="literal">false</span></span><br><span class="line">        runningTimes++</span><br><span class="line">        <span class="keyword">goto</span> INIT</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">doSomething</span><span class="params">(time <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Do something &quot;</span>, time, <span class="string">&quot; time&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">finishDoSomething</span><span class="params">(time <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Finish do something &quot;</span>, time, <span class="string">&quot; time&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时程序的输出为</p><figure class="highlight plaintext"><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">Do something  1  time</span><br><span class="line">Something has been done  1  time</span><br><span class="line">Star Over</span><br><span class="line">Do something  2  time</span><br><span class="line">Something has been done  2  time</span><br><span class="line">Finish do something  2  time</span><br><span class="line">Finish do something  1  time</span><br></pre></td></tr></table></figure><p>前五行看下来似乎都没什么问题，着重看最后两行，为什么<code>runningTimes</code>变成2之后仍旧打出了<br><code>Finish do something  1  time</code>呢?<br>这里其实与<code>goto</code>没有任何关系，是<code>defer</code>的特性，当程序经过<code>defer</code>时，会进行参数预计算，也就是说，当<code>runTimes</code>是1时，加载到<code>defer finishDoSomething(runningTimes)</code>的时候，就会把<code>finishDoSomething(1)</code>丢到栈里，所以最后可以打印出来<code>Finish do something  1  time</code>。<br>然后是打印次序及顺序的问题，把刚刚的执行过程用代码重新梳理一下可能就会好理解地多：</p><figure class="highlight go"><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">doSomething(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">defer</span> finishDoSomething(<span class="number">1</span>)</span><br><span class="line"><span class="built_in">println</span>(<span class="string">&quot;Something has been done &quot;</span>, <span class="number">1</span>, <span class="string">&quot; time&quot;</span>)</span><br><span class="line"><span class="built_in">println</span>(<span class="string">&quot;Star Over&quot;</span>)</span><br><span class="line">doSomething(<span class="number">2</span>)</span><br><span class="line"><span class="keyword">defer</span> finishDoSomething(<span class="number">2</span>)</span><br><span class="line"><span class="built_in">println</span>(<span class="string">&quot;Something has been done &quot;</span>, <span class="number">2</span>, <span class="string">&quot; time&quot;</span>)</span><br></pre></td></tr></table></figure><p>所以，<code>goto</code> 之前有<code>defer</code>的照样会执行，很显然，在这个例子里执行了两次<code>defer</code>（当然预计算的值不一样）。</p><h3 id="此时一个人畜无害的Mutex混入其中"><a href="#此时一个人畜无害的Mutex混入其中" class="headerlink" title="此时一个人畜无害的Mutex混入其中"></a>此时一个人畜无害的Mutex混入其中</h3><p>此时，出于某种原因，在<code>doSomething</code>的时候，必须对<code>locker</code>进行锁定，好让其他人不能同时一起<code>doSomething</code>，稍加修改：</p><figure class="highlight go"><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><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="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// 不想多写一个goroutine绝对不是我懒</span></span><br><span class="line">    restart := <span class="literal">true</span></span><br><span class="line">    runningTimes := <span class="number">1</span></span><br><span class="line">    locker := sync.Mutex&#123;&#125;</span><br><span class="line">INIT:</span><br><span class="line">    locker.Lock()</span><br><span class="line">    doSomething(runningTimes)</span><br><span class="line">    <span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        finishDoSomething(runningTimes)</span><br><span class="line">        <span class="keyword">defer</span> locker.Unlock()</span><br><span class="line">    &#125;()</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Something has been done &quot;</span>, runningTimes, <span class="string">&quot; time&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> restart &#123;</span><br><span class="line">        <span class="built_in">println</span>(<span class="string">&quot;Star Over&quot;</span>)</span><br><span class="line">        restart = <span class="literal">false</span></span><br><span class="line">        runningTimes++</span><br><span class="line">        <span class="keyword">goto</span> INIT</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">doSomething</span><span class="params">(time <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Do something &quot;</span>, time, <span class="string">&quot; time&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">finishDoSomething</span><span class="params">(time <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Finish do something &quot;</span>, time, <span class="string">&quot; time&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时执行此程序，似乎出现了不得了的问题<del>(经典故弄玄虚环节)</del></p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">Do something  1  time</span><br><span class="line">Something has been done  1  time</span><br><span class="line">Star Over</span><br><span class="line">fatal error: all goroutines are asleep - deadlock!</span><br></pre></td></tr></table></figure><p>竟然出现了死锁！<del>(似乎也不必这么惊讶)</del><br>这里面的死锁其实是由于锁重入导致的，Golang是不支持重入锁的，这就会导致死锁。<br>已知了原因似乎也很好解决了，大不了<code>goto</code>前解锁呗：</p><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// 不想多写一个goroutine绝对不是我懒</span></span><br><span class="line">    restart := <span class="literal">true</span></span><br><span class="line">    runningTimes := <span class="number">1</span></span><br><span class="line">    locker := sync.Mutex&#123;&#125;</span><br><span class="line">INIT:</span><br><span class="line">    locker.Lock()</span><br><span class="line">    doSomething(runningTimes)</span><br><span class="line">    <span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        finishDoSomething(runningTimes)</span><br><span class="line">        <span class="keyword">defer</span> locker.Unlock()</span><br><span class="line">    &#125;()</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Something has been done &quot;</span>, runningTimes, <span class="string">&quot; time&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> restart &#123;</span><br><span class="line">        <span class="built_in">println</span>(<span class="string">&quot;Star Over&quot;</span>)</span><br><span class="line">        restart = <span class="literal">false</span></span><br><span class="line">        runningTimes++</span><br><span class="line">        locker.Unlock()</span><br><span class="line">        <span class="keyword">goto</span> INIT</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">doSomething</span><span class="params">(time <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Do something &quot;</span>, time, <span class="string">&quot; time&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">finishDoSomething</span><span class="params">(time <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Finish do something &quot;</span>, time, <span class="string">&quot; time&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>于是梅开二度：</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">Do something  1  time</span><br><span class="line">Something has been done  1  time</span><br><span class="line">Star Over</span><br><span class="line">Do something  2  time</span><br><span class="line">Something has been done  2  time</span><br><span class="line">Finish do something  2  time</span><br><span class="line">Finish do something  2  time</span><br><span class="line">fatal error: sync: unlock of unlocked mutex</span><br></pre></td></tr></table></figure><p>确实，解决了重入问题，但是没有解决<code>Unlock</code>两次的问题。<br>从程序跑通的角度来看，利用<code>defer</code>的特性再加一个<code>defer locker.Lock()</code>便可解君愁了：</p><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// 不想多写一个goroutine绝对不是我懒</span></span><br><span class="line">    restart := <span class="literal">true</span></span><br><span class="line">    runningTimes := <span class="number">1</span></span><br><span class="line">    locker := sync.Mutex&#123;&#125;</span><br><span class="line">INIT:</span><br><span class="line">    locker.Lock()</span><br><span class="line">    doSomething(runningTimes)</span><br><span class="line">    <span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        finishDoSomething(runningTimes)</span><br><span class="line">        <span class="keyword">defer</span> locker.Unlock()</span><br><span class="line">    &#125;()</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Something has been done &quot;</span>, runningTimes, <span class="string">&quot; time&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> restart &#123;</span><br><span class="line">        <span class="built_in">println</span>(<span class="string">&quot;Star Over&quot;</span>)</span><br><span class="line">        restart = <span class="literal">false</span></span><br><span class="line">        runningTimes++</span><br><span class="line">        locker.Unlock()</span><br><span class="line">        <span class="keyword">defer</span> locker.Lock()</span><br><span class="line">        <span class="keyword">goto</span> INIT</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">doSomething</span><span class="params">(time <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Do something &quot;</span>, time, <span class="string">&quot; time&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">finishDoSomething</span><span class="params">(time <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Finish do something &quot;</span>, time, <span class="string">&quot; time&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>成功了！它跑起来了！开心！<del>(棒读)</del><br>原本加<code>locker</code>的目的在于执行本段代码的时候，有且只有一个<code>goroutine</code>在执行，但是在上述的修改中，更加积重难返，为了<code>Unlock</code>特意<code>Lock</code>这种操作其实大可不必，到这种程度了，修改原来代码的逻辑其实是更好的选择，比如：</p><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    restart := <span class="literal">true</span></span><br><span class="line">    runningTimes := <span class="number">1</span></span><br><span class="line">    locker := sync.Mutex&#123;&#125;</span><br><span class="line">    locker.Lock()</span><br><span class="line">    <span class="keyword">defer</span> locker.Unlock()</span><br><span class="line">INIT:</span><br><span class="line">    doSomething(runningTimes)</span><br><span class="line">    <span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        finishDoSomething(runningTimes)</span><br><span class="line">    &#125;()</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Something has been done &quot;</span>, runningTimes, <span class="string">&quot; time&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> restart &#123;</span><br><span class="line">        <span class="built_in">println</span>(<span class="string">&quot;Star Over&quot;</span>)</span><br><span class="line">        restart = <span class="literal">false</span></span><br><span class="line">        runningTimes++</span><br><span class="line">        <span class="keyword">goto</span> INIT</span><br><span class="line">    &#125;    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>又或者您坚持需要一个可以重入的锁<del>建议自己写一个</del>，可以参考一下以下解决方案：<br><a href="https://segmentfault.com/a/1190000040092635">Go语言如何实现可重入锁？ - SegmentFault 思否</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Golang" scheme="https://blog.archetto.moe/categories/Golang/"/>
    
    
    <category term="Golang" scheme="https://blog.archetto.moe/tags/Golang/"/>
    
  </entry>
  
  <entry>
    <title>操作系统中的进程管理</title>
    <link href="https://blog.archetto.moe/2022/04/24/Computer%20System/Process-Management/"/>
    <id>https://blog.archetto.moe/2022/04/24/Computer%20System/Process-Management/</id>
    <published>2022-04-24T15:49:17.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="进程"><a href="#进程" class="headerlink" title="进程"></a>进程</h2><p>在进程模型中，计算机上所有可运行的软件，通常也包括操作系统，被组织成若干顺序进程（sequential process），简称进程（process）。一个进程就是一个正在执行程序的实例，包括程序计数器、寄存器和变量的当前值。从概念上说，每个进程拥有它自己的虚拟CPU。一个进程是某种类型的一个活动，它有程序、输入、输出以及状态。单个处理器可以被若干进程共享，它使用某种调度算法决定何时停止一个进程的工作，并转而为另一个进程提供服务。</p><h2 id="PCB-Process-Control-Block"><a href="#PCB-Process-Control-Block" class="headerlink" title="PCB (Process Control Block)"></a>PCB (Process Control Block)</h2><table><thead><tr><th>进程管理字段</th><th>存储管理字段</th><th>文件管理字段</th></tr></thead><tbody><tr><td>寄存器</td><td>正文段指针</td><td>root目录</td></tr><tr><td>程序计数器</td><td>数据段指针</td><td>工作目录</td></tr><tr><td>程序状态字</td><td>堆栈段指针</td><td>文件描述符</td></tr><tr><td>堆栈指针</td><td></td><td>用户ID</td></tr><tr><td>进程状态</td><td></td><td>组ID</td></tr><tr><td>优先级</td><td></td><td></td></tr><tr><td>调度参数</td><td></td><td></td></tr><tr><td>进程ID</td><td></td><td></td></tr><tr><td>父进程</td><td></td><td></td></tr><tr><td>进程组</td><td></td><td></td></tr><tr><td>信号</td><td></td><td></td></tr><tr><td>进程开始时间</td><td></td><td></td></tr><tr><td>使用的CPU时间</td><td></td><td></td></tr><tr><td>子进程的CPU时间</td><td></td><td></td></tr><tr><td>下次报警时间</td><td></td><td></td></tr></tbody></table><p>从上表可以更好的理解进程的定义：一个进程是某种类型的一个活动，它有程序、输入、输出以及状态。</p><h2 id="进程状态变更"><a href="#进程状态变更" class="headerlink" title="进程状态变更"></a>进程状态变更</h2><p><img src="https://blog.archetto.moe/images/Linux/Process-State-Changes.svg" alt="Changes of Process State" loading="lazy"><br>对于进程的控制，使用的是原语，一般有创建原语、撤消原语、阻塞原语、唤醒原语等。</p><p><em>原语是指由若干条指令组成的程序段，在执行过程中不可被中断</em></p><h3 id="进程的各种状态"><a href="#进程的各种状态" class="headerlink" title="进程的各种状态"></a>进程的各种状态</h3><h4 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h4><p>进程的创建由创建原语实现，创建原语主要有四步操作：</p><ol><li>申请PCB</li><li>分配资源</li><li>初始化PCB</li><li>插入就绪队列</li></ol><p>进程的创建主要由四种事件触发：</p><ol><li>系统初始化</li><li>正在运行的进程创建子进程(fork)</li><li>用户请求创建</li><li>批处理初始化</li></ol><h4 id="就绪"><a href="#就绪" class="headerlink" title="就绪"></a>就绪</h4><p>此状态表示进程可运行，但因为其他进程正在运行而暂时停止。</p><h4 id="阻塞"><a href="#阻塞" class="headerlink" title="阻塞"></a>阻塞</h4><p>此状态下，除非某种外部事件发生，否则进程不能运行。</p><h4 id="运行"><a href="#运行" class="headerlink" title="运行"></a>运行</h4><p>此状态表示该时刻进程实际占用CPU，即正在运行。</p><h4 id="终止"><a href="#终止" class="headerlink" title="终止"></a>终止</h4><p>终止有两大类，一是自愿终止（正常退出&amp;出错退出），二是非自愿终止（严重错误&amp;被其他进程杀死），由撤销原语完成。</p><h3 id="进程三种主要状态的变更"><a href="#进程三种主要状态的变更" class="headerlink" title="进程三种主要状态的变更"></a>进程三种主要状态的变更</h3><h4 id="就绪状态-运行状态"><a href="#就绪状态-运行状态" class="headerlink" title="就绪状态 -&gt; 运行状态"></a>就绪状态 -&gt; 运行状态</h4><p>分配到CPU使用时间，状态变更至运行状态。</p><h4 id="运行状态-就绪状态"><a href="#运行状态-就绪状态" class="headerlink" title="运行状态 -&gt; 就绪状态"></a>运行状态 -&gt; 就绪状态</h4><p>CPU使用时间结束，状态变更至就绪状态。</p><h4 id="运行状态-阻塞状态"><a href="#运行状态-阻塞状态" class="headerlink" title="运行状态 -&gt; 阻塞状态"></a>运行状态 -&gt; 阻塞状态</h4><p>进程请求资源或者等待事件时，状态变更至阻塞状态，操作由阻塞原语完成。</p><h4 id="阻塞状态-就绪状态"><a href="#阻塞状态-就绪状态" class="headerlink" title="阻塞状态 -&gt; 就绪状态"></a>阻塞状态 -&gt; 就绪状态</h4><p>由唤醒原语进行状态的切换，由系统进程或事件发生进程唤醒。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://houbb.github.io/2020/10/04/os-01-process">https://houbb.github.io/2020/10/04/os-01-process</a><br><a href="http://kjwy.5any.com/czxt/content/czxt03/Czxt-kcjj-030302.htm">http://kjwy.5any.com/czxt/content/czxt03/Czxt-kcjj-030302.htm</a><br>ANDREW S.TANENBAUM. 现代操作系统</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="计算机系统" scheme="https://blog.archetto.moe/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F/"/>
    
    
    <category term="Process Management" scheme="https://blog.archetto.moe/tags/Process-Management/"/>
    
    <category term="Operating System" scheme="https://blog.archetto.moe/tags/Operating-System/"/>
    
  </entry>
  
  <entry>
    <title>2022的学习路线</title>
    <link href="https://blog.archetto.moe/2022/04/24/Roadmap/2022-Learning-Roadmap/"/>
    <id>https://blog.archetto.moe/2022/04/24/Roadmap/2022-Learning-Roadmap/</id>
    <published>2022-04-24T14:42:37.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="学习路线"><a href="#学习路线" class="headerlink" title="学习路线"></a>学习路线</h2><p><img src="https://blog.archetto.moe/images/Roadmap/Learning-Roadmap-2022.svg" alt="Learning-Roadmap-2022" loading="lazy"><br><em>A Roadmap with multi lines.</em></p><h2 id="Skill-Line"><a href="#Skill-Line" class="headerlink" title="Skill Line"></a>Skill Line</h2><h3 id="OS-Concepts"><a href="#OS-Concepts" class="headerlink" title="OS Concepts"></a>OS Concepts</h3><h3 id="API"><a href="#API" class="headerlink" title="API"></a>API</h3><h3 id="Database"><a href="#Database" class="headerlink" title="Database"></a>Database</h3><h3 id="Infrastructure-as-Code"><a href="#Infrastructure-as-Code" class="headerlink" title="Infrastructure as Code"></a>Infrastructure as Code</h3><h2 id="Algorithm-Line"><a href="#Algorithm-Line" class="headerlink" title="Algorithm Line"></a>Algorithm Line</h2><h3 id="Data-Structure"><a href="#Data-Structure" class="headerlink" title="Data Structure"></a>Data Structure</h3><h3 id="String-Number"><a href="#String-Number" class="headerlink" title="String&amp;Number"></a>String&amp;Number</h3><h3 id="Search"><a href="#Search" class="headerlink" title="Search"></a>Search</h3><h3 id="Graph-Theory"><a href="#Graph-Theory" class="headerlink" title="Graph Theory"></a>Graph Theory</h3><h3 id="Dynamic-Programming"><a href="#Dynamic-Programming" class="headerlink" title="Dynamic Programming"></a>Dynamic Programming</h3><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><strong>roadmap.sh真的好用</strong></p><p><a href="https://roadmap.sh/devops">https://roadmap.sh/devops</a></p><p><a href="https://roadmap.sh/golang">https://roadmap.sh/golang</a></p><p><a href="https://roadmap.sh/backend">https://roadmap.sh/backend</a></p><p><a href="https://oi.wiki/">https://oi.wiki</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="学习路线" scheme="https://blog.archetto.moe/categories/%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF/"/>
    
    
    <category term="Roadmap" scheme="https://blog.archetto.moe/tags/Roadmap/"/>
    
    <category term="Learning Roadmap" scheme="https://blog.archetto.moe/tags/Learning-Roadmap/"/>
    
  </entry>
  
  <entry>
    <title>记2022的第一次面试</title>
    <link href="https://blog.archetto.moe/2022/03/30/Interview/game-company-2022-03-30/"/>
    <id>https://blog.archetto.moe/2022/03/30/Interview/game-company-2022-03-30/</id>
    <published>2022-03-30T15:44:37.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="碎碎念"><a href="#碎碎念" class="headerlink" title="碎碎念"></a>碎碎念</h2><p>大家都跑路了，我也想跑路呀，于是乎，在同事的推荐下，有幸能去一家游戏公司面试。</p><h2 id="我的缺陷"><a href="#我的缺陷" class="headerlink" title="我的缺陷"></a>我的缺陷</h2><h3 id="对算法不够熟悉"><a href="#对算法不够熟悉" class="headerlink" title="对算法不够熟悉"></a>对算法不够熟悉</h3><p>磕磕巴巴地写完了笔试部分，发现以前打竞赛学的好多东西都忘了，LeetCode还是得坚持刷下去，以前DP问题都是小case。</p><h3 id="对Golang底层的理解不够深入"><a href="#对Golang底层的理解不够深入" class="headerlink" title="对Golang底层的理解不够深入"></a>对Golang底层的理解不够深入</h3><ol><li><p>GMP调度逻辑<br>我大概知道GMP调度流程，但是并没有仔细深究。</p></li><li><p>Profiling 经验不够<br>在现在这家公司，可能全部门也就我一个在Profiling自己写的程序，野路子对着网上的指南着实也有点难。</p></li><li><p>GC的深入了解<br>都知道Golang用的是三色标记+写屏障，但是内存逃逸如何分析着实经验不够。</p></li></ol><h3 id="对现实不够理解"><a href="#对现实不够理解" class="headerlink" title="对现实不够理解"></a>对现实不够理解</h3><ol><li><p>不熟悉游戏行业<br>我本身从事的是物联网行业，这次去面试的则是游戏行业，虽然都是后端开发，但是行业不一样，依旧有隔阂。<br>面试官很nice地向我介绍游戏行业的<em>规则</em>，每完成一个项目就可以开一次奖，可能是空的，但是开的奖多了，开出东西的概率就高了，确实与我现在在的行业截然不同，涨知识了。<br>不过同时，加班还是要的。</p></li><li><p>没有对职业生涯有较好的规划<br>同样是面试官跟我说的，我觉得很值得思考。在程序员的行业里，如何衡量一个人的价值，特别是工作几年之后。可能衡量地标准并不是技术的精深与否，而是业务能力的强弱。</p></li></ol><h2 id="想到的解决方案"><a href="#想到的解决方案" class="headerlink" title="想到的解决方案"></a>想到的解决方案</h2><ol><li><p>刷题<br>每日LeetCode我来了。</p></li><li><p>被Code Review<br>继续投入到目前参与的开源活动中，大佬教教我.jpg。</p></li><li><p>看看底层<br>虽然说大部分情况下，学习底层的东西收益不高，但是确实很好玩儿。</p></li><li><p>加强Profiling<br>Profiling自己写的代码，多看看人家是怎么分析的。</p></li><li><p>认真考虑一下以后从事的细分行业<br>隔行如隔山，所言甚是。<br>对于游戏，我谈不上特别热爱吧，但是确实很喜欢RTS游戏，基本不打FPS，确实有一段时间想如果可以一定给群星的CPU 0减减负（一核有难，7核围观.jpg）<del>，虽然P社玩家应该直接清空别人家的人口减负</del>。</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>今年开年的第一次面试，有些马虎，有些害怕，不过这一小时过去确实已经学到了很多，很高兴能去面试，很感谢同事的推荐。</p><h2 id="Update"><a href="#Update" class="headerlink" title="Update"></a>Update</h2><p><em>2022-04-02</em><br>今天去二面了，有两位面试官，第一位面试官给了个邮件分发的场景，考察了一下并发编程，问了问我数据库方面的知识，比如MongoDB和MySQL的区别，主备方案 .etc，然后复盘了一下我的上一个项目。<br>第二位面试官来了之后，综合考量了一下我的经历和项目，然后也是抛出了两个很关键的问题。</p><ol><li>加班强度能否接受</li><li>是否会在游戏行业继续下去<br>这是两个很值得反思的问题，这次想跳槽的原因，一是觉得工资不够，个人资本累积太慢，通胀太快，二是想学习精进一下自己的技术。<br>有一说一，起初并没有考虑这么多。<br>我觉得加班可能不是问题（虽然面试官觉得我可能顶不住），但是经过两轮的面试，无论是哪个面试官，给我的最直观的印象就是对于游戏的热诚。无论他们从事的是哪个岗位，对游戏的热爱可能是支持他们工作下去的动力，也是他们做出好游戏的动力。<br>就我对游戏而言，热情是有的，但是扪心自问可能赶不上他们这些Game Dev<del>，某种意义上对钱的热情可能不亚于他们</del>。<br>还有就是对个人整个职业生涯的规划，我觉得的面试官们，都在很大程度上给我提了个醒，真的非常nice。<br>无论最后结果如何，我觉得这段面试经历都很值得。</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="面试总结" scheme="https://blog.archetto.moe/categories/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/"/>
    
    
    <category term="Golang" scheme="https://blog.archetto.moe/tags/Golang/"/>
    
    <category term="Interview" scheme="https://blog.archetto.moe/tags/Interview/"/>
    
    <category term="Game Dev" scheme="https://blog.archetto.moe/tags/Game-Dev/"/>
    
  </entry>
  
  <entry>
    <title>一份远方朋友的书单</title>
    <link href="https://blog.archetto.moe/2022/03/24/Cultural/Book-List-From-A-Friend/"/>
    <id>https://blog.archetto.moe/2022/03/24/Cultural/Book-List-From-A-Friend/</id>
    <published>2022-03-24T22:32:39.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look.">  <script id="hbeData" type="hbeData" data-hmacdigest="e41988be0fc8fa8131fe51aaf98b592950e9320c3e21d51b17dc6f23dd53d21c">de1b41777fc74d019a3cac33152630a9751ff97e3c5ddff60e6e08ff2cb13ae15748d0dfeaf72a6014983b852f37527cb75b46e6f88fbef528b5b72115454d12c5367b2cc7d9d872a86d213b653d092939053b46444c6d75a03df94cc2d6823d7462df21b36fe81b4b74f7955c2526e0981bf51a75a9ec29610e994a5062da9ed47a2a4d8099b60db7adecf26bbeab353969d9c82dbb71fc7ca93486226b4c94aa043f31fde3a47edc32f461780e1091c68cdc94dc886200305f01bd9d407f08133bb2df5e4cb4e339f2a68b5b57ea026e3d5576b9f27deea25692a052a39a006d21deb3709aef8f2e52aa0238f4f1e2849ca14e8df2d529183401f967afd52aeec2c79d41812c9fc13e66b7a877d38055e3c8473420ba24874de3aced50f6d76e248079497cb8baf6947c29ff9b56476253df31de0efb2e09fcd6a9a8e68594d93ac7e26c6812ea43034526b0135220264eff2da9b78729ad5de08e78a1f853bca3274faebe286a9fd9ea1c6b82c7d896f740eef30368e8005eac19c49249487603aa7e7a1c421a15bf2233929fa930610bcd99bedbaf7dbdba818a9e9ea9fce7cee1fb558ffa8e0c34856ed1e383468ee3e9c6ffb01a7ceee17093e53c708f58ccc2b8594a6cebcdfe83f804f4f10fe1b08da5e7c02763e1cdbbefdfc9f6dfe239a7f585f39793f3be5885bd634c37cfb9dd6f01542a91acd8e4dba733fe0e33138660f23f4af9cd6f5a85e50fff19925b84876db7a8b73ddf616d0fa998116c4b92ce8a4f8e032ef6ab3f8de9bfede151f8ee9fbd7ec71de2a2693bf8b29f85a0c18ed1cde85aa82f9b2235492102550be536b0d8e60354dfad7513f4e2b20271d7b3f2cfe8fae7746298ccada2a0af1bb5c84e4e66dfa76a5f6eddef679a7c8b57dc9fccb047e31fb6a4d7e34cd8f831ef4f879417701af2fa1cba58900ac0faf861c6290115834d74d175a01bdca93a2e5a39829d81ee47776d65cd6b67e7002e3157b3bd4fbe6b02f3d6f8763189c70aa6c74cfa9b8decb9775261569a571f6a6579364d82efaac372f798cbe727c85a36254d5bdc64cf77c9e8053f4c0e06f81eb0a0c7c2bd760dbba229093a79505e17122a373812c2e69f285ab399ef3805f71f15b6823c3929e8436c34208fda980f4018ea7bcf3d6abde9b03920d988621b80c23869218ad8dc202dcf64e533c16a08f86372a7928ca23a052e18dd628a3d1808da03c30a6af39bae80b667f2f47e1386e961c1274034e6919adae369de88c6c59669c71635ab4464c6f6955a2c8ef700025f3e8a439c207c4c7459f9c0c9dfe3ef10f8c13561c429e3ad313571602911445af6187aea377527f53742e8a2350a3e70100c872dfcd46010c2ce60a9f34adf699ab8a787b9e07e7841160fd325f4377a02fc2a48cf0ee69c28982e0d4a346d2619c66faa3892e9fcac6d27a609898532d8f0c5260165755d31f3431341a9d1e9aa09ba441c1af29dc83b44b894b3f164fc70a26e158e24a54a1f5e85820f4ba2a3e37fa9a1073fc568082736fa9554de0080a4a21258022a59ad8990c5677f3bf7b65dd59992d1750d52832d0867eeb59038787fe9e17bfc4aec8033e5d8c618d70d899b00b44ea6466367fbbe580938315e1332af3b1962d705af7a0322eab30846a6a6bf11b1f384f6a08fe0c974e02ad841992571fbdcc7420ad9e1dec3bc73d883b23249692a20b37dd678381692cdd1baa58e816f826c13c573aa154f1f9d0a596e419ce0d398b9747a8b8155be182bdb9d581ed2b55d76ca2e77e01f41fa8146aa046fee05dc0a75002098ffa383037158041d334fae4875bc367512b75091c1aff7ebfd562268b7b17fc6ebb04bbfce103490a795a76ad03f7fe019681fc80b5de5f13bff5e7f7daba4ae5d759d0827887081c8a655339321dba850cfcd0d52451ed86c8863d576d5489aa3b7af3ead706634c307218d10d2e00c986d78b81c81f31cf293d7001438e7dfb6f5a28dea1ab9ddf8642e84c1f7b12330c4c7135482617fd10afa1deb4b7f9a7e49c6ca42e58dbc0f21dbe7250ca9111ad5c2ea4fc718ebf407368f5ea4f3663c0cbc5df4d130d36cd428a5d6cab4a3290922541a1782cbb81c74ced074a8ad04d7328ced7a09172badfdd23de1a888abb8079229f3b161ba4312dd1de90f4f7c694a65233a710a2692b3bd85105d36ef104dcab459d2e16bc31fd4ae63cd9ce7736846c2ab9c4817656792113b67aa757b2e89cdbd50d9514eb1835bc879deba5f68b9330da46a54d713fb5afda396dd940a7fb017373ce2e924cd06a5fd9d3467b4e4c97fcff2e5496a429a26ceed25e49926f94b7400063cbf8962d3dc596c8cfd5ff631495d0a96627266eb6dfa19e5bc637b1917f0af5a791baf3070906847ec096adbdf646af3e9280c09b24a3c71e1145e88bfa33d422d6ce34732d947efe36736b596e9a3738655df5eb566be0113f8c916f6f6bb095d563b7e87f0d15cfd75a3382477984fe628b7e2231e842181773fcc601b77ef9d91c8c2c815d2d5273cbf5474c06794426dd4b87d83c74e9fef6e4593eb8c20e45c08a22efddf564e6626c9bc41a40599009666d31324741a3d02395dbd832bb2b3d52775ebb1a456abd16081509864bd785ddaefef3ba6caeaccb88bdd1109f1c14e46057f41e5887b84327919be8823bfd0da1be8534755d37bfa4ece8804f5816cfbfa0ecf83d3caf38d781068936ee59e9f79801d349fbd666d6003c117e2728f17c65c8dd65054f47e975bc1e40b1a0678bd52abc6d81c961459b339719ecb7a8845efbc3b7173d56a116b0a0449e5d45503244ad816a1263c67dc4a707754d2a9bb84ed90a3939f61ba1b8b61daa71f372f56cf7b237aa02ccb98ce627e65de822dfbf9dc584420f7e2a392579b660a907edcbcdc8837a71decfe31c12e8a420b9d5f92bd00971d0f6019593163916d8968255bf371a04f29f0207775ec87c74f0c8c57e917ec05adf5394a28fa901986dc36f00167fd12525e31bfc7d8b5bebb7473f5f0db8b4f03b3209b8977f5e148591f738778d538347ec8af1e1dfe95f789167fcd55c254c55ccb0e235e0e135afe65fe54c619e2eaa453d8844fad8eb49976134ca13ea9865911d6bf1dc18de4f91ce7e19c07f0932169d45e3902fa896bd537cd3031a3b3ebc226b6c8157fa02755a48a158f429506382423a929c1265f9378e7930da6c81f70568cda9a68ebd6bdb26eb82d16555804b17887f86b75543174c43f44b393b48d3c5c7c9a0aca7ded376a1449ce1b2c2ad770d43f541336378b9786cb480efcd21b3d77bcde4d81a664861e0db54367c2b2d5f7df407f3b7f7576121375fc10b2d8a64953a1fb2cfdb227b3303683de1d67a607bca788e92b614da136dabf12b52e1667e7cf1791902f182a52321baeb49c999fb1b7bb575f14674c7823a9d342c229726bd419d9e0ce9e180ceb2c7ccb870f07daf83a8d4a93a8ad413051422942968eb4b991274b7d66ca6e2772ebb313f30c7ae562660cc1860f718ad2e88cd35abc0af8fbb3f596e9d061c8a27d7db628c71e6810c9c9a0bc4da2663281e663508e065d9e23dbbdc8a4b3d138b25c5ec302449912eac066cf7e9dd5caea3e9cf8ad242480e4db9d6b16830a3c26aa79aafe7df17d58b80c963a6186dd35103a3428b5e32e527264ee6fcc7c727c48d50a9f2293a23acbfb4bac6086798acdb4624447bf44ab9812c2324aabf496da02eb44223a74a2456cd91ee5731b72d4c60e604181e4fb409c2a968350d87966464a0aaa455ecb6344a4e9161dc2d658e707ad14be4d9d1da0466df4abbe62932a8d6509dc55a30b1d40148c02a5fb7249bd097fd73b275eccebce7d07a25f1dc43edd510d6206208dc64306f37b6b9c41192c7db6e6760e09d5a085d676c459177a</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-default">      <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-default">Hey, password is required here.</span>      </label>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    
    <summary type="html">Here&#39;s something encrypted, password is required to continue reading.</summary>
    
    
    
    <category term="人文社科" scheme="https://blog.archetto.moe/categories/%E4%BA%BA%E6%96%87%E7%A4%BE%E7%A7%91/"/>
    
    
    <category term="书单" scheme="https://blog.archetto.moe/tags/%E4%B9%A6%E5%8D%95/"/>
    
    <category term="人文" scheme="https://blog.archetto.moe/tags/%E4%BA%BA%E6%96%87/"/>
    
  </entry>
  
  <entry>
    <title>记一次FLV直播流服务器搭建</title>
    <link href="https://blog.archetto.moe/2022/03/24/Linux/Flv-Over-HTTP/"/>
    <id>https://blog.archetto.moe/2022/03/24/Linux/Flv-Over-HTTP/</id>
    <published>2022-03-24T20:55:00.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>为了压测公司开发的某视频服务，需要配置一个后端推流服务，以flv over http的方式进行推送，可能要承载一千个连接（512码率的视频）。</p><h2 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h2><h3 id="安装ffmpeg"><a href="#安装ffmpeg" class="headerlink" title="安装ffmpeg"></a>安装ffmpeg</h3><p>默认给我的机子是Cent OS 7.6，系统有点陈旧，内核也有点老，一开始想直接升级成Cent OS 8，但是换了个5.x的内核导致一些依赖出错，索性直接一键DD成Debian 10（运维那边也没有现成的系统）。</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 萌咖大佬的脚本</span></span><br><span class="line">wget --no-check-certificate -qO InstallNET.sh <span class="string">&#x27;https://moeclub.org/attachment/LinuxShell/InstallNET.sh&#x27;</span></span><br><span class="line"><span class="comment"># 华为云镜像站对公司网络友好些</span></span><br><span class="line">bash InstallNET.sh -d 10 -v 64 -a --mirror <span class="string">&#x27;https://mirrors.huaweicloud.com/debian/&#x27;</span></span><br></pre></td></tr></table></figure><p>一键DD后的默认密码是<code>MoeClub.org</code><br>让后换源</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">deb http://mirrors.huaweicloud.com/debian buster main contrib non-free</span><br><span class="line">deb http://mirrors.huaweicloud.com/debian buster-updates main contrib non-free</span><br><span class="line">deb http://mirrors.huaweicloud.com/debian buster-backports main contrib non-free</span><br><span class="line">deb http://mirrors.huaweicloud.com/debian-security/ buster/updates main contrib non-free</span><br></pre></td></tr></table></figure><p><em>换源之后安装<code>ca-certificates</code>，可以将上面的http换成https</em><br>随后安装ffmepg <code>apt install ffmepg</code>，成功后验证一下，输入<code>ffmpeg</code></p><figure class="highlight plaintext"><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><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">ffmpeg version 4.1.8-0+deb10u1 Copyright (c) 2000-2021 the FFmpeg developers</span><br><span class="line">  built with gcc 8 (Debian 8.3.0-6)</span><br><span class="line">  configuration: --prefix=/usr --extra-version=0+deb10u1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared</span><br><span class="line">  libavutil      56. 22.100 / 56. 22.100</span><br><span class="line">  libavcodec     58. 35.100 / 58. 35.100</span><br><span class="line">  libavformat    58. 20.100 / 58. 20.100</span><br><span class="line">  libavdevice    58.  5.100 / 58.  5.100</span><br><span class="line">  libavfilter     7. 40.101 /  7. 40.101</span><br><span class="line">  libavresample   4.  0.  0 /  4.  0.  0</span><br><span class="line">  libswscale      5.  3.100 /  5.  3.100</span><br><span class="line">  libswresample   3.  3.100 /  3.  3.100</span><br><span class="line">  libpostproc    55.  3.100 / 55.  3.100</span><br><span class="line">Hyper fast Audio and Video encoder</span><br><span class="line">usage: ffmpeg [options] [[infile options] -i infile]... &#123;[outfile options] outfile&#125;...</span><br></pre></td></tr></table></figure><h3 id="ffmpeg推流"><a href="#ffmpeg推流" class="headerlink" title="ffmpeg推流"></a>ffmpeg推流</h3><p>推流命令是<code>ffmpeg -re -i ./test.flv -c copy -f flv &quot;rtmp://&lt;IP地址&gt;/live/http&quot;</code>，<code>./test.flv</code>是flv文件的位置，<code>&quot;rtmp://&lt;IP地址&gt;/live/http&quot;</code> 则是推流的位置。</p><h3 id="NGINX-nginx-http-flv-module-编译"><a href="#NGINX-nginx-http-flv-module-编译" class="headerlink" title="NGINX + nginx-http-flv-module 编译"></a>NGINX + nginx-http-flv-module 编译</h3><p>下载NGINX 及 nginx-http-flv-module</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">wget http://nginx.org/download/nginx-1.20.2.tar.gz</span><br><span class="line">wget https://github.com/winshining/nginx-http-flv-module/archive/refs/tags/v1.2.10.zip</span><br><span class="line">tar -zxvf nginx-1.20.2.tar.gz</span><br><span class="line">unzip nginx-http-flv-module-1.2.10.zip</span><br></pre></td></tr></table></figure><p>随后编译</p><figure class="highlight plaintext"><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">./configure </span><br><span class="line">    --prefix= /opt/nginx </span><br><span class="line">    --with-http_ssl_module \</span><br><span class="line">    --with-http_secure_link_module \</span><br><span class="line">    --add-module= ../nginx-http-flv-module-1.2.10</span><br><span class="line">make -j$(($(nproc) + 1))</span><br><span class="line">make install</span><br></pre></td></tr></table></figure><p>安装成功后，启动nginx，输入命令<code>/opt/nginx/sbin/nginx</code>，测试一下。<br>随后修改配置文件<code>/opt/nginx/conf/nginx.conf</code></p><figure class="highlight plaintext"><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><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></pre></td><td class="code"><pre><span class="line">worker_processes  4;</span><br><span class="line"></span><br><span class="line">events &#123;</span><br><span class="line">    worker_connections  1024;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">http &#123;</span><br><span class="line">    include       mime.types;</span><br><span class="line">    default_type  application/octet-stream;</span><br><span class="line"></span><br><span class="line">    sendfile        on;</span><br><span class="line">    keepalive_timeout  65;</span><br><span class="line">    server &#123;</span><br><span class="line">        listen       80;</span><br><span class="line">        server_name  &lt;IP地址&gt;;</span><br><span class="line"></span><br><span class="line">        location / &#123;</span><br><span class="line">            root   html;</span><br><span class="line">            index  index.html index.htm;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        location /flv &#123;</span><br><span class="line">            flv_live on;</span><br><span class="line">            chunked_transfer_encoding on;</span><br><span class="line">            add_header &#x27;Access-Control-Allow-Origin&#x27; &#x27;*&#x27;;</span><br><span class="line">            add_header &#x27;Access-Control-Allow-Credentials&#x27; &#x27;true&#x27;;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        error_page   500 502 503 504  /50x.html;</span><br><span class="line">        location = /50x.html &#123;</span><br><span class="line">            root   html;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">rtmp_auto_push on;</span><br><span class="line">rtmp &#123;</span><br><span class="line">    server &#123;</span><br><span class="line">        listen 1935;</span><br><span class="line">        server_name 127.0.0.1;</span><br><span class="line">        notify_method get;</span><br><span class="line">        access_log  /var/log/nginx/access.log;</span><br><span class="line">        chunk_size 4096;</span><br><span class="line">        application live &#123;</span><br><span class="line">            live on;</span><br><span class="line">            allow publish all;</span><br><span class="line">            allow play all;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述&lt;IP地址&gt;是希望监听的地址，如可以是<code>localhost</code>，或者是<code>0.0.0.0</code>，又或者是局域网IP&#x2F;公网IP。</p><h3 id="出现问题"><a href="#出现问题" class="headerlink" title="出现问题"></a>出现问题</h3><p>以上方案进行推流，效果不是很理想，用VLC进行播放也会存在一定程度的卡顿，猜想原因可能是nginx对flv进行了切分，且nginx默认设置的buffer太大，导致buffer满了才发，然而对nginx动手脚也不是啥好办法，且还剩四小时就要验收（<del>接的烂摊子</del>）着实来不及。再网上重新调研后，我认为用srs搭建这个环境可能是比较不错的选择，抱着这种心态开始了进一步的尝试。</p><h3 id="编译srs"><a href="#编译srs" class="headerlink" title="编译srs"></a>编译srs</h3><p>srs比上面的方案好折腾地多。</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 直接下载最新的v4 beta 10</span></span><br><span class="line">wget https://github.com/ossrs/srs/releases/download/v4.0-b10/srs-server-4.0-b10.tar.gz</span><br><span class="line">tar -zxvf srs-server-4.0-b10.tar.gz</span><br><span class="line"><span class="built_in">cd</span> srs-4.0-b10/trunk &amp;&amp; ./configure &amp;&amp; make &amp;&amp; make install</span><br></pre></td></tr></table></figure><p>结束后，srs就会安装在<code>/usr/local/srs</code>下（想换个目录就去修改Makefile中的安装目录）。<br>写几个小脚本控制启动、关闭和状态查看。<br><em>start.sh</em></p><figure class="highlight bash"><table><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"><span class="meta">#!/bin/bash</span></span><br><span class="line">./objs/srs -c conf/srs.conf</span><br></pre></td></tr></table></figure><p><em>stop.sh</em></p><figure class="highlight bash"><table><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"><span class="meta">#!/bin/bash</span></span><br><span class="line">./etc/init.d/srs stop</span><br></pre></td></tr></table></figure><p><em>status.sh</em></p><figure class="highlight bash"><table><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"><span class="meta">#!/bin/bash</span></span><br><span class="line">./etc/init.d/srs status</span><br></pre></td></tr></table></figure><h3 id="ffmpeg-推流遇到的问题"><a href="#ffmpeg-推流遇到的问题" class="headerlink" title="ffmpeg 推流遇到的问题"></a>ffmpeg 推流遇到的问题</h3><p>如上面所示，我一开始采用的推流，不过是重复播放一个<code>test.flv</code>文件，这才另一边播放时，就会产生一个问题，会读到flv文件的末尾，导致播放器认为视频中止。<br>对于这个问题，可以去修改srs的源码，在复制流时去掉flv文件的头和尾，或者用一种更加方便的方案，复制别的rtmp流，推送给srs。<br>可见以下命令<br><code>ffmpeg -i &quot;rtmp://&lt;某个RTMP流的IP地址&gt;/live/http.flv&quot; -c copy -f flv &quot;rtmp://&lt;IP地址&gt;/live/http&quot;</code><br>需要注意的时，如果ffmpeg推送了一个错误的&#x2F;有问题的&#x2F;空的流给srs，可能导致srs崩溃。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>直接糊nginx一时爽，糊完火葬场。srs部署简单，承载力也不错，经测试，完全能跑满服务器的带宽，同时对CPU和内存的占用也不大。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Linux" scheme="https://blog.archetto.moe/categories/Linux/"/>
    
    
    <category term="flv" scheme="https://blog.archetto.moe/tags/flv/"/>
    
    <category term="RTMP" scheme="https://blog.archetto.moe/tags/RTMP/"/>
    
    <category term="ffmpeg" scheme="https://blog.archetto.moe/tags/ffmpeg/"/>
    
    <category term="nginx" scheme="https://blog.archetto.moe/tags/nginx/"/>
    
    <category term="nginx-http-flv-module" scheme="https://blog.archetto.moe/tags/nginx-http-flv-module/"/>
    
  </entry>
  
  <entry>
    <title>记一次Linux网络设置</title>
    <link href="https://blog.archetto.moe/2022/03/24/Linux/VM-Proxy/"/>
    <id>https://blog.archetto.moe/2022/03/24/Linux/VM-Proxy/</id>
    <published>2022-03-24T10:23:28.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>在使用虚拟机时，一般情况下桌面级虚拟化软件软件提供三种类型的网卡：bridge、host-only、NAT。使用bridge网卡可以使所有流量通过交换机进行交换，宿主机与虚拟机的互相访问也经由交换机，没网的情况下会很尴尬地连不上网，对于使用第三方终端（指宿主机SSH至虚拟机）操作Linux的用户来说，很不友好，且网络流量过大（宿主机&lt;-&gt;虚拟机）对网卡负担也比较重。<br>使用host-only网卡，可以实现宿主机与虚拟机直接进行通信，且可以设置固定IP，唯一问题在于不能访问外部网络。<br>使用NAT网卡，无法设置固定IP（可以设置使用宿主机的相关设置，但是这不就是桥接了？）。</p><h2 id="目的"><a href="#目的" class="headerlink" title="目的"></a>目的</h2><p>无论宿主机网络状态如何，都可以通过SSH访问虚拟机，且虚拟机可以正常访问网络。</p><h2 id="解决思路"><a href="#解决思路" class="headerlink" title="解决思路"></a>解决思路</h2><p>以host-only模式为网卡，解决host-only模式没有网络的问题（顺便解决科学上网）。</p><h2 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h2><h3 id="宿主机设置"><a href="#宿主机设置" class="headerlink" title="宿主机设置"></a>宿主机设置</h3><p>启用clash for windows （其他socks代理也可）的局域网访问功能，同时在Windows防火墙入站规则中添加规则，打开socks端口。<br><img src="https://blog.archetto.moe/images/Linux/Windows-Firewall-Setting.png" alt="Windows-Firewall-Setting" loading="lazy"><br><em>Windows 防火墙设置示例</em></p><h3 id="虚拟机设置（以Ubuntu为例）"><a href="#虚拟机设置（以Ubuntu为例）" class="headerlink" title="虚拟机设置（以Ubuntu为例）"></a>虚拟机设置（以Ubuntu为例）</h3><h4 id="安装redsocks"><a href="#安装redsocks" class="headerlink" title="安装redsocks"></a>安装redsocks</h4><figure class="highlight bash"><table><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"><span class="built_in">sudo</span> apt update -y &amp;&amp; \</span><br><span class="line"><span class="built_in">sudo</span> apt install redsocks -y</span><br></pre></td></tr></table></figure><h4 id="修改redsocks配置"><a href="#修改redsocks配置" class="headerlink" title="修改redsocks配置"></a>修改redsocks配置</h4><p><code>vim /etc/redsocks.conf</code></p><figure class="highlight plaintext"><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">redsocks &#123;</span><br><span class="line">    local_ip = 127.0.0.1;</span><br><span class="line">    local_port = 7890;</span><br><span class="line">ip = 192.168.x.1; // replace x by yourself</span><br><span class="line">port = 1080;</span><br><span class="line">    type = socks5;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="编写启动脚本"><a href="#编写启动脚本" class="headerlink" title="编写启动脚本"></a>编写启动脚本</h3><figure class="highlight bash"><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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># start redsocks</span></span><br><span class="line">systemctl start redsocks</span><br><span class="line"></span><br><span class="line"><span class="comment"># Create new chain</span></span><br><span class="line">iptables -t nat -N PROXY</span><br><span class="line"></span><br><span class="line"><span class="comment"># Ignore LANs and some other reserved addresses.</span></span><br><span class="line">iptables -t nat -A PROXY -d 0.0.0.0/8 -j RETURN</span><br><span class="line">iptables -t nat -A PROXY -d 10.0.0.0/8 -j RETURN</span><br><span class="line">iptables -t nat -A PROXY -d 127.0.0.0/8 -j RETURN</span><br><span class="line">iptables -t nat -A PROXY -d 169.254.0.0/16 -j RETURN</span><br><span class="line">iptables -t nat -A PROXY -d 172.16.0.0/12 -j RETURN</span><br><span class="line">iptables -t nat -A PROXY -d 192.168.0.0/16 -j RETURN</span><br><span class="line">iptables -t nat -A PROXY -d 224.0.0.0/4 -j RETURN</span><br><span class="line">iptables -t nat -A PROXY -d 240.0.0.0/4 -j RETURN</span><br><span class="line"></span><br><span class="line"><span class="comment"># Anything else should be redirected to port 7890</span></span><br><span class="line">iptables -t nat -A PROXY -p tcp -j REDIRECT  --to-ports 7890</span><br><span class="line">iptables -t nat -A OUTPUT -p tcp -j PROXY</span><br><span class="line">iptables -t nat -A OUTPUT -p udp -j PROXY</span><br></pre></td></tr></table></figure><p>使用root权限运行</p><h3 id="编写停止脚本"><a href="#编写停止脚本" class="headerlink" title="编写停止脚本"></a>编写停止脚本</h3><figure class="highlight bash"><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">iptables -F  </span><br><span class="line">iptables -X  </span><br><span class="line">iptables -Z  </span><br><span class="line">iptables -t nat -F  </span><br><span class="line">iptables -t nat -X  </span><br><span class="line">iptables -t nat -Z</span><br><span class="line">systemctl stop redsocks</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Linux" scheme="https://blog.archetto.moe/categories/Linux/"/>
    
    
    <category term="Linux" scheme="https://blog.archetto.moe/tags/Linux/"/>
    
    <category term="Proxy" scheme="https://blog.archetto.moe/tags/Proxy/"/>
    
    <category term="VMware" scheme="https://blog.archetto.moe/tags/VMware/"/>
    
    <category term="Ubuntu" scheme="https://blog.archetto.moe/tags/Ubuntu/"/>
    
  </entry>
  
  <entry>
    <title>记一次弱网环境搭建</title>
    <link href="https://blog.archetto.moe/2022/03/23/Linux/WANem-Deploy/"/>
    <id>https://blog.archetto.moe/2022/03/23/Linux/WANem-Deploy/</id>
    <published>2022-03-23T16:38:38.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>业务上有客户投诉，某服务工作不正常，具体表现为在客户的环境下，此服务请求及相应缓慢，但是处于某种原因并不能拿到该客户的网络情况。<br>前期研究中，对此服务进行压力测试，服务访问量远超业务峰值亦无法重现此问题，经客户配合后，得出客户的网络延迟较高，网络抖动较为严重。<br>为了优化用户体验，提升服务质量，需要在内网开发环境中设置一个弱网环境</p><h2 id="解决思路"><a href="#解决思路" class="headerlink" title="解决思路"></a>解决思路</h2><p>使用WANem在内网中搭建一个“网关”，使进行测试的流量“通过”这个网关，在此网关中进行延迟、丢包、损坏包等操作，进而构建弱网环境，复现问题。</p><h2 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h2><h3 id="配置虚拟机"><a href="#配置虚拟机" class="headerlink" title="配置虚拟机"></a>配置虚拟机</h3><h4 id="是否需要双网卡"><a href="#是否需要双网卡" class="headerlink" title="是否需要双网卡"></a>是否需要双网卡</h4><p>自己搭建WANem而不是使用同事搭建好的环境的原因在于，同事只给WANem配置了一张网卡，且WANem存在bug，不能指定对某个IP进行规则添加（理论上是可以的，但是在实践中碰壁），以至于丢包丢到web控制台无法控制，在自己搭建过程中，使用的版本是WANem，惊讶地发现竟然带着<a href="http://lxde.org/">LXDE</a>，一切都省力起来了。<br>假如开放给他人使用，需要额外添加网卡，仅做前期探索或测试可以不添加。</p><h4 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h4><p>我分配给这台虚拟机的配置是<code>1核CPU 1GRAM 8G HDD</code>，后面发现分多了，其实512M RAM + 4G HDD就够了，使用CD LIVE的话，甚至不需要磁盘。</p><h4 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h4><p>挂载完WANem镜像后直接启动，我是用的版本并不需要像其他版本一样在以下界面敲回车。<br><img src="https://blog.archetto.moe/images/Linux/WANem-boot.png" alt="WANem-boot" loading="lazy"><br>进入LXDE之后，设置网络（可选）。<br><em>公司网络IP MAC绑定，不能使用DHCP</em><br><img src="https://blog.archetto.moe/images/Linux/WANem-Network.png" alt="WANem-Network" loading="lazy"><br>在此处编辑连接，随后在以下位置连接网络（是的，不会主动连接）。<br><img src="https://blog.archetto.moe/images/Linux/WANem-Connect.png" alt="WANem-Connect" loading="lazy"><br>在已经打开的Midori中，选择Advanced Mode，进行简易的弱网环境配置，如下图。<br><img src="https://blog.archetto.moe/images/Linux/WANem-Advanced-Mode.png" alt="WANem-Advanced-Mode" loading="lazy"><br>上图中的eth0是将要设置的网卡，点击start后，即可开始配置网络情况。<br><img src="https://blog.archetto.moe/images/Linux/WANem-Start.png" alt="WANem-Advanced-Mode" loading="lazy"><br>简单介绍一下以上配置<br><em>针对单个IP的rule在我的实践中依旧没有成功</em><br><em>如果愿意帮忙可以留言，博主会发送具体情况，感激不尽</em></p><table><thead><tr><th>参数</th><th>含义</th></tr></thead><tbody><tr><td>Packet Limit</td><td>用来设置包的队列大小，默认为1000，当队列超过1000时，超过的包将被丢弃</td></tr><tr><td>Symmetrical network</td><td>对称网络，选中为Yes的时候，应用网络的规则将在数据包来回的方向上都生效</td></tr><tr><td>Choose BW</td><td>从快速列表选择带宽</td></tr><tr><td>Specify BW</td><td>自定义带宽</td></tr><tr><td>Delay</td><td>延时，这里设置的是单向的延时</td></tr><tr><td>Jitter</td><td>抖动</td></tr><tr><td>Correlation</td><td>相关性，用来设置这个包的延迟时间与上一个包的时间的相关度</td></tr><tr><td>Loss</td><td>丢包率</td></tr><tr><td>Correlation</td><td>相关性，以一定的概率发生突发的大量的丢包，但平均丢包率不会超过Loss定义的值</td></tr><tr><td>Duplication</td><td>重包率，以一定概率生成某个包的多份拷贝，并按随机时间到达目标端</td></tr><tr><td>Correlation</td><td>相关性，类似Loss的</td></tr><tr><td>Packet reordering</td><td>包重排序率，按概率将包的顺序打乱，gap用来确定包重排序的个数，不设置更接近真实的环境</td></tr><tr><td>Corruption</td><td>错包率，按概率产生噪音，即格式错误的包</td></tr><tr><td>Idle timer Disconnect</td><td>空闲断开定时器，当网络空闲时，按设定的时间对Type定义的协议断开连接</td></tr><tr><td>Random Disconnect</td><td>按Type随机断开</td></tr><tr><td>MTTF</td><td>平均失效前时间，用Low和High定义两个极值</td></tr><tr><td>MTTR</td><td>平均恢复前时间</td></tr><tr><td>IP source address</td><td>匹配的源IP地址</td></tr><tr><td>IP source subnet</td><td>源IP地址的子网</td></tr><tr><td>IP dest address</td><td>匹配的目标IP地址</td></tr><tr><td>IP dest subnet</td><td>匹配的目标IP地址的子网</td></tr></tbody></table><h4 id="配置客户端"><a href="#配置客户端" class="headerlink" title="配置客户端"></a>配置客户端</h4><p>在测试客户端设置路由的下一跳</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 删除原来默认的下一跳</span></span><br><span class="line">route add -net default netmask 0.0.0.0 gw &lt;原网关地址&gt;</span><br><span class="line"><span class="comment"># 设置默认的下一跳</span></span><br><span class="line">route add -net default netmask 0.0.0.0 gw &lt;WANemIP&gt;</span><br><span class="line"><span class="comment"># 设置某个IP的下一跳</span></span><br><span class="line">route add -net &lt;某IP&gt; netmask 255.255.255.255 gw &lt;WANemIP&gt;</span><br></pre></td></tr></table></figure><p>至此，测试客户端默认将以WANem为网关，进行网络通信。</p><h4 id="ICMP报文的奇怪现象"><a href="#ICMP报文的奇怪现象" class="headerlink" title="ICMP报文的奇怪现象"></a>ICMP报文的奇怪现象</h4><p>在完成以上操作后，ping命令发出的ICMP包并未丢包，延迟也极低，但是在此环境下的业务测试可以证实WANem的配置已生效。<del>推测原因可能是客户端是ARM架构的嵌入式设备，系统经过精简，可能存在bug，等有多余资源分配虚拟机后，在对此现象在X86上用正常发行版进行测试</del><br>事后又看了一眼路由表，发现ping的地址的网关并不是WANem的网关，强行修改后，一切正常。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Linux" scheme="https://blog.archetto.moe/categories/Linux/"/>
    
    
    <category term="traceroute" scheme="https://blog.archetto.moe/tags/traceroute/"/>
    
    <category term="WANem" scheme="https://blog.archetto.moe/tags/WANem/"/>
    
    <category term="弱网环境" scheme="https://blog.archetto.moe/tags/%E5%BC%B1%E7%BD%91%E7%8E%AF%E5%A2%83/"/>
    
  </entry>
  
  <entry>
    <title>Gitea 安装指北</title>
    <link href="https://blog.archetto.moe/2022/03/21/Linux/Gitea/"/>
    <id>https://blog.archetto.moe/2022/03/21/Linux/Gitea/</id>
    <published>2022-03-21T17:42:01.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="Background"><a href="#Background" class="headerlink" title="Background"></a>Background</h2><ol><li>需要托管一些自己的代码</li><li>这些代码可能要吃DMCA</li><li><a href="https://www.v2ex.com/t/836086">GitHub 无预警突然封号 - V2EX</a> Github的连坐机制</li><li><del>就是想折腾</del></li></ol><h2 id="Steps"><a href="#Steps" class="headerlink" title="Steps"></a>Steps</h2><h3 id="Install-Git-Version-2-0"><a href="#Install-Git-Version-2-0" class="headerlink" title="Install Git(Version &gt;&#x3D; 2.0)"></a>Install Git(Version &gt;&#x3D; 2.0)</h3><p><em>Cent OS 7 Example</em></p><p>Add wandisco-git Repo<br><code>vim /etc/yum.repos.d/wandisco-git.repo</code></p><figure class="highlight ini"><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></pre></td><td class="code"><pre><span class="line"><span class="section">[wandisco-git]</span></span><br><span class="line"><span class="attr">name</span>=Wandisco GIT Repository</span><br><span class="line"><span class="attr">baseurl</span>=http://opensource.wandisco.com/centos/<span class="number">7</span>/git/<span class="variable">$basearch</span>/</span><br><span class="line"><span class="attr">enabled</span>=<span class="number">1</span></span><br><span class="line"><span class="attr">gpgcheck</span>=<span class="number">1</span></span><br><span class="line"><span class="attr">gpgkey</span>=http://opensource.wandisco.com/RPM-GPG-KEY-WANdisco</span><br></pre></td></tr></table></figure><p>Import repo GPG Key<br><code>rpm --import http://opensource.wandisco.com/RPM-GPG-KEY-WANdisco</code><br>Install latest version of git<br><code>yum install git</code></p><h3 id="Download"><a href="#Download" class="headerlink" title="Download"></a>Download</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget -O gitea https://dl.gitea.io/gitea/1.16.4/gitea-1.16.4-linux-amd64``</span><br></pre></td></tr></table></figure><h3 id="Verify"><a href="#Verify" class="headerlink" title="Verify"></a>Verify</h3><figure class="highlight sh"><table><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">gpg --keyserver keys.openpgp.org --recv 7C9E68152594688862D62AF62D9AE806EC1592E2</span><br><span class="line">gpg --verify gitea-1.16.4-linux-amd64.asc gitea-1.16.4-linux-amd64</span><br></pre></td></tr></table></figure><h3 id="Create-Git-user"><a href="#Create-Git-user" class="headerlink" title="Create Git user"></a>Create Git user</h3><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line">adduser \</span><br><span class="line">   --system \</span><br><span class="line">   --shell /bin/bash \</span><br><span class="line">   --gecos <span class="string">&#x27;Git Version Control&#x27;</span> \</span><br><span class="line">   --group \</span><br><span class="line">   --disabled-password \</span><br><span class="line">   --home /home/git \</span><br><span class="line">   git</span><br></pre></td></tr></table></figure><h3 id="Preper-Directory"><a href="#Preper-Directory" class="headerlink" title="Preper Directory"></a>Preper Directory</h3><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> -p /opt/gitea/&#123;custom,data,<span class="built_in">log</span>&#125;</span><br><span class="line"><span class="built_in">chown</span> -R git:git /opt/gitea/</span><br><span class="line"><span class="built_in">chmod</span> -R 750 /opt/gitea/</span><br><span class="line"><span class="built_in">mkdir</span> /opt/gitea/config</span><br><span class="line"><span class="built_in">chown</span> root:git /opt/gitea/config</span><br><span class="line"><span class="built_in">chmod</span> 770 /opt/gitea/config</span><br><span class="line"><span class="built_in">chmod</span> 640 /opt/gitea/config/app.ini</span><br><span class="line"><span class="built_in">mv</span> gitea /opt/gitea/</span><br></pre></td></tr></table></figure><h3 id="Install-and-Run"><a href="#Install-and-Run" class="headerlink" title="Install and Run"></a>Install and Run</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">su git &amp;&amp; GITEA_WORK_DIR=/opt/gitea/ /opt/gitea/gitea web -c /opt/gitea/config/app.ini</span><br></pre></td></tr></table></figure><h3 id="Mod-the-config"><a href="#Mod-the-config" class="headerlink" title="Mod the config"></a>Mod the config</h3><p>修改此处配置文件&#x2F;opt&#x2F;gitea&#x2F;config&#x2F;app.ini</p><figure class="highlight plaintext"><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><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><br><span class="line">[service]</span><br><span class="line">REGISTER_EMAIL_CONFIRM            = false</span><br><span class="line">ENABLE_NOTIFY_MAIL                = false</span><br><span class="line">DISABLE_REGISTRATION              = true</span><br><span class="line">ALLOW_ONLY_EXTERNAL_REGISTRATION  = false</span><br><span class="line">ENABLE_CAPTCHA                    = false</span><br><span class="line">REQUIRE_SIGNIN_VIEW               = false</span><br><span class="line">DEFAULT_KEEP_EMAIL_PRIVATE        = false</span><br><span class="line">DEFAULT_ALLOW_CREATE_ORGANIZATION = true</span><br><span class="line">DEFAULT_ENABLE_TIMETRACKING       = true</span><br><span class="line">NO_REPLY_ADDRESS                  = noreply.localhost</span><br><span class="line"></span><br><span class="line"># 并不想启动OPENID，目前用不到</span><br><span class="line">[openid]</span><br><span class="line">ENABLE_OPENID_SIGNIN = false</span><br><span class="line">ENABLE_OPENID_SIGNUP = false</span><br></pre></td></tr></table></figure><h3 id="Run-with-pm2"><a href="#Run-with-pm2" class="headerlink" title="Run with pm2"></a>Run with pm2</h3><p>创建 start.sh</p><figure class="highlight bash"><table><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"><span class="meta">#!/bin/bash</span></span><br><span class="line">GITEA_WORK_DIR=/opt/gitea/ /opt/gitea/gitea web -c /opt/gitea/config/app.ini</span><br></pre></td></tr></table></figure><p>使用git用户启动</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> -u git pm2 start start.sh</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Linux" scheme="https://blog.archetto.moe/categories/Linux/"/>
    
    
    <category term="Gitea" scheme="https://blog.archetto.moe/tags/Gitea/"/>
    
    <category term="git" scheme="https://blog.archetto.moe/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>Golang小技巧</title>
    <link href="https://blog.archetto.moe/2022/03/17/Golang/Golang-Tips/"/>
    <id>https://blog.archetto.moe/2022/03/17/Golang/Golang-Tips/</id>
    <published>2022-03-17T20:42:41.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="小对象存储"><a href="#小对象存储" class="headerlink" title="小对象存储"></a>小对象存储</h2><p>对于小对象，直接将数据交由 map 保存，远比用指针高效。这不但减少了堆内存分配，关键还在于垃圾回收器不会扫描非指针类型 key&#x2F;value 对象。</p><h2 id="Map空间回收"><a href="#Map空间回收" class="headerlink" title="Map空间回收"></a>Map空间回收</h2><p>map 不会收缩 “不再使用” 的空间。就算把所有键值删除，它依然保留内存空间以待后用。<br>如果一个非常大的map里面的元素很少的话，可以考虑新建一个map将老的map元素手动复制到新的map中。</p><h2 id="update-if-not-exists"><a href="#update-if-not-exists" class="headerlink" title="update if not exists"></a>update if not exists</h2><p>可以使用遍历的方式，但是时间复杂度会是哈希进行存储及操作，时间复杂度是O(1)，样例如下</p><figure class="highlight go"><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><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">set := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">int</span>]<span class="keyword">struct</span>&#123;&#125;)</span><br><span class="line">set[<span class="number">1</span>] = <span class="keyword">struct</span>&#123;&#125;&#123;&#125;</span><br><span class="line">set[<span class="number">2</span>] = <span class="keyword">struct</span>&#123;&#125;&#123;&#125;</span><br><span class="line">set[<span class="number">1</span>] = <span class="keyword">struct</span>&#123;&#125;&#123;&#125;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> key := <span class="keyword">range</span>(set) &#123;</span><br><span class="line">  fmt.Println(key)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// each value will be printed only once, in no particular order</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// you can use the ,ok idiom to check for existing keys</span></span><br><span class="line"><span class="keyword">if</span> _, ok := set[<span class="number">1</span>]; ok &#123;</span><br><span class="line">  fmt.Println(<span class="string">&quot;element found&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  fmt.Println(<span class="string">&quot;element not found&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中使用空的struct可以规避空间分配，int可以替换为其他类型。</p><h2 id="Slice是否相等比较"><a href="#Slice是否相等比较" class="headerlink" title="Slice是否相等比较"></a>Slice是否相等比较</h2><h3 id="reflect方法"><a href="#reflect方法" class="headerlink" title="reflect方法"></a>reflect方法</h3><figure class="highlight go"><table><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="function"><span class="keyword">func</span> <span class="title">StringSliceReflectEqual</span><span class="params">(a, b []<span class="type">string</span>)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> reflect.DeepEqual(a, b)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用反射的方法进行对比，效率低下，但是代码简单</p><h3 id="循环遍历进行比较"><a href="#循环遍历进行比较" class="headerlink" title="循环遍历进行比较"></a>循环遍历进行比较</h3><figure class="highlight go"><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><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="function"><span class="keyword">func</span> <span class="title">StringSliceEqual</span><span class="params">(a, b []<span class="type">string</span>)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(a) != <span class="built_in">len</span>(b) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (a == <span class="literal">nil</span>) != (b == <span class="literal">nil</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> i, v := <span class="keyword">range</span> a &#123;</span><br><span class="line">        <span class="keyword">if</span> v != b[i] &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="BCE-优化版"><a href="#BCE-优化版" class="headerlink" title="BCE 优化版"></a>BCE 优化版</h3><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">StringSliceEqualBCE</span><span class="params">(a, b []<span class="type">string</span>)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(a) != <span class="built_in">len</span>(b) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (a == <span class="literal">nil</span>) != (b == <span class="literal">nil</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line">b = b[:<span class="built_in">len</span>(a)]</span><br><span class="line"><span class="keyword">for</span> i, v := <span class="keyword">range</span> a &#123;</span><br><span class="line"><span class="keyword">if</span> v != b[i] &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="分布式锁"><a href="#分布式锁" class="headerlink" title="分布式锁"></a>分布式锁</h2><h3 id="基于redis的setnx锁"><a href="#基于redis的setnx锁" class="headerlink" title="基于redis的setnx锁"></a>基于redis的setnx锁</h3><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;sync&quot;</span></span><br><span class="line">    <span class="string">&quot;time&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="string">&quot;github.com/go-redis/redis&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">incr</span><span class="params">()</span></span> &#123;</span><br><span class="line">    client := redis.NewClient(&amp;redis.Options&#123;</span><br><span class="line">        Addr:     <span class="string">&quot;localhost:6379&quot;</span>,</span><br><span class="line">        Password: <span class="string">&quot;&quot;</span>, <span class="comment">// no password set</span></span><br><span class="line">        DB:       <span class="number">0</span>,  <span class="comment">// use default DB</span></span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> lockKey = <span class="string">&quot;counter_lock&quot;</span></span><br><span class="line">    <span class="keyword">var</span> counterKey = <span class="string">&quot;counter&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// lock</span></span><br><span class="line">    resp := client.SetNX(lockKey, <span class="number">1</span>, time.Second*<span class="number">5</span>)</span><br><span class="line">    lockSuccess, err := resp.Result()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> || !lockSuccess &#123;</span><br><span class="line">        fmt.Println(err, <span class="string">&quot;lock result: &quot;</span>, lockSuccess)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// counter ++</span></span><br><span class="line">    getResp := client.Get(counterKey)</span><br><span class="line">    cntValue, err := getResp.Int64()</span><br><span class="line">    <span class="keyword">if</span> err == <span class="literal">nil</span> || err == redis.Nil &#123;</span><br><span class="line">        cntValue++</span><br><span class="line">        resp := client.Set(counterKey, cntValue, <span class="number">0</span>)</span><br><span class="line">        _, err := resp.Result()</span><br><span class="line">        <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">            <span class="comment">// log err</span></span><br><span class="line">            <span class="built_in">println</span>(<span class="string">&quot;set value error!&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;current counter is &quot;</span>, cntValue)</span><br><span class="line"></span><br><span class="line">    delResp := client.Del(lockKey)</span><br><span class="line">    unlockSuccess, err := delResp.Result()</span><br><span class="line">    <span class="keyword">if</span> err == <span class="literal">nil</span> &amp;&amp; unlockSuccess &gt; <span class="number">0</span> &#123;</span><br><span class="line">        <span class="built_in">println</span>(<span class="string">&quot;unlock success!&quot;</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="built_in">println</span>(<span class="string">&quot;unlock failed&quot;</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">10</span>; i++ &#123;</span><br><span class="line">        wg.Add(<span class="number">1</span>)</span><br><span class="line">        <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">            <span class="keyword">defer</span> wg.Done()</span><br><span class="line">            incr()</span><br><span class="line">        &#125;()</span><br><span class="line">    &#125;</span><br><span class="line">    wg.Wait()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>竞争型的锁，不阻塞，失败时不执行。<code>setnx</code>很适合在高并发场景下，用来争抢一些“唯一”的资源。</p><h3 id="基于ZK的锁"><a href="#基于ZK的锁" class="headerlink" title="基于ZK的锁"></a>基于ZK的锁</h3><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;time&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="string">&quot;github.com/samuel/go-zookeeper/zk&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    c, _, err := zk.Connect([]<span class="type">string</span>&#123;<span class="string">&quot;127.0.0.1&quot;</span>&#125;, time.Second) <span class="comment">//*10)</span></span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="built_in">panic</span>(err)</span><br><span class="line">    &#125;</span><br><span class="line">    l := zk.NewLock(c, <span class="string">&quot;/lock&quot;</span>, zk.WorldACL(zk.PermAll))</span><br><span class="line">    err = l.Lock()</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="built_in">panic</span>(err)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;lock succ, do your business logic&quot;</span>)</span><br><span class="line"></span><br><span class="line">    time.Sleep(time.Second * <span class="number">10</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// do some thing</span></span><br><span class="line">    l.Unlock()</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;unlock succ, finish business logic&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种分布式的阻塞锁比较适合分布式任务调度场景，但不适合高频次持锁时间短的抢锁场景。</p><h3 id="ETCD锁"><a href="#ETCD锁" class="headerlink" title="ETCD锁"></a>ETCD锁</h3><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;log&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="string">&quot;github.com/zieckey/etcdsync&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    m, err := etcdsync.New(<span class="string">&quot;/lock&quot;</span>, <span class="number">10</span>, []<span class="type">string</span>&#123;<span class="string">&quot;http://127.0.0.1:2379&quot;</span>&#125;)</span><br><span class="line">    <span class="keyword">if</span> m == <span class="literal">nil</span> || err != <span class="literal">nil</span> &#123;</span><br><span class="line">        log.Printf(<span class="string">&quot;etcdsync.New failed&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    err = m.Lock()</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        log.Printf(<span class="string">&quot;etcdsync.Lock failed&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    log.Printf(<span class="string">&quot;etcdsync.Lock OK&quot;</span>)</span><br><span class="line">    log.Printf(<span class="string">&quot;Get the lock. Do something here.&quot;</span>)</span><br><span class="line"></span><br><span class="line">    err = m.Unlock()</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        log.Printf(<span class="string">&quot;etcdsync.Unlock failed&quot;</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        log.Printf(<span class="string">&quot;etcdsync.Unlock OK&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>扩展：<a href="http://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%AD%E9%97%B4%E4%BB%B6%E5%AE%9E%E8%B7%B5%E4%B9%8B%E8%B7%AF%EF%BC%88%E5%AE%8C%EF%BC%89/10%20%E5%9F%BA%E4%BA%8E%20Etcd%20%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E5%8F%8A%E6%96%B9%E6%A1%88.md">基于 Etcd 的分布式锁实现原理及方案.md</a></p><h3 id="三种锁的区别"><a href="#三种锁的区别" class="headerlink" title="三种锁的区别"></a>三种锁的区别</h3><p><img src="https://blog.archetto.moe/images/Golang/%E4%B8%89%E7%A7%8D%E9%94%81%E7%9A%84%E5%8C%BA%E5%88%AB.png" alt="三种锁的区别" loading="lazy"><br>由上图可以看出三种组件各自的特点，其中对于分布式锁来说至关重要的一点是要求CP。但是，Redis集群却不支持CP，而是支持AP。虽然，官方也给出了redlock的方案，但由于需要部署多个实例(超过一半实例成功才视为成功)，部署、维护比较复杂。所以在对一致性要求很高的业务场景下(电商、银行支付)，一般选择使用zookeeper或者etcd。对比zookeeper与etcd，如果考虑性能、并发量、维护成本来看。由于etcd是用Go语言开发，直接编译为二进制可执行文件，并不依赖其他任何东西，则更具有优势。</p><h2 id="Sync-Map"><a href="#Sync-Map" class="headerlink" title="Sync.Map"></a>Sync.Map</h2><h3 id="Struct"><a href="#Struct" class="headerlink" title="Struct"></a>Struct</h3><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Map <span class="keyword">struct</span> &#123;</span><br><span class="line">    <span class="comment">// 保护dirty的锁</span></span><br><span class="line">    mu Mutex                        </span><br><span class="line">    <span class="comment">// 只读数据（修改采用原子操作)</span></span><br><span class="line">    read atomic.Value                </span><br><span class="line">    <span class="comment">// 包含只读中所有数据（冗余），写入新数据时也在dirty中操作</span></span><br><span class="line">    dirty <span class="keyword">map</span>[<span class="keyword">interface</span>&#123;&#125;]*entry  </span><br><span class="line">    <span class="comment">// 当原子操作访问只读read时找不到数据时会去dirty中寻找，此时misses+1，dirty及作为存储新写入的数据，又冗余了只读结构中的数据，所以当misses &gt; dirty 的长度时， 会将dirty升级为read，同时将老的dirty置nil</span></span><br><span class="line">    misses <span class="type">int</span> </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Map struct 中的 read 就是readOnly 的指针</span></span><br><span class="line"><span class="keyword">type</span> readOnly <span class="keyword">struct</span> &#123;</span><br><span class="line">    <span class="comment">// 基础Map</span></span><br><span class="line">    m   <span class="keyword">map</span>[<span class="keyword">interface</span>&#123;&#125;]*entry </span><br><span class="line">    <span class="comment">// 用于表示当前dirty中是否有read中不存在的数据， 在写入数据时， 如果发现dirty中没有新数据且dirty为nil时，会将read中未被删除的数据拷贝一份冗余到dirty中， 过程与Map struct中的 misses相呼应</span></span><br><span class="line">    amended <span class="type">bool</span> </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 数据项</span></span><br><span class="line"><span class="keyword">type</span> entry <span class="keyword">struct</span> &#123;</span><br><span class="line">    p unsafe.Pointer </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 用于标记数据项已被删除（主要保证数据冗余时的并发安全）</span></span><br><span class="line"><span class="comment">// 上述Map结构中说到有一个将read数据拷贝冗余至dirty的过程， 因为删除数据项是将*entry置nil, 为了避免冗余过程中因并发问题导致*entry改变而影响到拷贝后的dirty正确性，所以sync.Map使用expunged来标记entry是否被删除</span></span><br><span class="line"><span class="keyword">var</span> expunged = unsafe.Pointer(<span class="built_in">new</span>(<span class="keyword">interface</span>&#123;&#125;))</span><br></pre></td></tr></table></figure><h3 id="Store"><a href="#Store" class="headerlink" title="Store"></a>Store</h3><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *Map)</span></span> Store(key, value <span class="keyword">interface</span>&#123;&#125;) &#123;</span><br><span class="line">    <span class="comment">// 先不上锁，而是从只读数据中按key读取， 如果已存在以compareAndSwap操作进行覆盖(update)</span></span><br><span class="line">    read, _ := m.read.Load().(readOnly)</span><br><span class="line">    <span class="keyword">if</span> e, ok := read.m[key]; ok &amp;&amp; e.tryStore(&amp;value) &#123;</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    m.mu.Lock()</span><br><span class="line">    <span class="comment">// 双检查获取read</span></span><br><span class="line">    read, _ = m.read.Load().(readOnly)</span><br><span class="line">    <span class="comment">// 如果data在read中，更新entry</span></span><br><span class="line">    <span class="keyword">if</span> e, ok := read.m[key]; ok &#123;</span><br><span class="line">        <span class="comment">// 如果原子操作读到的数据是被标记删除的， 则视为新数据写入dirty</span></span><br><span class="line">        <span class="keyword">if</span> e.unexpungeLocked() &#123;</span><br><span class="line">            m.dirty[key] = e</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 原子操作写新数据</span></span><br><span class="line">        e.storeLocked(&amp;value)</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> e, ok := m.dirty[key]; ok &#123;</span><br><span class="line">        <span class="comment">// 原子操作写新数据</span></span><br><span class="line">        e.storeLocked(&amp;value)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 新数据 </span></span><br><span class="line">        <span class="comment">// 当dirty中没有新数据时，将read中数据冗余到dirty</span></span><br><span class="line">        <span class="keyword">if</span> !read.amended &#123;</span><br><span class="line">            m.dirtyLocked()</span><br><span class="line">            m.read.Store(readOnly&#123;m: read.m, amended: <span class="literal">true</span>&#125;)</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        m.dirty[key] = newEntry(value)</span><br><span class="line">    &#125;</span><br><span class="line">    m.mu.Unlock()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e *entry)</span></span> tryStore(i *<span class="keyword">interface</span>&#123;&#125;) <span class="type">bool</span> &#123;</span><br><span class="line">    p := atomic.LoadPointer(&amp;e.p)</span><br><span class="line">    <span class="keyword">if</span> p == expunged &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> atomic.CompareAndSwapPointer(&amp;e.p, p, unsafe.Pointer(i)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">        p = atomic.LoadPointer(&amp;e.p)</span><br><span class="line">        <span class="keyword">if</span> p == expunged &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 在dirty中没有比read多出的新数据时触发冗余</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *Map)</span></span> dirtyLocked() &#123;</span><br><span class="line">    <span class="keyword">if</span> m.dirty != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    read, _ := m.read.Load().(readOnly)</span><br><span class="line">    m.dirty = <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">interface</span>&#123;&#125;]*entry, <span class="built_in">len</span>(read.m))</span><br><span class="line">    <span class="keyword">for</span> k, e := <span class="keyword">range</span> read.m &#123;</span><br><span class="line">        <span class="comment">// 检查entry是否被删除， 被删除的数据不冗余</span></span><br><span class="line">        <span class="keyword">if</span> !e.tryExpungeLocked() &#123;</span><br><span class="line">            m.dirty[k] = e</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e *entry)</span></span> tryExpungeLocked() (isExpunged <span class="type">bool</span>) &#123;</span><br><span class="line">    p := atomic.LoadPointer(&amp;e.p)</span><br><span class="line">    <span class="keyword">for</span> p == <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="comment">// 将被删除（置nil）的数据以cas原子操作标记为expunged（防止因并发情况下其他操作导致冗余进dirty的数据不正确）</span></span><br><span class="line">        <span class="keyword">if</span> atomic.CompareAndSwapPointer(&amp;e.p, <span class="literal">nil</span>, expunged) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">        p = atomic.LoadPointer(&amp;e.p)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> p == expunged</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Load-Read"><a href="#Load-Read" class="headerlink" title="Load&#x2F;Read"></a>Load&#x2F;Read</h3><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *Map)</span></span> Load(key <span class="keyword">interface</span>&#123;&#125;) (value <span class="keyword">interface</span>&#123;&#125;, ok <span class="type">bool</span>) &#123;</span><br><span class="line">    read, _ := m.read.Load().(readOnly)</span><br><span class="line">    e, ok := read.m[key]</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 只读数据中没有，并且dirty有比read多的数据，加锁在dirty中找</span></span><br><span class="line">    <span class="keyword">if</span> !ok &amp;&amp; read.amended &#123;</span><br><span class="line">        m.mu.Lock()</span><br><span class="line">        <span class="comment">// 双检查， 因为上锁之前的语句是非原子性的</span></span><br><span class="line">        read, _ = m.read.Load().(readOnly)</span><br><span class="line">        e, ok = read.m[key]</span><br><span class="line">        <span class="keyword">if</span> !ok &amp;&amp; read.amended &#123;</span><br><span class="line">            <span class="comment">// 只读中没有读取到的次数+1</span></span><br><span class="line">            e, ok = m.dirty[key]</span><br><span class="line">            <span class="comment">// 检查是否达到触发dirty升级read的条件</span></span><br><span class="line">            m.missLocked()</span><br><span class="line">        &#125;</span><br><span class="line">        m.mu.Unlock()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> !ok &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nil</span>, <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// atomic.Load 但被标记为删除的会返回nil</span></span><br><span class="line">    <span class="keyword">return</span> e.load()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *Map)</span></span> missLocked() &#123;</span><br><span class="line">    m.misses++</span><br><span class="line">    <span class="keyword">if</span> m.misses &lt; <span class="built_in">len</span>(m.dirty) &#123;</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    m.read.Store(readOnly&#123;m: m.dirty&#125;)</span><br><span class="line">    m.dirty = <span class="literal">nil</span></span><br><span class="line">    m.misses = <span class="number">0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Delete"><a href="#Delete" class="headerlink" title="Delete"></a>Delete</h3><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *Map)</span></span> Delete(key <span class="keyword">interface</span>&#123;&#125;) &#123;</span><br><span class="line">    read, _ := m.read.Load().(readOnly)</span><br><span class="line">    e, ok := read.m[key]</span><br><span class="line">    <span class="comment">// 只读中不存在需要到dirty中去删除</span></span><br><span class="line">    <span class="keyword">if</span> !ok &amp;&amp; read.amended &#123;</span><br><span class="line">        m.mu.Lock() </span><br><span class="line">        <span class="comment">// 双检查， 因为上锁之前的语句是非原子性的</span></span><br><span class="line">        read, _ = m.read.Load().(readOnly)</span><br><span class="line">        e, ok = read.m[key]</span><br><span class="line">        <span class="keyword">if</span> !ok &amp;&amp; read.amended &#123;</span><br><span class="line">            <span class="built_in">delete</span>(m.dirty, key)</span><br><span class="line">        &#125;</span><br><span class="line">        m.mu.Unlock()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> ok &#123;</span><br><span class="line">        e.<span class="built_in">delete</span>()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e *entry)</span></span> <span class="built_in">delete</span>() (hadValue <span class="type">bool</span>) &#123;</span><br><span class="line">    <span class="keyword">for</span> &#123;</span><br><span class="line">        p := atomic.LoadPointer(&amp;e.p)</span><br><span class="line">        <span class="keyword">if</span> p == <span class="literal">nil</span> || p == expunged &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> atomic.CompareAndSwapPointer(&amp;e.p, p, <span class="literal">nil</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Go-字符串查找效率问题"><a href="#Go-字符串查找效率问题" class="headerlink" title="Go 字符串查找效率问题"></a>Go 字符串查找效率问题</h2><h3 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h3><p>Golang从字符串查找时，strings.Contains 与 正则对比<br>以下为查找函数</p><figure class="highlight go"><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"># strings.Contains</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">stringsMatch</span><span class="params">(str <span class="type">string</span>, subStr <span class="type">string</span>)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line"><span class="keyword">return</span> strings.Contains(str, subStr)</span><br><span class="line">&#125;</span><br><span class="line"># 正则查找</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">regexMatch</span><span class="params">(str <span class="type">string</span>, subStr <span class="type">string</span>)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line">reg := regexp.MustCompile(<span class="string">&quot;(&quot;</span> + subStr + <span class="string">&quot;)&quot;</span>)</span><br><span class="line"><span class="keyword">return</span> reg.MatchString(str)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以下为benchmark函数</p><figure class="highlight go"><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><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">const</span> letterBytes = <span class="string">&quot;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">randomString</span><span class="params">(n <span class="type">int</span>)</span></span> <span class="type">string</span> &#123;</span><br><span class="line">b := <span class="built_in">make</span>([]<span class="type">byte</span>, n)</span><br><span class="line"><span class="keyword">for</span> i := <span class="keyword">range</span> b &#123;</span><br><span class="line">b[i] = letterBytes[rand.Intn(<span class="built_in">len</span>(letterBytes))]</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="type">string</span>(b)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">benchmark</span><span class="params">(b *testing.B, f <span class="keyword">func</span>(<span class="type">string</span>, <span class="type">string</span>)</span></span> <span class="type">bool</span>) &#123;</span><br><span class="line"><span class="keyword">var</span> str = <span class="string">&quot;测试test&quot;</span></span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; b.N; i++ &#123;</span><br><span class="line">f(randomString(<span class="number">10</span>)+str+randomString(<span class="number">10</span>), str)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>正则Benchmark</p><figure class="highlight plaintext"><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">goos: windows</span><br><span class="line">goarch: amd64</span><br><span class="line">pkg: gotest</span><br><span class="line">cpu: AMD Ryzen 5 5600X 6-Core Processor             </span><br><span class="line">BenchmarkRegexMatch-12      595849      1916 ns/op    2327 B/op      27 allocs/op</span><br><span class="line">PASS</span><br><span class="line">ok  gotest1.188s</span><br></pre></td></tr></table></figure><p>strings.Contains Benchmark输出</p><figure class="highlight plaintext"><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">goos: windows</span><br><span class="line">goarch: amd64</span><br><span class="line">pkg: gotest</span><br><span class="line">cpu: AMD Ryzen 5 5600X 6-Core Processor             </span><br><span class="line">BenchmarkStringsMatch-12     5357433       227.2 ns/op      96 B/op       5 allocs/op</span><br><span class="line">PASS</span><br><span class="line">ok  gotest1.584s</span><br></pre></td></tr></table></figure><p>可见strings.Contains性能好于使用这种方式的正则匹配判断。<br>由上面的代码又引发出一个可能更为常见的对比场景，那就是所需的正则是固定的，而所进行对比的字符串不固定，即*regexp.Regexp只生成一次，由此再次进行对比<br>此时的正则查找函数变为</p><figure class="highlight go"><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"><span class="keyword">var</span> reg = regexp.MustCompile(<span class="string">&quot;(&quot;</span> + <span class="string">&quot;测试test&quot;</span> + <span class="string">&quot;)&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">regexMatch</span><span class="params">(str <span class="type">string</span>, _ <span class="type">string</span>)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line"><span class="keyword">return</span> reg.MatchString(str)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>得结果如下</p><figure class="highlight plaintext"><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">goos: windows</span><br><span class="line">goarch: amd64</span><br><span class="line">pkg: gotest</span><br><span class="line">cpu: AMD Ryzen 5 5600X 6-Core Processor             </span><br><span class="line">BenchmarkRegexMatch-12     3832815       301.7 ns/op      97 B/op       5 allocs/op</span><br><span class="line">PASS</span><br><span class="line">ok  gotest1.614s</span><br></pre></td></tr></table></figure><p>可见strings.Contains性能仍然占上风，因此推荐只在做严格匹配时使用正则，其他场合能用strings.Contains解决尽量不采用正则，以提高效率。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Golang" scheme="https://blog.archetto.moe/categories/Golang/"/>
    
    
    <category term="Golang" scheme="https://blog.archetto.moe/tags/Golang/"/>
    
  </entry>
  
  <entry>
    <title>Golang学习手记</title>
    <link href="https://blog.archetto.moe/2021/05/11/Golang/Golang-Start/"/>
    <id>https://blog.archetto.moe/2021/05/11/Golang/Golang-Start/</id>
    <published>2021-05-11T23:14:41.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="Go项目结构"><a href="#Go项目结构" class="headerlink" title="Go项目结构"></a>Go项目结构</h2><ul><li><p>go.mod</p></li><li><p>go.sum</p></li><li><p>*.go</p></li></ul><h3 id="go-mod"><a href="#go-mod" class="headerlink" title="go.mod"></a>go.mod</h3><p>是相关Go包的集合，是源代码交换和版本控制的单元</p><p>其提供了<code>module</code>, <code>require</code>、<code>replace</code>和<code>exclude</code>四个命令</p><figure class="highlight plaintext"><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">module hello</span><br><span class="line"></span><br><span class="line">go 1.16</span><br><span class="line"></span><br><span class="line">replace example.com/greetings =&gt; ../greetings</span><br><span class="line"></span><br><span class="line">require example.com/greetings v1.1.0</span><br></pre></td></tr></table></figure><p> <em>一个典型的go.mod示例</em></p><h3 id="go-sum"><a href="#go-sum" class="headerlink" title="go.sum"></a>go.sum</h3><p>是版本控制相关文件，包含了依赖的module的版本及其Hash</p><h3 id="go"><a href="#go" class="headerlink" title="*.go"></a>*.go</h3><p>Go的源文件</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><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> hello</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;testing&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestHello</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">    want := <span class="string">&quot;Hello, world.&quot;</span></span><br><span class="line">    <span class="keyword">if</span> got := Hello(); got != want &#123;</span><br><span class="line">        t.Errorf(<span class="string">&quot;Hello() = %q, want %q&quot;</span>, got, want)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><em>一个典型的Go程序代码</em></p><h2 id="创建Go-Module的典型过程（一点点shell命令）"><a href="#创建Go-Module的典型过程（一点点shell命令）" class="headerlink" title="创建Go Module的典型过程（一点点shell命令）"></a>创建Go Module的典型过程（一点点shell命令）</h2><h3 id="创建文件夹"><a href="#创建文件夹" class="headerlink" title="创建文件夹"></a>创建文件夹</h3><figure class="highlight shell"><table><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">mkdir hello</span><br><span class="line">cd hello</span><br></pre></td></tr></table></figure><h3 id="初始化Go-Module"><a href="#初始化Go-Module" class="headerlink" title="初始化Go Module"></a>初始化Go Module</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go mod init example.com/hello</span><br></pre></td></tr></table></figure><h3 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h3><figure class="highlight shell"><table><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">go test</span><br><span class="line">go test -v # More Detail</span><br></pre></td></tr></table></figure><h3 id="显示全部依赖的包"><a href="#显示全部依赖的包" class="headerlink" title="显示全部依赖的包"></a>显示全部依赖的包</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go list -m all</span><br></pre></td></tr></table></figure><h3 id="添加依赖"><a href="#添加依赖" class="headerlink" title="添加依赖"></a>添加依赖</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go get example.com/example</span><br></pre></td></tr></table></figure><h3 id="清理依赖"><a href="#清理依赖" class="headerlink" title="清理依赖"></a>清理依赖</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go mod tidy</span><br></pre></td></tr></table></figure><h2 id="语法"><a href="#语法" class="headerlink" title="语法"></a>语法</h2><h3 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h3><p>支持 C 风格的块注释 &#x2F;* *&#x2F; 和 C++ 风格的行注释 &#x2F;&#x2F;</p><p>btw,顶级声明前面的注释都将作为该声明的<strong>文档注释</strong></p><h3 id="控制结构"><a href="#控制结构" class="headerlink" title="控制结构"></a>控制结构</h3><h3 id="if"><a href="#if" class="headerlink" title="if"></a>if</h3><figure class="highlight go"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> condition &#123;</span><br><span class="line"><span class="comment">/* Code Here */</span></span><br><span class="line">    <span class="keyword">return</span> result</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><em>最简单的if结构</em></p><h3 id="for"><a href="#for" class="headerlink" title="for"></a>for</h3><figure class="highlight go"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 类似 C 语言中的 for 用法</span></span><br><span class="line"><span class="keyword">for</span> init; condition; post &#123; &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 类似 C 语言中的 while 用法</span></span><br><span class="line"><span class="keyword">for</span> condition &#123; &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 类似 C 语言中的 for(;;) 用法</span></span><br><span class="line"><span class="keyword">for</span> &#123; &#125;</span><br></pre></td></tr></table></figure><p><em>三种常用的方式</em></p><figure class="highlight go"><table><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">for</span> key, value := <span class="keyword">range</span> oldMap &#123;</span><br><span class="line">    newMap[key] = value</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><em>很方便的切片</em></p><h3 id="Switch"><a href="#Switch" class="headerlink" title="Switch"></a>Switch</h3><figure class="highlight go"><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><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">unhex</span><span class="params">(c <span class="type">byte</span>)</span></span> <span class="type">byte</span> &#123;</span><br><span class="line">    <span class="keyword">switch</span> &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;0&#x27;</span> &lt;= c &amp;&amp; c &lt;= <span class="string">&#x27;9&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> c - <span class="string">&#x27;0&#x27;</span></span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;a&#x27;</span> &lt;= c &amp;&amp; c &lt;= <span class="string">&#x27;f&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> c - <span class="string">&#x27;a&#x27;</span> + <span class="number">10</span></span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;A&#x27;</span> &lt;= c &amp;&amp; c &lt;= <span class="string">&#x27;F&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> c - <span class="string">&#x27;A&#x27;</span> + <span class="number">10</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><em>大概是源码里的</em></p><p>switch不会自动向下追溯，所以要使用逗号分隔相同处理的条件，例如：</p><figure class="highlight go"><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">shouldEscape</span><span class="params">(c <span class="type">byte</span>)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line">    <span class="keyword">switch</span> c &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27; &#x27;</span>, <span class="string">&#x27;?&#x27;</span>, <span class="string">&#x27;&amp;&#x27;</span>, <span class="string">&#x27;=&#x27;</span>, <span class="string">&#x27;#&#x27;</span>, <span class="string">&#x27;+&#x27;</span>, <span class="string">&#x27;%&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="结构体、方法与接口"><a href="#结构体、方法与接口" class="headerlink" title="结构体、方法与接口"></a>结构体、方法与接口</h3><h4 id="结构体与方法"><a href="#结构体与方法" class="headerlink" title="结构体与方法"></a>结构体与方法</h4><figure class="highlight go"><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><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">type</span> Student <span class="keyword">struct</span> &#123;</span><br><span class="line">name <span class="type">string</span></span><br><span class="line">age  <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(stu *Student)</span></span> hello(person <span class="type">string</span>) <span class="type">string</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Sprintf(<span class="string">&quot;hello %s, I am %s&quot;</span>, person, stu.name)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">stu := &amp;Student&#123;</span><br><span class="line">name: <span class="string">&quot;Tom&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line">msg := stu.hello(<span class="string">&quot;Jack&quot;</span>)</span><br><span class="line">fmt.Println(msg) <span class="comment">// hello Jack, I am Tom</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="其他零碎内容"><a href="#其他零碎内容" class="headerlink" title="其他零碎内容"></a>其他零碎内容</h2><h3 id="单元测试-1"><a href="#单元测试-1" class="headerlink" title="单元测试"></a>单元测试</h3><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> greetings</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;testing&quot;</span></span><br><span class="line">    <span class="string">&quot;regexp&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// TestHelloName calls greetings.Hello with a name, checking </span></span><br><span class="line"><span class="comment">// for a valid return value.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestHelloName</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">    name := <span class="string">&quot;Gladys&quot;</span></span><br><span class="line">    want := regexp.MustCompile(<span class="string">`\b`</span>+name+<span class="string">`\b`</span>)</span><br><span class="line">    msg, err := Hello(<span class="string">&quot;Gladys&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> !want.MatchString(msg) || err != <span class="literal">nil</span> &#123;</span><br><span class="line">        t.Fatalf(<span class="string">`Hello(&quot;Gladys&quot;) = %q, %v, want match for %#q, nil`</span>, msg, err, want)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// TestHelloEmpty calls greetings.Hello with an empty string, </span></span><br><span class="line"><span class="comment">// checking for an error.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestHelloEmpty</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">    msg, err := Hello(<span class="string">&quot;&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> msg != <span class="string">&quot;&quot;</span> || err == <span class="literal">nil</span> &#123;</span><br><span class="line">        t.Fatalf(<span class="string">`Hello(&quot;&quot;) = %q, %v, want &quot;&quot;, error`</span>, msg, err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><em>一个单元测试的样例</em>      <em>测试文件一定要包含<strong>test</strong>字样</em></p><h4 id="testing-T与testing-M"><a href="#testing-T与testing-M" class="headerlink" title="testing.T与testing.M"></a>testing.T与testing.M</h4><p>testing.T 是普通测试包<br>testing.M函数可以在测试函数执行之前做一些其他操作</p><h3 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h3><figure class="highlight shell"><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><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">- cmd//函数主程序</span><br><span class="line">- internal//私有库</span><br><span class="line">- pkg//公开库</span><br><span class="line">- vendor//依赖</span><br><span class="line">- web//静态Web资源</span><br><span class="line">- configs//配置</span><br><span class="line">- init//系统初始化</span><br><span class="line">- scripts//安装、构建、部署等脚本</span><br><span class="line">- build//打包和持续集成</span><br><span class="line">- deploymentsIaaS, Paas, 系统, 容器编排的部署配置和模板</span><br><span class="line">- test//额外的外部测试软件和测试数据</span><br><span class="line">- docs//用户及设计文档</span><br><span class="line">- examples//应用或者库的示例文件</span><br><span class="line">- tools//项目的支持工具</span><br><span class="line">- third_party//外部辅助工具, forked 代码, 以及其他第三方工具 </span><br><span class="line">- githooks</span><br><span class="line">- assets//资源文件</span><br><span class="line">- website//站点配置数据</span><br></pre></td></tr></table></figure><h3 id="错误处理"><a href="#错误处理" class="headerlink" title="错误处理"></a>错误处理</h3><figure class="highlight go"><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> greetings</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;errors&quot;</span></span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Hello returns a greeting for the named person.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Hello</span><span class="params">(name <span class="type">string</span>)</span></span> (<span class="type">string</span>, <span class="type">error</span>) &#123;</span><br><span class="line">    <span class="comment">// If no name was given, return an error with a message.</span></span><br><span class="line">    <span class="keyword">if</span> name == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&quot;</span>, errors.New(<span class="string">&quot;empty name&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// If a name was received, return a value that embeds the name </span></span><br><span class="line">    <span class="comment">// in a greeting message.</span></span><br><span class="line">    message := fmt.Sprintf(<span class="string">&quot;Hi, %v. Welcome!&quot;</span>, name)</span><br><span class="line">    <span class="keyword">return</span> message, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="并发"><a href="#并发" class="headerlink" title="并发"></a>并发</h3><h4 id="协程"><a href="#协程" class="headerlink" title="协程"></a>协程</h4><p>在函数或方法前添加 <code>go</code> 关键字能够在新的 Go 协程中调用它。当调用完成后， 该 Go 协程也会安静地退出。（效果有点像 Unix Shell 中的 <code>&amp;</code> 符号，它能让命令在后台运行。）</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">go</span> list.Sort()  <span class="comment">// 同时运行 list.Sort ; 不需要等待</span></span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://learnku.com/docs/effective-go/2020">https://learnku.com/docs/effective-go/2020</a></p><p><a href="https://golang.org/doc/tutorial">https://golang.org/doc/tutorial</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Golang" scheme="https://blog.archetto.moe/categories/Golang/"/>
    
    
    <category term="Golang" scheme="https://blog.archetto.moe/tags/Golang/"/>
    
  </entry>
  
  <entry>
    <title>软件工程 - 概述</title>
    <link href="https://blog.archetto.moe/2020/10/12/Software-Engineering/Overview/"/>
    <id>https://blog.archetto.moe/2020/10/12/Software-Engineering/Overview/</id>
    <published>2020-10-12T12:41:39.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="软件的概念和特点"><a href="#软件的概念和特点" class="headerlink" title="软件的概念和特点"></a>软件的概念和特点</h2><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><p>软件 &#x3D; 程序 + 文档 + 数据</p><h3 id="特点"><a href="#特点" class="headerlink" title="特点"></a>特点</h3><ol><li>软件是开发的</li><li>软件是简单的拷贝</li><li>软件测试非常困难</li><li>软件需要维护，维护易产生新的问题</li><li>软件开发时间和工作量难以估计</li><li>软件开发时间进度几乎没有客观衡量标准</li><li>软件不会磨损，但是会退化和废弃</li></ol><h2 id="软件危机的概念和成因"><a href="#软件危机的概念和成因" class="headerlink" title="软件危机的概念和成因"></a>软件危机的概念和成因</h2><h3 id="概念-1"><a href="#概念-1" class="headerlink" title="概念"></a>概念</h3><p>在计算机软件开发和维护中碰到的一系列严重问题。</p><ol><li>项目超出预算</li><li>项目超过计划完成时间</li><li>软件运行效率很低</li><li>软件质量差</li><li>软件通常不符合要求</li><li>项目难以管理并且代码难以维护</li><li>软件不能交付</li></ol><h3 id="成因"><a href="#成因" class="headerlink" title="成因"></a>成因</h3><h4 id="软件自身特点"><a href="#软件自身特点" class="headerlink" title="软件自身特点"></a>软件自身特点</h4><ol><li>软件是逻辑部件，缺乏可预见性</li><li>软件维护困难</li><li>软件越来越庞大复杂，需要分工协作</li></ol><h4 id="软件开发和维护方法不正确"><a href="#软件开发和维护方法不正确" class="headerlink" title="软件开发和维护方法不正确"></a>软件开发和维护方法不正确</h4><ol><li>需求分析不充分或者存在错误</li><li>开发过程不规范</li><li>不注重文档工作，软件难以维护</li><li>缺少软件评测手段</li></ol><h2 id="软件工程定义"><a href="#软件工程定义" class="headerlink" title="软件工程定义"></a>软件工程定义</h2><ol><li>应用系统的、规范的、可度量的方法来开发，运行和维护软件，即把工程应用到软件。</li><li>对 1 各种方法的研究</li></ol><h2 id="软件工程三要素"><a href="#软件工程三要素" class="headerlink" title="软件工程三要素"></a>软件工程三要素</h2><ol><li>方法</li><li>工具</li><li>过程</li></ol><h2 id="软件工程发展过程"><a href="#软件工程发展过程" class="headerlink" title="软件工程发展过程"></a>软件工程发展过程</h2><p>传统软件工程（late 60s - 70s）： 将软件工程纳入工程化轨道 <br>对象工程（80s - 90s）：面向对象的分析与设计 <br>过程工程（mid 80s）：提高软件生产率，保证软件质量 <br>构建工程（90s - ）：复用构件</p><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><h3 id="软件工程的目标"><a href="#软件工程的目标" class="headerlink" title="软件工程的目标"></a>软件工程的目标</h3><p>在给定的时间和预算内，按照用户的需求，开发易修改、高效、可靠、可维护、适应力强、可移动、可重用的软件。 <br><del>钱少，要求多</del></p><h3 id="软件分类"><a href="#软件分类" class="headerlink" title="软件分类"></a>软件分类</h3><ol><li>系统软件</li><li>应用软件</li><li>工程&#x2F;科学软件</li><li>嵌入式软件</li><li>Web应用软件</li><li>产品线软件</li><li>人工智能</li></ol><h3 id="Wasserman-规范"><a href="#Wasserman-规范" class="headerlink" title="Wasserman 规范"></a>Wasserman 规范</h3><ol><li>抽象 Abstration</li><li>分析和设计方法以及表示方法 Analysis and design methods and notations</li><li>用户界面原型化 User interface prototyping</li><li>软件体系结构 Software architecture</li><li>软件过程 Software process</li><li>复用 Reuse</li><li>测度 Measurement</li><li>计算机辅助软件工程 CASE (Computer Aided Software Engineering)</li></ol><h3 id="软件工程七原则"><a href="#软件工程七原则" class="headerlink" title="软件工程七原则"></a>软件工程七原则</h3><ol><li>使用阶段性生命周期计划的管理</li><li>阶段性审查</li><li>保证严格的产品控制</li><li>使用现代编程工具 <del>Fuck Dev C++</del></li><li>保持清晰的责任分配，结果审查清楚 <del>对摸鱼神器？</del></li><li>开发小组人员少而精</li><li>不断改进软件工程实践</li></ol><h3 id="憨批大纲，不考了不更了"><a href="#憨批大纲，不考了不更了" class="headerlink" title="憨批大纲，不考了不更了"></a><del>憨批大纲，不考了不更了</del></h3>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="软件工程" scheme="https://blog.archetto.moe/categories/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/"/>
    
    
    <category term="软件工程" scheme="https://blog.archetto.moe/tags/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/"/>
    
    <category term="软件" scheme="https://blog.archetto.moe/tags/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="软件危机" scheme="https://blog.archetto.moe/tags/%E8%BD%AF%E4%BB%B6%E5%8D%B1%E6%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>计算机网络 - 应用层</title>
    <link href="https://blog.archetto.moe/2020/09/19/Computer-Network/Section2-Appllication-Layer/"/>
    <id>https://blog.archetto.moe/2020/09/19/Computer-Network/Section2-Appllication-Layer/</id>
    <published>2020-09-19T10:39:37.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p><del><em>生气！极其不友好的考纲</em></del></p><h2 id="应用层协议的原理"><a href="#应用层协议的原理" class="headerlink" title="应用层协议的原理"></a>应用层协议的原理</h2><h3 id="网络应用体系结构"><a href="#网络应用体系结构" class="headerlink" title="网络应用体系结构"></a>网络应用体系结构</h3><p>规定如何在各种端系统上组织应用程序，由研发者设计,规定了如何在各种端系统上组织该应用程序。 <br>e.g.</p><ol><li>CS体系结构(Client-Server architecture) </li><li>P2P体系结构(P2P architecture)</li></ol><h3 id="进程通信"><a href="#进程通信" class="headerlink" title="进程通信"></a>进程通信</h3><p>进程通信实际上是进程(Process)而不是程序，多个进程运行于相同的端系统上时，它们利用进程间通信机制相互通信；进程运行于不同的端系统上时，通过跨越计算机网络交换报文(Message)而互相通信。 <br>进程与计算机网络之间的接口是套接字(Socket)，其中一部分套接字建立了网络应用程序的可编程接口，被称之为应用程序与网络之间的应用程序编程接口(API, Application Programming Interface)。 <br><del>此处值得一提的是</del>进程寻址需要IP与端口号。<del>地址与门牌号</del></p><h3 id="可供应用程序使用的运输服务"><a href="#可供应用程序使用的运输服务" class="headerlink" title="可供应用程序使用的运输服务"></a>可供应用程序使用的运输服务</h3><p><em>以下为分类</em></p><ol><li>可靠数据传输</li><li>吞吐量</li><li>定时 <del>其实是延迟</del></li><li>安全性</li></ol><h3 id="因特网提供的运输服务"><a href="#因特网提供的运输服务" class="headerlink" title="因特网提供的运输服务"></a>因特网提供的运输服务</h3><ol><li>TCP</li><li>UDP</li></ol><table><thead><tr><th align="center">应用</th><th align="center">应用层协议</th><th align="center">支持的运输协议</th></tr></thead><tbody><tr><td align="center">电子邮件</td><td align="center">SMTP</td><td align="center">TCP</td></tr><tr><td align="center">远程终端访问</td><td align="center">Telnet</td><td align="center">TCP</td></tr><tr><td align="center">Web</td><td align="center">HTTP</td><td align="center">TCP</td></tr><tr><td align="center">文件传输</td><td align="center">FTP</td><td align="center">TCP</td></tr><tr><td align="center">流式多媒体</td><td align="center">HTTP</td><td align="center">TCP</td></tr><tr><td align="center">因特网电话</td><td align="center">SIP、RTP</td><td align="center">TCP&#x2F;UDP</td></tr></tbody></table><h2 id="应用层协议的实现过程"><a href="#应用层协议的实现过程" class="headerlink" title="应用层协议的实现过程"></a>应用层协议的实现过程</h2><p>应用层协议定义了运行在不同端系统上的应用程序如何进行报文传递，特别定义了一下几部分：</p><ol><li>交换的报文类型</li><li>各种报文类型的语法</li><li>字段的语义</li><li>确定一个进程何时以及如何发送报文，对报文进行响应的规则</li></ol><h2 id="Web-应用和-HTTP-协议"><a href="#Web-应用和-HTTP-协议" class="headerlink" title="Web 应用和 HTTP 协议"></a>Web 应用和 HTTP 协议</h2><p><del>终于到我会一点点的地方了</del> <br>Web的应用层协议核心就是HTTP(HyperText Transfer Protocol)协议，HTTP协议是一个无状态协议。</p><h4 id="持续连接与非持续连接"><a href="#持续连接与非持续连接" class="headerlink" title="持续连接与非持续连接"></a>持续连接与非持续连接</h4><p>每个请求&#x2F;响应都是由单独的一个TCP连接发送的，是为非持续性连接，反之则是持续连接。 <br>非持续连接存在以下缺点：</p><ol><li>必须为每个请求的对象建立和维护一个全新的连接。</li><li>每个对象经受两倍RTT(Round-Trip Time)的交付延迟，即一个RTT用于创建TCP，一个RTT用于请求和接收一个对象。</li></ol><h4 id="报文格式"><a href="#报文格式" class="headerlink" title="报文格式"></a>报文格式</h4><p><del>请打开F12</del> <br><del>Chrome似乎看不到raw的，打开fiddler</del> <br><del>众所周知，百度是用来测试网络连通性的</del> <br><em>以下报文删去了一部分内容</em> <br><em>请注意HTTPS类型的traffic请在fiddler中信任根证书</em> <br>请求报文样例</p><figure class="highlight plaintext"><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><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">GET https://www.baidu.com/ HTTP/1.1</span><br><span class="line">Host: www.baidu.com</span><br><span class="line">Connection: keep-alive</span><br><span class="line">Accept: text/plain, */*; q=0.01</span><br><span class="line">User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36</span><br><span class="line">X-Requested-With: XMLHttpRequest</span><br><span class="line">Sec-Fetch-Site: same-origin</span><br><span class="line">Sec-Fetch-Mode: cors</span><br><span class="line">Sec-Fetch-Dest: empty</span><br><span class="line">Referer: https://www.baidu.com/</span><br><span class="line">Accept-Encoding: gzip, deflate, br</span><br><span class="line">Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7</span><br></pre></td></tr></table></figure><p>响应报文样例</p><figure class="highlight plaintext"><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><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">HTTP/1.1 200 OK</span><br><span class="line">Cache-Control: private</span><br><span class="line">Connection: keep-alive</span><br><span class="line">Content-Encoding: gzip</span><br><span class="line">Content-Type: text/html;charset=utf-8</span><br><span class="line">Date: Mon, 12 Oct 2020 08:05:44 GMT</span><br><span class="line">Expires: Mon, 12 Oct 2020 08:05:44 GMT</span><br><span class="line">Server: BWS/1.0</span><br><span class="line">Vary: Accept-Encoding</span><br><span class="line">Content-Length: 49</span><br></pre></td></tr></table></figure><p>没什么好讲的（确信<br>来记一些状态码吧！</p><ul><li>200 OK</li><li>301 Moved Permanently</li><li>400 Bad Request</li><li>404 Not Found</li><li>505 HTTP Version Not Supported</li><li>500 Internal Server Error</li></ul><h4 id="Cookie"><a href="#Cookie" class="headerlink" title="Cookie"></a>Cookie</h4><p>前文已经提到了，HTTP是一种无状态协议，这意味着将无法识别用户，为了解决这个问题，HTTP使用了Cookie对用户进行识别追踪。<br><img src="https://cdn.jsdelivr.net/gh/SkadiWo/BlogResources/img/Using%20Cookie.jpg" alt="Using Cookie" loading="lazy"></p><h4 id="Web缓存器"><a href="#Web缓存器" class="headerlink" title="Web缓存器"></a>Web缓存器</h4><p>Web Cache A.K.A Proxy Server <br>How does it work: </p><ol><li>浏览器创建一个到Web缓存器的TCP连接，并为向其中所请求的对象发送一个HTTP请求</li><li>Web缓存器检查其本地是否存有该对象，若有则响应报文并返回该对象</li><li>若不存在该对象，则在与服务器(该对象本来的来源处)的TCP连接上，发送对此对象的请求。</li><li>当Web缓存器接收到此对象时，先于本地存储一份副本，随后将其发至客户端。</li></ol><p>Why: 代替原始服务器满足HTTP请求 <del>并且减少接入公共因特网带宽成本</del><br>e.g. CDN(Content Distribution Network)</p><h4 id="条件GET方法"><a href="#条件GET方法" class="headerlink" title="条件GET方法"></a>条件GET方法</h4><p>Client请求：If-modified-since: &lt;date&gt; <br>未修改则：HTTP&#x2F;1.0 304 Not Modified <br>修改则：&lt;data&gt;</p><h2 id="FTP-协议的实现机制"><a href="#FTP-协议的实现机制" class="headerlink" title="FTP 协议的实现机制"></a>FTP 协议的实现机制</h2><p><em>书上没有这一节</em> <br><em>参阅以前的课件作如下描述</em><br>FTP用于传输文件到远程主机&#x2F;从远程主机下载文件，存在以下两种模式<br>client&#x2F;server模式</p><ul><li>client: 发起传输的一方</li><li>server: 远程主机</li></ul><p>对于ftp服务器，其默认端口号为21(控制端口)。</p><h3 id="FTP传输过程"><a href="#FTP传输过程" class="headerlink" title="FTP传输过程"></a>FTP传输过程</h3><ol><li>FTP客户首先发起建立1个与FTP服务器端口21之间的TCP控制连接, TCP为传输层协议</li><li>客户在控制连接上获得身份认证，发送各种控制命令.</li><li>服务器收到1个文件传输命令时, 服务器在端口20(数据传输端口)创建1个与客户端口的TCP数据连接（Port）</li><li>1个文件传输后,服务器结束这个TCP数据连接.</li></ol><h3 id="其他？（我也不知道怎么描述"><a href="#其他？（我也不知道怎么描述" class="headerlink" title="其他？（我也不知道怎么描述"></a>其他？（我也不知道怎么描述</h3><ol><li>服务器创建第2个TCP与客户的数据连接来传输下一个文件.</li><li>控制连接: 带外发送控制信息</li><li>FTP 服务器维护用户状态信息: 当前目录, 先前身份认证</li></ol><h3 id="PORT模式"><a href="#PORT模式" class="headerlink" title="PORT模式"></a>PORT模式</h3><p>PORT模式下的FTP服务： 缺省情况下PORT模式的数据端口是20， 控制端口是21（控制端口可以设定， 本文假定使用21）。 当进行连接时,客户端使用一个随机的端口N（N大于1024)连接服务器的控制端口21， 然后客户端开始监听端口N+1，并向服务器发送命令 PORT N+1，服务器用自己的数据端口20连回客户的N+1端口。 由于PORT模式仅仅是发送端口给服务器，由服务器连回客户端，如果客户端有防火墙，这样的连接会被认为是外部主机试图连接内部的主机， 通常情况下是不允许的。 </p><h3 id="PASV模式"><a href="#PASV模式" class="headerlink" title="PASV模式"></a>PASV模式</h3><p>PASV模式下的FTP服务： 当进行连接时,客户端使用一个随机的端口N（N大于1024) 连接服务器的控制端口21， 并向服务器发送命令 PASV，服务器使用一个随机的数据端口M(M&gt;1024)并发回客户端, 客户端用数据端口N+1连接服务器的端口M。 由于客户端发起数据连接， 这样就解决了防火墙带来的问题。 </p><h2 id="DNS-的功能和实现方法"><a href="#DNS-的功能和实现方法" class="headerlink" title="DNS 的功能和实现方法"></a>DNS 的功能和实现方法</h2><h3 id="功能"><a href="#功能" class="headerlink" title="功能"></a>功能</h3><ol><li>主机名到IP地址转换 (Main)</li><li>主机别名</li><li>邮件服务器别名(MX记录)</li><li>负载均衡</li></ol><h3 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h3><h4 id="存在的问题"><a href="#存在的问题" class="headerlink" title="存在的问题"></a>存在的问题</h4><ol><li>单点故障</li><li>通信容量</li><li>远距离的集中式数据库</li><li>维护</li></ol><h4 id="解决方案-——-分布式、层次数据库"><a href="#解决方案-——-分布式、层次数据库" class="headerlink" title="解决方案 —— 分布式、层次数据库"></a>解决方案 —— 分布式、层次数据库</h4><h5 id="层次结构"><a href="#层次结构" class="headerlink" title="层次结构"></a>层次结构</h5><p><img src="https://cdn.jsdelivr.net/gh/SkadiWo/BlogResources/img/DNS%20architecture.jpg" alt="DNS Architecture" loading="lazy"><br>由根DNS服务器提供TLD(Top-Level Domain)服务器的IP地址，每个TLD服务器又提供了权威DNS服务器的IP地址，最后由权威DNS服务器，将这些主机名映射为IP地址。</p><h5 id="DNS缓存"><a href="#DNS缓存" class="headerlink" title="DNS缓存"></a>DNS缓存</h5><p>其实是本地DNS服务器，可参见Web Cache做类比。</p><h5 id="DNS记录和报文"><a href="#DNS记录和报文" class="headerlink" title="DNS记录和报文"></a>DNS记录和报文</h5><p>DNS记录由四元组构成，如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">（Name, Value, Type, TTL)</span><br></pre></td></tr></table></figure><p>其中TTL代表该记录的生存时间，Type决定了Name和Value。 <br>以下是几种常见的Type与其对应的Name和Value：</p><ul><li>(Name, IPv4 Address, A, TTL) e.g. (blog, IPv4 Address, A, TTL)</li><li>(Name, IPv6 Address, AAA, TTL) e.g. (blog, IPv6 Address, AAA, TTL)</li><li>(Name, Domain, NS, TTL) e.g. (Tinysnow, A NS Server, NS, TTL)</li><li>(Name, Domain, CNAME, TTL) e.g. (blog, skadiwo.github.io, CNAME, TTL)</li><li>(Name, Domain, MX, TTL) e.g. (mail, mail.163.com, MX, TTL) <em>禁止指向CNAME</em></li></ul><p>报文构成 <br><img src="https://cdn.jsdelivr.net/gh/SkadiWo/BlogResources/img/DNS%20Message.png" alt="DNS Message" loading="lazy"></p><h2 id="电子邮件系统的构成、传输机制和协议"><a href="#电子邮件系统的构成、传输机制和协议" class="headerlink" title="电子邮件系统的构成、传输机制和协议"></a>电子邮件系统的构成、传输机制和协议</h2><h3 id="构成与传输机制"><a href="#构成与传输机制" class="headerlink" title="构成与传输机制"></a>构成与传输机制</h3><p><img src="https://cdn.jsdelivr.net/gh/SkadiWo/BlogResources/img/Email%20Achitecture.jpg" alt="Email Architecture" loading="lazy"><br>于上图可知，因特网电子邮件系统有三个组成部分：用户代理(User Agent)、邮件服务器(Mail Server)、简单邮件传输协议(Simple Mail Transfer Protocol,SMTP)<br>一个典型的邮件发送过程是：从发送方的用户代理开始．传输到发送方的邮件服务器、再传输到接收方的邮件服务器，然后在这里被分发到接收方的邮箱中。</p><h3 id="协议"><a href="#协议" class="headerlink" title="协议"></a>协议</h3><p><del><em>写不动了自己看吧</em></del></p><ul><li>SMTP -&gt; <a href="https://www.youtube.com/watch?v=PJo5yOtu7o8">https://www.youtube.com/watch?v=PJo5yOtu7o8</a></li><li>POP3 &amp; IMAP -&gt; <a href="https://www.youtube.com/watch?v=SBaARws0hy4">https://www.youtube.com/watch?v=SBaARws0hy4</a></li></ul><h2 id="TCP-和-UDP-套接字编程"><a href="#TCP-和-UDP-套接字编程" class="headerlink" title="TCP 和 UDP 套接字编程"></a>TCP 和 UDP 套接字编程</h2><h3 id="UDP套接字"><a href="#UDP套接字" class="headerlink" title="UDP套接字"></a>UDP套接字</h3><p><img src="https://cdn.jsdelivr.net/gh/SkadiWo/BlogResources/img/UDP%20CS%20Application.jpg" alt="UDP" loading="lazy"></p><h4 id="UDP-Client"><a href="#UDP-Client" class="headerlink" title="UDP Client"></a>UDP Client</h4><figure class="highlight plaintext"><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><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">from socket import *  # 导包</span><br><span class="line">serverName = &#x27;localhost&#x27;  # 设置服务器主机名</span><br><span class="line">serverPort = 12000  # 设置服务器端口号</span><br><span class="line"># AF_INET代表了IPv4，SOCK_DGRAM代表了这是个UDP的Socket</span><br><span class="line">clientSocket = socket(AF_INET, SOCK_DGRAM)</span><br><span class="line">message = input(&#x27;Input lowercase sentence&#x27;)  # 输入要哔哔的话</span><br><span class="line">clientSocket.sendto(message.encode(), (serverName,</span><br><span class="line">                                       serverPort))  # 咱就给您送出去！（顺便编个码）</span><br><span class="line">modifiedMessage, serverAddress = clientSocket.recvfrom(2048)  # 好咧，别人给您的回信来啦！</span><br><span class="line">print(modifiedMessage.decode())  # 看看别人咋哔哔的（顺便解个码）</span><br><span class="line">clientSocket.close()    # 再见了您咧！</span><br></pre></td></tr></table></figure><h4 id="UDP-Server"><a href="#UDP-Server" class="headerlink" title="UDP Server"></a>UDP Server</h4><figure class="highlight plaintext"><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><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">from socket import *  # 导包</span><br><span class="line">serverName = &#x27;localhost&#x27;  # 设置服务器主机名</span><br><span class="line">serverPort = 12000  # 设置服务器端口号</span><br><span class="line"># AF_INET代表了IPv4，SOCK_DGRAM代表了这是个UDP的Socket</span><br><span class="line">serverSocket = socket(AF_INET, SOCK_DGRAM)</span><br><span class="line">serverSocket.bind((&#x27;&#x27;, serverPort))  # 绑定IP和端口喔！</span><br><span class="line">print(&quot;The Server is ready to recive!&quot;)  # 准备好啦！</span><br><span class="line">while True:</span><br><span class="line">    message, clientAddress = clientSocket.recvfrom(2048)  # 好呀，看看谁来信了！</span><br><span class="line">    modifiedMessage = message.decode().upper()  # 解个码变大写！</span><br><span class="line">    serverSocket.sendto(modifiedMessage.encode(),</span><br><span class="line">                        clientAddress)  # 咱就给您送回去！（顺便编个码）</span><br><span class="line"></span><br><span class="line"># 惨，Server不能关（?</span><br></pre></td></tr></table></figure><h2 id="P2P-文件共享原理"><a href="#P2P-文件共享原理" class="headerlink" title="P2P 文件共享原理"></a>P2P 文件共享原理</h2><p><img src="https://cdn.jsdelivr.net/gh/SkadiWo/BlogResources/img/CS%20VS%20P2P.PNG" alt="CS VS P2P" loading="lazy"></p><h3 id="P2P-集中式目录"><a href="#P2P-集中式目录" class="headerlink" title="P2P 集中式目录"></a>P2P 集中式目录</h3><p>上图中，若在P2P网络中加入一个作为目录（索引）的服务器，为对等方提供检索服务，即为P2P集中式目录(结构)。</p><ul><li>单点故障</li><li>性能瓶颈</li><li>侵犯版权</li></ul><h3 id="BitTorrent"><a href="#BitTorrent" class="headerlink" title="BitTorrent"></a>BitTorrent</h3><p>BitTorrent 是一种用于文件分发的流行P2P协议。参与一个特定文件分发的所有对等方的集合被称之为一个torrent；在一个torrent中，对等方彼此下载等长度的chunk，典型的chunk长度(size)为256KB。</p><h3 id="DHT-Distributed-Hash-Table"><a href="#DHT-Distributed-Hash-Table" class="headerlink" title="DHT(Distributed Hash Table)"></a>DHT(Distributed Hash Table)</h3><p><img src="https://cdn.jsdelivr.net/gh/SkadiWo/BlogResources/img/DHT.PNG" alt="DHT" loading="lazy"><br>使用键值对(Key-Value)进行存储，每个对等方皆能查询(get)与插入(put)。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="计算机网络" scheme="https://blog.archetto.moe/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    
    
    <category term="应用层" scheme="https://blog.archetto.moe/tags/%E5%BA%94%E7%94%A8%E5%B1%82/"/>
    
    <category term="HTTP" scheme="https://blog.archetto.moe/tags/HTTP/"/>
    
    <category term="Web Cache" scheme="https://blog.archetto.moe/tags/Web-Cache/"/>
    
    <category term="Cookie" scheme="https://blog.archetto.moe/tags/Cookie/"/>
    
    <category term="DNS" scheme="https://blog.archetto.moe/tags/DNS/"/>
    
  </entry>
  
  <entry>
    <title>计算机网络 - 计算机与因特网</title>
    <link href="https://blog.archetto.moe/2020/08/01/Computer-Network/Section1-Computer-And-Internet/"/>
    <id>https://blog.archetto.moe/2020/08/01/Computer-Network/Section1-Computer-And-Internet/</id>
    <published>2020-08-01T20:10:41.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="网络协议"><a href="#网络协议" class="headerlink" title="网络协议"></a>网络协议</h2><p>类比于人类活动，协议是一种预先规定好的行为方式。<br>在网络中，协议用于控制因特网信息的接受和发送。<br><strong>网络协议定义了在两个或多个通信实体之间交换的报文的格式和顺序，以及报文发送和或接收一条报文或其他事件所采取的动作。</strong></p><h2 id="无连接和面向连接服务"><a href="#无连接和面向连接服务" class="headerlink" title="无连接和面向连接服务"></a>无连接和面向连接服务</h2><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><h4 id="无连接服务"><a href="#无连接服务" class="headerlink" title="无连接服务"></a>无连接服务</h4><p>无连接协议中的分组被称为数据报(datagram)，每个分组都是独立寻址，并由应用程序发送的。<br>从协议的角度来看，每个数据报都是一个独立的实体，与在两个相同的对等实体之间传送的任何其他数据报都没有关系，这就意味着协议很可能是不可靠的。也就是说，网络会尽最大努力传送每一个数据报，但并不保证数据报不丢失、不延迟或者不错序传输。<br><strong>无连接服务仅有数据传输这个阶段</strong><br>显而易见的，无连接服务有以下几个特点：</p><ol><li>不提供数据的可靠传输</li><li>不提供流量控制(Traffic Control)</li><li>不提供拥塞控制</li></ol><h4 id="面向连接服务"><a href="#面向连接服务" class="headerlink" title="面向连接服务"></a>面向连接服务</h4><p>面向连接的协议则维护了分组之间的状态，使用这种协议的应用程序通常都会进行长期的对话。记住这些状态，协议就可以提供可靠的传输。<br>比如，发送端可以记住哪些数据已经发送出去了但还未被确认，以及数据是什么时候发送的。如果在某段时间间隔内没有收到确认，发送端可以重传数据。接收端可以记住已经收到了哪些数据，并将重复的数据丢弃。如果分组不是按序到达的，接收端可以将其保存下来，直到逻辑上先于它的分组到达为止。<br><strong>面向连接服务也要经过三个阶段：数据传数前，先建立连接，连接建立后再传输数据，数据传送完后，释放连接。</strong><br>对比于无连接，面向连接显然有:</p><ol><li>可靠的、按序提交 数据包传输服务</li><li>流量控制</li><li>拥塞控制</li></ol><h3 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h3><p>其一：面向连接分为三个阶段，第一是建立连接，在此阶段，发出一个建立连接的请求。只有在连接成功建立之后，才能开始数据传输，这是第二阶段。接着，当数据传输完毕，必须释放连接。而面向无连接没有这么多阶段，它直接进行数据传输。 <br>其二：面向连接的通信具有数据的保序性， 而面向无连接的通信不能保证接收数据的顺序与发送数据的顺序一致。 <br>值得一提的是，无连接一般采用UDP,面向连接一般采用TCP。<del>废话</del></p><h2 id="数据交换"><a href="#数据交换" class="headerlink" title="数据交换"></a>数据交换</h2><p><img src="https://cdn.jsdelivr.net/gh/SkadiWo/BlogResources/img/Three%20Method%20to%20Switching.png" alt="三种数据交换" loading="lazy"></p><h3 id="报文交换"><a href="#报文交换" class="headerlink" title="报文交换"></a>报文交换</h3><h4 id="概念-1"><a href="#概念-1" class="headerlink" title="概念"></a>概念</h4><p>报文交换(Message Switching)的单位是报文，报文携带有目标地址、源地址等信息。报文交换在交换结点采用的是存储转发(store-and-forward transmission)的传输方式。 <br>报文交换有两种复用方式：频分复用与时分复用。</p><h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><ol><li>无需建立连接： 报文交换不需要为通信双方预先建立一条专用的通信线路，不存在建立连接时延， 用户可以随时发送报文。</li><li>动态分配线路： 当发送方把报文交给交换设备时，交换设备先存储整个报文，然后选择一条合适的空闲线路，将报文发送出去。</li><li>提高线路可靠性： 如果某条传输路径发生故障， 可重新选择另一条路径传输数据， 所以提高了传输的可靠性。</li><li>提高线路利用率： 通信双方不是固定占有一条通信线路，而是在不同的时间一段一段地部分占有这条物理通道，因而大大提高了通信线路的利用率。</li><li>提供多目标服务： 一个报文可以同时发送往多个目的地址，这在电路交换中是很难实现的．</li></ol><h4 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h4><ol><li>由千数据进入交换结点后要经历存储、转发这一过程，从而引起转发时延(包括接收报文、检验正确性、排队、发送时间等)</li><li>报文交换对报文的大小没有限制，这就要求网络结点需要有较大的缓存空间。</li></ol><h3 id="分组交换"><a href="#分组交换" class="headerlink" title="分组交换"></a>分组交换</h3><h4 id="概念-2"><a href="#概念-2" class="headerlink" title="概念"></a>概念</h4><p>分组交换(Packet Switching)也采用了存储转发方式，分组交换限制了每次传送的数据块大小的上限，把大的数据块划分为合理的小数据块，再加上一些必要的控制信息(如源地址、目的地址和编号信息等) ， 构成分组( Packet ) 。网络结点根据控制信息把分组送到下一结点， 下一结点接收到分组后，暂时保存下来并排队等待传输，然后根据分组控制信息选择它的下一个结点，直到到达目的结点。 <br>虚电路网络与数据报网络属于此类。</p><h4 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h4><ol><li>无建立时延。不需要为通信双方预先建立一条专用的通信线路，不存在连接建立时延，用户可随时发送分组。</li><li>线路利用率高。通信双方不是固定占有一条通信线路，而是在不同的时间一段一段地部分占有这条物理通路， 因而大大提高了通信线路的利用率。</li><li>简化了存储管理(相对千报文交换)。因为分组的长度固定， 相应的缓冲区的大小也固定，在交换结点中存储器的管理通常被简化为对缓冲区的管理， 相对比较容易。</li><li>加速传输： 分组是逐个传输， 可以使后一个分组的存储操作与前一个分组的转发操作并行，这种流水线方式减少了报文的传输时间。此外， 传输一个分组所需的缓冲区比传输一次报文所需的缓冲区小得多，这样因缓冲区不足而等待发送的几率及时间也必然少得多．</li><li>减少了出错几率和重发数据呈： 因为分组较短，其出错几率必然减少， 所以每次重发的数据证也就大大减小，这样不仅提高了可靠性，也减少了传输时延。</li></ol><h4 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h4><p>l. 存在传输时延。尽管分组交换比报文交换的传输时延少，但相对于电路交换仍存在存储一转发时延， 而且其结点交换机必须具有更强的处理能力。<br>2. 需要传输额外的信息批。每个小数据块都要加上源、目的地址和分组编号等信息，从而构成分组，使传送的信息杂大约增大5%~1 0%， 一定程度上降低了通信效率， 增加了处理的时第间，使控制复杂，时延增加。<br>3. 当分组交换采用数据报服务时，可能出现失序、丢失或重复分组， 分组到达目的结点时，要对分组按编号进行排序等工作，增加了麻烦。若采用虚电路服务， 虽无失序问题，但有呼叫建物立、数据传输和虚电路释放三个过程。</p><h3 id="电路交换"><a href="#电路交换" class="headerlink" title="电路交换"></a>电路交换</h3><h4 id="概念-3"><a href="#概念-3" class="headerlink" title="概念"></a>概念</h4><p>对于电路交换(Circuit Switching)，在进行数据传输前，两个结点之间必须先建立一条专用(双方独占)的物理通信路径(由通信双方之间的交换设备和链路逐段连接而成)，该路径能经过许多中间结点。该线路在整个数据传输期间一直被独占，直到通信结束后才被释放。因此， 电路交换技术分为三个阶段： 连接建立、数据传输和连接释放。 <br>从通信资源的分配角度来看，“交换”就是按照某种方式动态地分配传输线路的资源。电路交换的关键点是： 在数据传输的过程中，用户始终占用端到端的固定传输带宽。</p><h4 id="优点-2"><a href="#优点-2" class="headerlink" title="优点"></a>优点</h4><ol><li>通信时延小。由千通信线路为通信双方用户专用，数据直达，所以传输数据的时延非常小。当传输的数据拉较大时，这一优点非常明显。</li><li>有序传输。双方通信时按发送顺序传送数据， 不存在失序问题。</li><li>没有冲突。不同的通信双方拥有不同的信道，不会出现争用物理信道的问题。</li><li>适用范围广。电路交换既适用于传输模拟信号， 又适用千传输数字信号。</li><li>实时性强。通信双方之间的物理通路一旦建立，双方可以随时通信。</li><li>控制简单。电路交换的交换设备(交换机等)及控制均较简单。</li></ol><h4 id="缺点-2"><a href="#缺点-2" class="headerlink" title="缺点"></a>缺点</h4><ol><li>建立连接时间长。电路交换的平均连接建立时间对计算机通信来说太长。</li><li>线路独占，使用效率低。电路交换连接建立后，物理通路被通信双方独占，即使通信线路空闲，也不能供其他用户使用，因而信道利用率低。</li><li>灵活性差。只要在通信双方建立的通路中的任何一点出了故院，就必须亟新拨号建立新的连接， 这对十分紧急和重要的通信是很不利的。</li><li>难以规格化。电路交换时，数据直达，不同类型、不同规格、不同速率的终端很难相互进行通信，也难以在通信过程中进行差错控制。</li></ol><h3 id="分组交换与电路交换的对比"><a href="#分组交换与电路交换的对比" class="headerlink" title="分组交换与电路交换的对比"></a>分组交换与电路交换的对比</h3><p>分组交换的性能由于电路交换</p><table><thead><tr><th align="center">电路交换</th><th align="center">分组交换</th></tr></thead><tbody><tr><td align="center">电路交换不考虑需求，而预先分配传输链路的使用</td><td align="center">分组交换按需分配链路使用</td></tr></tbody></table><h2 id="网络时延"><a href="#网络时延" class="headerlink" title="网络时延"></a>网络时延</h2><h3 id="分组转发时延"><a href="#分组转发时延" class="headerlink" title="分组转发时延"></a>分组转发时延</h3><h4 id="单节点时延"><a href="#单节点时延" class="headerlink" title="单节点时延"></a>单节点时延</h4><ol><li>节点处理时延 Nodal Processing delay</li><li>排队时延 Queueing delay</li><li>传输时延 Transmission delay</li><li>传播时延 Propagation delay</li></ol><h3 id="计算方法"><a href="#计算方法" class="headerlink" title="计算方法"></a>计算方法</h3><h4 id="单节点时延-1"><a href="#单节点时延-1" class="headerlink" title="单节点时延"></a>单节点时延</h4><p>$$d_{nodal} &#x3D; d_{proc} + d_{queue} + d_{prop}$$</p><h4 id="端时延"><a href="#端时延" class="headerlink" title="端时延"></a>端时延</h4><p>$$d_{end-end} &#x3D; N(d_{proc} + d_{trans} + d_{prop})$$<br>$$d_{trans} &#x3D; L&#x2F;R$$<br>上式中，L为分组长度，R为传输速度，N-1为链路中路由器个数。</p><h4 id="排队时延-流量强度"><a href="#排队时延-流量强度" class="headerlink" title="排队时延 流量强度"></a><del>排队时延</del> 流量强度</h4><p>$$La&#x2F;R$$<br>上式中，a的单位是分组&#x2F;秒，L是单组比特长度，R为传输速度，上式计算流量强度 <br>$La&#x2F;R$ -&gt; 0: 分组稀疏到达,无队列,平均排队延迟极小接近于0 <br>$La&#x2F;R$ -&gt; 1: 分组猝发到达,形成队列,队列长度迅速增加,排队延迟大幅增大 <br>$La&#x2F;R$ &gt; 1: 输出队列平均位到达速率超过送走这些位的极限速率，输出队列持续增长，排队延迟趋于无穷大 <br><img src="https://cdn.jsdelivr.net/gh/SkadiWo/BlogResources/img/traffic%20intensity%20with%20average%20queueing%20delay.jpg" loading="lazy"></p><h2 id="计算机网络的体系结构"><a href="#计算机网络的体系结构" class="headerlink" title="计算机网络的体系结构"></a>计算机网络的体系结构</h2><h2 id="分层"><a href="#分层" class="headerlink" title="分层"></a>分层</h2><h3 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h3><p>使复杂系统简化(分而治之)<br>易于维护、系统更新</p><h3 id="协议分层"><a href="#协议分层" class="headerlink" title="协议分层"></a>协议分层</h3><p>采用分层的方式组织协议及实现协议的网络硬件与软件。 \</p><h4 id="分层的特点"><a href="#分层的特点" class="headerlink" title="分层的特点"></a>分层的特点</h4><p>每层都有对应的一系列协议 <br>每层协议通过软件、硬件或者两者结合实现 <br>每层协议可分布在网络的不同组件之中</p><h4 id="分层的缺点"><a href="#分层的缺点" class="headerlink" title="分层的缺点"></a>分层的缺点</h4><p>有些功能可嫩在不同层重复出现 <del>这个缺点真扯淡</del> <br>某些功能可能需要仅存在其他层的信息 <del>那解耦啊</del></p><h3 id="体系结构"><a href="#体系结构" class="headerlink" title="体系结构"></a>体系结构</h3><p><img src="https://cdn.jsdelivr.net/gh/SkadiWo/BlogResources/img/Computer%20Network%20Architecture.jpg" loading="lazy"></p><h3 id="各层交互的封装过程"><a href="#各层交互的封装过程" class="headerlink" title="各层交互的封装过程"></a>各层交互的封装过程</h3><p><img src="https://cdn.jsdelivr.net/gh/SkadiWo/BlogResources/img/Encapsulation.jpg" loading="lazy"><br>运输层获得应用层报文(application-layer message)$M$，附上运输层头部信息$H_1$，构成运输层报文段(lransport-layer segment)，$H_1$也许包括了：如允许接收端运输层向上向适当的应用程序交付报文的信息;如差错检测位信息，该信息让接收方能够判断报文中的比特是存在途中已被改变。 <br>随后运输层向网络层提交该报文段，网络层则增加了网络层首部信息$H_2$，构成网络层数据报(network-layer dal唔ram)，$H_2$包含如源和目的端系统地址等。 <br>此数据报随后交付给链路层，链路层也增加了链路层首部信息，构成链路层帧(link-layer frame)。 <br>在此过程中，每一个分组都有两种类型的字段：首部字段和有效载荷字段(payload filed)。</p><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p>计算机网络：自顶向下方法(原书第七版) <br>《计算机网络(第7版)》 <br>2019年王道计算机网络考研复习指导 <br><a href="https://blog.csdn.net/tennysonsky/article/details/44455565">https://blog.csdn.net/tennysonsky/article/details/44455565</a> <br><a href="https://www.zhihu.com/question/289026686/answer/1199958592">https://www.zhihu.com/question/289026686/answer/1199958592</a> <br><a href="https://blog.csdn.net/tennysonsky/article/details/44455565">https://blog.csdn.net/tennysonsky/article/details/44455565</a> </p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="计算机网络" scheme="https://blog.archetto.moe/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    
    
    <category term="网络协议" scheme="https://blog.archetto.moe/tags/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/"/>
    
    <category term="无连接" scheme="https://blog.archetto.moe/tags/%E6%97%A0%E8%BF%9E%E6%8E%A5/"/>
    
    <category term="面向连接" scheme="https://blog.archetto.moe/tags/%E9%9D%A2%E5%90%91%E8%BF%9E%E6%8E%A5/"/>
    
    <category term="电路交换" scheme="https://blog.archetto.moe/tags/%E7%94%B5%E8%B7%AF%E4%BA%A4%E6%8D%A2/"/>
    
    <category term="分组交换" scheme="https://blog.archetto.moe/tags/%E5%88%86%E7%BB%84%E4%BA%A4%E6%8D%A2/"/>
    
    <category term="网络延时" scheme="https://blog.archetto.moe/tags/%E7%BD%91%E7%BB%9C%E5%BB%B6%E6%97%B6/"/>
    
    <category term="计算机网络体系结构" scheme="https://blog.archetto.moe/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/"/>
    
  </entry>
  
  <entry>
    <title>不咕咕！-&gt; 咕咕咕-&gt; 不咕咕！</title>
    <link href="https://blog.archetto.moe/2020/07/19/start/"/>
    <id>https://blog.archetto.moe/2020/07/19/start/</id>
    <published>2020-07-19T11:34:02.000Z</published>
    <updated>2024-11-08T09:46:39.639Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="前世"><a href="#前世" class="headerlink" title="前世"></a>前世</h2><p>17年至今，笔者已经换了很多个博客程序了，从Wordpress到Typecho，又从Typecho到Hexo。<br>每次都想留下一些什么，然而日常咕咕咕。<br>也许是时候不咕咕了。</p><h2 id="今生"><a href="#今生" class="headerlink" title="今生"></a>今生</h2><p>这个博客将会用来记录我的CS学习&#x2F;复习内容。<br>包括但不限于一下内容：</p><ul><li><del>计算机网络</del></li><li>数据结构与算法</li><li>操作系统</li><li><del>计算机组成原理</del></li><li><del>炼丹</del></li><li>软件工程</li><li>Golang</li><li>Elasticsearch</li><li>ClickHouse</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    
  </entry>
  
</feed>
