<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>我的主页</title>
  
  
  <link href="http://example.com/atom.xml" rel="self"/>
  
  <link href="http://example.com/"/>
  <updated>2024-12-11T08:32:52.775Z</updated>
  <id>http://example.com/</id>
  
  <author>
    <name>len</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>MySQL MVCC 多版本并发控制</title>
    <link href="http://example.com/2024/12/11/MySQL-MVCC-%E5%A4%9A%E7%89%88%E6%9C%AC%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/"/>
    <id>http://example.com/2024/12/11/MySQL-MVCC-%E5%A4%9A%E7%89%88%E6%9C%AC%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/</id>
    <published>2024-12-11T07:09:24.000Z</published>
    <updated>2024-12-11T08:32:52.775Z</updated>
    
    <content type="html"><![CDATA[<h1 id="MVCC-多版本并发控制"><a href="#MVCC-多版本并发控制" class="headerlink" title="MVCC 多版本并发控制"></a>MVCC 多版本并发控制</h1><h2 id="一、引言"><a href="#一、引言" class="headerlink" title="一、引言"></a>一、引言</h2><p>本文深入剖析 MySQL InnoDB 存储引擎中 MVCC（多版本并发控制）工作原理，涵盖快照读、当前读概念，MVCC 关键构成（隐藏字段、Undo Log、ReadView），及其在不同事务隔离级别下的数据可见性处理与幻读问题解决，且附实例展示读提交和可重复读隔离级别下的 MVCC 操作流程。</p><h2 id="二、MVCC-概述"><a href="#二、MVCC-概述" class="headerlink" title="二、MVCC 概述"></a>二、MVCC 概述</h2><h3 id="（一）定义"><a href="#（一）定义" class="headerlink" title="（一）定义"></a>（一）定义</h3><p>MVCC（Multiversion Concurrency Control）借由管理数据行多版本达成数据库事务并发时的一致性读。当事务读取正被更新行时，可获取更新前版本，有效化解读写冲突，此技术确保 InnoDB 事务隔离级别下一致性读操作顺畅执行，各 DBMS 实现方式有别且无统一标准，本文聚焦 InnoDB 实现机制（MySQL 其他存储引擎不支持）。</p><h3 id="（二）快照读与当前读"><a href="#（二）快照读与当前读" class="headerlink" title="（二）快照读与当前读"></a>（二）快照读与当前读</h3><h4 id="1-快照读"><a href="#1-快照读" class="headerlink" title="1. 快照读"></a>1. 快照读</h4><ul><li><strong>概念</strong>：MVCC 于 MySQL InnoDB 核心应用为快照读，是不加锁非阻塞读，如 <code>SELECT * FROM player WHERE...</code>，基于提升并发性能考量，以多版本技术实现，可能读取历史版本数据，前提为隔离级别非串行，否则退化为当前读。</li><li><strong>原理</strong>：MVCC 本质是乐观锁思维体现，于事务并发场景，读操作无需等待写事务释放锁，直接读取合适版本数据，减少锁竞争与等待开销，提升系统整体并发性能，其多版本数据存储与管理依赖隐藏字段、Undo Log 与 ReadView 协同。</li></ul><h4 id="2-当前读"><a href="#2-当前读" class="headerlink" title="2. 当前读"></a>2. 当前读</h4><ul><li><strong>概念</strong>：与快照读相反，当前读为加锁操作，属悲观锁实现，读取记录最新版本且锁定，防并发事务修改，如加锁 <code>SELECT</code>、增删改操作皆引发当前读，像 <code>SELECT * FROM student LOCK IN SHARE MODE;</code>（共享锁）与 <code>SELECT * FROM student FOR UPDATE;</code>（排他锁）等。</li><li><strong>原理</strong>：数据库执行当前读时，依操作类型精准施加合适锁（共享或排他），确保数据一致性与完整性，此过程需权衡锁粒度与并发性能，粗粒度锁虽易控制数据一致性但并发度受限，细粒度锁并发高但管理复杂、易引发死锁，数据库依业务场景抉择最优锁策略。</li></ul><figure class="highlight sql"><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">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> student LOCK <span class="keyword">IN</span> SHARE MODE; # 共享锁</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> student <span class="keyword">FOR</span> <span class="keyword">UPDATE</span>; # 排他锁</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> student <span class="keyword">values</span> ... # 排他锁</span><br><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> student <span class="keyword">WHERE</span> ... # 排他锁</span><br><span class="line"><span class="keyword">UPDATE</span> student <span class="keyword">SET</span> ... # 排他锁</span><br></pre></td></tr></table></figure><h2 id="三、MVCC-三剑客"><a href="#三、MVCC-三剑客" class="headerlink" title="三、MVCC 三剑客"></a>三、MVCC 三剑客</h2><h3 id="（一）回顾隔离级别"><a href="#（一）回顾隔离级别" class="headerlink" title="（一）回顾隔离级别"></a>（一）回顾隔离级别</h3><p>事务隔离级别调控并发事务对资源访问隔离程度，越高则事务干扰越小、安全性越高，主要级别含读未提交、读提交、可重复读（MySQL 默认）、串行化，各有特性与读问题表现：</p><ul><li><strong>读未提交</strong>：事务可读未提交数据，无锁机制，性能顶尖却易现脏读（读未提交事务修改数据）。</li><li><strong>读提交</strong>：事务仅见已提交事务数据，MVCC 底层支撑，每次快照读生读视图，解脏读之困，如多事务并发场景，一事务读取数据不受未提交事务干扰，保数据有效性。</li><li><strong>可重复读</strong>：事务多次读数据恒定，MVCC 为基，首次快照读制读视图复用，除脏读、克不可重复读（多次读数据变）难题，InnoDB 引擎下可解幻读（读数据集合变），读事务不受其他事务数据变更影响，保障数据稳定性与可预测性。</li><li><strong>串行化</strong>：事务加锁阻塞，读共享锁、写排他锁，性能居末，却根治脏读、不可重复读与幻读，以牺牲并发换高数据一致性，适用对数据准确性严苛场景。</li><li><img src="/./mvcc%E5%A4%9A%E7%89%88%E6%9C%AC%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/12ae1c5fc49f6acb1be229824e1ec291.png" alt="img"></li></ul><h3 id="（二）隐藏字段、Undo-Log-版本链"><a href="#（二）隐藏字段、Undo-Log-版本链" class="headerlink" title="（二）隐藏字段、Undo Log 版本链"></a>（二）隐藏字段、Undo Log 版本链</h3><h4 id="1-隐藏字段"><a href="#1-隐藏字段" class="headerlink" title="1. 隐藏字段"></a>1. 隐藏字段</h4><p>InnoDB 表聚簇索引记录含关键隐藏列：</p><ul><li><strong>trx_id（事务 id）</strong>：事务改动聚簇索引记录时，自身事务 id 赋于此列，标记数据修改者，助 MVCC 追踪数据版本所属事务，为数据可见性判断奠基。</li><li><strong>roll_pointer（回滚指针）</strong>：指向 Undo Log 里记录版本链近节点，记录更新时旧版本入 Undo Log，此指针串起历史版本形成单向链表，回溯数据变更轨迹，供 MVCC 按需取历史版本。</li></ul><h4 id="2-Undo-Log（回滚日志）"><a href="#2-Undo-Log（回滚日志）" class="headerlink" title="2. Undo Log（回滚日志）"></a>2. Undo Log（回滚日志）</h4><p>事务写操作时，MySQL 先存旧版本数据于 Undo Log，再更新数据库，事务提交前用于回滚，提交后助提供历史版本读取数据，其版本链为链表结构，各版本依时间或事务推进有序排列，头部为最新版本，MVCC 依此定位检索历史版本，保数据完整性与一致性，助事务回滚与多版本并发控制。</p><h3 id="（三）ReadView"><a href="#（三）ReadView" class="headerlink" title="（三）ReadView"></a>（三）ReadView</h3><h4 id="1-ReadView（读视图）简约版"><a href="#1-ReadView（读视图）简约版" class="headerlink" title="1. ReadView（读视图）简约版"></a>1. ReadView（读视图）简约版</h4><p>事务快照读时生成 ReadView，快照记录系统活跃事务 id（递增），主用于数据可见性判定。含关键全局属性：</p><ul><li><strong>creator_trx_id</strong>：创建读视图事务 ID，增删改事务有资格分配（读事务 id 为 0），标识读视图所属事务，助判断事务与数据版本关联及可见性。</li><li><strong>trx_ids</strong>：生成 ReadView 时系统活跃读写事务 id 列表，反映系统并发事务活跃态势，MVCC 依此对比数据版本事务 id 判断可见性，界定事务能访问的数据版本范围。</li><li><strong>up_limit_id</strong>：活跃事务列表最小事务 ID，界定事务生成 ReadView 前已提交事务下限，小于此 id 的数据版本对当前事务可见，因在当前事务启动前已稳定提交。</li><li><strong>low_limit_id</strong>：系统应分配给下一事务的 id 值（非 trx_ids 最大值，事务 id 递增），示活跃事务 id 上限，大于等于此 id 的数据版本在当前事务后产生不可见，标志事务启动后新事务修改数据不可访问。</li></ul><h4 id="2-设计思路"><a href="#2-设计思路" class="headerlink" title="2. 设计思路"></a>2. 设计思路</h4><ul><li><strong>不同隔离级别处理逻辑</strong><ul><li><strong>READ UNCOMMITTED</strong>：事务可读未提交记录最新版，因无数据一致性严格要求，无需复杂版本控制，直取最新数据，适对数据时效敏感轻一致性场景，如实时监控数据采集，及时反映最新数据变化，不顾及数据短暂不一致性。</li><li><strong>SERIALIZABLE</strong>：InnoDB 强用加锁访问记录，保数据强一致性，多事务串行执行无并发干扰，如金融核心交易处理，数据准确重于性能，确保各交易有序、准确处理，杜绝并发异常致数据错误风险。</li><li><strong>READ COMMITTED 和 REPEATABLE READ</strong>：须保证读已提交事务修改数据，核心是精准判断版本链数据版本可见性，ReadView 在此关键，屏蔽未提交事务干扰，READ COMMITTED 每次读新生成 ReadView，REPEATABLE READ 首次读生成后复用，适配不同并发控制与数据一致性平衡需求。</li></ul></li></ul><h4 id="3-ReadView-的规则"><a href="#3-ReadView-的规则" class="headerlink" title="3. ReadView 的规则"></a>3. ReadView 的规则</h4><p>依 ReadView 判断记录版本可见性规则如下：</p><ul><li>若被访问版本 <code>trx_id</code> 与 <code>creator_trx_id</code> 同，事务访问自身修改记录，版本可见。</li><li>被访问版本 <code>trx_id</code> 小于 <code>up_limit_id</code>，此版本在 ReadView 生成前已提交，事务可访问。</li><li>被访问版本 <code>trx_id</code> 大于等于 <code>low_limit_id</code>，版本于 ReadView 生成后产生，事务不可访问。</li><li>被访问版本 <code>trx_id</code> 在 <code>up_limit_id</code> 与 <code>low_limit_id</code> 间，若在 <code>trx_ids</code> 列表，版本对应事务活跃，不可访问；若不在，版本事务已提交，事务可访问。</li></ul><h4 id="4-MVCC-整体操作流程"><a href="#4-MVCC-整体操作流程" class="headerlink" title="4. MVCC 整体操作流程"></a>4. MVCC 整体操作流程</h4><p>MVCC 查询记录流程为：</p><ul><li>先取事务自身版本号（事务 ID），为后续判断数据版本归属与可见性锚点。</li><li>获取 ReadView，依事务隔离级别与执行阶段生成或复用，含系统活跃事务关键信息，定数据可见性判断依据。</li><li>查询数据并与 ReadView 事务版本号比，不符规则从 Undo Log 取历史快照，Undo Log 依 roll_pointer 存历史版本链，MVCC 回溯检索，直至得符合规则数据返回；若版本链遍历完无可见数据，事务对该记录不可见，查询结果排除此记录。</li></ul><h2 id="四、举例说明-MVCC-流程"><a href="#四、举例说明-MVCC-流程" class="headerlink" title="四、举例说明 MVCC 流程"></a>四、举例说明 MVCC 流程</h2><p><img src="/./mvcc%E5%A4%9A%E7%89%88%E6%9C%AC%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/585df0a3e7b10eab06b78ae87ac51888.png" alt="img"></p><h3 id="（一）读提交的-MVCC-流程"><a href="#（一）读提交的-MVCC-流程" class="headerlink" title="（一）读提交的 MVCC 流程"></a>（一）读提交的 MVCC 流程</h3><blockquote><p><strong>读提交：</strong>事务每次读到的都是最新已提交的数据。底层由MVVC实现 ，每次读取数据前都生成一个ReadView。只解决脏读问题。</p></blockquote><p><strong>读提交的MVCC原理：</strong>每次读都生成Read View，不断对比版本链各版本的trx_id，直到发现某版本trx_id比Read View的活跃事务列表里最小trx_id还小，该版本则是<strong>读前最新已提交的数据</strong>。</p><p>现在有两个 <strong>事务id</strong> 分别为 <strong>10 、 20</strong> 的事务在执行:</p><figure class="highlight sql"><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"># Transaction <span class="number">10</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> student <span class="keyword">SET</span> name<span class="operator">=</span>&quot;李四&quot; <span class="keyword">WHERE</span> id<span class="operator">=</span><span class="number">1</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> student <span class="keyword">SET</span> name<span class="operator">=</span>&quot;王五&quot; <span class="keyword">WHERE</span> id<span class="operator">=</span><span class="number">1</span>;</span><br><span class="line"># Transaction <span class="number">20</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"># 更新了一些别的表的记录</span><br><span class="line">...</span><br></pre></td></tr></table></figure><blockquote><p>说明：事务执行过程中，只有在第一次真正修改记录时（比如使用INSERT、DELETE、UPDATE语句），才会被分配一个单独的事务id，这个事务id是递增的。所以我们才在事务2中更新一些别的表的记录，目的是让它分配事务id。</p></blockquote><p>此刻，表student 中 id 为 1 的记录得到的版本链表如下所示：</p><p><img src="/./mvcc%E5%A4%9A%E7%89%88%E6%9C%AC%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/95ad6e787a810e8409cff77a5324667c.png" alt="img"></p><p>假设现在有一个使用 <strong>读提交</strong>READ COMMITTED 隔离级别的事务开始执行：</p><figure class="highlight sql"><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"># 使用READ COMMITTED隔离级别的事务</span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"> </span><br><span class="line"># SELECT1：Transaction <span class="number">10</span>、<span class="number">20</span>未提交</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> student <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>; # 得到的列name的值为<span class="string">&#x27;张三&#x27;</span>，对应事务id为<span class="number">8</span></span><br></pre></td></tr></table></figure><p>这个 SELECT1 的执行过程如下:</p><ol><li><p>在执行 SELECT 语句（快照读）时会先生成一个 ReadView。ReadView 的 trx_ids 列表的内容就是[10，20]。up_limit_id为10，low_limit_id为21，creator_trx_id为0。</p></li><li><p>从版本链中挑选可见的记录，从图中看出，最新版本的列 name 的内容是王五，该版本的 trx_id 值为10，在trx_ids 列表内，所以不符合可见性要求，根据 roll_pointer 跳到下一个版本。</p></li><li><p>下一个版本的列name 的内容是，李四，该版本的 trx_id值也为 10，也在 trx_ids 列表内，所以也不符合要求，继续跳到下一个版本。</p></li><li><p>下一个版本的列name 的内容是，张三，该版本的trx_id值为 8，小于ReadView中的最小活跃事务up_limit_id值10，说明该版本是生成ReadView 之前的最近版本，所以这个版本是符合要求的，最后返回给用户的版本就是这条列 name 为，张三的记录。</p></li></ol><p>之后，我们把 <code>事务id</code> 为 <code>10</code> 的事务提交一下：</p><figure class="highlight sql"><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"># Transaction <span class="number">10</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> student <span class="keyword">SET</span> name<span class="operator">=</span>&quot;李四&quot; <span class="keyword">WHERE</span> id<span class="operator">=</span><span class="number">1</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> student <span class="keyword">SET</span> name<span class="operator">=</span>&quot;王五&quot; <span class="keyword">WHERE</span> id<span class="operator">=</span><span class="number">1</span>;</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure><p>然后再到 <code>事务id</code> 为 <code>20</code> 的事务中更新一下表 <code>student</code> 中 <code>id</code> 为 <code>1</code> 的记录：</p><figure class="highlight sql"><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"># Transaction <span class="number">20</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"># 更新了一些别的表的记录</span><br><span class="line">...</span><br><span class="line"><span class="keyword">UPDATE</span> student <span class="keyword">SET</span> name<span class="operator">=</span>&quot;钱七&quot; <span class="keyword">WHERE</span> id<span class="operator">=</span><span class="number">1</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> student <span class="keyword">SET</span> name<span class="operator">=</span>&quot;宋八&quot; <span class="keyword">WHERE</span> id<span class="operator">=</span><span class="number">1</span>;</span><br></pre></td></tr></table></figure><p><img src="/./mvcc%E5%A4%9A%E7%89%88%E6%9C%AC%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/47e220a3c9065fb60b8d28c3b1f1b55e.png" alt="img"></p><p>然后再到刚才使用 <code>READ COMMITTED</code> 隔离级别的事务中继续查找这个 id 为 1 的记录，如下：</p><figure class="highlight sql"><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"># 使用READ COMMITTED隔离级别的事务</span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"> </span><br><span class="line"># SELECT1：Transaction <span class="number">10</span>、<span class="number">20</span>均未提交</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> student <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>; # 得到的列name的值为<span class="string">&#x27;张三&#x27;</span></span><br><span class="line"> </span><br><span class="line"># SELECT2：Transaction <span class="number">10</span>提交，Transaction <span class="number">20</span>未提交</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> student <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>; # 得到的列name的值为<span class="string">&#x27;王五&#x27;</span></span><br></pre></td></tr></table></figure><p>这个 SELECT2 的执行过程如下</p><ol><li><p> 在执行 SELECT 语句时会又会单独生成一个 ReadView，该ReadView的 trx_ids 列表的内容[20]，就是up_limit_id为20，low_limit_id为21， creator_trx_id为0。</p></li><li><p>从版本链中挑选可见的记录，从图中看出，最新版本的列 name 的内容是，宋八，该版本的 trx_id 值为20，在 trx_ids 列表内，所以不符合可见性要求，根据 roll_pointer 跳到下一个版本。</p></li><li><p>下一个版本的列name 的内容是钱七’，该版本的 trx_id值为 28，也在 trx_ids 列表内，所以也不符合要求，继续跳到下一个版本。</p></li><li><p>下一个版本的列name 的内容是’王五’，该版本的trx_id值为10，小于ReadView 中的up_limit_id值28，所以这个版本是符合要求的，最后返回给用户的版本就是这条列 name 为，王五’的记录。 </p></li></ol><h3 id="（二）可重复读的-MVCC-流程"><a href="#（二）可重复读的-MVCC-流程" class="headerlink" title="（二）可重复读的 MVCC 流程"></a>（二）可重复读的 MVCC 流程</h3><p><strong>只在第一次查询时生成ReadView</strong>，之后查询用第一次快照读时生成的ReadView。</p><p>比如，系统里有两个 <code>事务id</code> 分别为 <code>10</code> 、 <code>20</code> 的事务在执行：</p><figure class="highlight sql"><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"># Transaction <span class="number">10</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> student <span class="keyword">SET</span> name<span class="operator">=</span>&quot;李四&quot; <span class="keyword">WHERE</span> id<span class="operator">=</span><span class="number">1</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> student <span class="keyword">SET</span> name<span class="operator">=</span>&quot;王五&quot; <span class="keyword">WHERE</span> id<span class="operator">=</span><span class="number">1</span>;</span><br><span class="line"># Transaction <span class="number">20</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"># 更新了一些别的表的记录</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>此刻，表student 中 id 为 1 的记录得到的版本链表如下所示：<img src="/./mvcc%E5%A4%9A%E7%89%88%E6%9C%AC%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/e28a31dbfd2c53928fbfaae543377fc3.png" alt="img"></p><p>假设现在有一个使用 <code>REPEATABLE READ</code> 隔离级别的事务开始执行：</p><figure class="highlight sql"><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"># 使用REPEATABLE READ隔离级别的事务</span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"> </span><br><span class="line"># SELECT1：Transaction <span class="number">10</span>、<span class="number">20</span>未提交</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> student <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>; # 得到的列name的值为<span class="string">&#x27;张三&#x27;</span></span><br></pre></td></tr></table></figure><p>这个SELECT1 的执行过程如下:</p><ol><li><p>在执行 SELECT 语句时会先生成一个Readview，ReadView的 trx_ids 列表的内容就是[10，20].up_limit_id为10，low_limit_id为21，creator_trx_id为0。</p></li><li><p>然后从版本链中挑选可见的记录，从图中看出，最新版本的列 name 的内容是，王五’，该版本的 trx_id值为 10，在trx_ids 列表内，所以不符合可见性要求，根据 rol1_pointer 跳到下一个版本.</p></li><li><p>下一个版本的列name 的内容是，李四’，该版本的trx_id值也为 10，也在 trx_ids 列表内，所以也不符合要求，继续跳到下一个版本。</p></li><li><p>下一个版本的列name 的内容是’张三’，该版本的trx_id值为 8，小于Readview 中的up_limit_id值10，所以这个版本是符合要求的，最后返回给用户的版本就是这条列 name 为，张三’的记录。</p></li></ol><p>之后，我们把 事务id 为 10 的事务提交一下，就像这样：</p><figure class="highlight sql"><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"># Transaction <span class="number">10</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">UPDATE</span> student <span class="keyword">SET</span> name<span class="operator">=</span>&quot;李四&quot; <span class="keyword">WHERE</span> id<span class="operator">=</span><span class="number">1</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> student <span class="keyword">SET</span> name<span class="operator">=</span>&quot;王五&quot; <span class="keyword">WHERE</span> id<span class="operator">=</span><span class="number">1</span>;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure><p>然后再到 事务id 为 20 的事务中更新一下表 student 中 id 为 1 的记录：</p><figure class="highlight sql"><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"># Transaction <span class="number">20</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"># 更新了一些别的表的记录</span><br><span class="line">...</span><br><span class="line"><span class="keyword">UPDATE</span> student <span class="keyword">SET</span> name<span class="operator">=</span>&quot;钱七&quot; <span class="keyword">WHERE</span> id<span class="operator">=</span><span class="number">1</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> student <span class="keyword">SET</span> name<span class="operator">=</span>&quot;宋八&quot; <span class="keyword">WHERE</span> id<span class="operator">=</span><span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>此刻，表student 中 <code>id</code> 为 <code>1</code> 的记录的版本链长这样：</p><p><img src="/./mvcc%E5%A4%9A%E7%89%88%E6%9C%AC%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/8e99a34323dea4bb8a3d5f26981ee9a2.png" alt="img"></p><p>然后再到刚才使用 <code>REPEATABLE READ</code> 隔离级别的事务中继续查找这个 <code>id</code> 为 <code>1</code> 的记录，如下：</p><figure class="highlight sql"><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"># 使用REPEATABLE READ隔离级别的事务</span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"># SELECT1：Transaction <span class="number">10</span>、<span class="number">20</span>均未提交</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> student <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>; # 得到的列name的值为<span class="string">&#x27;张三&#x27;</span></span><br><span class="line"># SELECT2：Transaction <span class="number">10</span>提交，Transaction <span class="number">20</span>未提交</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> student <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>; # 得到的列name的值仍为<span class="string">&#x27;张三&#x27;</span></span><br></pre></td></tr></table></figure><p>SELECT2 的执行过程如下:</p><ol><li><p>因为当前事务的隔离级别为 REPEATABLE READ，而之前在执行SELECT1 时已经生成过 ReadView了，所以此时直接复用之前的 ReadView，之前的 ReadView的 trx_ids 列表的内容就是[10，20]，up_limit_id为1， low_limit_id为21， creator_trx_id为0</p></li><li><p>然后从版本链中挑选可见的记录，从图中可以看出，最新版本的列 name 的内容是宋八’，该版本的trx_id值为29，在trx_ds列表内，所以不符合可见性要求，根据ro11 pointer 跳到下一个版本</p></li><li><p>下一个版本的列name 的内容是’钱七’，该版本的 trx_id值为20，也在 trx_ids 列表内，所以也不符合要求，继续跳到下一个版本。</p></li><li><p>下一个版本的列name 的内容是，王五’，该版本的trx_id值为10，而trx_ids 列表中是包含值为 10的事务id 的，所以该版本也不符合要求，同理下一个列 name 的内容是，李四的版本也不符合要求。继续跳到下个版本</p></li><li><p>下一个版本的列name 的内容是’张三’，该版本的trx_id值为 80，小于 ReadView 中的up_limit_id值18，所以这个版本是符合要求的，最后返回给用户的版本就是这条列 c为，张三’的记录。</p></li></ol><p>这次SELECT查询得到的结果是重复的，记录的列c值都是张三，这就是可重复读的含义。如果我们之后再把事务id为20的记录提交了，然后再到刚才使用REPEATABLE READ隔离级别的事务中继续查找这个id为1的记录，得到的结果还是张三，具体执行过程大家可以自己分析一下。</p><h3 id="（三）InnoDB-解决幻读问题"><a href="#（三）InnoDB-解决幻读问题" class="headerlink" title="（三）InnoDB 解决幻读问题"></a>（三）InnoDB 解决幻读问题</h3><p>接下来说明InnoDB 是如何解决幻读的。</p><p>假设现在表 student 中只有一条数据，数据内容中，主键 id=1，隐藏的 trx_id=10，它的 undo log 如下图所示。</p><p><img src="/./mvcc%E5%A4%9A%E7%89%88%E6%9C%AC%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/6f3537dcef8645502b3be06625120db7.png" alt="img"></p><p>假设现在有事务 A 和事务 B 并发执行，<strong>事务 A 的事务 id 为 20 ， 事务 B 的事务 id 为 30</strong> 。</p><p><strong>步骤1：</strong>事务 A 开始第一次查询数据，查询的 SQL 语句如下。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> student <span class="keyword">where</span> id <span class="operator">&gt;=</span> <span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>在开始查询之前，MySQL 会为事务 A 产生一个 ReadView，此时 ReadView 的内容如下： trx_ids= [20,30] ， up_limit_id=20 ， low_limit_id=31 ， creator_trx_id=20 。</p><p>由于此时表 student 中只有一条数据，且符合 where id&gt;=1 条件，因此会查询出来。然后根据 ReadView 机制，发现该行数据的trx_id=10，小于事务 A 的 ReadView 里 up_limit_id，这表示这条数据是事务 A 开启之前，其他事务就已经提交了的数据，因此事务 A 可以读取到。</p><p>结论：事务 A 的第一次查询，能读取到一条数据，id=1。</p><p><strong>步骤2</strong>：接着事务 B(trx_id=30)，往表 student 中新插入两条数据，并提交事务。</p><figure class="highlight sql"><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="keyword">insert</span> <span class="keyword">into</span> student(id,name) <span class="keyword">values</span>(<span class="number">2</span>,<span class="string">&#x27;李四&#x27;</span>);</span><br><span class="line"><span class="keyword">insert</span> <span class="keyword">into</span> student(id,name) <span class="keyword">values</span>(<span class="number">3</span>,<span class="string">&#x27;王五&#x27;</span>);</span><br></pre></td></tr></table></figure><p>此时表student 中就有三条数据了，对应的 undo 如下图所示：</p><p><img src="/./mvcc%E5%A4%9A%E7%89%88%E6%9C%AC%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/cc09d37474631bb4fab4f5e67c310f68.png" alt="img"></p><p>步骤3：接着事务 A 开启第二次查询，根据可重复读隔离级别的规则，此时事务 A 并不会再重新生成 ReadView。此时表 student 中的 3 条数据都满足 where id&gt;=1 的条件，因此会先查出来。然后根据 ReadView 机制，判断每条数据是不是都可以被事务 A 看到。</p><p>1）首先 id=1 的这条数据，前面已经说过了，可以被事务 A 看到。</p><p>2）然后是 id=2 的数据，它的 trx_id=30，此时事务 A 发现，这个值处于 up_limit_id 和 low_limit_id 之 间，因此还需要再判断 30 是否处于 trx_ids 数组内。由于事务 A 的 trx_ids=[20,30]，因此在数组内，这表 示 id=2 的这条数据是与事务 A 在同一时刻启动的其他事务提交的，所以这条数据不能让事务 A 看到。</p><p>3）同理，id=3 的这条数据，trx_id 也为 30，因此也不能被事务 A 看见。<br><img src="/./mvcc%E5%A4%9A%E7%89%88%E6%9C%AC%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/373de799dabc560f459c616922931ab0.png" alt="img"></p><p>结论：最终<strong>事务 A 的第二次查询</strong>，只能查询出 id=1 的这条数据。这和事务 A 的<strong>第一次查询的结果是一样 的</strong>，因此<strong>没有出现幻读现</strong>象，所以说在 <strong>MySQL 的可重复读隔离级别下，不存在幻读问题。</strong></p><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><blockquote><p>这里介绍了 MVCC 在 READ COMMITTD 、 REPEATABLE READ 这两种隔离级别的事务在执行快照读操作时 访问记录的版本链的过程。这样使不同事务的 读-写 、 写-读 操作并发执行，从而提升系统性能。</p></blockquote><p>核心点在于 ReadView 的原理， READ COMMITTD 、 REPEATABLE READ 这两个隔离级别的一个很大不同 就是生成ReadView的时机不同：</p><ul><li><p>READ COMMITTD 在每一次进行普通SELECT操作前都会生成一个ReadView</p></li><li><p>REPEATABLE READ 只在第一次进行普通SELECT操作前生成一个ReadView，之后的查询操作都重复 使用这个ReadView就好了。</p></li></ul><blockquote><p> 说明: 我们之前说执行DELETE语句或者更新主键的UPDATE语句并不会立即把对应的记录完全从页面中删除而是执行一个所谓的delete mark操作，相当于只是对记录打上了一个删除标志位，这主要就是为MVCC服务的。</p></blockquote><p>通过MVCC我们可以解决：</p><ul><li>读写之间阻塞的问题。通过 MVCC 可以让读写互相不阻塞，即读不阻塞写，写不阻塞读，这样就可以提升事务并发处理能力。</li><li>降低了死锁的概率。这是因为 MVCC 采用了乐观锁的方式，读取数据时并不需要加锁，对于写操作，也只锁定必要的行。</li><li>解决快照读的问题。当我们查询数据库在某个时间点的快照时，只能看到这个时间点之前事务提交更新的结3果，而不能看到这个时间点之后事务提交的更新结果.</li><li></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;MVCC-多版本并发控制&quot;&gt;&lt;a href=&quot;#MVCC-多版本并发控制&quot; class=&quot;headerlink&quot; title=&quot;MVCC 多版本并发控制&quot;&gt;&lt;/a&gt;MVCC 多版本并发控制&lt;/h1&gt;&lt;h2 id=&quot;一、引言&quot;&gt;&lt;a href=&quot;#一、引言&quot; cla</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>jupyter lab配置列表清单</title>
    <link href="http://example.com/2023/10/10/jupyter-lab%E9%85%8D%E7%BD%AE%E5%88%97%E8%A1%A8%E6%B8%85%E5%8D%95/"/>
    <id>http://example.com/2023/10/10/jupyter-lab%E9%85%8D%E7%BD%AE%E5%88%97%E8%A1%A8%E6%B8%85%E5%8D%95/</id>
    <published>2023-10-10T07:04:06.000Z</published>
    <updated>2024-09-10T06:02:47.162Z</updated>
    
    <content type="html"><![CDATA[<h2 id="jupyter-lab配置列表清单"><a href="#jupyter-lab配置列表清单" class="headerlink" title="jupyter lab配置列表清单"></a>jupyter lab配置列表清单</h2><p>Jupyter Notebook和Jupyter Lab提供了广泛的配置选项，允许用户根据自己的需求定制运行环境。这些配置项涉及了从日志设置、安全性选项、内核管理，到内容管理等多个方面，每项配置都有其特定的作用和默认值。</p><h4 id="日志和输出格式"><a href="#日志和输出格式" class="headerlink" title="日志和输出格式"></a>日志和输出格式</h4><ul><li><code>c.Application.log_datefmt</code>、<code>c.Application.log_format</code> 和 <code>c.Application.log_level</code> 等配置控制日志记录的格式和详细程度。这些设置对于调试和监控Jupyter服务器的运行非常重要。</li></ul><h4 id="安全性和访问控制"><a href="#安全性和访问控制" class="headerlink" title="安全性和访问控制"></a>安全性和访问控制</h4><ul><li>安全性相关的配置项，如 <code>c.NotebookApp.certfile</code> 和 <code>c.NotebookApp.keyfile</code>，允许您设置SSL证书，以便在HTTPS上运行Jupyter实例。</li><li>访问控制设置，例如 <code>c.NotebookApp.password</code> 和 <code>c.NotebookApp.token</code>，用于配置身份验证机制，保护你的笔记本免受未经授权的访问。</li></ul><h4 id="内核和会话管理"><a href="#内核和会话管理" class="headerlink" title="内核和会话管理"></a>内核和会话管理</h4><ul><li><code>c.KernelManager</code> 和 <code>c.Session</code> 相关配置影响内核的行为，如自动重启策略、会话签名机制等。</li><li><code>c.MultiKernelManager</code> 和 <code>c.MappingKernelManager</code> 提供了对于多内核管理的配置，这对于使用多种编程语言或在单个服务器上运行多个项目很有用。</li></ul><h4 id="内容管理"><a href="#内容管理" class="headerlink" title="内容管理"></a>内容管理</h4><ul><li><code>c.ContentsManager</code> 和 <code>c.FileContentsManager</code> 配置项涉及到笔记本内容的存储和检索方式，包括是否允许访问隐藏文件、如何处理文件的保存和加载等。</li></ul><h4 id="终端和网关选项"><a href="#终端和网关选项" class="headerlink" title="终端和网关选项"></a>终端和网关选项</h4><ul><li><code>c.TerminalManager</code> 设置控制Jupyter终端的行为，包括终端的清理和超时设置。</li><li>对于使用Jupyter Gateway的用户来说，<code>c.GatewayClient</code> 相关配置提供了与远程内核和网关交互所需的设置。</li></ul><p>这个配置文件是Jupyter环境的核心，正确配置这些选项将帮助你创建一个安全、高效、符合个人需求的Jupyter工作环境。根据你的具体使用场景（如教学、数据分析、机器学习等），可能需要调整不同的配置项以获得最佳体验。</p><p>下面是一个完整的配置说明表，涵盖了配置文件中出现的所有配置项及其描述。</p><table><thead><tr><th>配置项</th><th>描述</th><th>默认值</th></tr></thead><tbody><tr><td><code>c.Application.log_datefmt</code></td><td>日志记录中使用的日期格式。</td><td><code>&#39;%Y-%m-%d %H:%M:%S&#39;</code></td></tr><tr><td><code>c.Application.log_format</code></td><td>日志格式模板。</td><td><code>&#39;[%(name)s]%(highlevel)s %(message)s&#39;</code></td></tr><tr><td><code>c.Application.log_level</code></td><td>设置日志级别。</td><td><code>30</code> (WARN)</td></tr><tr><td><code>c.Application.logging_config</code></td><td>配置额外的日志处理器。</td><td><code>&#123;&#125;</code></td></tr><tr><td><code>c.Application.show_config</code></td><td>启动时是否打印配置到标准输出。</td><td><code>False</code></td></tr><tr><td><code>c.Application.show_config_json</code></td><td>启动时是否以JSON格式打印配置到标准输出。</td><td><code>False</code></td></tr><tr><td><code>c.JupyterApp.answer_yes</code></td><td>自动回答是对于任何提示。</td><td><code>False</code></td></tr><tr><td><code>c.JupyterApp.config_file</code></td><td>配置文件的完整路径。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.JupyterApp.config_file_name</code></td><td>要加载的配置文件名。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.JupyterApp.generate_config</code></td><td>是否生成默认配置文件。</td><td><code>False</code></td></tr><tr><td><code>c.NotebookApp.allow_credentials</code></td><td>设置<code>Access-Control-Allow-Credentials</code>头。</td><td><code>False</code></td></tr><tr><td><code>c.NotebookApp.allow_origin</code></td><td>设置<code>Access-Control-Allow-Origin</code>头。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookApp.allow_origin_pat</code></td><td>使用正则表达式设置<code>Access-Control-Allow-Origin</code>头。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookApp.allow_password_change</code></td><td>允许在登录时更改密码。</td><td><code>True</code></td></tr><tr><td><code>c.NotebookApp.allow_remote_access</code></td><td>允许远程访问。</td><td><code>False</code></td></tr><tr><td><code>c.NotebookApp.allow_root</code></td><td>是否允许以root用户运行。</td><td><code>False</code></td></tr><tr><td><code>c.NotebookApp.authenticate_prometheus</code></td><td>是否需要认证以访问prometheus指标。</td><td><code>True</code></td></tr><tr><td><code>c.NotebookApp.autoreload</code></td><td>是否自动重新加载任何Python源文件的更改。</td><td><code>False</code></td></tr><tr><td><code>c.NotebookApp.base_url</code></td><td>Notebook服务器的基本URL。</td><td><code>&#39;/&#39;</code></td></tr><tr><td><code>c.NotebookApp.certfile</code></td><td>SSL/TLS证书文件的完整路径。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookApp.client_ca</code></td><td>SSL/TLS客户端认证的证书颁发机构证书的完整路径。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookApp.config_manager_class</code></td><td>配置管理器类。</td><td><code>&#39;notebook.services.config.manager.ConfigManager&#39;</code></td></tr><tr><td><code>c.NotebookApp.contents_manager_class</code></td><td>内容管理器类。</td><td><code>&#39;notebook.services.contents.largefilemanager.LargeFileManager&#39;</code></td></tr><tr><td><code>c.NotebookApp.cookie_options</code></td><td>传递给<code>set_secure_cookie</code>的额外关键字参数。</td><td><code>&#123;&#125;</code></td></tr><tr><td><code>c.NotebookApp.cookie_secret</code></td><td>用于保护cookies的随机字节。</td><td><code>b&#39;&#39;</code></td></tr><tr><td><code>c.NotebookApp.cookie_secret_file</code></td><td>存储cookie密钥的文件。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookApp.custom_display_url</code></td><td>覆盖显示给用户的URL。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookApp.default_url</code></td><td>从<code>/</code>重定向到的默认URL。</td><td><code>&#39;/tree&#39;</code></td></tr><tr><td><code>c.NotebookApp.disable_check_xsrf</code></td><td>禁用跨站请求伪造保护。</td><td><code>False</code></td></tr><tr><td><code>c.NotebookApp.enable_mathjax</code></td><td>是否启用MathJax进行数学/TeX排版。</td><td><code>True</code></td></tr><tr><td><code>c.NotebookApp.extra_nbextensions_path</code></td><td>查找JavaScript notebook扩展的额外路径。</td><td><code>[]</code></td></tr><tr><td><code>c.NotebookApp.extra_services</code></td><td>高优先级加载的额外服务。</td><td><code>[]</code></td></tr><tr><td><code>c.NotebookApp.extra_static_paths</code></td><td>用于服务静态文件的额外路径。</td><td><code>[]</code></td></tr><tr><td><code>c.NotebookApp.extra_template_paths</code></td><td>用于服务jinja模板的额外路径。</td><td><code>[]</code></td></tr><tr><td><code>c.NotebookApp.file_to_run</code></td><td>启动时运行的文件路径。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookApp.generate_config</code></td><td>是否生成默认配置文件。</td><td><code>False</code></td></tr><tr><td><code>c.NotebookApp.get_secure_cookie_kwargs</code></td><td>传递给<code>get_secure_cookie</code>的额外关键字参数。</td><td><code>&#123;&#125;</code></td></tr><tr><td><code>c.NotebookApp.ignore_minified_js</code></td><td>是否忽略压缩的JS文件，主要在开发过程中使用。</td><td><code>False</code></td></tr><tr><td><code>c.NotebookApp.iopub_data_rate_limit</code></td><td>IOPub数据通道的最大数据传输速率（字节/秒）。</td><td><code>1000000</code></td></tr><tr><td><code>c.NotebookApp.iopub_msg_rate_limit</code></td><td>IOPub消息的最大发送速率（消息/秒）。</td><td><code>1000</code></td></tr><tr><td><code>c.NotebookApp.ip</code></td><td>Notebook服务器监听的IP地址。</td><td><code>&#39;localhost&#39;</code></td></tr><tr><td><code>c.NotebookApp.jinja_environment_options</code></td><td>传递给Jinja环境的额外参数。</td><td><code>&#123;&#125;</code></td></tr><tr><td><code>c.NotebookApp.jinja_template_vars</code></td><td>渲染时提供给jinja模板的额外变量。</td><td><code>&#123;&#125;</code></td></tr><tr><td><code>c.NotebookApp.kernel_manager_class</code></td><td>内核管理器类。</td><td><code>&#39;notebook.services.kernels.kernelmanager.MappingKernelManager&#39;</code></td></tr><tr><td><code>c.NotebookApp.kernel_spec_manager_class</code></td><td>内核规格管理器类。</td><td><code>&#39;jupyter_client.kernelspec.KernelSpecManager&#39;</code></td></tr><tr><td><code>c.NotebookApp.keyfile</code></td><td>SSL/TLS私钥文件的完整路径。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookApp.local_hostnames</code></td><td>允许作为本地主机名的主机名列表。</td><td><code>[&#39;localhost&#39;]</code></td></tr><tr><td><code>c.NotebookApp.log_json</code></td><td>是否启用JSON格式化日志。</td><td><code>False</code></td></tr><tr><td><code>c.NotebookApp.logging_config</code></td><td>额外的日志配置。</td><td><code>&#123;&#125;</code></td></tr><tr><td><code>c.NotebookApp.login_handler_class</code></td><td>登录处理器类。</td><td><code>&#39;notebook.auth.login.LoginHandler&#39;</code></td></tr><tr><td><code>c.NotebookApp.logout_handler_class</code></td><td>注销处理器类。</td><td><code>&#39;notebook.auth.logout.LogoutHandler&#39;</code></td></tr><tr><td><code>c.NotebookApp.mathjax_config</code></td><td>使用的MathJax.js配置文件。</td><td><code>&#39;TeX-AMS-MML_HTMLorMML-full,Safe&#39;</code></td></tr><tr><td><code>c.NotebookApp.mathjax_url</code></td><td>MathJax.js的自定义URL。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookApp.max_body_size</code></td><td>客户端请求体的最大允许大小（字节）。</td><td><code>536870912</code></td></tr><tr><td><code>c.NotebookApp.max_buffer_size</code></td><td>缓冲管理器可分配用于使用的最大内存量（字节）。</td><td><code>536870912</code></td></tr><tr><td><code>c.NotebookApp.min_open_files_limit</code></td><td>打开文件句柄进程资源限制的下限。</td><td><code>0</code></td></tr><tr><td><code>c.NotebookApp.nbserver_extensions</code></td><td>要加载为notebook服务器扩展的Python模块列表。</td><td><code>&#123;&#125;</code></td></tr><tr><td><code>c.NotebookApp.notebook_dir</code></td><td>笔记本和内核的目录。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookApp.open_browser</code></td><td>启动后是否在浏览器中打开。</td><td><code>True</code></td></tr><tr><td><code>c.NotebookApp.password</code></td><td>Web认证的哈希密码。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookApp.password_required</code></td><td>是否强制用户使用密码。</td><td><code>False</code></td></tr><tr><td><code>c.NotebookApp.port</code></td><td>Notebook服务器监听的端口。</td><td><code>8888</code></td></tr><tr><td><code>c.NotebookApp.port_retries</code></td><td>指定端口不可用时尝试的附加端口数。</td><td><code>50</code></td></tr><tr><td><code>c.NotebookApp.pylab</code></td><td>是否启用pylab。</td><td><code>&#39;disabled&#39;</code></td></tr><tr><td><code>c.NotebookApp.quit_button</code></td><td>是否显示退出按钮。</td><td><code>True</code></td></tr><tr><td><code>c.NotebookApp.rate_limit_window</code></td><td>用于检查消息和数据速率限制的时间窗口（秒）。</td><td><code>3</code></td></tr><tr><td><code>c.NotebookApp.reraise_server_extension_failures</code></td><td>重新引发加载服务器扩展时遇到的异常。</td><td><code>False</code></td></tr><tr><td><code>c.NotebookApp.server_extensions</code></td><td>DEPRECATED: 使用<code>nbserver_extensions</code>字典。</td><td><code>[]</code></td></tr><tr><td><code>c.NotebookApp.session_manager_class</code></td><td>会话管理器类。</td><td><code>&#39;notebook.services.sessions.sessionmanager.SessionManager&#39;</code></td></tr><tr><td><code>c.NotebookApp.show_banner</code></td><td>是否在页面上显示横幅。</td><td><code>True</code></td></tr><tr><td><code>c.NotebookApp.shutdown_no_activity_timeout</code></td><td>无活动时自动关闭服务器的超时时间（秒）。</td><td><code>0</code></td></tr><tr><td><code>c.NotebookApp.sock</code></td><td>Notebook服务器将监听的UNIX套接字。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookApp.sock_mode</code></td><td>UNIX套接字创建的权限模式。</td><td><code>&#39;0600&#39;</code></td></tr><tr><td><code>c.NotebookApp.ssl_options</code></td><td>为tornado HTTPServer提供SSL选项。</td><td><code>&#123;&#125;</code></td></tr><tr><td><code>c.NotebookApp.terminado_settings</code></td><td>terminado的配置重写。</td><td><code>&#123;&#125;</code></td></tr><tr><td><code>c.NotebookApp.terminals_enabled</code></td><td>是否启用终端。</td><td><code>True</code></td></tr><tr><td><code>c.NotebookApp.token</code></td><td>用于验证首次连接到服务器的令牌。</td><td><code>&#39;&lt;generated&gt;&#39;</code></td></tr><tr><td><code>c.NotebookApp.tornado_settings</code></td><td>重写用于Jupyter notebook的tornado.web.Application的配置。</td><td><code>&#123;&#125;</code></td></tr><tr><td><code>c.NotebookApp.trust_xheaders</code></td><td>是否信任X-Scheme/X-Forwarded-Proto和X-Real-Ip/X-Forwarded-For头。</td><td><code>False</code></td></tr><tr><td><code>c.NotebookApp.use_redirect_file</code></td><td>是否通过重定向文件禁用启动浏览器。</td><td><code>True</code></td></tr><tr><td><code>c.NotebookApp.webapp_settings</code></td><td>DEPRECATED，使用<code>tornado_settings</code>。</td><td><code>&#123;&#125;</code></td></tr><tr><td><code>c.NotebookApp.webbrowser_open_new</code></td><td>启动时在浏览器中打开notebook的位置。</td><td><code>2</code></td></tr><tr><td><code>c.NotebookApp.websocket_compression_options</code></td><td>设置websocket连接的tornado压缩选项。</td><td><code>None</code></td></tr><tr><td><code>c.NotebookApp.websocket_url</code></td><td>websocket的基本URL。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.ConnectionFileMixin.connection_file</code></td><td>存储连接信息的JSON文件。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.ConnectionFileMixin.control_port</code></td><td>控制（ROUTER）端口。</td><td><code>0</code></td></tr><tr><td><code>c.ConnectionFileMixin.hb_port</code></td><td>心跳端口。</td><td><code>0</code></td></tr><tr><td><code>c.ConnectionFileMixin.iopub_port</code></td><td>iopub（PUB）端口。</td><td><code>0</code></td></tr><tr><td><code>c.ConnectionFileMixin.ip</code></td><td>内核的IP地址。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.ConnectionFileMixin.shell_port</code></td><td>shell（ROUTER）端口。</td><td><code>0</code></td></tr><tr><td><code>c.ConnectionFileMixin.stdin_port</code></td><td>stdin（ROUTER）端口。</td><td><code>0</code></td></tr><tr><td><code>c.ConnectionFileMixin.transport</code></td><td>传输协议（tcp或ipc）。</td><td><code>&#39;tcp&#39;</code></td></tr><tr><td><code>c.KernelManager.autorestart</code></td><td>内核崩溃后是否自动重启。</td><td><code>True</code></td></tr><tr><td><code>c.KernelManager.shutdown_wait_time</code></td><td>等待内核终止的时间（秒）。</td><td><code>5.0</code></td></tr><tr><td><code>c.Session.buffer_threshold</code></td><td>对象缓冲区超过此阈值时避免pickle。</td><td><code>1024</code></td></tr><tr><td><code>c.Session.check_pid</code></td><td>是否检查PID以保护fork后的调用。</td><td><code>True</code></td></tr><tr><td><code>c.Session.copy_threshold</code></td><td>缓冲区超过此阈值时发送而不复制。</td><td><code>65536</code></td></tr><tr><td><code>c.Session.debug</code></td><td>在Session中启用调试输出。</td><td><code>False</code></td></tr><tr><td><code>c.Session.digest_history_size</code></td><td>记住的摘要最大数量。</td><td><code>65536</code></td></tr><tr><td><code>c.Session.item_threshold</code></td><td>自定义序列化时检查容器大小的阈值。</td><td><code>64</code></td></tr><tr><td><code>c.Session.key</code></td><td>执行消息签名的密钥。</td><td><code>b&#39;&#39;</code></td></tr><tr><td><code>c.Session.keyfile</code></td><td>包含执行密钥的文件的路径。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.Session.metadata</code></td><td>默认顶层元数据字典，用于每个消息。</td><td><code>&#123;&#125;</code></td></tr><tr><td><code>c.Session.packer</code></td><td>用于序列化消息的打包器名称。</td><td><code>&#39;json&#39;</code></td></tr><tr><td><code>c.Session.session</code></td><td>此会话的UUID。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.Session.signature_scheme</code></td><td>用于构造消息签名的摘要方案。</td><td><code>&#39;hmac-sha256&#39;</code></td></tr><tr><td><code>c.Session.unpacker</code></td><td>用于反序列化消息的解包器名称。</td><td><code>&#39;json&#39;</code></td></tr><tr><td><code>c.Session.username</code></td><td>会话的用户名。</td><td><code>&#39;username&#39;</code></td></tr><tr><td><code>c.MultiKernelManager.default_kernel_name</code></td><td>启动的默认内核名称。</td><td><code>&#39;python3&#39;</code></td></tr><tr><td><code>c.MultiKernelManager.kernel_manager_class</code></td><td>内核管理器类。</td><td><code>&#39;jupyter_client.ioloop.IOLoopKernelManager&#39;</code></td></tr><tr><td><code>c.MultiKernelManager.shared_context</code></td><td>是否共享一个zmq.Context与所有内核通信。</td><td><code>True</code></td></tr><tr><td><code>c.MappingKernelManager.allowed_message_types</code></td><td>允许的内核消息类型白名单。</td><td><code>[]</code></td></tr><tr><td><code>c.MappingKernelManager.buffer_offline_messages</code></td><td>是否缓冲断线前端的内核消息。</td><td><code>True</code></td></tr><tr><td><code>c.MappingKernelManager.cull_busy</code></td><td>是否考虑清理忙碌的内核。</td><td><code>False</code></td></tr><tr><td><code>c.MappingKernelManager.cull_connected</code></td><td>是否考虑清理有连接的内核。</td><td><code>False</code></td></tr><tr><td><code>c.MappingKernelManager.cull_idle_timeout</code></td><td>内核被认为空闲并准备被清理的超时时间（秒）。</td><td><code>0</code></td></tr><tr><td><code>c.MappingKernelManager.cull_interval</code></td><td>检查空闲内核超过清理超时值的间隔时间（秒）。</td><td><code>300</code></td></tr><tr><td><code>c.MappingKernelManager.kernel_info_timeout</code></td><td>放弃内核的超时时间（秒）。</td><td><code>60</code></td></tr><tr><td><code>c.KernelSpecManager.allowed_kernelspecs</code></td><td>允许的内核规格名称列表。</td><td><code>set()</code></td></tr><tr><td><code>c.KernelSpecManager.ensure_native_kernel</code></td><td>如果没有注册Python内核规格且IPython内核可用，则确保添加它。</td><td><code>True</code></td></tr><tr><td><code>c.KernelSpecManager.kernel_spec_class</code></td><td>内核规格类。</td><td><code>&#39;jupyter_client.kernelspec.KernelSpec&#39;</code></td></tr><tr><td><code>c.ContentsManager.allow_hidden</code></td><td>是否允许访问隐藏文件。</td><td><code>False</code></td></tr><tr><td><code>c.ContentsManager.pre_save_hook</code></td><td>保存前调用的Python可调用对象或其导入字符串。</td><td><code>None</code></td></tr><tr><td><code>c.FileManagerMixin.use_atomic_writing</code></td><td>是否在写入新笔记本时使用“原子写入”过程。</td><td><code>True</code></td></tr><tr><td><code>c.FileContentsManager.delete_to_trash</code></td><td>删除文件是否发送到回收站。</td><td><code>True</code></td></tr><tr><td><code>c.FileContentsManager.post_save_hook</code></td><td>保存文件后调用的Python可调用对象或其导入字符串。</td><td><code>None</code></td></tr><tr><td><code>c.FileContentsManager.pre_save_hook</code></td><td>保存前调用的Python可调用对象或其导入字符串。</td><td><code>None</code></td></tr><tr><td><code>c.FileContentsManager.save_script</code></td><td>DEPRECATED，使用<code>post_save_hook</code>代替。</td><td><code>False</code></td></tr><tr><td><code>c.NotebookNotary.algorithm</code></td><td>笔记本签名使用的哈希算法。</td><td><code>&#39;sha256&#39;</code></td></tr><tr><td><code>c.NotebookNotary.data_dir</code></td><td>存储公证秘密和数据库的存储目录。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookNotary.db_file</code></td><td>存储笔记本签名的sqlite文件。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.NotebookNotary.secret</code></td><td>笔记本签名使用的秘密。</td><td><code>b&#39;&#39;</code></td></tr><tr><td><code>c.NotebookNotary.secret_file</code></td><td>存储秘密的文件。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.AsyncMultiKernelManager.default_kernel_name</code></td><td>启动的默认内核名称。</td><td><code>&#39;python3&#39;</code></td></tr><tr><td><code>c.AsyncMultiKernelManager.kernel_manager_class</code></td><td>内核管理器类。</td><td><code>&#39;jupyter_client.ioloop.AsyncIOLoopKernelManager&#39;</code></td></tr><tr><td><code>c.AsyncMultiKernelManager.shared_context</code></td><td>是否共享一个zmq.Context与所有内核通信。</td><td><code>True</code></td></tr><tr><td><code>c.AsyncMultiKernelManager.use_pending_kernels</code></td><td>是否在进程启动之前使内核可用。</td><td><code>False</code></td></tr><tr><td><code>c.AsyncMappingKernelManager.allowed_message_types</code></td><td>允许的内核消息类型白名单。</td><td><code>[]</code></td></tr><tr><td><code>c.AsyncMappingKernelManager.buffer_offline_messages</code></td><td>是否缓冲断线前端的内核消息。</td><td><code>True</code></td></tr><tr><td><code>c.AsyncMappingKernelManager.cull_busy</code></td><td>是否考虑清理忙碌的内核。</td><td><code>False</code></td></tr><tr><td><code>c.AsyncMappingKernelManager.cull_connected</code></td><td>是否考虑清理有连接的内核。</td><td><code>False</code></td></tr><tr><td><code>c.AsyncMappingKernelManager.cull_idle_timeout</code></td><td>内核被认为空闲并准备被清理的超时时间（秒）。</td><td><code>0</code></td></tr><tr><td><code>c.AsyncMappingKernelManager.cull_interval</code></td><td>检查空闲内核超过清理超时值的间隔时间（秒）。</td><td><code>300</code></td></tr><tr><td><code>c.AsyncMappingKernelManager.default_kernel_name</code></td><td>启动的默认内核名称。</td><td><code>&#39;python3&#39;</code></td></tr><tr><td><code>c.AsyncMappingKernelManager.kernel_info_timeout</code></td><td>放弃内核的超时时间（秒）。</td><td><code>60</code></td></tr><tr><td><code>c.AsyncMappingKernelManager.kernel_manager_class</code></td><td>内核管理器类。</td><td><code>&#39;jupyter_client.ioloop.AsyncIOLoopKernelManager&#39;</code></td></tr><tr><td><code>c.AsyncMappingKernelManager.root_dir</code></td><td>根目录路径。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.GatewayKernelManager.allowed_message_types</code></td><td>允许的内核消息类型白名单。</td><td><code>[]</code></td></tr><tr><td><code>c.GatewayKernelManager.buffer_offline_messages</code></td><td>是否缓冲断线前端的内核消息。</td><td><code>True</code></td></tr><tr><td><code>c.GatewayKernelManager.cull_busy</code></td><td>是否考虑清理忙碌的内核。</td><td><code>False</code></td></tr><tr><td><code>c.GatewayKernelManager.cull_connected</code></td><td>是否考虑清理有连接的内核。</td><td><code>False</code></td></tr><tr><td><code>c.GatewayKernelManager.cull_idle_timeout</code></td><td>内核被认为空闲并准备被清理的超时时间（秒）。</td><td><code>0</code></td></tr><tr><td><code>c.GatewayKernelManager.cull_interval</code></td><td>检查空闲内核超过清理超时值的间隔时间（秒）。</td><td><code>300</code></td></tr><tr><td><code>c.GatewayKernelManager.default_kernel_name</code></td><td>启动的默认内核名称。</td><td><code>&#39;python3&#39;</code></td></tr><tr><td><code>c.GatewayKernelManager.kernel_info_timeout</code></td><td>放弃内核的超时时间（秒）。</td><td><code>60</code></td></tr><tr><td><code>c.GatewayKernelManager.kernel_manager_class</code></td><td>内核管理器类。</td><td><code>&#39;jupyter_client.ioloop.AsyncIOLoopKernelManager&#39;</code></td></tr><tr><td><code>c.GatewayKernelManager.root_dir</code></td><td>根目录路径。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.GatewayKernelManager.shared_context</code></td><td>是否共享一个zmq.Context与所有内核通信。</td><td><code>True</code></td></tr><tr><td><code>c.GatewayKernelManager.use_pending_kernels</code></td><td>是否在进程启动之前使内核可用。</td><td><code>False</code></td></tr><tr><td><code>c.GatewayKernelSpecManager.allowed_kernelspecs</code></td><td>允许的内核规格名称列表。</td><td><code>set()</code></td></tr><tr><td><code>c.GatewayKernelSpecManager.ensure_native_kernel</code></td><td>如果没有注册Python内核规格且IPython内核可用，则确保添加它。</td><td><code>True</code></td></tr><tr><td><code>c.GatewayKernelSpecManager.kernel_spec_class</code></td><td>内核规格类。</td><td><code>&#39;jupyter_client.kernelspec.KernelSpec&#39;</code></td></tr><tr><td><code>c.GatewayKernelSpecManager.whitelist</code></td><td>DEPRECATED, 使用<code>allowed_kernelspecs</code>。</td><td><code>set()</code></td></tr><tr><td><code>c.GatewayClient.auth_token</code></td><td>HTTP头中使用的授权令牌。</td><td><code>None</code></td></tr><tr><td><code>c.GatewayClient.ca_certs</code></td><td>CA证书的文件名或None以使用默认值。</td><td><code>None</code></td></tr><tr><td><code>c.GatewayClient.client_cert</code></td><td>客户端SSL证书的文件名。</td><td><code>None</code></td></tr><tr><td><code>c.GatewayClient.client_key</code></td><td>客户端SSL密钥的文件名。</td><td><code>None</code></td></tr><tr><td><code>c.GatewayClient.connect_timeout</code></td><td>与Gateway服务器建立HTTP连接的允许时间。</td><td><code>40.0</code></td></tr><tr><td><code>c.GatewayClient.env_whitelist</code></td><td>将包含在内核启动请求中的环境变量名称的逗号分隔列表。</td><td><code>&#39;&#39;</code></td></tr><tr><td><code>c.GatewayClient.gateway_retry_interval</code></td><td>首次与Gateway服务器重新连接的允许时间。</td><td><code>1.0</code></td></tr><tr><td><code>c.GatewayClient.gateway_retry_interval_max</code></td><td>与Gateway服务器重新连接重试的最大时间。</td><td><code>30.0</code></td></tr><tr><td><code>c.GatewayClient.gateway_retry_max</code></td><td>与Gateway服务器重新连接的最大重试次数。</td><td><code>5</code></td></tr><tr><td><code>c.GatewayClient.headers</code></td><td>传递给请求的额外HTTP头。</td><td><code>&#39;&#123;&#125;&#39;</code></td></tr><tr><td><code>c.GatewayClient.http_pwd</code></td><td>HTTP认证的密码。</td><td><code>None</code></td></tr><tr><td><code>c.GatewayClient.http_user</code></td><td>HTTP认证的用户名。</td><td><code>None</code></td></tr><tr><td><code>c.GatewayClient.kernels_endpoint</code></td><td>访问内核资源的Gateway API端点。</td><td><code>&#39;/api/kernels&#39;</code></td></tr><tr><td><code>c.GatewayClient.kernelspecs_endpoint</code></td><td>访问kernelspecs的Gateway API端点。</td><td><code>&#39;/api/kernelspecs&#39;</code></td></tr><tr><td><code>c.GatewayClient.kernelspecs_resource_endpoint</code></td><td>访问kernelspecs资源的Gateway端点。</td><td><code>&#39;/kernelspecs&#39;</code></td></tr><tr><td><code>c.GatewayClient.request_timeout</code></td><td>HTTP请求完成的允许时间。</td><td><code>40.0</code></td></tr><tr><td><code>c.GatewayClient.url</code></td><td>Kernel或Enterprise Gateway服务器的URL。</td><td><code>None</code></td></tr><tr><td><code>c.GatewayClient.validate_cert</code></td><td>对于HTTPS请求，确定是否验证服务器的证书。</td><td><code>True</code></td></tr><tr><td><code>c.GatewayClient.ws_url</code></td><td>Kernel或Enterprise Gateway服务器的websocket URL。</td><td><code>None</code></td></tr><tr><td><code>c.TerminalManager.cull_inactive_timeout</code></td><td>终端处于非活动状态的超时时间。</td><td><code>0</code></td></tr><tr><td><code>c.TerminalManager.cull_interval</code></td><td>检查终端超过非活动超时值的间隔时间。</td><td><code>300</code></td></tr></tbody></table><p>这个表格覆盖了Jupyter配置文件中的所有配置项，涵盖了日志设置、安全性设置、内核管理和内容管理等方面的配置。这些设置可以根据你的具体需求进行调整。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;jupyter-lab配置列表清单&quot;&gt;&lt;a href=&quot;#jupyter-lab配置列表清单&quot; class=&quot;headerlink&quot; title=&quot;jupyter lab配置列表清单&quot;&gt;&lt;/a&gt;jupyter lab配置列表清单&lt;/h2&gt;&lt;p&gt;Jupyter No</summary>
      
    
    
    
    
    <category term="jupyter" scheme="http://example.com/tags/jupyter/"/>
    
  </entry>
  
  <entry>
    <title>vim批量替换命令实践</title>
    <link href="http://example.com/2023/09/10/vim%E6%89%B9%E9%87%8F%E6%9B%BF%E6%8D%A2%E5%91%BD%E4%BB%A4%E5%AE%9E%E8%B7%B5/"/>
    <id>http://example.com/2023/09/10/vim%E6%89%B9%E9%87%8F%E6%9B%BF%E6%8D%A2%E5%91%BD%E4%BB%A4%E5%AE%9E%E8%B7%B5/</id>
    <published>2023-09-10T05:59:54.000Z</published>
    <updated>2024-09-10T06:00:54.303Z</updated>
    
    <content type="html"><![CDATA[<p>语法为 </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">:[addr]s&#x2F;源字符串&#x2F;目的字符串&#x2F;[option]</span><br></pre></td></tr></table></figure><p>全局替换命令为：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">:%s&#x2F;源字符串&#x2F;目的字符串&#x2F;g</span><br></pre></td></tr></table></figure><p><strong>[addr] 表示检索范围，省略时表示当前行。</strong></p><p>“1,20” ：表示从第1行到20行；</p><p>“%” ：表示整个文件，同“1,$”；</p><p>“. ,$” ：从当前行到文件尾；</p><p><strong>s : 表示替换操作</strong></p><p><strong>[option] : 表示操作类型</strong></p><p>g 表示全局替换; </p><p>c 表示进行确认</p><p>p 表示替代结果逐行显示（Ctrl + L恢复屏幕）;</p><p>省略option时仅对每行第一个匹配串进行替换;</p><p>如果在源字符串和目的字符串中出现特殊字符，需要用”\”转义 如 \t</p><p><strong>下面是一些例子：</strong></p><p><strong>==================================================</strong></p><p>#将That or this 换成 This or that<br>:%s/(That) or (this)/\u\2 or \l\1/</p><p>=======================================================<br>#将句尾的child换成children<br>:%s/child([ ,.;!:?])/children\1/g</p><p>=======================================================<br>#将mgi/r/abox换成mgi/r/asquare<br>:g/mg([ira])box/s//mg//my\1square/g  &lt;=&gt; :g/mg[ira]box/s/box/square/g</p><p>=======================================================<br>#将多个空格换成一个空格<br>:%s/ */ /g</p><p>=======================================================<br>#使用空格替换句号或者冒号后面的一个或者多个空格<br>:%s/([:.]) */\1 /g</p><p>=======================================================<br>#删除所有空行<br>:g/^$/d</p><p>=======================================================<br>#删除所有的空白行和空行<br>:g/^[  ][  ]*$/d</p><p>=======================================================<br>#在每行的开始插入两个空白<br>:%s/^/&gt; /</p><p>=======================================================<br>#在接下来的6行末尾加入.<br>:.,5/$/./</p><p>=======================================================<br>#颠倒文件的行序<br>:g/.*/m0O &lt;=&gt; :g/^/m0O</p><p>=======================================================<br>#寻找不是数字的开始行,并将其移到文件尾部<br>:g!/^[0-9]/m$ &lt;=&gt; g/^[^0-9]/m$</p><p>=======================================================<br>#将文件的第12到17行内容复制10词放到当前文件的尾部<br>:1,10g/^/12,17t$<br>~~~~重复次数的作用</p><p>=======================================================<br>#将chapter开始行下面的第二行的内容写道begin文件中<br>:g/^chapter/.+2w&gt;&gt;begin</p><p>=======================================================<br>:/^part2/,/^part3/g/^chapter/.+2w&gt;&gt;begin</p><p>=======================================================<br>:/^part2/,/^part3/g/^chapter/.+2w&gt;&gt;begin|+t$</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;语法为 &lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;p</summary>
      
    
    
    
    
    <category term="vim" scheme="http://example.com/tags/vim/"/>
    
  </entry>
  
  <entry>
    <title>引用上一次命令的参数</title>
    <link href="http://example.com/2023/07/10/%E5%BC%95%E7%94%A8%E4%B8%8A%E4%B8%80%E6%AC%A1%E5%91%BD%E4%BB%A4%E7%9A%84%E5%8F%82%E6%95%B0/"/>
    <id>http://example.com/2023/07/10/%E5%BC%95%E7%94%A8%E4%B8%8A%E4%B8%80%E6%AC%A1%E5%91%BD%E4%BB%A4%E7%9A%84%E5%8F%82%E6%95%B0/</id>
    <published>2023-07-10T05:56:33.000Z</published>
    <updated>2024-09-10T05:58:40.938Z</updated>
    
    <content type="html"><![CDATA[<p>在 Shell 中，可以通过特殊变量引用上一次命令的参数。以下是几种常见的方式：</p><ol><li><p><strong><code>!!</code></strong> - 重复执行上一条命令：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">!!</span><br></pre></td></tr></table></figure></li><li><p><strong><code>!$</code></strong> - 引用上一条命令的最后一个参数：</p><figure class="highlight plain"><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">echo &quot;Hello World&quot;</span><br><span class="line">echo !$</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hello World</span><br></pre></td></tr></table></figure></li><li><p><strong><code>!\*</code></strong> - 引用上一条命令的所有参数：</p><figure class="highlight plain"><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">cp file1.txt &#x2F;path&#x2F;to&#x2F;destination</span><br><span class="line">echo !*</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">file1.txt &#x2F;path&#x2F;to&#x2F;destination</span><br></pre></td></tr></table></figure></li><li><p><strong><code>!^</code></strong> - 引用上一条命令的第一个参数：</p><figure class="highlight plain"><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">mv file.txt &#x2F;new&#x2F;location&#x2F;</span><br><span class="line">echo !^</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">file.txt</span><br></pre></td></tr></table></figure></li><li><p><strong><code>!n</code></strong> - 引用第 <code>n</code> 条历史命令：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">!3</span><br></pre></td></tr></table></figure><p>执行历史命令列表中的第三条命令。</p></li></ol><p>这些技巧可以提高 Shell 操作的效率，特别是在重复操作或使用相似命令时。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在 Shell 中，可以通过特殊变量引用上一次命令的参数。以下是几种常见的方式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;!!&lt;/code&gt;&lt;/strong&gt; - 重复执行上一条命令：&lt;/p&gt;
&lt;figure class=&quot;highlight plain</summary>
      
    
    
    
    <category term="问题" scheme="http://example.com/categories/%E9%97%AE%E9%A2%98/"/>
    
    
    <category term="linux" scheme="http://example.com/tags/linux/"/>
    
  </entry>
  
  <entry>
    <title>计算机网络</title>
    <link href="http://example.com/2023/03/30/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    <id>http://example.com/2023/03/30/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/</id>
    <published>2023-03-30T09:07:39.000Z</published>
    <updated>2023-06-06T15:56:09.500Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第一部分：传输层"><a href="#第一部分：传输层" class="headerlink" title="第一部分：传输层"></a>第一部分：传输层</h1><h2 id="1-说一下OSI七层模型-TCP-IP四层模型-五层协议"><a href="#1-说一下OSI七层模型-TCP-IP四层模型-五层协议" class="headerlink" title="1. 说一下OSI七层模型 TCP/IP四层模型 五层协议"></a>1. 说一下OSI七层模型 TCP/IP四层模型 五层协议</h2><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/assets/1536486064767.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/1536486064767-20230330174535977.png" alt="img"></a></p><h3 id="（1）五层协议"><a href="#（1）五层协议" class="headerlink" title="（1）五层协议"></a>（1）五层协议</h3><ul><li><p><strong>应用层</strong> ：提供用户接口，特指能够发起网络流量的程序，比如客户端程序：QQ，MSN，浏览器等；服务器程序：web服务器，邮件服务器，流媒体服务器等等。数据单位为报文。</p></li><li><p>运输层</p><p>：提供的是进程间的通用数据传输服务。由于应用层协议很多，定义通用的运输层协议就可以支持不断增多的应用层协议。运输层包括两种协议：</p><ul><li>传输控制协议 TCP，提供面向连接、可靠的数据传输服务，数据单位为报文段；</li><li>用户数据报协议 UDP，提供无连接、尽最大努力的数据传输服务，数据单位为用户数据报。</li><li>TCP 主要提供完整性服务，UDP 主要提供及时性服务。</li></ul></li><li><p>网络层</p><p>：为主机间提供数据传输服务，而运输层协议是为主机中的进程提供服务。网络层把运输层传递下来的报文段或者用户数据报封装成分组。（负责选择最佳路径 规划IP地址）</p><ul><li>路由器查看数据包目标IP地址，根据路由表为数据包选择路径。路由表中的类目可以人工添加（静态路由）也可以动态生成（动态路由）。</li></ul></li><li><p>数据链路层</p><p>：不同的网络类型，发送数据的机制不同，数据链路层就是将数据包封装成能够在不同的网络传输的帧。能够进行差错检验，但不纠错，监测处错误丢掉该帧。</p><ul><li>帧的开始和结束，透明传输，差错校验</li></ul></li><li><p>物理层</p><p>：物理层解决如何在连接各种计算机的传输媒体上传输数据比特流，而不是指具体的传输媒体。物理层的主要任务描述为：确定与传输媒体的接口的一些特性，即：</p><ul><li>机械特性：例接口形状，大小，引线数目</li><li>电气特性：例规定电压范围 ( -5V 到 +5V )</li><li>功能特性：例规定 -5V 表示 0，＋5V 表示 1</li><li>过程特性：也称规程特性，规定建立连接时各个相关部件的工作步骤</li></ul></li></ul><h3 id="（2）ISO七层模型中表示层和会话层功能是什么？"><a href="#（2）ISO七层模型中表示层和会话层功能是什么？" class="headerlink" title="（2）ISO七层模型中表示层和会话层功能是什么？"></a>（2）ISO七层模型中表示层和会话层功能是什么？</h3><ul><li><strong>表示层</strong> ：数据压缩、加密以及数据描述。这使得应用程序不必担心在各台主机中表示/存储的内部格式（二进制、ASCII，比如乱码）不同的问题。</li><li><strong>会话层</strong> ：建立会话，如session认证、断点续传。通信的应用程序之间建立、维护和释放面向用户的连接。通信的应用程序之间建立会话，需要传输层建立1个或多个连接。（…后台运行的木马，netstat -n）</li><li>说明：五层协议没有表示层和会话层，而是将这些功能留给应用程序开发者处理。</li></ul><h3 id="（3）数据在各层之间的传递过程"><a href="#（3）数据在各层之间的传递过程" class="headerlink" title="（3）数据在各层之间的传递过程"></a>（3）数据在各层之间的传递过程</h3><p>　　在向下的过程中，需要添加下层协议所需要的首部或者尾部，而在向上的过程中不断拆开首部和尾部。</p><ol><li>路由器只有下面三层协议，因为路由器位于网络核心中，不需要为进程或者应用程序提供服务，因此也就不需要运输层和应用层。</li><li>交换机只有下面两层协议</li></ol><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/transfer.jpg"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/transfer-20230330174534406.jpg" alt="img"></a></p><h3 id="（4）TCP-IP四层模型"><a href="#（4）TCP-IP四层模型" class="headerlink" title="（4）TCP/IP四层模型"></a>（4）TCP/IP四层模型</h3><p>它只有四层，相当于五层协议中<strong>数据链路层和物理层合并为网络接口层</strong>。</p><p>现在的 TCP/IP 体系结构不严格遵循 OSI 分层概念，应用层可能会直接使用 IP 层或者网络接口层。</p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/tcp_ip_4.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/tcp_ip_4-20230330174531184.png" alt="img"></a></p><p>TCP/IP 协议族是一种沙漏形状，中间小两边大，IP 协议在其中占用举足轻重的地位。</p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/tcp_ip_protocol_family.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/tcp_ip_protocol_family-20230330174538710.png" alt="img"></a></p><p>参考资料：</p><ul><li><a href="https://arch-long.cn/articles/network/OSI%E6%A8%A1%E5%9E%8BTCPIP%E5%8D%8F%E8%AE%AE%E6%A0%88.html">OSI模型、TCP/IP协议栈</a></li></ul><h2 id="2-TCP报头格式和UDP报头格式"><a href="#2-TCP报头格式和UDP报头格式" class="headerlink" title="2. TCP报头格式和UDP报头格式"></a>2. TCP报头格式和UDP报头格式</h2><p>网络层只把分组发送到目的主机，但是真正通信的并不是主机而是主机中的进程。运输层提供了进程间的逻辑通信，运输层向高层用户屏蔽了下面网络层的核心细节，使应用程序看起来像是在两个运输层实体之间有一条端到端的逻辑通信信道。</p><h3 id="（1）UDP-和-TCP-的特点"><a href="#（1）UDP-和-TCP-的特点" class="headerlink" title="（1）UDP 和 TCP 的特点"></a>（1）UDP 和 TCP 的特点</h3><ul><li><strong>用户数据报协议 UDP</strong>（User Datagram Protocol）是无连接的，尽最大可能交付，没有拥塞控制，面向报文（对于应用程序传下来的报文不合并也不拆分，只是添加 UDP 首部），支持一对一、一对多、多对一和多对多的交互通信。例如：视频传输、实时通信</li><li><strong>传输控制协议 TCP</strong>（Transmission Control Protocol）是面向连接的，提供可靠交付，有流量控制，拥塞控制，提供全双工通信，面向字节流（把应用层传下来的报文看成字节流，把字节流组织成大小不等的数据块），每一条 TCP 连接只能是点对点的（一对一）。</li></ul><h3 id="（2）UDP-首部格式"><a href="#（2）UDP-首部格式" class="headerlink" title="（2）UDP 首部格式"></a>（2）UDP 首部格式</h3><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/udp-head2.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/udp-head2-20230330174534752.png" alt="img"></a></p><p>首部字段只有 8 个字节，包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。</p><h3 id="（3）TCP-首部格式"><a href="#（3）TCP-首部格式" class="headerlink" title="（3）TCP 首部格式"></a>（3）TCP 首部格式</h3><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/tcp-head.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/tcp-head-20230330174534717.png" alt="img"></a></p><ul><li><strong>序号 seq</strong> ：用于对字节流进行编号，例如序号为 301，表示第一个字节的编号为 301，如果携带的数据长度为 100 字节，那么下一个报文段的序号应为 401。[301,400]为序号301的数据长度，下一个则为401</li><li><strong>确认号 ack</strong> ：期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段，序号为 501，携带的数据长度为 200 字节，因此 B 期望下一个报文段的序号为 701，B 发送给 A 的确认报文段中确认号就为 701。</li><li><strong>数据偏移</strong> ：指的是数据部分距离报文段起始处的偏移量，实际上指的是首部的长度。</li><li><strong>确认 ACK</strong> ：当 ACK=1 时确认号字段有效，否则无效。TCP 规定，在连接建立后所有传送的报文段都必须把 ACK 置 1。</li><li><strong>同步 SYN</strong> ：在连接建立时用来同步序号。当 SYN=1，ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接，则响应报文中 SYN=1，ACK=1。</li><li><strong>终止 FIN</strong> ：用来释放一个连接，当 FIN=1 时，表示此报文段的发送方的数据已发送完毕，并要求释放连接。</li><li><strong>窗口</strong> ：窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制，是因为接收方的数据缓存空间是有限的。</li></ul><p>参考资料：</p><ul><li><a href="https://samanthachen.github.io/2016/08/15/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C3/">计算机网络-运输层-笔记 | SamanthaChen’s Blog</a></li></ul><h2 id="3-TCP三次握手？那四次挥手呢？如何保障可靠传输"><a href="#3-TCP三次握手？那四次挥手呢？如何保障可靠传输" class="headerlink" title="3. TCP三次握手？那四次挥手呢？如何保障可靠传输"></a>3. TCP三次握手？那四次挥手呢？如何保障可靠传输</h2><h3 id="（1）三次握手"><a href="#（1）三次握手" class="headerlink" title="（1）三次握手"></a>（1）三次握手</h3><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/tcp-3.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/tcp-3-20230330174531806.png" alt="img"></a></p><p><strong>假设 A 为客户端，B 为服务器端。</strong></p><ul><li>首先 B 处于 LISTEN（监听）状态，等待客户的连接请求。</li><li>A 向 B 发送连接请求报文段，SYN=1，ACK=0，选择一个初始的序号 seq = x。</li><li>B 收到连接请求报文段，如果同意建立连接，则向 A 发送连接确认报文段，SYN=1，ACK=1，确认号为 x+1，同时也选择一个初始的序号 seq = y。</li><li>A 收到 B 的连接确认报文段后，还要向 B 发出确认，确认号为 ack = y+1，序号为 seq = x+1。</li><li>A 的 TCP 通知上层应用进程，连接已经建立。</li><li>B 收到 A 的确认后，连接建立。</li><li>B 的 TCP 收到主机 A 的确认后，也通知其上层应用进程：TCP 连接已经建立。</li></ul><h3 id="（2）为什么TCP连接需要三次握手，两次不可以吗，为什么"><a href="#（2）为什么TCP连接需要三次握手，两次不可以吗，为什么" class="headerlink" title="（2）为什么TCP连接需要三次握手，两次不可以吗，为什么"></a>（2）为什么TCP连接需要三次握手，两次不可以吗，为什么</h3><p><strong>为了防止已失效的连接请求报文段突然又传送到了服务端，占用服务器资源。 （假设主机A为客户端，主机B为服务器端）</strong></p><p>现假定出现一种异常情况，即A发出的第一个连接请求报文段并没有丢失，而是在某些网络节点长时间滞留了，以致延误到连接释放以后的某个时间才到B。本来这是一个已失效的报文段。但是B收到此失效的连接请求报文段后，就误认为是A有发出一次新的连接请求。于是就向A发出确认报文段，同意建立连接。假定不采用三次握手，那么只要B发出确认，新的连接就建立了。</p><p>由于现在A并没有发出建立连接的请求，因此不会理睬B的确认，也不会向B发送数据。但B却以为新的运输连接已经建立了，并一直等待A发来数据。B的许多资源就这样白白浪费了。</p><p>采用三次握手的办法可以防止上述现象的发生。例如在刚才的情况下，A不会向B的确认发出确认。B由于收不到确认，就知道A并没有要求建立连接。</p><h3 id="（3）四次挥手"><a href="#（3）四次挥手" class="headerlink" title="（3）四次挥手"></a>（3）四次挥手</h3><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/tcp-4.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/tcp-4-20230330174542717.png" alt="img"></a></p><p>数据传输结束后，通信的双方都可释放连接。现在 A 的应用进程先向其 TCP 发出连接释放报文段，并停止再发送数据，主动关闭 TCP连接。</p><ul><li>A 把连接释放报文段首部的 FIN = 1，其序号 seq = u，等待 B 的确认。</li><li>B 发出确认，确认号 ack = u+1，而这个报文段自己的序号 seq = v。（TCP 服务器进程通知高层应用进程）</li><li>从 A 到 B 这个方向的连接就释放了，TCP 连接处于半关闭状态。A 不能向 B 发送数据；B 若发送数据，A 仍要接收。</li><li>当 B 不再需要连接时，发送连接释放请求报文段，FIN=1。</li><li>A 收到后发出确认，进入 TIME-WAIT 状态，等待 2 MSL（2*2 = 4 mins）时间后释放连接。</li><li>B 收到 A 的确认后释放连接。</li></ul><h3 id="（4）四次挥手的原因"><a href="#（4）四次挥手的原因" class="headerlink" title="（4）四次挥手的原因"></a>（4）四次挥手的原因</h3><p>客户端发送了 FIN 连接释放报文之后，服务器收到了这个报文，就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据，传送完毕之后，服务器会发送 FIN 连接释放报文。</p><h3 id="（5）TIME-WAIT"><a href="#（5）TIME-WAIT" class="headerlink" title="（5）TIME_WAIT"></a>（5）TIME_WAIT</h3><blockquote><p>MSL是Maximum Segment Lifetime英文的缩写，中文可以译为 “报文最大生存时间”，他是任何报文在网络上存在的最长时间，超过这个时间报文将被丢弃。2MSL = 2*2mins = 4mins</p></blockquote><p>客户端接收到服务器端的 FIN 报文后进入此状态，此时并不是直接进入 CLOSED 状态，还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由：</p><ul><li>确保最后一个确认报文段能够到达。如果 B 没收到 A 发送来的确认报文段，那么就会重新发送连接释放请求报文段，A 等待一段时间就是为了处理这种情况的发生。</li><li>等待一段时间是为了让本连接持续时间内所产生的所有报文段都从网络中消失，使得下一个新的连接不会出现旧的连接请求报文段。</li></ul><h3 id="（6）如何保证可靠传输"><a href="#（6）如何保证可靠传输" class="headerlink" title="（6）如何保证可靠传输"></a>（6）如何保证可靠传输</h3><p><strong>【详细请查阅】：《计算机网络原理 创新教程》P356——8.4节，可靠传输</strong></p><ul><li>应用数据被分割成TCP认为最适合发送的数据块。 </li><li><strong>超时重传</strong>：当TCP发出一个段后，它启动一个定时器，等待目的端确认收到这个报文段。如果不能及时收到一个确认，将重发这个报文段。</li><li>TCP给发送的每一个包进行编号，接收方对数据包进行排序，把有序数据传送给应用层。 </li><li><strong>校验和</strong>：TCP将保持它首部和数据的检验和。这是一个端到端的检验和，目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错，TCP将丢弃这个报文段和不确认收到此报文段。</li><li>TCP的接收端会丢弃重复的数据。</li><li><strong>流量控制</strong>：TCP连接的每一方都有固定大小的缓冲空间，TCP的接收端只允许发送端发送接收端缓冲区能接纳的我数据。当接收方来不及处理发送方的数据，能提示发送方降低发送的速率，防止包丢失。TCP使用的流量控制协议是可变大小的滑动窗口协议。</li><li><strong>拥塞控制</strong>：当网络拥塞时，减少数据的发送。</li></ul><h3 id="（7）TCP连接状态？"><a href="#（7）TCP连接状态？" class="headerlink" title="（7）TCP连接状态？"></a>（7）TCP连接状态？</h3><ul><li>CLOSED：初始状态。</li><li>LISTEN：服务器处于监听状态。</li><li>SYN_SEND：客户端socket执行CONNECT连接，发送SYN包，进入此状态。</li><li>SYN_RECV：服务端收到SYN包并发送服务端SYN包，进入此状态。</li><li>ESTABLISH：表示连接建立。客户端发送了最后一个ACK包后进入此状态，服务端接收到ACK包后进入此状态。</li><li>FIN_WAIT_1：终止连接的一方（通常是客户机）发送了FIN报文后进入。等待对方FIN。</li><li>CLOSE_WAIT：（假设服务器）接收到客户机FIN包之后等待关闭的阶段。在接收到对方的FIN包之后，自然是需要立即回复ACK包的，表示已经知道断开请求。但是本方是否立即断开连接（发送FIN包）取决于是否还有数据需要发送给客户端，若有，则在发送FIN包之前均为此状态。</li><li>FIN_WAIT_2：此时是半连接状态，即有一方要求关闭连接，等待另一方关闭。客户端接收到服务器的ACK包，但并没有立即接收到服务端的FIN包，进入FIN_WAIT_2状态。</li><li>LAST_ACK：服务端发动最后的FIN包，等待最后的客户端ACK响应，进入此状态。</li><li>TIME_WAIT：客户端收到服务端的FIN包，并立即发出ACK包做最后的确认，在此之后的2MSL时间称为TIME_WAIT状态。</li></ul><h3 id="（8）TCP和HTTP"><a href="#（8）TCP和HTTP" class="headerlink" title="（8）TCP和HTTP"></a>（8）TCP和HTTP</h3><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/tcp-and-http.jpg"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/tcp-and-http-20230330174533895.jpg" alt="img"></a></p><h2 id="4-TCP连接中如果断电怎么办"><a href="#4-TCP连接中如果断电怎么办" class="headerlink" title="4. TCP连接中如果断电怎么办"></a>4. TCP连接中如果断电怎么办</h2><p>TCP新手误区–心跳的意义 - CSDN博客 <a href="https://blog.csdn.net/bjrxyz/article/details/71076442">https://blog.csdn.net/bjrxyz/article/details/71076442</a></p><h2 id="5-TCP和UDP区别？如何改进TCP"><a href="#5-TCP和UDP区别？如何改进TCP" class="headerlink" title="5. TCP和UDP区别？如何改进TCP"></a>5. TCP和UDP区别？如何改进TCP</h2><ul><li>TCP和UDP区别<ul><li>UDP 是无连接的，即发送数据之前不需要建立连接。</li><li>UDP 使用尽最大努力交付，即不保证可靠交付，同时也不使用拥塞控制。</li><li>UDP 是面向报文的。UDP 没有拥塞控制，很适合多媒体通信的要求。</li><li>UDP 支持一对一、一对多、多对一和多对多的交互通信。</li><li>UDP 的首部开销小，只有 8 个字节。</li><li>TCP 是面向连接的运输层协议。</li><li>每一条 TCP 连接只能有两个端点(endpoint)，每一条 TCP 连接只能是点对点的（一对一）。</li><li>TCP 提供可靠交付的服务。</li><li>TCP 提供全双工通信。</li><li>TCP是面向字节流。  </li><li>首部最低20个字节。</li></ul></li><li>TCP加快传输效率的方法<ul><li>采取一块确认的机制</li></ul></li></ul><h2 id="6-TCP滑动窗口"><a href="#6-TCP滑动窗口" class="headerlink" title="6. TCP滑动窗口"></a>6. TCP滑动窗口</h2><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/sliding_win.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/sliding_win-20230330174543435.png" alt="img"></a></p><p>窗口是缓存的一部分，用来暂时存放字节流。发送方和接收方各有一个窗口，<strong>接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小，发送方根据这个值和其它信息设置自己的窗口大小</strong>。</p><p>发送窗口内的字节都允许被发送，接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认，那么就将发送窗口向右滑动一定距离，直到左部第一个字节不是已发送并且已确认的状态；接收窗口的滑动类似，接收窗口左部字节已经发送确认并交付主机，就向右滑动接收窗口。</p><p>接收窗口只会对窗口内最后一个按序到达的字节进行确认，例如接收窗口已经收到的字节为 {31, 34, 35}，其中 {31} 按序到达，而 {32, 33} 就不是，因此只对字节 31 进行确认。发送方得到一个字节的确认之后，就知道这个字节之前的所有字节都已经被接收。</p><p><strong>以下进行滑动窗口模拟</strong></p><p>在 TCP 中，<strong>滑动窗口是为了实现流量控制</strong>。如果对方发送数据过快，接收方就来不及接收，接收方就需要通告对方，减慢数据的发送。</p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/sliding_windows.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/sliding_windows-20230330174538027.png" alt="img"></a></p><ul><li><strong>发送方接收到了对方发来的报文 ack = 33, win = 10，知道对方收到了 33 号前的数据</strong>，现在期望接收 [33, 43) 号数据。发送方连续发送了 4 个报文段假设为 A, B, C, D, 分别携带 [33, 35), [35, 36), [36, 38), [38, 41) 号数据。</li><li>接收方接收到了报文段 A, C，但是没收到 B 和 D，也就是只收到了 [33, 35) 和 [36, 38) 号数据。接收方发送回对报文段 A 的确认：ack = 35, win = 10。</li><li>发送方收到了 ack = 35, win = 10，对方期望接收 [35, 45) 号数据。接着发送了一个报文段 E，它携带了 [41, 44) 号数据。</li><li>接收方接收到了报文段 B: [35, 36), D:[38, 41)，接收方发送对 D 的确认：ack = 41, win = 10. （这是一个累积确认）</li><li>发送方收到了 ack = 41, win = 10，对方期望接收 [41, 51) 号数据。</li><li>……</li><li>需要注意的是，接收方接收 tcp 报文的顺序是不确定的，并非是一定先收到 35 再收到 36，也可能是先收到 36，37，再收到 35.</li></ul><p>参考资料：</p><ul><li><a href="https://blog.csdn.net/q1007729991/article/details/70142341">20-TCP 协议（滑动窗口——基础） - CSDN博客</a></li><li><a href="https://blog.csdn.net/q1007729991/article/details/70143062">21-TCP 协议（滑动窗口——抓包分析） - CSDN博客</a></li><li><a href="https://coolshell.cn/articles/11609.html">TCP 的那些事儿（下） | | 酷 壳 - CoolShell</a></li></ul><h2 id="7-TCP流量控制"><a href="#7-TCP流量控制" class="headerlink" title="7. TCP流量控制"></a>7. TCP流量控制</h2><p>流量控制是为了控制发送方发送速率，保证接收方来得及接收。</p><p>接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小，从而影响发送方的发送速率。将窗口字段设置为 0，则发送方不能发送数据。</p><h2 id="8-TCP拥塞处理（Congestion-Handling）"><a href="#8-TCP拥塞处理（Congestion-Handling）" class="headerlink" title="8. TCP拥塞处理（Congestion Handling）"></a>8. TCP拥塞处理（Congestion Handling）</h2><p>拥塞控制的一般原理</p><ul><li>在某段时间，若对网络中某资源的需求超过了该资源所能提供的可用部分，网络的性能就要变坏——产生拥塞(congestion)。</li><li>出现资源拥塞的条件：对资源需求的总和 &gt; 可用资源</li><li>若网络中有许多资源同时产生拥塞，网络的性能就要明显变坏，整个网络的吞吐量将随输入负荷的增大而下降。</li></ul><p>如果网络出现拥塞，分组将会丢失，此时发送方会继续重传，从而导致网络拥塞程度更高。因此当出现拥塞时，应当控制发送方的速率。这一点和流量控制很像，但是出发点不同。流量控制是为了让接收方能来得及接收，而拥塞控制是为了降低整个网络的拥塞程度。</p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/congest1.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/congest1-20230330174538721.png" alt="img"></a></p><p>TCP 主要通过四种算法来进行拥塞控制：慢开始、拥塞避免、快重传、快恢复。</p><p>发送方需要维护一个叫做拥塞窗口（cwnd）的状态变量，注意拥塞窗口与发送方窗口的区别：拥塞窗口只是一个状态变量，实际决定发送方能发送多少数据的是发送方窗口。</p><p>为了便于讨论，做如下假设：</p><ul><li>接收方有足够大的接收缓存，因此不会发生流量控制；</li><li>虽然 TCP 的窗口基于字节，但是这里设窗口的大小单位为报文段。</li></ul><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/congest2-3.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/congest2-3-20230330174538926.png" alt="img"></a></p><h3 id="（1）慢开始与拥塞避免"><a href="#（1）慢开始与拥塞避免" class="headerlink" title="（1）慢开始与拥塞避免"></a>（1）慢开始与拥塞避免</h3><p>　　发送的最初执行慢开始，令 cwnd=1，发送方只能发送 1 个报文段；当收到确认后，将 cwnd 加倍，因此之后发送方能够发送的报文段数量为：2、4、8 …</p><p>　　注意到慢开始每个轮次都将 cwnd 加倍，这样会让 cwnd 增长速度非常快，从而使得发送方发送的速度增长速度过快，网络拥塞的可能也就更高。设置一个慢启动阈值 ssthresh，当 cwnd &gt;= ssthresh 时，进入拥塞避免，每个轮次只将 cwnd 加 1。</p><p>　　如果出现了超时，则令 ssthresh = cwnd/2，然后重新执行慢开始。</p><h3 id="（2）快重传与快恢复"><a href="#（2）快重传与快恢复" class="headerlink" title="（2）快重传与快恢复"></a>（2）快重传与快恢复</h3><p>　　在接收方，要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2，此时收到 M4，应当发送对 M2 的确认。</p><p>　　在发送方，如果收到三个重复确认，那么可以知道下一个报文段丢失，此时执行快重传，立即重传下一个报文段。例如收到三个 M2，则 M3 丢失，立即重传 M3。</p><p>　　在这种情况下，只是丢失个别报文段，而不是网络拥塞。因此执行快恢复，令 ssthresh = cwnd/2 ，cwnd = ssthresh，注意到此时直接进入拥塞避免。慢开始和快恢复的快慢指的是 cwnd 的设定值，而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1，而快恢复 cwnd 设定为 ssthresh。</p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/congest3-2.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/congest3-2-20230330174538971.png" alt="img"></a></p><h3 id="（3）发送窗口的上限值"><a href="#（3）发送窗口的上限值" class="headerlink" title="（3）发送窗口的上限值"></a>（3）发送窗口的上限值</h3><p>　　发送方的发送窗口的上限值应当取为接收方窗口 rwnd 和拥塞窗口 cwnd 这两个变量中较小的一个，即应按以下公式确定：</p><ul><li>发送窗口的上限值 = Min {rwnd, cwnd}<ul><li>当 rwnd &lt; cwnd 时，是接收方的接收能力限制发送窗口的最大值。</li><li>当 cwnd &lt; rwnd 时，则是网络的拥塞限制发送窗口的最大值。</li></ul></li></ul><h2 id="9-如何区分流量控制和拥塞控制"><a href="#9-如何区分流量控制和拥塞控制" class="headerlink" title="9. 如何区分流量控制和拥塞控制"></a>9. 如何区分流量控制和拥塞控制</h2><ul><li>拥塞控制所要做的都有一个前提，就是网络能够承受现有的网络负荷。</li><li>拥塞控制是一个全局性的过程，涉及到所有的主机、所有的路由器，以及与降低网络传输性能有关的所有因素。</li><li>流量控制往往指在给定的发送端和接收端之间的点对点通信量的控制。</li><li>流量控制所要做的就是抑制发送端发送数据的速率，以便使接收端来得及接收。</li><li>流量控制属于通信双方协商；拥塞控制涉及通信链路全局。</li><li>流量控制需要通信双方各维护一个发送窗、一个接收窗，对任意一方，接收窗大小由自身决定，发送窗大小由接收方响应的TCP报文段中窗口值确定；拥塞控制的拥塞窗口大小变化由试探性发送一定数据量数据探查网络状况后而自适应调整。</li><li>实际最终发送窗口 = min{流控发送窗口，拥塞窗口}。</li></ul><h2 id="10-解释RTO，RTT和超时重传"><a href="#10-解释RTO，RTT和超时重传" class="headerlink" title="10. 解释RTO，RTT和超时重传"></a>10. 解释RTO，RTT和超时重传</h2><ul><li><p>超时重传</p><p>：发送端发送报文后若长时间未收到确认的报文则需要重发该报文。可能有以下几种情况：</p><ul><li>发送的数据没能到达接收端，所以对方没有响应。</li><li>接收端接收到数据，但是ACK报文在返回过程中丢失。</li><li>接收端拒绝或丢弃数据。</li></ul></li><li><p>RTO</p><p>：从上一次发送数据，因为长期没有收到ACK响应，到下一次重发之间的时间。就是重传间隔。</p><ul><li>通常每次重传RTO是前一次重传间隔的两倍，计量单位通常是RTT。例：1RTT，2RTT，4RTT，8RTT……</li><li>重传次数到达上限之后停止重传。</li></ul></li><li><p><strong>RTT</strong>：数据从发送到接收到对方响应之间的时间间隔，即数据报在网络中一个往返用时。大小不稳定。</p></li></ul><h2 id="11-停止等待和超时重传"><a href="#11-停止等待和超时重传" class="headerlink" title="11. 停止等待和超时重传"></a>11. 停止等待和超时重传</h2><h2 id="12-从输入网址到获得页面的网络请求过程"><a href="#12-从输入网址到获得页面的网络请求过程" class="headerlink" title="12. 从输入网址到获得页面的网络请求过程"></a>12. 从输入网址到获得页面的网络请求过程</h2><ul><li><p>查询 DNS</p><ul><li>浏览器搜索自身的DNS缓存</li><li>搜索操作系统的DNS缓存，本地host文件查询</li><li>如果 DNS 服务器和我们的主机在同一个子网内，系统会按照下面的 ARP 过程对 DNS 服务器进行 ARP查询</li><li>如果 DNS 服务器和我们的主机在不同的子网，系统会按照下面的 ARP 过程对默认网关进行查询</li></ul></li><li><p>浏览器获得域名对应的IP地址后，发起HTTP三次握手</p></li><li><p>TCP/IP连接建立起来后，浏览器就可以向服务器发送HTTP请求了</p></li><li><p>TLS 握手</p><ul><li>客户端发送一个 <code>ClientHello</code> 消息到服务器端，消息中同时包含了它的 Transport Layer Security (TLS) 版本，可用的加密算法和压缩算法。</li><li>服务器端向客户端返回一个 <code>ServerHello</code> 消息，消息中包含了服务器端的TLS版本，服务器所选择的加密和压缩算法，以及数字证书认证机构（Certificate Authority，缩写 CA）签发的服务器公开证书，证书中包含了公钥。客户端会使用这个公钥加密接下来的握手过程，直到协商生成一个新的对称密钥</li><li>客户端根据自己的信任CA列表，验证服务器端的证书是否可信。如果认为可信，客户端会生成一串伪随机数，使用服务器的公钥加密它。这串随机数会被用于生成新的对称密钥</li><li>服务器端使用自己的私钥解密上面提到的随机数，然后使用这串随机数生成自己的对称主密钥</li><li>客户端发送一个 <code>Finished</code> 消息给服务器端，使用对称密钥加密这次通讯的一个散列值</li><li>服务器端生成自己的 hash 值，然后解密客户端发送来的信息，检查这两个值是否对应。如果对应，就向客户端发送一个 <code>Finished</code> 消息，也使用协商好的对称密钥加密</li><li>从现在开始，接下来整个 TLS 会话都使用对称秘钥进行加密，传输应用层（HTTP）内容</li></ul></li><li><p>HTTP 服务器请求处理</p><p>HTTPD(HTTP Daemon)在服务器端处理请求/响应。最常见的 HTTPD 有 Linux 上常用的 Apache 和 nginx，以及 Windows 上的 IIS。</p><ul><li><p>HTTPD 接收请求</p></li><li><ul><li><p>服务器把请求拆分为以下几个参数：</p><p>HTTP 请求方法(<code>GET</code>, <code>POST</code>, <code>HEAD</code>, <code>PUT</code>, <code>DELETE</code>, <code>CONNECT</code>, <code>OPTIONS</code>, 或者 <code>TRACE</code>)。直接在地址栏中输入 URL 这种情况下，使用的是 GET 方法域名：google.com请求路径/页面：/ (我们没有请求google.com下的指定的页面，因此 / 是默认的路径)</p></li></ul></li><li><p>服务器验证其上已经配置了 google.com 的虚拟主机</p></li><li><p>服务器验证 google.com 接受 GET 方法</p></li><li><p>服务器验证该用户可以使用 GET 方法(根据 IP 地址，身份信息等)</p></li><li><p>如果服务器安装了 URL 重写模块（例如 Apache 的 mod_rewrite 和 IIS 的 URL Rewrite），服务器会尝试匹配重写规则，如果匹配上的话，服务器会按照规则重写这个请求</p></li><li><p>服务器根据请求信息获取相应的响应内容，这种情况下由于访问路径是 “/“ ,会访问首页文件（你可以重写这个规则，但是这个是最常用的）。</p></li><li><p>服务器会使用指定的处理程序分析处理这个文件，假如 Google 使用 PHP，服务器会使用 PHP 解析 index 文件，并捕获输出，把 PHP 的输出结果返回给请求者</p></li></ul></li><li><p>服务器接受到这个请求，根据路径参数，经过后端的一些处理生成HTML页面代码返回给浏览器</p></li><li><p>浏览器拿到完整的HTML页面代码开始解析和渲染，如果遇到引用的外部<a href="http://lib.csdn.net/base/javascript">js</a>，CSS,图片等静态资源，它们同样也是一个个的HTTP请求，都需要经过上面的步骤</p></li><li><p>浏览器根据拿到的资源对页面进行渲染，最终把一个完整的页面呈现给用户</p></li></ul><p>超详细版本请转向阅读：<a href="https://github.com/skyline75489/what-happens-when-zh_CN">what-happens-when-zh_CN</a></p><h1 id="第二部分：应用层（HTTP）"><a href="#第二部分：应用层（HTTP）" class="headerlink" title="第二部分：应用层（HTTP）"></a>第二部分：应用层（HTTP）</h1><h2 id="1-URL、URI、URN区别"><a href="#1-URL、URI、URN区别" class="headerlink" title="1. URL、URI、URN区别"></a>1. URL、URI、URN区别</h2><ul><li><p>URI（Uniform Resource Identifier，统一资源标识符）</p><p>web服务器资源的名字，例如： index.html</p></li><li><p>URL（Uniform Resource Locator，统一资源定位符）</p></li><li><p>URN（Uniform Resource Name，统一资源名称），例如 urn:isbn:0-486-27557-4。</p></li></ul><p>URI 包含 URL 和 URN，目前 WEB 只有 URL 比较流行，所以见到的基本都是 URL。</p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/url_uri_urn.jpg"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/url_uri_urn-20230330174541682.jpg" alt="img"></a></p><h2 id="2-HTTP的请求和响应报文"><a href="#2-HTTP的请求和响应报文" class="headerlink" title="2. HTTP的请求和响应报文"></a>2. HTTP的请求和响应报文</h2><h3 id="（1）请求报文"><a href="#（1）请求报文" class="headerlink" title="（1）请求报文"></a>（1）请求报文</h3><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/HTTP_RequestMessageExample.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/HTTP_RequestMessageExample-20230330174539012.png" alt="img"></a></p><p><strong>GET请求</strong></p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/http_request_get.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/http_request_get-20230330174536424.png" alt="img"></a></p><p><strong>POST请求</strong></p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/http_request_post.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/http_request_post-20230330174540679.png" alt="img"></a></p><h3 id="（2）响应报文"><a href="#（2）响应报文" class="headerlink" title="（2）响应报文"></a>（2）响应报文</h3><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/HTTP_ResponseMessageExample.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/HTTP_ResponseMessageExample-20230330174546258.png" alt="img"></a></p><p><strong>200响应</strong></p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/http_response_200.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/http_response_200-20230330174540046.png" alt="img"></a></p><p><strong>404响应</strong></p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/http_response_400.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/http_response_400-20230330174539583.png" alt="img"></a></p><p>参考资料：</p><ul><li><a href="https://juejin.im/post/5a4f782c5188257326469d7c">这一次,让我们再深入一点 - HTTP报文 - 掘金</a></li></ul><h2 id="3-HTTP状态"><a href="#3-HTTP状态" class="headerlink" title="3. HTTP状态"></a>3. HTTP状态</h2><p>服务器返回的 <strong>响应报文</strong> 中第一行为状态行，包含了状态码以及原因短语，用来告知客户端请求的结果。</p><table><thead><tr><th>状态码</th><th>类别</th><th>原因短语</th></tr></thead><tbody><tr><td>1XX</td><td>Informational（信息性状态码）</td><td>接收的请求正在处理</td></tr><tr><td>2XX</td><td>Success（成功状态码）</td><td>请求正常处理完毕</td></tr><tr><td>3XX</td><td>Redirection（重定向状态码）</td><td>需要进行附加操作以完成请求</td></tr><tr><td>4XX</td><td>Client Error（客户端错误状态码）</td><td>服务器无法处理请求</td></tr><tr><td>5XX</td><td>Server Error（服务器错误状态码）</td><td>服务器处理请求出错</td></tr></tbody></table><h3 id="（1）1XX-信息"><a href="#（1）1XX-信息" class="headerlink" title="（1）1XX 信息"></a>（1）1XX 信息</h3><ul><li><strong>100 Continue</strong> ：表明到目前为止都很正常，客户端可以继续发送请求或者忽略这个响应。</li></ul><h3 id="（2）2XX-成功"><a href="#（2）2XX-成功" class="headerlink" title="（2）2XX 成功"></a>（2）2XX 成功</h3><ul><li><strong>200 OK</strong></li><li><strong>204 No Content</strong> ：请求已经成功处理，但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息，而不需要返回数据时使用。</li><li><strong>206 Partial Content</strong> ：表示客户端进行了范围请求。响应报文包含由 Content-Range 指定范围的实体内容。</li></ul><h3 id="（3）3XX-重定向"><a href="#（3）3XX-重定向" class="headerlink" title="（3）3XX 重定向"></a>（3）3XX 重定向</h3><ul><li><strong>301 Moved Permanently</strong> ：永久性重定向</li><li><strong>302 Found</strong> ：临时性重定向</li><li><strong>303 See Other</strong> ：和 302 有着相同的功能，但是 303 明确要求客户端应该采用 GET 方法获取资源。</li><li>注：虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法，但是大多数浏览器都会在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。</li><li><strong>304 Not Modified</strong> ：如果请求报文首部包含一些条件，例如：If-Match，If-Modified-Since，If-None-Match，If-Range，If-Unmodified-Since，如果不满足条件，则服务器会返回 304 状态码。</li><li><strong>307 Temporary Redirect</strong> ：临时重定向，与 302 的含义类似，但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。</li></ul><h3 id="（4）4XX-客户端错误"><a href="#（4）4XX-客户端错误" class="headerlink" title="（4）4XX 客户端错误"></a>（4）4XX 客户端错误</h3><ul><li><strong>400 Bad Request</strong> ：请求报文中存在语法错误。</li><li><strong>401 Unauthorized</strong> ：该状态码表示发送的请求需要有认证信息（BASIC 认证、DIGEST 认证）。如果之前已进行过一次请求，则表示用户认证失败。</li><li><strong>403 Forbidden</strong> ：请求被拒绝，服务器端没有必要给出拒绝的详细理由。</li><li><strong>404 Not Found</strong></li></ul><h3 id="（5）5XX-服务器错误"><a href="#（5）5XX-服务器错误" class="headerlink" title="（5）5XX 服务器错误"></a>（5）5XX 服务器错误</h3><ul><li><strong>500 Internal Server Error</strong> ：服务器正在执行请求时发生错误。</li><li><strong>503 Service Unavailable</strong> ：服务器暂时处于超负载或正在进行停机维护，现在无法处理请求。</li></ul><h2 id="4-HTTP方法"><a href="#4-HTTP方法" class="headerlink" title="4. HTTP方法"></a>4. HTTP方法</h2><p>客户端发送的 <strong>请求报文</strong> 第一行为请求行，包含了方法字段。</p><h3 id="（1）GET"><a href="#（1）GET" class="headerlink" title="（1）GET"></a>（1）GET</h3><blockquote><p>获取资源</p></blockquote><p>当前网络请求中，绝大部分使用的是 GET 方法。</p><h3 id="（2）HEAD"><a href="#（2）HEAD" class="headerlink" title="（2）HEAD"></a>（2）HEAD</h3><blockquote><p>获取报文首部</p></blockquote><p>和 GET 方法一样，但是不返回报文实体主体部分。</p><p>主要用于确认 URL 的有效性以及资源更新的日期时间等。</p><h3 id="（3）POST"><a href="#（3）POST" class="headerlink" title="（3）POST"></a>（3）POST</h3><blockquote><p>传输实体主体</p></blockquote><p>POST 主要用来传输数据，而 GET 主要用来获取资源。</p><p>更多 POST 与 GET 的比较请见第八章。</p><h3 id="（4）PUT"><a href="#（4）PUT" class="headerlink" title="（4）PUT"></a>（4）PUT</h3><blockquote><p>上传文件</p></blockquote><p>由于自身不带验证机制，任何人都可以上传文件，因此存在安全性问题，一般不使用该方法。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">PUT &#x2F;new.html HTTP&#x2F;1.1</span><br><span class="line">Host: example.com</span><br><span class="line">Content-type: text&#x2F;html</span><br><span class="line">Content-length: 16</span><br><span class="line"></span><br><span class="line">&lt;p&gt;New File&lt;&#x2F;p&gt;</span><br></pre></td></tr></table></figure><h3 id="（5）PATCH"><a href="#（5）PATCH" class="headerlink" title="（5）PATCH"></a>（5）PATCH</h3><blockquote><p>对资源进行部分修改</p></blockquote><p>PUT 也可以用于修改资源，但是只能完全替代原始资源，PATCH 允许部分修改。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">PATCH &#x2F;file.txt HTTP&#x2F;1.1</span><br><span class="line">Host: www.example.com</span><br><span class="line">Content-Type: application&#x2F;example</span><br><span class="line">If-Match: &quot;e0023aa4e&quot;</span><br><span class="line">Content-Length: 100</span><br><span class="line"></span><br><span class="line">[description of changes]</span><br></pre></td></tr></table></figure><h3 id="（6）DELETE"><a href="#（6）DELETE" class="headerlink" title="（6）DELETE"></a>（6）DELETE</h3><blockquote><p>删除文件</p></blockquote><p>与 PUT 功能相反，并且同样不带验证机制。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DELETE &#x2F;file.html HTTP&#x2F;1.1</span><br></pre></td></tr></table></figure><h3 id="（7）OPTIONS"><a href="#（7）OPTIONS" class="headerlink" title="（7）OPTIONS"></a>（7）OPTIONS</h3><blockquote><p>查询支持的方法</p></blockquote><p>查询指定的 URL 能够支持的方法。</p><p>会返回 Allow: GET, POST, HEAD, OPTIONS 这样的内容。</p><h3 id="（8）CONNECT"><a href="#（8）CONNECT" class="headerlink" title="（8）CONNECT"></a>（8）CONNECT</h3><blockquote><p>要求在与代理服务器通信时建立隧道</p></blockquote><p>使用 SSL（Secure Sockets Layer，安全套接层）和 TLS（Transport Layer Security，传输层安全）协议把通信内容加密后经网络隧道传输。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CONNECT www.example.com:443 HTTP&#x2F;1.1</span><br></pre></td></tr></table></figure><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/http_connect.jpg"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/http_connect-20230330174543913.jpg" alt="img"></a></p><h3 id="（9）TRACE"><a href="#（9）TRACE" class="headerlink" title="（9）TRACE"></a>（9）TRACE</h3><blockquote><p>追踪路径</p></blockquote><p>服务器会将通信路径返回给客户端。</p><p>发送请求时，在 Max-Forwards 首部字段中填入数值，每经过一个服务器就会减 1，当数值为 0 时就停止传输。</p><p>通常不会使用 TRACE，并且它容易受到 XST 攻击（Cross-Site Tracing，跨站追踪）。</p><h2 id="5-GET和POST的区别？【阿里面经OneNote】"><a href="#5-GET和POST的区别？【阿里面经OneNote】" class="headerlink" title="5. GET和POST的区别？【阿里面经OneNote】"></a>5. GET和POST的区别？【阿里面经OneNote】</h2><blockquote><p>就下面的找几个点和面试官侃侃而谈即可，不可能全部都记得，想到什么讲什么吧</p></blockquote><ul><li>GET 被强制服务器支持</li><li>浏览器对URL的长度有限制，所以GET请求不能代替POST请求发送大量数据</li><li>GET请求发送数据更小</li><li>GET请求是不安全的</li><li>GET请求是幂等的<ul><li>幂等的意味着对同一URL的多个请求应该返回同样的结果</li></ul></li><li>POST请求不能被缓存</li><li>POST请求相对GET请求是「安全」的<ul><li>这里安全的含义仅仅是指是非修改信息</li></ul></li><li>GET用于信息获取，而且是安全的和幂等的<ul><li>所谓安全的意味着该操作用于获取信息而非修改信息。换句话说，GET 请求一般不应产生副作用。就是说，它仅仅是获取资源信息，就像数据库查询一样，不会修改，增加数据，不会影响资源的状态。</li></ul></li><li>POST是用于修改服务器上的资源的请求</li><li>发送包含未知字符的用户输入时，POST 比 GET 更稳定也更可靠</li></ul><p><strong>引申：说完原理性的问题，我们从表面上来看看GET和POST的区别：</strong></p><ul><li>GET是从服务器上获取数据，POST是向服务器传送数据。 GET和 POST只是一种传递数据的方式，GET也可以把数据传到服务器，他们的本质都是发送请求和接收结果。只是组织格式和数据量上面有差别，http协议里面有介绍</li><li>GET是把参数数据队列加到提交表单的ACTION属性所指的URL中，值和表单内各个字段一一对应，在URL中可以看到。POST是通过HTTP POST机制，将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。 因为GET设计成传输小数据，而且最好是不修改服务器的数据，所以浏览器一般都在地址栏里面可以看到，但POST一般都用来传递大数据，或比较隐私的数据，所以在地址栏看不到，能不能看到不是协议规定，是浏览器规定的。</li><li>对于GET方式，服务器端用Request.QueryString获取变量的值，对于POST方式，服务器端用Request.Form获取提交的数据。 没明白，怎么获得变量和你的服务器有关，和GET或POST无关，服务器都对这些请求做了封装</li><li>GET传送的数据量较小，不能大于2KB。POST传送的数据量较大，一般被默认为不受限制。但理论上，IIS4中最大量为80KB，IIS5中为100KB。 POST基本没有限制，我想大家都上传过文件，都是用POST方式的。只不过要修改form里面的那个type参数</li><li>GET安全性非常低，POST安全性较高。 如果没有加密，他们安全级别都是一样的，随便一个监听器都可以把所有的数据监听到。</li></ul><h2 id="6-如何理解HTTP协议是无状态的"><a href="#6-如何理解HTTP协议是无状态的" class="headerlink" title="6. 如何理解HTTP协议是无状态的"></a>6. 如何理解HTTP协议是无状态的</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">HTTP协议是无状态的（stateless），指的是协议对于事务处理没有记忆能力，服务器不知道客户端是什么状态。也就是说，打开一个服务器上的网页和上一次打开这个服务器上的网页之间没有任何联系。HTTP是一个无状态的面向连接的协议，无状态不代表HTTP不能保持TCP连接，更不能代表HTTP使用的是UDP协议（无连接）。 </span><br><span class="line"></span><br><span class="line">缺少状态意味着如果后续处理需要前面的信息，则它必须重传，这样可能导致每次连接传送的数据量增大。另一方面，在服务器不需要先前信息时它的应答就较快。 </span><br></pre></td></tr></table></figure><h2 id="7-什么是短连接和长连接"><a href="#7-什么是短连接和长连接" class="headerlink" title="7. 什么是短连接和长连接"></a>7. 什么是短连接和长连接</h2><p>在HTTP/1.0中默认使用短连接。也就是说，客户端和服务器每进行一次HTTP操作，就建立一次连接，任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源（如JavaScript文件、图像文件、CSS文件等），每遇到这样一个Web资源，浏览器就会重新建立一个HTTP会话。</p><p>而从HTTP/1.1起，默认使用长连接，用以保持连接特性。使用长连接的HTTP协议，会在响应头加入这行代码：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Connection:keep-alive</span><br></pre></td></tr></table></figure><p>在使用长连接的情况下，当一个网页打开完成后，客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭，客户端再次访问这个服务器时，会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接，它有一个保持时间，可以在不同的服务器软件（如Apache）中设定这个时间。实现长连接需要客户端和服务端都支持长连接。</p><p>HTTP协议的长连接和短连接，实质上是TCP协议的长连接和短连接。</p><h2 id="★-微信二维码登录如何实现"><a href="#★-微信二维码登录如何实现" class="headerlink" title="★ 微信二维码登录如何实现"></a>★ 微信二维码登录如何实现</h2><h2 id="8-Cookie"><a href="#8-Cookie" class="headerlink" title="8. Cookie"></a>8. Cookie</h2><p>HTTP 协议是无状态的，主要是为了让 HTTP 协议尽可能简单，使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。</p><p>Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据，它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。它用于告知服务端两个请求是否来自同一浏览器，并保持用户的登录状态。</p><h3 id="（1）用途"><a href="#（1）用途" class="headerlink" title="（1）用途"></a>（1）用途</h3><ul><li>会话状态管理（如用户登录状态、购物车、游戏分数或其它需要记录的信息）</li><li>个性化设置（如用户自定义设置、主题等）</li><li>浏览器行为跟踪（如跟踪分析用户行为等）</li></ul><p>Cookie 曾一度用于客户端数据的存储，因为当时并没有其它合适的存储办法而作为唯一的存储手段，但现在随着现代浏览器开始支持各种各样的存储方式，Cookie 渐渐被淘汰。由于服务器指定 Cookie 后，浏览器的每次请求都会携带 Cookie 数据，会带来额外的性能开销（尤其是在移动环境下）。新的浏览器 API 已经允许开发者直接将数据存储到本地，如使用 Web storage API （本地存储和会话存储）或 IndexedDB。</p><h3 id="（2）创建过程"><a href="#（2）创建过程" class="headerlink" title="（2）创建过程"></a>（2）创建过程</h3><p>服务器发送的响应报文包含 Set-Cookie 首部字段，客户端得到响应报文后把 Cookie 内容保存到浏览器中。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">HTTP&#x2F;1.0 200 OK</span><br><span class="line">Content-type: text&#x2F;html</span><br><span class="line">Set-Cookie: yummy_cookie&#x3D;choco</span><br><span class="line">Set-Cookie: tasty_cookie&#x3D;strawberry</span><br><span class="line"></span><br><span class="line">[page content]</span><br></pre></td></tr></table></figure><p>客户端之后对同一个服务器发送请求时，会从浏览器中读出 Cookie 信息通过 Cookie 请求首部字段发送给服务器。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">GET &#x2F;sample_page.html HTTP&#x2F;1.1</span><br><span class="line">Host: www.example.org</span><br><span class="line">Cookie: yummy_cookie&#x3D;choco; tasty_cookie&#x3D;strawberry</span><br></pre></td></tr></table></figure><h3 id="（3）分类"><a href="#（3）分类" class="headerlink" title="（3）分类"></a>（3）分类</h3><ul><li>会话期 Cookie：浏览器关闭之后它会被自动删除，也就是说它仅在会话期内有效。</li><li>持久性 Cookie：指定一个特定的过期时间（Expires）或有效期（max-age）之后就成为了持久性的 Cookie。</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Set-Cookie: id&#x3D;a3fWa; Expires&#x3D;Wed, 21 Oct 2015 07:28:00 GMT;</span><br></pre></td></tr></table></figure><h3 id="（4）JavaScript-获取-Cookie"><a href="#（4）JavaScript-获取-Cookie" class="headerlink" title="（4）JavaScript 获取 Cookie"></a>（4）JavaScript 获取 Cookie</h3><p>通过 <code>Document.cookie</code> 属性可创建新的 Cookie，也可通过该属性访问非 HttpOnly 标记的 Cookie。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">document.cookie &#x3D; &quot;yummy_cookie&#x3D;choco&quot;;</span><br><span class="line">document.cookie &#x3D; &quot;tasty_cookie&#x3D;strawberry&quot;;</span><br><span class="line">console.log(document.cookie);</span><br></pre></td></tr></table></figure><h3 id="（5）Secure-和-HttpOnly"><a href="#（5）Secure-和-HttpOnly" class="headerlink" title="（5）Secure 和 HttpOnly"></a>（5）Secure 和 HttpOnly</h3><p>标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记，敏感信息也不应该通过 Cookie 传输，因为 Cookie 有其固有的不安全性，Secure 标记也无法提供确实的安全保障。</p><p>标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。因为跨域脚本 (XSS) 攻击常常使用 JavaScript 的 <code>Document.cookie</code>API 窃取用户的 Cookie 信息，因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Set-Cookie: id&#x3D;a3fWa; Expires&#x3D;Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly</span><br></pre></td></tr></table></figure><h3 id="（6）作用域"><a href="#（6）作用域" class="headerlink" title="（6）作用域"></a>（6）作用域</h3><p>Domain 标识指定了哪些主机可以接受 Cookie。如果不指定，默认为当前文档的主机（不包含子域名）。如果指定了 Domain，则一般包含子域名。例如，如果设置 Domain=mozilla.org，则 Cookie 也包含在子域名中（如 developer.mozilla.org）。</p><p>Path 标识指定了主机下的哪些路径可以接受 Cookie（该 URL 路径必须存在于请求 URL 中）。以字符 %x2F (“/“) 作为路径分隔符，子路径也会被匹配。例如，设置 Path=/docs，则以下地址都会匹配：</p><ul><li>/docs</li><li>/docs/Web/</li><li>/docs/Web/HTTP</li></ul><h2 id="9-Session"><a href="#9-Session" class="headerlink" title="9. Session"></a>9. Session</h2><p>除了可以将用户信息通过 Cookie 存储在用户浏览器中，也可以利用 Session 存储在服务器端，存储在服务器端的信息更加安全。</p><p>Session 可以存储在服务器上的文件、数据库或者内存中，现在最常见的是将 Session 存储在内存型数据库中，比如 Redis。</p><p>使用 Session 维护用户登录的过程如下：</p><ul><li>用户进行登录时，用户提交包含用户名和密码的表单，放入 HTTP 请求报文中；</li><li>服务器验证该用户名和密码；</li><li>如果正确则把用户信息存储到 Redis 中，它在 Redis 中的 ID 称为 Session ID；</li><li>服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID，客户端收到响应报文之后将该 Cookie 值存入浏览器中；</li><li>客户端之后对同一个服务器进行请求时会包含该 Cookie 值，服务器收到之后提取出 Session ID，从 Redis 中取出用户信息，继续之后的业务操作。</li></ul><p>应该注意 Session ID 的安全性问题，不能让它被恶意攻击者轻易获取，那么就不能产生一个容易被猜到的 Session ID 值。此外，还需要经常重新生成 Session ID。在对安全性要求极高的场景下，例如转账等操作，除了使用 Session 管理用户状态之外，还需要对用户进行重新验证，比如重新输入密码，或者使用短信验证码等方式。</p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/session_mechanism.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/session_mechanism-20230330174545927.png" alt="img"></a></p><h2 id="10-浏览器禁用-Cookie"><a href="#10-浏览器禁用-Cookie" class="headerlink" title="10. 浏览器禁用 Cookie"></a>10. 浏览器禁用 Cookie</h2><p>此时无法使用 Cookie 来保存用户信息，只能使用 Session。除此之外，不能再将 Session ID 存放到 Cookie 中，而是使用 URL 重写技术，将 Session ID 作为 URL 的参数进行传递。</p><h2 id="11-Cookie-与-Session-选择"><a href="#11-Cookie-与-Session-选择" class="headerlink" title="11. Cookie 与 Session 选择"></a>11. Cookie 与 Session 选择</h2><ul><li>Cookie 只能存储 ASCII 码字符串，而 Session 则可以存取任何类型的数据，因此在考虑数据复杂性时首选 Session；</li><li>Cookie 存储在浏览器中，容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中，可以将 Cookie 值进行加密，然后在服务器进行解密；</li><li>对于大型网站，如果用户所有的信息都存储在 Session 中，那么开销是非常大的，因此不建议将所有的用户信息都存储到 Session 中。</li></ul><h2 id="12-HTTPs安全性"><a href="#12-HTTPs安全性" class="headerlink" title="12. HTTPs安全性"></a>12. HTTPs安全性</h2><p><strong>HTTP 有以下安全性问题：</strong></p><ul><li>使用明文进行通信，内容可能会被窃听；</li><li>不验证通信方的身份，通信方的身份有可能遭遇伪装；</li><li>无法证明报文的完整性，报文有可能遭篡改。</li></ul><p>HTTPs（Hyper Text Transfer Protocol over Secure Socket Layer），是以安全为目标的HTTP通道，简单讲是HTTP的安全版。</p><p>HTTPs 并不是新协议，而是让 HTTP 先和 SSL（Secure Sockets Layer）通信，再由 SSL 和 TCP 通信。也就是说 HTTPs 使用了隧道进行通信。</p><p>通过使用 SSL，HTTPs 具有了加密（防窃听）、认证（防伪装）和完整性保护（防篡改）。</p><p><a href="https://github.com/CyC2018/Interview-Notebook/raw/master/pics/ssl-offloading.jpg"><img src="https://github.com/CyC2018/Interview-Notebook/raw/master/pics/ssl-offloading.jpg" alt="img"></a></p><h3 id="（1）对称密钥加密"><a href="#（1）对称密钥加密" class="headerlink" title="（1）对称密钥加密"></a>（1）对称密钥加密</h3><p>对称密钥加密（Symmetric-Key Encryption），加密和解密使用同一密钥。</p><ul><li>优点：运算速度快；</li><li>缺点：无法安全地将密钥传输给通信方。</li></ul><p><a href="https://raw.githubusercontent.com/CyC2018/Interview-Notebook/master/pics/7fffa4b8-b36d-471f-ad0c-a88ee763bb76.png"><img src="https://raw.githubusercontent.com/CyC2018/Interview-Notebook/master/pics/7fffa4b8-b36d-471f-ad0c-a88ee763bb76.png" alt="img"></a></p><h3 id="（2）非对称密钥加密"><a href="#（2）非对称密钥加密" class="headerlink" title="（2）非对称密钥加密"></a>（2）非对称密钥加密</h3><p>非对称密钥加密，又称公开密钥加密（Public-Key Encryption），加密和解密使用不同的密钥。</p><p>公开密钥所有人都可以获得，通信发送方获得接收方的公开密钥之后，就可以使用公开密钥进行加密，接收方收到通信内容后使用私有密钥解密。</p><p>非对称密钥除了用来加密，还可以用来进行签名。因为私有密钥无法被其他人获取，因此通信发送方使用其私有密钥进行签名，通信接收方使用发送方的公开密钥对签名进行解密，就能判断这个签名是否正确。</p><ul><li>优点：可以更安全地将公开密钥传输给通信发送方；</li><li>缺点：运算速度慢。</li></ul><p><a href="https://raw.githubusercontent.com/CyC2018/Interview-Notebook/master/pics/39ccb299-ee99-4dd1-b8b4-2f9ec9495cb4.png"><img src="https://raw.githubusercontent.com/CyC2018/Interview-Notebook/master/pics/39ccb299-ee99-4dd1-b8b4-2f9ec9495cb4.png" alt="img"></a></p><h3 id="（3）HTTPs-采用的加密方式"><a href="#（3）HTTPs-采用的加密方式" class="headerlink" title="（3）HTTPs 采用的加密方式"></a>（3）HTTPs 采用的加密方式</h3><p>HTTPs 采用混合的加密机制，使用非对称密钥加密用于传输对称密钥来保证安全性，之后使用对称密钥加密进行通信来保证效率。</p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/How-HTTPS-Works2.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/How-HTTPS-Works2-20230330174544565.png" alt="img"></a></p><h2 id="13-SSL-TLS协议的握手过程"><a href="#13-SSL-TLS协议的握手过程" class="headerlink" title="13. SSL/TLS协议的握手过程"></a>13. SSL/TLS协议的握手过程</h2><p>我们知道，HTTP 协议都是明文传输内容，在早期只展示静态内容时没有问题。伴随着互联网的快速发展，人们对于网络传输安全性的要求也越来越高，HTTPS 协议因此出现。如上图所示，在 HTTPS 加密中真正起作用的其实是 SSL/TLS 协议。SSL/TLS 协议作用在 HTTP 协议之下，对于上层应用来说，原来的发送接收数据流程不变，这就很好地兼容了老的 HTTP 协议，这也是软件开发中分层实现的体现。</p><h3 id="SSL-Secure-Socket-Layer，安全套接字层"><a href="#SSL-Secure-Socket-Layer，安全套接字层" class="headerlink" title="SSL (Secure Socket Layer，安全套接字层)"></a>SSL (Secure Socket Layer，安全套接字层)</h3><p>SSL为Netscape所研发，用以保障在Internet上数据传输之安全，利用数据加密(Encryption)技术，可确保数据在网络上之传输过程中不会被截取，当前为3.0版本。</p><p>SSL协议可分为两层： SSL记录协议（SSL Record Protocol）：它建立在可靠的传输协议（如TCP）之上，为高层协议提供数据封装、压缩、加密等基本功能的支持。 SSL握手协议（SSL Handshake Protocol）：它建立在SSL记录协议之上，用于在实际的数据传输开始前，通讯双方进行身份认证、协商加密算法、交换加密密钥等。</p><h3 id="TLS-Transport-Layer-Security，传输层安全协议"><a href="#TLS-Transport-Layer-Security，传输层安全协议" class="headerlink" title="TLS (Transport Layer Security，传输层安全协议)"></a>TLS (Transport Layer Security，传输层安全协议)</h3><p>用于两个应用程序之间提供保密性和数据完整性。 TLS 1.0是IETF（Internet Engineering Task Force，Internet工程任务组）制定的一种新的协议，它建立在SSL 3.0协议规范之上，是SSL 3.0的后续版本，可以理解为SSL 3.1，它是写入了 RFC 的。该协议由两层组成： TLS 记录协议（TLS Record）和 TLS 握手协议（TLS Handshake）。较低的层为 TLS 记录协议，位于某个可靠的传输协议（例如 TCP）上面。</p><p>SSL/TLS 握手是为了<strong>安全</strong>地协商出一份<strong>对称加密</strong>的秘钥，这个过程很有意思，下面我们一起来了解一下。</p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/https_com.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/https_com-20230330174546092.png" alt="img"></a></p><h3 id="（1）client-hello"><a href="#（1）client-hello" class="headerlink" title="（1）client hello"></a>（1）client hello</h3><p>握手第一步是客户端向服务端发送 Client Hello 消息，这个消息里包含了一个客户端生成的随机数 <strong>Random1</strong>、客户端支持的<strong>加密套件</strong>（Support Ciphers）和 SSL Version 等信息。</p><h3 id="（2）server-hello"><a href="#（2）server-hello" class="headerlink" title="（2）server hello"></a>（2）server hello</h3><p>第二步是服务端向客户端发送 Server Hello 消息，这个消息会从 Client Hello 传过来的 Support Ciphers 里确定一份加密套件，这个套件决定了后续加密和生成摘要时具体使用哪些算法，另外还会生成一份随机数 <strong>Random2</strong>。注意，至此客户端和服务端都拥有了两个随机数（Random1+ Random2），这两个随机数会在后续生成对称秘钥时用到。</p><h3 id="（3）server-certificate"><a href="#（3）server-certificate" class="headerlink" title="（3）server certificate"></a>（3）server certificate</h3><p>这一步是服务端将自己的证书下发给客户端，让客户端验证自己的身份，客户端验证通过后取出证书中的公钥。</p><h3 id="（4）Server-Hello-Done"><a href="#（4）Server-Hello-Done" class="headerlink" title="（4）Server Hello Done"></a>（4）Server Hello Done</h3><p>Server Hello Done 通知客户端 Server Hello 过程结束。</p><h3 id="（5）Client-Key-Exchange"><a href="#（5）Client-Key-Exchange" class="headerlink" title="（5）Client Key Exchange"></a>（5）Client Key Exchange</h3><p>上面客户端根据服务器传来的公钥生成了 <strong>PreMaster Key</strong>，Client Key Exchange 就是将这个 key 传给服务端，服务端再用自己的私钥解出这个 <strong>PreMaster Key</strong> 得到客户端生成的 <strong>Random3</strong>。至此，客户端和服务端都拥有 <strong>Random1</strong> + <strong>Random2</strong> + <strong>Random3</strong>，两边再根据同样的算法就可以生成一份秘钥，握手结束后的应用层数据都是使用这个秘钥进行对称加密。</p><p>为什么要使用三个随机数呢？这是因为 SSL/TLS 握手过程的数据都是明文传输的，并且多个随机数种子来生成秘钥不容易被暴力破解出来。</p><h3 id="（6）Change-Cipher-Spec-Client"><a href="#（6）Change-Cipher-Spec-Client" class="headerlink" title="（6）Change Cipher Spec(Client)"></a>（6）Change Cipher Spec(Client)</h3><p>这一步是客户端通知服务端后面再发送的消息都会使用前面协商出来的秘钥加密了，是一条事件消息。</p><h3 id="（7）Finished-Client"><a href="#（7）Finished-Client" class="headerlink" title="（7）Finished(Client)"></a>（7）Finished(Client)</h3><p>客户端发送Finished报文。该报文包含连接至今全部报文的整理校验值。这次握手协议是否能成功，要以服务器是否能够正确解密该报文作为判定标准。</p><h3 id="（8）Change-Cipher-Spec-Server"><a href="#（8）Change-Cipher-Spec-Server" class="headerlink" title="（8）Change Cipher Spec(Server)"></a>（8）Change Cipher Spec(Server)</h3><p>服务器同样发送Change Cipher Spec报文给客户端</p><h3 id="（9）Finished-Server"><a href="#（9）Finished-Server" class="headerlink" title="（9）Finished(Server)"></a>（9）Finished(Server)</h3><p>服务器同样发送Finished报文给客户端</p><h3 id="（10-11）Application-Data"><a href="#（10-11）Application-Data" class="headerlink" title="（10-11）Application Data"></a>（10-11）Application Data</h3><p>到这里，双方已安全地协商出了同一份秘钥，所有的应用层数据都会用这个秘钥加密后再通过 TCP 进行可靠传输。</p><h3 id="（12）Alert：warning-close-notify"><a href="#（12）Alert：warning-close-notify" class="headerlink" title="（12）Alert：warning, close notify"></a>（12）Alert：warning, close notify</h3><p>最后由客户端断开连接。断开连接时，发送close_notify报文。上图做了一些省略，在这步之后再发送一种叫做MAC（Message Authentication Code）的报文摘要。MAC能够查知报文是否遭到篡改，从而保护报文的完整性。</p><h3 id="（-）demand-client-certificate"><a href="#（-）demand-client-certificate" class="headerlink" title="（*）demand client certificate"></a>（*）demand client certificate</h3><p>Certificate Request 是服务端要求客户端上报证书，这一步是可选的，对于安全性要求高的场景会用到。</p><h3 id="（-）check-server-certificate"><a href="#（-）check-server-certificate" class="headerlink" title="（*）check server certificate"></a>（*）check server certificate</h3><p>客户端收到服务端传来的证书后，先从 CA 验证该证书的合法性，验证通过后取出证书中的服务端公钥，再生成一个随机数 <strong>Random3</strong>，再用服务端公钥非对称加密 <strong>Random3</strong> 生成 <strong>PreMaster Key</strong>。</p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/SSL_handshake.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/SSL_handshake-20230330174549539.png" alt="img"></a></p><p>参考资料：</p><ul><li><a href="https://upload.wikimedia.org/wikipedia/commons/a/ae/SSL_handshake_with_two_way_authentication_with_certificates.svg">SSL_handshake_with_two_way_authentication_with_certificates.svg</a></li><li><a href="https://www.jianshu.com/p/7158568e4867">SSL/TLS 握手过程详解 - 简书</a></li><li><a href="http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html">图解SSL/TLS协议 - 阮一峰的网络日志</a></li><li><a href="https://www.imooc.com/learn/969">【慕课视频】ios开发网络协议https请求视频教程</a></li><li><a href="http://levy.work/2017-12-28-http2/">学习HTTP/2 | levy</a></li><li><a href="https://juejin.im/entry/570469035bbb500051d4eceb">详解 https 是如何确保安全的？ - 后端 - 掘金</a></li></ul><h2 id="14-数字签名、数字证书、SSL、https是什么关系？"><a href="#14-数字签名、数字证书、SSL、https是什么关系？" class="headerlink" title="14. 数字签名、数字证书、SSL、https是什么关系？"></a>14. 数字签名、数字证书、SSL、https是什么关系？</h2><p>HTTPS 是建立在密码学基础之上的一种安全通信协议，严格来说是基于 HTTP 协议和 SSL/TLS 的组合。理解 HTTPS 之前有必要弄清楚一些密码学的相关基础概念，比如：明文、密文、密码、密钥、对称加密、非对称加密、信息摘要、数字签名、数字证书。接下来我会逐个解释这些术语，文章里面提到的『数据』、『消息』都是同一个概念，表示用户之间通信的内容载体，此外文章中提到了以下几个角色：</p><ul><li>Alice：消息发送者</li><li>Bob：消息接收者</li><li>Attacker：中间攻击者</li><li>Trent：第三方认证机构</li></ul><h3 id="密码"><a href="#密码" class="headerlink" title="密码"></a>密码</h3><p>密码学中的“密码”术语与网站登录时用的密码（password）是不一样的概念，password 翻译过来其实是“口令”，它是用于认证用途的一组文本字符串。</p><p>而密码学中的密码（cipher）是一套算法(algorithm)，这套算法用于对消息进行加密和解密，从明文到密文的过程称之为加密，密文反过来生成明文称之为解密，加密算法与解密算法合在一起称为密码算法。</p><h3 id="密钥"><a href="#密钥" class="headerlink" title="密钥"></a>密钥</h3><p>密钥（key）是在使用密码算法过程中输入的一段参数。同一个明文在相同的密码算法和不同的密钥计算下会产生不同的密文。很多知名的密码算法都是公开的，密钥才是决定密文是否安全的重要参数，通常密钥越长，破解的难度越大，比如一个8位的密钥最多有256种情况，使用穷举法，能非常轻易的破解。根据密钥的使用方法，密码可分为对称加密和公钥加密。</p><h3 id="对称加密"><a href="#对称加密" class="headerlink" title="对称加密"></a>对称加密</h3><p>对称密钥（Symmetric-key algorithm）又称为共享密钥加密，加密和解密使用相同的密钥。常见的对称加密算法有DES、3DES、AES、RC5、RC6。对称密钥的优点是计算速度快，但是它有缺点，接收者需要发送者告知密钥才能解密，因此密钥如何安全的发送给接收者成为了一个问题。</p><p><a href="https://camo.githubusercontent.com/5f31519506387c874f1e4ffa5fae615ba7f83cbdd1777cab7fc617d27c2d8e99/68747470733a2f2f706963332e7a68696d672e636f6d2f38302f76322d39356632356331326165343036646132326537633462343230356134393166625f722e6a7067"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/68747470733a2f2f706963332e7a68696d672e636f6d2f38302f76322d39356632356331326165343036646132326537633462343230356134393166625f722e6a7067-20230330174530325.jpeg" alt="img"></a></p><p>Alice 给 Bob 发送数据时，把数据用对称加密后发送给 Bob，发送过程中由于对数据进行了加密，因此即使有人窃取了数据也没法破解，因为它不知道密钥是什么。但是同样的问题是 Bob 收到数据后也一筹莫展，因为它也不知道密钥是什么，那么 Alice 是不是可以把数据和密钥一同发给 Bob 呢。当然不行，一旦把密钥和密钥一起发送的话，那就跟发送明文没什么区别了，因为一旦有人把密钥和数据同时获取了，密文就破解了。所以对称加密的密钥配是个问题。如何解决呢，公钥加密是一个办法。</p><h3 id="公钥加密（非对称加密）"><a href="#公钥加密（非对称加密）" class="headerlink" title="公钥加密（非对称加密）"></a>公钥加密（非对称加密）</h3><p>公开密钥加密（public-key cryptography）简称公钥加密，这套密码算法包含配对的密钥对，分为加密密钥和解密密钥。发送者用加密密钥进行加密，接收者用解密密钥进行解密。加密密钥是公开的，任何人都可以获取，因此加密密钥又称为公钥（public key），解密密钥不能公开，只能自己使用，因此它又称为私钥（private key）。常见的公钥加密算法有 RSA。</p><p>还是以Alice 给 Bob 发送数据为例，公钥加密算法由接收者 Bob 发起</p><ol><li>Bob 生成公钥和私钥对，私钥自己保存，不能透露给任何人。</li><li>Bob 把公钥发送给 Alice，发送过程中即使被人窃取也没关系</li><li>Alice 用公钥把数据进行加密，并发送给 Bob，发送过程中被人窃取了同样没关系，因为没有配对的私钥进行解密是没法破解的</li><li>Bob 用配对的私钥解密。</li></ol><p><a href="https://camo.githubusercontent.com/0272f75b015d9ebae06f2e74c0dd52e88385884dfbba870b1d906c3adfb69f6c/68747470733a2f2f706963342e7a68696d672e636f6d2f38302f76322d35626435303462383263636338376165363433613664366530393537396631335f722e6a7067"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/68747470733a2f2f706963342e7a68696d672e636f6d2f38302f76322d35626435303462383263636338376165363433613664366530393537396631335f722e6a7067-20230330174530814.jpeg" alt="img"></a></p><p>虽然公钥加密解决了密钥配送的问题，但是你没法确认公钥是不是合法的，Bob 发送的公钥你不能肯定真的是 Bob 发的，因为也有可能在 Bob 把公钥发送给 Alice 的过程中出现中间人攻击，把真实的公钥掉包替换。而对于 Alice 来说完全不知。还有一个缺点是它的运行速度比对称加密慢很多。</p><h3 id="消息摘要"><a href="#消息摘要" class="headerlink" title="消息摘要"></a>消息摘要</h3><p>消息摘要（message digest）函数是一种用于判断数据完整性的算法，也称为散列函数或哈希函数，函数返回的值叫散列值，散列值又称为消息摘要或者指纹（fingerprint）。这种算法是一个不可逆的算法，因此你没法通过消息摘要反向推倒出消息是什么。所以它也称为<strong>单向散列函数</strong>。下载软件时如何确定是官方提供的完整版呢，如果有中间人在软件里面嵌入了病毒，你也不得而知。所以我们可以使用散列函数对消息进行运算，生成散列值，通常软件提供方会同时提供软件的下载地址和软件的散列值，用户把软件下载后在本地用相同的散列算法计算出散列值，与官方提供的散列值对比，如果相同，说明该软件是完成的，否则就是被人修改过了。常用的散列算法有MD5、SHA。</p><p><a href="https://camo.githubusercontent.com/7bc0db463949f33adc024b6a2ed55c157a74e6ecb696fee917b995f5d23d0c58/68747470733a2f2f706963332e7a68696d672e636f6d2f38302f76322d31643663663230356365333062303135313964386238366530333936383634335f722e6a7067"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/68747470733a2f2f706963332e7a68696d672e636f6d2f38302f76322d31643663663230356365333062303135313964386238366530333936383634335f722e6a7067-20230330174531888.jpeg" alt="img"></a></p><p>下载 Eclipse 时，官方网站同时提供了软件地址和消息摘要</p><p><a href="https://camo.githubusercontent.com/4b92bf146f76ed60031957a284b3c50931fddfb7ad984a3d53a83593e0afa733/68747470733a2f2f706963342e7a68696d672e636f6d2f38302f76322d66363838626334663166356132383838323339346232626539626232656465395f722e6a7067"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/68747470733a2f2f706963342e7a68696d672e636f6d2f38302f76322d66363838626334663166356132383838323339346232626539626232656465395f722e6a7067-20230330174532720.png" alt="img"></a></p><p>散列函数可以保证数据的完整性，识别出数据是否被篡改，但它并不能识别出数据是不是伪装的，因为中间人可以把数据和消息摘要同时替换，数据虽然是完整的，但真实数据被掉包了，接收者收到的并不是发送者发的，而是中间人的。消息认证是解决数据真实性的办法。认证使用的技术有消息认证码和数字签名。</p><h3 id="消息认证码"><a href="#消息认证码" class="headerlink" title="消息认证码"></a>消息认证码</h3><p>消息认证码（message authentication code）是一种可以确认消息完整性并进行认证（消息认证是指确认消息来自正确的发送者）的技术，简称 MAC。消息认证码可以简单理解为一种与密钥相关的单向散列函数。</p><p><a href="https://camo.githubusercontent.com/fe8ba10349175e08bfe70ca3c304a5f395e8ee725b20cf96e29f894e7e23cff3/68747470733a2f2f706963312e7a68696d672e636f6d2f38302f76322d37336435326263326433383238656563346630646465356561363865666633365f722e6a7067"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/68747470733a2f2f706963312e7a68696d672e636f6d2f38302f76322d37336435326263326433383238656563346630646465356561363865666633365f722e6a7067-20230330174531926.jpeg" alt="img"></a></p><p>Alice 给 Bob 发送消息前，先把共享密钥（key）发送给 Bob，Alice 把消息计算出 MAC 值，连同消息一起发送给 Bob，Bob 接收到消息和 MAC 值后，与本地计算得到 MAC 值对比，如果两者相同，就说明消息是完整的，而且可以确定是 Alice 发送的，没有中间人伪造。不过，消息认证码同样会遇到对称加密的密钥配送问题，因此解决密钥配送问题还是要采用公钥加密的方式。</p><p>此外，消息认证码还有一个无法解决的问题，Bob 虽然可以识别出消息的篡改和伪装，但是 Alice 可以否认说：“我没发消息，应该是 Bob 的密钥被 Attacker 盗取了，这是 Attacker 发的吧”。Alice 这么说你还真没什么可以反驳的，那么如何防止 Alice 不承认呢，数字签名可以实现。</p><h3 id="数字签名"><a href="#数字签名" class="headerlink" title="数字签名"></a>数字签名</h3><p>Alice 发邮件找 Bob 借1万钱，因为邮件可以被人篡改（改成10万），也可以被伪造（Alice 根本就没发邮件，而是 Attacker 伪造 Alice 在发邮件），Alice 借了钱之后还可以不承认（不是我借的，我没有签名啊）。</p><p><strong>消息认证码</strong>可以解决篡改和伪造的问题，Alice 不承认自己借了钱时，Bob 去找第三方机构做公正，即使这样，公正方也没法判断 Alice 有没有真的借钱，因为他们俩共享了密钥，也就是说两个都可以计算出正确的 MAC 值，Bob 说：“明明你发的消息和 MAC 值和我自己生成的 MAC 值一样，肯定是你发的消息”，Alice 说：“你把密钥透露给了其他人，是他发的邮件，你找他去吧”。Alice 矢口否认。</p><p>数字签名（Digital Signature）就可以解决否认的问题，发送消息时，Alice 和 Bob 使用不同的密钥，把公钥加密算法反过来使用，发送者 Alice 使用私钥对消息进行签名，而且只能是拥有私钥的 Alice 可以对消息签名，Bob 用配对的公钥去验证签名，第三方机构也可以用公钥验证签名，如果验证通过，说明消息一定是 Alice 发送的，抵赖也不行，因为你只有 Alice 可以生成签名。这就防止了否认的问题。</p><p><a href="https://camo.githubusercontent.com/2562b2ad50125e3c7eb921fdd134d506583e08b10fd1dafc86562acfd86b0198/68747470733a2f2f706963332e7a68696d672e636f6d2f38302f76322d65313831396165643130393632613964313438613862343436306436303662355f722e6a7067"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/68747470733a2f2f706963332e7a68696d672e636f6d2f38302f76322d65313831396165643130393632613964313438613862343436306436303662355f722e6a7067-20230330174530628.png" alt="img"></a></p><p>它的流程是:</p><p>第一步：发送者 Alice <strong>把消息哈希函数处理生成消息摘要，摘要信息使用私钥加密之后生成签名</strong>，连同消息一起发送给接收者 Bob。</p><p>第二步：数据经过网络传输，Bob收到数据后，把签名和消息分别提取出来。</p><p>第三步：对签名进行验证，验证的过程是先把消息提取出来做同样的Hash处理，得到消息摘要，再与 Alice 传过来的签名用公钥解密，如果两者相等，就表示签名验证成功，否则验证失败，表示不是 Alice发的。</p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/ca-sign.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/ca-sign-20230330174546008.png" alt="img"></a></p><h3 id="公钥证书"><a href="#公钥证书" class="headerlink" title="公钥证书"></a>公钥证书</h3><p>公钥密码在数字签名技术里面扮演举足轻重的角色，但是如何保证公钥是合法的呢，如果是遭到中间人攻击，掉包怎么办？这个时候公钥就应该交给一个第三方权威机构来管理，这个机构就是认证机构（Certification Authority）CA，CA 把用户的姓名、组织、邮箱地址等个人信息收集起来，还有此人的公钥，并由 CA 提供数字签名生成公钥证书（Public-Key Certificate）PKC，简称证书。</p><p><a href="https://camo.githubusercontent.com/ade8daf2e234b75854243419ec187ae9dd3422199e396217f5d5a2e78e07dc12/68747470733a2f2f706963342e7a68696d672e636f6d2f38302f76322d66393664373063306438383438393231633265626236656635393166336336365f722e6a7067"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/68747470733a2f2f706963342e7a68696d672e636f6d2f38302f76322d66393664373063306438383438393231633265626236656635393166336336365f722e6a7067-20230330174530821.jpeg" alt="img"></a></p><p>Alice 向 Bob 发送消息时，是通过 Bob 提供的公钥加密后的数据，而 Alice 获取的公钥并不是由 Bob 直接给的，而是由委托一个受信任的第三方机构给的。</p><ol><li>Bob 生成密钥对，私钥自己保管，公钥交给认证机构 Trent。</li><li>Trent 经过一系列严格的检查确认公钥是 Bob 本人的</li><li>Trent 事先也生成自己的一套密钥对，用自己的私钥对 Bob 的公钥进行数字签名并生成数字证书。证书中包含了 Bob 的公钥。公钥在这里是不需要加密的，因为任何人获取 Bob 的公钥都没事，只要确定是 Bob 的公钥就行。</li><li>Alice 获取 Trent 提供的证书。</li><li>Alice 用 Trent 提供的公钥对证书进行签名验证，签名验证成功就表示证书中的公钥是 Bob 的。</li><li>于是 Alice 就可以用 Bob 提供的公钥对消息加密后发送给 Bob。</li><li>Bob 收到密文后，用与之配对的私钥进行解密。</li></ol><p>至此，一套比较完善的数据传输方案就完成了。HTTPS（SSL/TLS）就是在这样一套流程基础之上建立起来的。</p><p>参考资料：</p><ul><li><a href="https://www.zhihu.com/question/52493697">数字签名、数字证书、SSL、https是什么关系？ - 知乎</a></li><li><a href="https://zhuanlan.zhihu.com/p/25324735">【Python之禅 】HTTPS 为什么更安全，先看这些</a></li></ul><h2 id="15-HTTP和HTTPS的区别【阿里面经OneNote】"><a href="#15-HTTP和HTTPS的区别【阿里面经OneNote】" class="headerlink" title="15. HTTP和HTTPS的区别【阿里面经OneNote】"></a>15. HTTP和HTTPS的区别【阿里面经OneNote】</h2><ul><li>http是HTTP协议运行在TCP之上。所有传输的内容都是明文，客户端和服务器端都无法验证对方的身份。</li><li>https是HTTP运行在SSL/TLS之上，SSL/TLS运行在TCP之上。所有传输的内容都经过加密，加密采用对称加密，但对称加密的密钥用服务器方的证书进行了非对称加密。此外客户端可以验证服务器端的身份，如果配置了客户端验证，服务器方也可以验证客户端的身份。</li><li>https协议需要到ca申请证书，一般免费证书很少，需要交费。</li><li>http是超文本传输协议，信息是明文传输，https 则是具有安全性的ssl加密传输协议</li><li>http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。</li><li>http的连接很简单,是无状态的</li><li>HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全</li></ul><h2 id="16-HTTP2-0特性"><a href="#16-HTTP2-0特性" class="headerlink" title="16. HTTP2.0特性"></a>16. HTTP2.0特性</h2><p>HTTP/2的通过支持请求与响应的多路复用来减少延迟，通过压缩HTTP首部字段将协议开销降至最低，同时增加对请求优先级和服务器端推送的支持。</p><h3 id="（1）二进制分帧"><a href="#（1）二进制分帧" class="headerlink" title="（1）二进制分帧"></a>（1）二进制分帧</h3><p>先来理解几个概念：</p><p>帧：HTTP/2 数据通信的最小单位消息：指 HTTP/2 中逻辑上的 HTTP 消息。例如请求和响应等，消息由一个或多个帧组成。</p><p>流：存在于连接中的一个虚拟通道。流可以承载双向消息，每个流都有一个唯一的整数ID。</p><p>HTTP/2 采用二进制格式传输数据，而非 HTTP 1.x 的文本格式，二进制协议解析起来更高效。 HTTP / 1 的请求和响应报文，都是由起始行，首部和实体正文（可选）组成，各部分之间以文本换行符分隔。HTTP/2 将请求和响应数据分割为更小的帧，并且它们采用二进制编码。</p><p><strong>HTTP/2 中，同域名下所有通信都在单个连接上完成，该连接可以承载任意数量的双向数据流。</strong>每个数据流都以消息的形式发送，而消息又由一个或多个帧组成。多个帧之间可以乱序发送，根据帧首部的流标识可以重新组装。</p><h3 id="（2）多路复用"><a href="#（2）多路复用" class="headerlink" title="（2）多路复用"></a>（2）多路复用</h3><p>多路复用，代替原来的序列和阻塞机制。所有就是请求的都是通过一个 TCP连接并发完成。 HTTP 1.x 中，如果想并发多个请求，必须使用多个 TCP 链接，且浏览器为了控制资源，还会对单个域名有 6-8个的TCP链接请求限制，如下图，红色圈出来的请求就因域名链接数已超过限制，而被挂起等待了一段时间。</p><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/http2-tcp.jpg"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/http2-tcp-20230330174543217.jpg" alt="img"></a></p><p>在 HTTP/2 中，有了二进制分帧之后，HTTP /2 不再依赖 TCP 链接去实现多流并行了，在 HTTP/2中：</p><ul><li>同域名下所有通信都在单个连接上完成。</li><li>单个连接可以承载任意数量的双向数据流。</li><li>数据流以消息的形式发送，而消息又由一个或多个帧组成，多个帧之间可以乱序发送，因为根据帧首部的流标识可以重新组装。</li></ul><p>这一特性，使性能有了极大提升：</p><ul><li><strong>同个域名只需要占用一个 TCP 连接</strong>，消除了因多个 TCP 连接而带来的延时和内存消耗。</li><li>单个连接上可以并行交错的请求和响应，之间互不干扰。</li><li>在HTTP/2中，每个请求都可以带一个31bit的优先值，0表示最高优先级， 数值越大优先级越低。有了这个优先值，客户端和服务器就可以在处理不同的流时采取不同的策略，以最优的方式发送流、消息和帧。</li></ul><h3 id="（3）服务器推送"><a href="#（3）服务器推送" class="headerlink" title="（3）服务器推送"></a>（3）服务器推送</h3><p>服务端可以在发送页面HTML时主动推送其它资源，而不用等到浏览器解析到相应位置，发起请求再响应。例如服务端可以主动把JS和CSS文件推送给客户端，而不需要客户端解析HTML时再发送这些请求。</p><p>服务端可以主动推送，客户端也有权利选择是否接收。如果服务端推送的资源已经被浏览器缓存过，浏览器可以通过发送RST_STREAM帧来拒收。主动推送也遵守同源策略，服务器不会随便推送第三方资源给客户端。</p><h3 id="（4）头部压缩"><a href="#（4）头部压缩" class="headerlink" title="（4）头部压缩"></a>（4）头部压缩</h3><p>HTTP 1.1请求的大小变得越来越大，有时甚至会大于TCP窗口的初始大小，因为它们需要等待带着ACK的响应回来以后才能继续被发送。HTTP/2对消息头采用HPACK（专为http/2头部设计的压缩格式）进行压缩传输，能够节省消息头占用的网络的流量。而HTTP/1.x每次请求，都会携带大量冗余头信息，浪费了很多带宽资源。</p><p>参考资料：</p><ul><li><a href="https://zhuanlan.zhihu.com/p/26559480">一文读懂 HTTP/2 特性</a></li><li>[【体验http1.1和http2的性能对比动画】HTTP/2: the Future of the Internet | Akamai](HTTP/2: the Future of the Internet | Akamai)</li></ul><h1 id="第三部分：网络层"><a href="#第三部分：网络层" class="headerlink" title="第三部分：网络层"></a>第三部分：网络层</h1><h2 id="1-mac和ip怎么转换"><a href="#1-mac和ip怎么转换" class="headerlink" title="1. mac和ip怎么转换"></a>1. mac和ip怎么转换</h2><p><strong>ARP协议：</strong></p><p>将IP地址通过广播 目标MAC地址是FF-FF-FF-FF-FF-FF 解析目标IP地址的MAC地址 扫描本网段MAC地址。</p><p><strong>DHCP协议：</strong></p><p>DHCP租约过程就是DHCP客户机动态获取IP地址的过程。</p><p>DHCP租约过程分为4步：</p><ol><li>客户机请求IP（客户机发DHCPDISCOVER广播包）；</li><li>服务器响应（服务器发DHCPOFFER广播包）；</li><li>客户机选择IP（客户机发DHCPREQUEST广播包）；</li><li>服务器确定租约（服务器发DHCPACK/DHCPNAK广播包）。</li></ol><p>参考资料：</p><ul><li><a href="http://blog.51cto.com/yuanbin/109574">图解DHCP的4步租约过程-大浪淘沙-51CTO博客</a></li></ul><h2 id="2-IP地址子网划分"><a href="#2-IP地址子网划分" class="headerlink" title="2. IP地址子网划分"></a>2. IP地址子网划分</h2><table><thead><tr><th>二进制</th><th>十进制</th></tr></thead><tbody><tr><td>1</td><td>1</td></tr><tr><td>10</td><td>2</td></tr><tr><td>100</td><td>4</td></tr><tr><td>1000</td><td>8</td></tr><tr><td>10000</td><td>16</td></tr><tr><td>100000</td><td>32</td></tr><tr><td>1000000</td><td>64</td></tr><tr><td>10000000</td><td>128</td></tr><tr><td></td><td></td></tr><tr><td>10000000</td><td>128</td></tr><tr><td>11000000</td><td>192</td></tr><tr><td>11100000</td><td>224</td></tr><tr><td>11110000</td><td>240</td></tr><tr><td>11111000</td><td>248</td></tr><tr><td>11111100</td><td>252</td></tr><tr><td>11111110</td><td>254</td></tr><tr><td>11111111</td><td>255</td></tr></tbody></table><p><a href="https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/assets/%E5%AD%90%E7%BD%91%E5%88%92%E5%88%86.png"><img src="/../assets/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E5%AD%90%E7%BD%91%E5%88%92%E5%88%86-20230330174544238.png" alt="img"></a></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">IP分类</span><br><span class="line">公有地址：</span><br><span class="line">IP分类        缺省掩码</span><br><span class="line">A 1－127      &#x2F;8</span><br><span class="line">B 128－191        &#x2F;16</span><br><span class="line">C 192－223      &#x2F;24</span><br><span class="line">D 224－239      组播地址</span><br><span class="line">E 240－247    保留地址</span><br><span class="line">私有地址：</span><br><span class="line">A：10.0.0.0 - 10.255.255.255</span><br><span class="line">B:  172.16.0.0 - 172.31.255.255</span><br><span class="line">C:  192.168.0.0 - 192.168.255.255</span><br><span class="line"></span><br><span class="line">判断合法的主机（IP）地址：</span><br><span class="line">192.168.10.240&#x2F;24        合法</span><br><span class="line">192.168.10.0&#x2F;24          不合法，主机位全为0，网络地址</span><br><span class="line">192.168.10.255&#x2F;24        不合法，主机位全为1，子网广播地址</span><br><span class="line">255.255.255.255              不合法，网络和主机位全为1，全网广播地址</span><br><span class="line">127.x.x.x&#x2F;8                不合法，本地环回地址</span><br><span class="line">172.16.3.5&#x2F;24              合法</span><br><span class="line">192.168.5.240&#x2F;32        合法</span><br><span class="line">224.10.10.10.1              不合法，组播地址</span><br><span class="line">300.2.4.200&#x2F;24              不合法</span><br></pre></td></tr></table></figure><ul><li>IP特殊地址<ul><li>本地环回地址：127.0.0.0 – 127.255.255.255，测试主机TCP/IP协议栈是否安装正确。</li><li>本地链路地址：169.254.0.0 – 169.254.255.255，自动地址无法获取时系统自动配置占位。</li><li>受限广播地址：255.255.255.255，发往这个地址的数据不能跨越三层设备，但本地网络内所有的主机都可以接收到数据</li></ul></li><li>参考资料：<ul><li><a href="https://canliture.github.io/s/NetworkEngineer/04internetLayer.html">4internetLayer</a></li><li><a href="https://www.cnblogs.com/lifi/p/7353279.html">IP地址和子网划分 - 混沌的光与影 - 博客园</a></li><li>[ZenCloud2/13-网络管理.md at a78722799508a7ac3fc7d055ff8d2d88edd0b595 · destinyplan/ZenCloud2](ZenCloud2/13-网络管理.md at a78722799508a7ac3fc7d055ff8d2d88edd0b595 · destinyplan/ZenCloud2 )</li></ul></li></ul><h2 id="3-地址解析协议ARP"><a href="#3-地址解析协议ARP" class="headerlink" title="3. 地址解析协议ARP"></a>3. 地址解析协议ARP</h2><h2 id="4-交换机和路由器的区别"><a href="#4-交换机和路由器的区别" class="headerlink" title="4. 交换机和路由器的区别"></a>4. 交换机和路由器的区别</h2><ol><li>路由器可以给你的局域网自动分配IP，虚拟拨号，就像一个交通警察，指挥着你的电脑该往哪走，你自己不用操心那么多了。交换机只是用来分配网络数据的。</li><li>路由器在网络层，路由器根据IP地址寻址，路由器可以处理TCP/IP协议，交换机不可以。</li><li>交换机在中继层，交换机根据MAC地址寻址。路由器可以把一个IP分配给很多个主机使用，这些主机对外只表现出一个IP。交换机可以把很多主机连起来，这些主机对外各有各的IP。</li><li>路由器提供防火墙的服务，交换机不能提供该功能。集线器、交换机都是做端口扩展的，就是扩大局域网(通常都是以太网)的接入点，也就是能让局域网可以连进来更多的电脑。路由器是用来做网间连接，也就是用来连接不同的网络。</li></ol><p>交换机是利用<strong>物理地址或者说MAC地址</strong>来确定转发数据的目的地址。而路由器则是利用不同网络的ID号(即IP地址)来确定数据转发的地址。IP地址是在软件中实现的，描述的是设备所在的网络，有时这些第三层的地址也称为协议地址或者网络地址。MAC地址通常是硬件自带的，由网卡生产商来分配的，而且已经固化到了网卡中去，一般来说是不可更改的。而IP地址则通常由网络管理员或系统自动分配。</p><p><strong>路由器和交换机的区别一</strong>：交换机是一根网线上网，但是大家上网是分别拨号，各自使用自己的宽带，大家上网没有影响。而路由器比交换机多了一个虚拟拨号功能，通过同一台路由器上网的电脑是共用一个宽带账号，大家上网要相互影响。 <strong>路由器和交换机的区别二</strong>：交换机工作在中继层，交换机根据MAC地址寻址。路由器工作在网络层，根据IP地址寻址，路由器可以处理TCP/IP协议，而交换机不可以。</p><p><strong>路由器和交换机的区别三</strong>：交换机可以使连接它的多台电脑组成局域网，如果还有代理服务器的话还可以实现同时上网功能而且局域网所有电脑是共享它的带宽速率的，但是交换机没有路由器的自动识别数据包发送和到达地址的功能。路由器可以自动识别数据包发送和到达的地址，路由器相当于马路上的警察，负责交通疏导和指路的。</p><p><strong>路由器和交换机的区别四</strong>：举几个例子,路由器是小邮局，就一个地址(IP)，负责一个地方的收发(个人电脑，某个服务器，所以你家上网要这个东西)，交换机是省里的大邮政中心，负责由一个地址给各个小地方的联系。简单的说路由器专管入网，交换机只管配送，路由路由就是给你找路让你上网的，交换机只负责开门，交换机上面要没有路由你是上不了网的。</p><p><strong>路由器和交换机的区别五</strong>：路由器提供了防火墙的服务。路由器仅仅转发特定地址的数据包，不传送不支持路由协议的数据包传送和未知目标网络数据包的传送，从而可以防止广播风暴。</p><h2 id="5-子网掩码的作用"><a href="#5-子网掩码的作用" class="headerlink" title="5. 子网掩码的作用"></a>5. 子网掩码的作用</h2><p>内网中192.168.1.199的前三组是网络号，后一组是主机号，子网掩码就是255.255.255.0</p><p><strong>首先要说明的是</strong>：不是某个IP的网络号和主机号决定子网掩码是什么，而是子网掩码决定了某个IP地址的网络号与主机号是什么，IP地址是要搭配子网掩码使用的。例如上面的子网掩码决定了192.168.1.199的前三段192.168.1是网络号，最后一段199是主机号。</p><p>我们再来理解子网掩码的作用，先举个例子，市面上的两个厂家都生产电子秤，每个厂家都坚称他们的秤最准，那你是怎么知道他们的秤到底准不准？很简单，你去找一个 1KG 的国际千克原器，各放到他们的秤上测量，如果秤的测量值是1KG，那这把秤就是准的，<strong>子网掩码的作用就相当于这个大家公认的国际千克原器，是我们测量两个IP是否属于同一个网段的一个工具（应该说是让你知道某个IP地址的网络号与主机号分别是什么） 。</strong></p><p><strong>如果让你判断一个IP地址：192.168.1.199的网络号和主机号分别是什么？</strong></p><p>请问你怎么判断？你凭什么说192.168.1是网络号？199是主机号？有什么根据吗？</p><p>但是如果我给你一个IP地址是以下（带子网掩码）形式的：</p><p>IP：192.168.1.199</p><p>子网掩码：255.255.255.0</p><p>那么根据大家公认的规则，你就可以得出这个IP的网络号和主机号了，怎么算呢？</p><p>子网掩码的长度和IP地址一样也是一串32位的二进制数字，只不过为人类的可读性和记忆性的方便，通常使用十进制数字来表示，例如把上面的IP地址和子网掩码都转换成相应的二进制就是下面这样的：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">**十进制**                                                   **二进制**</span><br></pre></td></tr></table></figure><p>IP 地址：192.168.1.199 <strong>‐＞</strong>11000000.10101000.00000001.11000111</p><p>子网掩码：255.255.255.0 <strong>‐＞</strong>11111111.11111111.11111111.00000000</p><p>十进制的显示形式是给人看的，二进制的显示形式是给计算机看的。。。</p><p>子网掩码的左边是网络位，用二进制数字“1”表示，1的数目等于网络位的长度；右边是主机位，用二进制数字“0”表示，0的数目等于主机位的长度。</p><p>例如上面的子网掩码255.255.255.0的 “1”的个数是左边24位，则对应IP地址左边的位数也是24位;</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">**十进制**                                                   **二进制**</span><br></pre></td></tr></table></figure><p>IP 地址：192.168.1.199 <strong>‐＞11000000.10101000.00000001</strong>.11000111</p><p>子网掩码：255.255.255.0 <strong>‐＞11111111.11111111.11111111</strong>.00000000</p><p>则这个IP地址的网络号就是11000000.10101000.00000001 ，转换成十进制就是 192.168.1，网掩码255.255.255.0的 “0”的个数是右边8位，则这个IP地址的主机号就是11000111，转换成十进制就是199.</p><h1 id="附录：参考资料"><a href="#附录：参考资料" class="headerlink" title="附录：参考资料"></a>附录：参考资料</h1><ul><li><a href="http://www.jikexueyuan.com/course/1400.html">OSI 七层参考模型-极客学院（4课时，47分钟）</a></li><li>《计算机网络原理 创新教程》（韩立刚主编）</li><li>《自顶向下计算机网络》（第4版）</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;第一部分：传输层&quot;&gt;&lt;a href=&quot;#第一部分：传输层&quot; class=&quot;headerlink&quot; title=&quot;第一部分：传输层&quot;&gt;&lt;/a&gt;第一部分：传输层&lt;/h1&gt;&lt;h2 id=&quot;1-说一下OSI七层模型-TCP-IP四层模型-五层协议&quot;&gt;&lt;a href=&quot;#1</summary>
      
    
    
    
    <category term="基础" scheme="http://example.com/categories/%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="计算机网络" scheme="http://example.com/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>MySQL · 最佳实践 · 如何索引JSON字段</title>
    <link href="http://example.com/2023/03/03/MySQL%20%C2%B7%20%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%20%C2%B7%20%E5%A6%82%E4%BD%95%E7%B4%A2%E5%BC%95JSON%E5%AD%97%E6%AE%B5/"/>
    <id>http://example.com/2023/03/03/MySQL%20%C2%B7%20%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%20%C2%B7%20%E5%A6%82%E4%BD%95%E7%B4%A2%E5%BC%95JSON%E5%AD%97%E6%AE%B5/</id>
    <published>2023-03-03T10:06:10.000Z</published>
    <updated>2023-06-06T15:56:09.499Z</updated>
    
    <content type="html"><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>MySQL从5.7.8起开始支持JSON字段，这极大的丰富了MySQL的数据类型。也方便了广大开发人员。但MySQL并没有提供对JSON对象中的字段进行索引的功能，至少没有直接对其字段进行索引的方法。本文将介绍利用MySQL 5.7中的虚拟字段的功能来对JSON对象中的字段进行索引。</p><h3 id="示例数据"><a href="#示例数据" class="headerlink" title="示例数据"></a>示例数据</h3><p>我们将基于下面的JSON对象进行演示</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">&#123;</span><br><span class="line">    &quot;id&quot;: 1,  </span><br><span class="line">    &quot;name&quot;: &quot;Sally&quot;,  </span><br><span class="line">    &quot;games_played&quot;:&#123;    </span><br><span class="line">       &quot;Battlefield&quot;: &#123;</span><br><span class="line">          &quot;weapon&quot;: &quot;sniper rifle&quot;,</span><br><span class="line">          &quot;rank&quot;: &quot;Sergeant V&quot;,</span><br><span class="line">          &quot;level&quot;: 20</span><br><span class="line">        &#125;,                                                                                                                          </span><br><span class="line">       &quot;Crazy Tennis&quot;: &#123;</span><br><span class="line">          &quot;won&quot;: 4,</span><br><span class="line">          &quot;lost&quot;: 1</span><br><span class="line">        &#125;,  </span><br><span class="line">       &quot;Puzzler&quot;: &#123;</span><br><span class="line">          &quot;time&quot;: 7</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>表的基本结构</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">CREATE TABLE &#96;players&#96; (  </span><br><span class="line">    &#96;id&#96; INT UNSIGNED NOT NULL,</span><br><span class="line">    &#96;player_and_games&#96; JSON NOT NULL,</span><br><span class="line">    PRIMARY KEY (&#96;id&#96;)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>如果只是基于上面的表的结构我们是无法对JSON字段中的Key进行索引的。接下来我们演示如何借助虚拟字段对其进行索引</p><h3 id="增加虚拟字段"><a href="#增加虚拟字段" class="headerlink" title="增加虚拟字段"></a>增加虚拟字段</h3><p>虚拟列语法如下</p><figure class="highlight plain"><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">&lt;type&gt; [ GENERATED ALWAYS ] AS ( &lt;expression&gt; ) [ VIRTUAL|STORED ]</span><br><span class="line">[ UNIQUE [KEY] ] [ [PRIMARY] KEY ] [ NOT NULL ] [ COMMENT &lt;text&gt; ]</span><br></pre></td></tr></table></figure><p>在MySQL 5.7中，支持两种Generated Column，即Virtual Generated Column和Stored Generated Column，前者只将Generated Column保存在数据字典中（表的元数据），并不会将这一列数据持久化到磁盘上；后者会将Generated Column持久化到磁盘上，而不是每次读取的时候计算所得。很明显，后者存放了可以通过已有数据计算而得的数据，需要更多的磁盘空间，与Virtual Column相比并没有优势，因此，MySQL 5.7中，不指定Generated Column的类型，默认是Virtual Column。</p><p>如果需要Stored Generated Golumn的话，可能在Virtual Generated Column上建立索引更加合适，一般情况下，都使用Virtual Generated Column，这也是MySQL默认的方式</p><p>加完虚拟列的建表语句如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">CREATE TABLE &#96;players&#96; (  </span><br><span class="line">   &#96;id&#96; INT UNSIGNED NOT NULL,</span><br><span class="line">   &#96;player_and_games&#96; JSON NOT NULL,</span><br><span class="line">   &#96;names_virtual&#96; VARCHAR(20) GENERATED ALWAYS AS (&#96;player_and_games&#96; -&gt;&gt; &#39;$.name&#39;) NOT NULL, </span><br><span class="line">   PRIMARY KEY (&#96;id&#96;)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>Note: 利用操作符-» 来引用JSON字段中的KEY。在本例中字段names_virtual为虚拟字段，我把它定义成不可以为空。在实际的工作中，一定要集合具体的情况来定。因为JSON本身是一种弱结构的数据对象。也就是说的它的结构不是固定不变的。</p><p>我们插入数据</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">INSERT INTO &#96;players&#96; (&#96;id&#96;, &#96;player_and_games&#96;) VALUES (1, &#39;&#123;  </span><br><span class="line">    &quot;id&quot;: 1,  </span><br><span class="line">    &quot;name&quot;: &quot;Sally&quot;,</span><br><span class="line">    &quot;games_played&quot;:&#123;    </span><br><span class="line">       &quot;Battlefield&quot;: &#123;</span><br><span class="line">          &quot;weapon&quot;: &quot;sniper rifle&quot;,</span><br><span class="line">          &quot;rank&quot;: &quot;Sergeant V&quot;,</span><br><span class="line">          &quot;level&quot;: 20</span><br><span class="line">        &#125;,                                                                                                                          </span><br><span class="line">       &quot;Crazy Tennis&quot;: &#123;</span><br><span class="line">          &quot;won&quot;: 4,</span><br><span class="line">          &quot;lost&quot;: 1</span><br><span class="line">        &#125;,  </span><br><span class="line">       &quot;Puzzler&quot;: &#123;</span><br><span class="line">          &quot;time&quot;: 7</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">   &#125;&#39;</span><br><span class="line">);</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>查看表里的数据</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">SELECT * FROM &#96;players&#96;;</span><br><span class="line"></span><br><span class="line">+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+</span><br><span class="line">| id | player_and_games                                                                                                                                                                                           | names_virtual |</span><br><span class="line">+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+</span><br><span class="line">|  1 | &#123;&quot;id&quot;: 1, &quot;name&quot;: &quot;Sally&quot;, &quot;games_played&quot;: &#123;&quot;Puzzler&quot;: &#123;&quot;time&quot;: 7&#125;, &quot;Battlefield&quot;: &#123;&quot;rank&quot;: &quot;Sergeant V&quot;, &quot;level&quot;: 20, &quot;weapon&quot;: &quot;sniper rifle&quot;&#125;, &quot;Crazy Tennis&quot;: &#123;&quot;won&quot;: 4, &quot;lost&quot;: 1&#125;&#125;&#125;                  | Sally         |</span><br><span class="line">|  2 | &#123;&quot;id&quot;: 2, &quot;name&quot;: &quot;Thom&quot;, &quot;games_played&quot;: &#123;&quot;Puzzler&quot;: &#123;&quot;time&quot;: 25&#125;, &quot;Battlefield&quot;: &#123;&quot;rank&quot;: &quot;Major General VIII&quot;, &quot;level&quot;: 127, &quot;weapon&quot;: &quot;carbine&quot;&#125;, &quot;Crazy Tennis&quot;: &#123;&quot;won&quot;: 10, &quot;lost&quot;: 30&#125;&#125;&#125;            | Thom          |</span><br><span class="line">|  3 | &#123;&quot;id&quot;: 3, &quot;name&quot;: &quot;Ali&quot;, &quot;games_played&quot;: &#123;&quot;Puzzler&quot;: &#123;&quot;time&quot;: 12&#125;, &quot;Battlefield&quot;: &#123;&quot;rank&quot;: &quot;First Sergeant II&quot;, &quot;level&quot;: 37, &quot;weapon&quot;: &quot;machine gun&quot;&#125;, &quot;Crazy Tennis&quot;: &#123;&quot;won&quot;: 30, &quot;lost&quot;: 21&#125;&#125;&#125;           | Ali           |</span><br><span class="line">|  4 | &#123;&quot;id&quot;: 4, &quot;name&quot;: &quot;Alfred&quot;, &quot;games_played&quot;: &#123;&quot;Puzzler&quot;: &#123;&quot;time&quot;: 10&#125;, &quot;Battlefield&quot;: &#123;&quot;rank&quot;: &quot;Chief Warrant Officer Five III&quot;, &quot;level&quot;: 73, &quot;weapon&quot;: &quot;pistol&quot;&#125;, &quot;Crazy Tennis&quot;: &#123;&quot;won&quot;: 47, &quot;lost&quot;: 2&#125;&#125;&#125; | Alfred        |</span><br><span class="line">|  5 | &#123;&quot;id&quot;: 5, &quot;name&quot;: &quot;Phil&quot;, &quot;games_played&quot;: &#123;&quot;Puzzler&quot;: &#123;&quot;time&quot;: 7&#125;, &quot;Battlefield&quot;: &#123;&quot;rank&quot;: &quot;Lt. Colonel III&quot;, &quot;level&quot;: 98, &quot;weapon&quot;: &quot;assault rifle&quot;&#125;, &quot;Crazy Tennis&quot;: &#123;&quot;won&quot;: 130, &quot;lost&quot;: 75&#125;&#125;&#125;          | Phil          |</span><br><span class="line">|  6 | &#123;&quot;id&quot;: 6, &quot;name&quot;: &quot;Henry&quot;, &quot;games_played&quot;: &#123;&quot;Puzzler&quot;: &#123;&quot;time&quot;: 17&#125;, &quot;Battlefield&quot;: &#123;&quot;rank&quot;: &quot;Captain II&quot;, &quot;level&quot;: 87, &quot;weapon&quot;: &quot;assault rifle&quot;&#125;, &quot;Crazy Tennis&quot;: &#123;&quot;won&quot;: 68, &quot;lost&quot;: 149&#125;&#125;&#125;             | Henry         |</span><br><span class="line">+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+</span><br></pre></td></tr></table></figure><p>查看表Players的字段</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">SHOW COLUMNS FROM &#96;players&#96;;</span><br><span class="line"></span><br><span class="line">+------------------+------------------+------+-----+---------+-------------------+</span><br><span class="line">| Field            | Type             | Null | Key | Default | Extra             |</span><br><span class="line">+------------------+------------------+------+-----+---------+-------------------+</span><br><span class="line">| id               | int(10) unsigned | NO   | PRI | NULL    |                   |</span><br><span class="line">| player_and_games | json             | NO   |     | NULL    |                   |</span><br><span class="line">| names_virtual    | varchar(20)      | NO   |     | NULL    | VIRTUAL GENERATED |</span><br><span class="line">+------------------+------------------+------+-----+---------+-------------------+</span><br></pre></td></tr></table></figure><p>我们看到虚拟字段names_virtual的类型是VIRTUAL GENERATED。MySQL只是在数据字典里保存该字段元数据，并没有真正的存储该字段的值。这样表的大小并没有增加。我们可以利用索引把这个字段上的值进行物理存储。</p><h3 id="在虚拟字段上加索引"><a href="#在虚拟字段上加索引" class="headerlink" title="在虚拟字段上加索引"></a>在虚拟字段上加索引</h3><p>再添加索引之前，让我们先看下面查询的执行计划</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">EXPLAIN SELECT * FROM &#96;players&#96; WHERE &#96;names_virtual&#96; &#x3D; &quot;Sally&quot;\G  </span><br><span class="line">*************************** 1. row ***************************</span><br><span class="line">           id: 1</span><br><span class="line">  select_type: SIMPLE</span><br><span class="line">        table: players</span><br><span class="line">   partitions: NULL</span><br><span class="line">         type: ALL</span><br><span class="line">possible_keys: NULL  </span><br><span class="line">          key: NULL</span><br><span class="line">      key_len: NULL</span><br><span class="line">          ref: NULL</span><br><span class="line">         rows: 6</span><br><span class="line">     filtered: 16.67</span><br><span class="line">        Extra: Using where</span><br></pre></td></tr></table></figure><p>添加索引</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CREATE INDEX &#96;names_idx&#96; ON &#96;players&#96;(&#96;names_virtual&#96;);  </span><br></pre></td></tr></table></figure><p>再执行上面的查询语句，我们将得到不一样的执行计划</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">EXPLAIN SELECT * FROM &#96;players&#96; WHERE &#96;names_virtual&#96; &#x3D; &quot;Sally&quot;\G  </span><br><span class="line">*************************** 1. row ***************************</span><br><span class="line">           id: 1</span><br><span class="line">  select_type: SIMPLE</span><br><span class="line">        table: players</span><br><span class="line">   partitions: NULL</span><br><span class="line">         type: ref</span><br><span class="line">possible_keys: names_idx  </span><br><span class="line">          key: names_idx</span><br><span class="line">      key_len: 22</span><br><span class="line">          ref: const</span><br><span class="line">         rows: 1</span><br><span class="line">     filtered: 100.00</span><br><span class="line">        Extra: NULL</span><br></pre></td></tr></table></figure><p>如我们所见，最新的执行计划走了新建的索引。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>本文介绍了如何在MySQL 5.7中保存JSON文档。为了高效的检索JSON中内容，我们可以利用5.7的虚拟字段来对JSON的不同的KEY来建索引。极大的提高检索的速度。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;概述&quot;&gt;&lt;a href=&quot;#概述&quot; class=&quot;headerlink&quot; title=&quot;概述&quot;&gt;&lt;/a&gt;概述&lt;/h2&gt;&lt;p&gt;MySQL从5.7.8起开始支持JSON字段，这极大的丰富了MySQL的数据类型。也方便了广大开发人员。但MySQL并没有提供对JSON对象</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>MYSQL中的json数据操作</title>
    <link href="http://example.com/2023/02/06/MYSQL%E4%B8%AD%E7%9A%84json%E6%95%B0%E6%8D%AE%E6%93%8D%E4%BD%9C/"/>
    <id>http://example.com/2023/02/06/MYSQL%E4%B8%AD%E7%9A%84json%E6%95%B0%E6%8D%AE%E6%93%8D%E4%BD%9C/</id>
    <published>2023-02-06T10:31:29.000Z</published>
    <updated>2023-06-06T15:56:09.499Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>mysql5.7以上提供了一种新的字段格式-json，大概是mysql想把非关系型和关系型数据库一口通吃，所以推出了这种非常好用的格式，这样，我们的很多基于mongoDb或者clickHouse的业务都可以用mysql去实现了。当然了，5.7的版本只是最基础的版本，对于海量数据的效率是远远不够的，不过这些都在mysql8.0解决了。今天我们就针对mysql的json数据格式操作做一个简单的介绍。</p></blockquote><h2 id="1-2-基础查询操作"><a href="#1-2-基础查询操作" class="headerlink" title="1.2 基础查询操作"></a>1.2 基础查询操作</h2><p>用法提示：</p><ul><li>如果<code>json</code>字符串不是数组，则直接使用<code>$.字段名</code></li><li>如果<code>json</code>字符串是数组<code>[Array]</code>，则直接使用<code>$[对应元素的索引id]</code></li></ul><h3 id="1-2-1-一般json查询"><a href="#1-2-1-一般json查询" class="headerlink" title="1.2.1 一般json查询"></a>1.2.1 一般json查询</h3><p>使用 <code>json字段名-&gt;’$.json属性’</code> 进行查询条件,注意：如果 ‘-&gt;’ 不能用也可用 ‘-&gt;&gt;’ 查询<br>举个例子：如果想查询deptLeader=张五的数据，那么sql语句如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * from dept WHERE json_value-&gt;&#39;$.deptLeaderId&#39;&#x3D;&#39;5&#39;;</span><br></pre></td></tr></table></figure><h3 id="1-2-2-多个条件查询"><a href="#1-2-2-多个条件查询" class="headerlink" title="1.2.2 多个条件查询"></a>1.2.2 多个条件查询</h3><p>比如想查dept为“部门3”和deptLeaderId=5的数据，sql如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * from dept WHERE json_value-&gt;&#39;$.deptLeaderId&#39;&#x3D;&#39;5&#39; and json_value-&gt;&#39;$.deptId&#39;&#x3D;&#39;5&#39;;</span><br></pre></td></tr></table></figure><h3 id="1-2-3-json中多个字段关系查询"><a href="#1-2-3-json中多个字段关系查询" class="headerlink" title="1.2.3 json中多个字段关系查询"></a>1.2.3 json中多个字段关系查询</h3><p>比如想查询json格式中deptLeader=张五和deptId=5的数据</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * from dept WHERE json_value-&gt;&#39;$.deptLeaderId&#39;&#x3D;&#39;5&#39; and json_value-&gt;&#39;$.deptId&#39;&#x3D;&#39;5&#39;;</span><br></pre></td></tr></table></figure><h3 id="1-2-4-关联表查询"><a href="#1-2-4-关联表查询" class="headerlink" title="1.2.4 关联表查询"></a>1.2.4 关联表查询</h3><p>这里我们要连表查询在dept 表中部门leader在dept_leader 中的详情</p><figure class="highlight plain"><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">SELECT * from dept,dept_leader </span><br><span class="line">WHERE dept.json_value-&gt;&#39;$.deptLeaderId&#39;&#x3D;dept_leader.json_value-&gt;&#39;$.id&#39; ;</span><br></pre></td></tr></table></figure><h2 id="1-3-JSON函数操作"><a href="#1-3-JSON函数操作" class="headerlink" title="1.3 JSON函数操作"></a>1.3 JSON函数操作</h2><p>写到这里大家都发现了，我们查询的json都是整条json数据，这样看起来不是很方便，那么如果我们只想看json中的某个字段怎么办？</p><h3 id="1-3-1-官方json函数"><a href="#1-3-1-官方json函数" class="headerlink" title="1.3.1 官方json函数"></a>1.3.1 官方json函数</h3><table><thead><tr><th>Name</th><th>Description</th><th>解释</th></tr></thead><tbody><tr><td>-&gt;</td><td>Return value from JSON column after evaluating path; equivalent to JSON_EXTRACT()</td><td>计算路径后返回JSON列的值;相当于JSON_EXTRACT ()</td></tr><tr><td>-&gt;&gt;</td><td>Return value from JSON column after evaluating path and unquoting the result; equivalent to JSON_UNQUOTE(JSON_EXTRACT()).</td><td>从JSON列返回值后，就算路径和取消引号的结果;相当于JSON_UNQUOTE (JSON_EXTRACT ())</td></tr><tr><td>JSON_ARRAY()</td><td>Create JSON array</td><td>创建JSON数组</td></tr><tr><td>JSON_ARRAY_APPEND()</td><td>Append data to JSON document</td><td>向JSON文档追加数据</td></tr><tr><td>JSON_ARRAY_INSERT()</td><td>Insert into JSON array</td><td>插入JSON数组</td></tr><tr><td>JSON_CONTAINS()</td><td>Whether JSON document contains specific object at path</td><td>JSON文档是否包含路径上的特定对象</td></tr><tr><td>JSON_CONTAINS_PATH()</td><td>Whether JSON document contains any data at path</td><td>JSON文档是否在路径上包含任何数据</td></tr><tr><td>JSON_DEPTH()</td><td>Maximum depth of JSON document</td><td>JSON文档的最大深度</td></tr><tr><td>JSON_EXTRACT()</td><td>Return data from JSON document</td><td>从JSON文档返回数据</td></tr><tr><td>JSON_INSERT()</td><td>Insert data into JSON document</td><td>将数据插入JSON文档</td></tr><tr><td>JSON_KEYS()</td><td>Array of keys from JSON document</td><td>来自JSON文档的键数组</td></tr><tr><td>JSON_LENGTH()</td><td>Number of elements in JSON document</td><td>JSON文档中的元素数量</td></tr><tr><td>JSON_MERGE_PATCH()</td><td>Merge JSON documents, replacing values of duplicate keys</td><td>合并JSON文档，替换重复键的值</td></tr><tr><td>JSON_MERGE_PRESERVE()</td><td>Merge JSON documents, preserving duplicate keys</td><td>合并JSON文档，保留重复的密钥</td></tr><tr><td>JSON_OBJECT()</td><td>Create JSON object</td><td>创建JSON对象</td></tr><tr><td>JSON_OVERLAPS()</td><td>Compares two JSON documents, returns TRUE (1) if these have any key-value pairs or array elements in common, otherwise FALSE (0)</td><td>比较两个JSON文档，如果它们有共同的键值对或数组元素，则返回TRUE(1)，否则返回FALSE (0)</td></tr><tr><td>JSON_PRETTY()</td><td>Print a JSON document in human-readable format</td><td>以人类可读的格式打印JSON文档</td></tr><tr><td>JSON_QUOTE()</td><td>Quote JSON document</td><td>引用JSON文档</td></tr><tr><td>JSON_REMOVE()</td><td>Remove data from JSON document</td><td>从JSON文档中删除数据</td></tr><tr><td>JSON_REPLACE()</td><td>Replace values in JSON document</td><td>替换JSON文档中的值</td></tr><tr><td>JSON_SCHEMA_VALID()</td><td>Validate JSON document against JSON schema; returns TRUE/1 if document validates against schema, or FALSE/0 if it does not</td><td>针对JSON模式验证JSON文档;如果文档针对模式进行验证，则返回TRUE/1，否则返回FALSE/0</td></tr><tr><td>JSON_SCHEMA_VALIDATION_REPORT()</td><td>Validate JSON document against JSON schema; returns report in JSON format on outcome on validation including success or failure and reasons for failure</td><td>针对JSON模式验证JSON文档;以JSON格式返回关于验证结果的报告，包括成功或失败以及失败原因</td></tr><tr><td>JSON_SEARCH()</td><td>Path to value within JSON document</td><td>JSON文档中值的路径</td></tr><tr><td>JSON_SET()</td><td>Insert data into JSON document</td><td>将数据插入JSON文档</td></tr><tr><td>JSON_STORAGE_FREE()</td><td>Freed space within binary representation of JSON column value following partial update</td><td>在部分更新后释放JSON列值的二进制表示形式中的空间</td></tr><tr><td>JSON_STORAGE_SIZE()</td><td>pace used for storage of binary representation of a JSON document</td><td>用于存储JSON文档的二进制表示的空间</td></tr><tr><td>JSON_TABLE()</td><td>Return data from a JSON expression as a relational table</td><td>以关系表的形式从JSON表达式返回数据</td></tr><tr><td>JSON_TYPE()</td><td>Type of JSON value</td><td>JSON值类型</td></tr><tr><td>JSON_UNQUOTE()</td><td>Unquote JSON value</td><td>不引用JSON值</td></tr><tr><td>JSON_VALID()</td><td>Whether JSON value is valid</td><td>JSON值是否有效</td></tr><tr><td>JSON_VALUE()</td><td>Extract value from JSON document at location pointed to by path provided; return this value as VARCHAR(512) or specified type</td><td>根据所提供的路径从JSON文档中所指向的位置提取值;返回该值为VARCHAR(512)或指定的类型</td></tr><tr><td>MEMBER OF()</td><td>Returns true (1) if first operand matches any element of JSON array passed as second operand, otherwise returns false (0)</td><td>如果第一个操作数匹配作为第二个操作数的JSON数组中的任何元素，则返回true(1)，否则返回false (0)</td></tr></tbody></table><h3 id="1-3-2-gt-、-gt-gt-区别"><a href="#1-3-2-gt-、-gt-gt-区别" class="headerlink" title="1.3.2 -&gt;、-&gt;&gt;区别"></a>1.3.2 -&gt;、-&gt;&gt;区别</h3><p><code>-&gt;</code>在<code>field</code>中使用的时候结果带引号，<code>-&gt;&gt;</code>的结果不带引号</p><h4 id="1-3-2-2-在where条件中使用"><a href="#1-3-2-2-在where条件中使用" class="headerlink" title="1.3.2.2 在where条件中使用"></a>1.3.2.2 在where条件中使用</h4><p>特别注意：<code>-&gt;</code>当做<code>where</code>查询是要注意类型的，<code>-&gt;&gt;</code>是不用注意类型的</p><h3 id="1-3-3-json-extract-从json中返回想要的字段"><a href="#1-3-3-json-extract-从json中返回想要的字段" class="headerlink" title="1.3.3 json_extract():从json中返回想要的字段"></a>1.3.3 json_extract():从json中返回想要的字段</h3><p>用法：<code>json_extract(字段名,$.json字段名)</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select id,json_extract(json_value,&#39;$.deptName&#39;) as deptName from dept;</span><br></pre></td></tr></table></figure><h3 id="1-3-4-JSON-CONTAINS-JSON格式数据是否在字段中包含特定对象"><a href="#1-3-4-JSON-CONTAINS-JSON格式数据是否在字段中包含特定对象" class="headerlink" title="1.3.4 JSON_CONTAINS():JSON格式数据是否在字段中包含特定对象"></a>1.3.4 JSON_CONTAINS():JSON格式数据是否在字段中包含特定对象</h3><p>用法: <code>JSON_CONTAINS(target, candidate[, path])</code><br>事例:如果我们想查询包含<code>deptName=部门5</code>的对象</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select * from dept WHERE JSON_CONTAINS(json_value, JSON_OBJECT(&quot;deptName&quot;,&quot;部门5&quot;))</span><br></pre></td></tr></table></figure><h3 id="1-3-5-JSON-OBJECT-将一个键值对列表转换成json对象"><a href="#1-3-5-JSON-OBJECT-将一个键值对列表转换成json对象" class="headerlink" title="1.3.5 JSON_OBJECT():将一个键值对列表转换成json对象"></a>1.3.5 JSON_OBJECT():将一个键值对列表转换成json对象</h3><p>比如我们想查询某个对象里面的值等于多少<br>比如我们添加这么一组数据到dept表中：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">insert</span> <span class="keyword">into</span> dept <span class="keyword">VALUES</span>(<span class="number">6</span>,<span class="string">&#x27;部门9&#x27;</span>,<span class="string">&#x27;&#123;&quot;deptName&quot;: &#123;&quot;dept&quot;:&quot;de&quot;,&quot;depp&quot;:&quot;dd&quot;&#125;, &quot;deptId&quot;: &quot;5&quot;, &quot;deptLeaderId&quot;: &quot;5&quot;&#125;&#x27;</span>);</span><br></pre></td></tr></table></figure><p>我们可以看到<code>deptName</code>中还有一个对象，里面还有dept和depp两个属性字段，那么我们应该怎么查询depp=dd的员工呢。</p><p>用法：<code>JSON_OBJECT([key, val[, key, val] …])</code><br>事例：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">SELECT * from (</span><br><span class="line">    SELECT *,json_value-&gt;&#39;$.deptName&#39; as deptName FROM dept</span><br><span class="line">) t WHERE JSON_CONTAINS(deptName,JSON_OBJECT(&quot;depp&quot;,&quot;dd&quot;));</span><br></pre></td></tr></table></figure><h3 id="1-3-6-JSON-ARRAY-创建JSON数组"><a href="#1-3-6-JSON-ARRAY-创建JSON数组" class="headerlink" title="1.3.6 JSON_ARRAY():创建JSON数组"></a>1.3.6 JSON_ARRAY():创建JSON数组</h3><p>比如我们添加这么一组数据到dept表中：</p><figure class="highlight sql"><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="keyword">insert</span> <span class="keyword">into</span> dept <span class="keyword">VALUES</span>(<span class="number">7</span>,<span class="string">&#x27;部门9&#x27;</span>,<span class="string">&#x27;&#123;&quot;deptName&quot;: [&quot;1&quot;,&quot;2&quot;,&quot;3&quot;], &quot;deptId&quot;: &quot;5&quot;, &quot;deptLeaderId&quot;: &quot;5&quot;&#125;&#x27;</span>);</span><br><span class="line"><span class="keyword">insert</span> <span class="keyword">into</span> dept <span class="keyword">VALUES</span>(<span class="number">7</span>,<span class="string">&#x27;部门9&#x27;</span>,<span class="string">&#x27;&#123;&quot;deptName&quot;: [&quot;5&quot;,&quot;6&quot;,&quot;7&quot;], &quot;deptId&quot;: &quot;5&quot;, &quot;deptLeaderId&quot;: &quot;5&quot;&#125;&#x27;</span>);</span><br></pre></td></tr></table></figure><p>用法：<code>JSON_ARRAY([val[, val] …])</code></p><p>事例：我们要查询deptName包含1的数据</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * from dept WHERE JSON_CONTAINS(json_value-&gt;&#39;$.deptName&#39;,JSON_ARRAY(&quot;1&quot;))</span><br></pre></td></tr></table></figure><h3 id="1-3-7-JSON-TYPE-查询某个json字段属性类型"><a href="#1-3-7-JSON-TYPE-查询某个json字段属性类型" class="headerlink" title="1.3.7 JSON_TYPE():查询某个json字段属性类型"></a>1.3.7 JSON_TYPE():查询某个json字段属性类型</h3><p>用法：<code>JSON_TYPE(json_val)</code><br>事例：比如我们想查询deptName的字段属性是什么</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT json_value-&gt;&#39;$.deptName&#39; ,JSON_TYPE(json_value-&gt;&#39;$.deptName&#39;) as type from dept </span><br></pre></td></tr></table></figure><h3 id="1-3-8-JSON-KEYS-JSON文档中的键数组"><a href="#1-3-8-JSON-KEYS-JSON文档中的键数组" class="headerlink" title="1.3.8 JSON_KEYS():JSON文档中的键数组"></a>1.3.8 JSON_KEYS():JSON文档中的键数组</h3><p>用法：<code>JSON_KEYS(json_value)</code><br>事例：比如我们想查询json格式数据中的所有key</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT JSON_KEYS(json_value) FROM dept </span><br></pre></td></tr></table></figure><p>接下来的3种函数都是新增数据类型的：<br>JSON_SET(json_doc, path, val[, path, val] …)<br>JSON_INSERT(json_doc, path, val[, path, val] …)<br>JSON_REPLACE(json_doc, path, val[, path, val] …)</p><h3 id="1-3-9-JSON-SET-将数据插入JSON格式中，有key则替换，无key则新增"><a href="#1-3-9-JSON-SET-将数据插入JSON格式中，有key则替换，无key则新增" class="headerlink" title="1.3.9 JSON_SET():将数据插入JSON格式中，有key则替换，无key则新增"></a>1.3.9 JSON_SET():将数据插入JSON格式中，有key则替换，无key则新增</h3><p>这也是我们开发过程中经常会用到的一个函数<br>用法：<code>JSON_SET(json_doc, path, val[, path, val] …)</code><br>事例：比如我们想针对id=2的数据新增一组：newData:新增的数据,修改deptName为新增的部门1<br>sql语句如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">update dept set json_value&#x3D;JSON_SET(&#39;&#123;&quot;deptName&quot;: &quot;部门2&quot;, &quot;deptId&quot;: &quot;2&quot;, &quot;deptLeaderId&quot;: &quot;4&quot;&#125;&#39;,&#39;$.deptName&#39;,&#39;新增的部门1&#39;,&#39;$.newData&#39;,&#39;新增的数据&#39;) WHERE id&#x3D;2;</span><br></pre></td></tr></table></figure><p><code>注意</code>：<code>json_doc</code>如果不带这个单元格之前的值，之前的值是会新值被覆盖的，比如我们如果更新的语句换成:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">update dept set json_value&#x3D;JSON_SET(&#39;&#123;&quot;a&quot;:&quot;1&quot;,&quot;b&quot;:&quot;2&quot;&#125;&#39;,&#39;$.deptName&#39;,&#39;新增的部门1&#39;,&#39;$.newData&#39;,&#39;新增的数据&#39;) WHERE id&#x3D;2我们可以看到这里json_doc是&#123;“a”:“1”,“b”:“2”&#125;，这样的话会把之前的单元格值覆盖后再新增&#x2F;覆盖这个单元格字段</span><br></pre></td></tr></table></figure><h3 id="1-3-10-JSON-INSERT-插入值（往json中插入新值，但不替换已经存在的旧值）"><a href="#1-3-10-JSON-INSERT-插入值（往json中插入新值，但不替换已经存在的旧值）" class="headerlink" title="1.3.10 JSON_INSERT():插入值（往json中插入新值，但不替换已经存在的旧值）"></a>1.3.10 JSON_INSERT():插入值（往json中插入新值，但不替换已经存在的旧值）</h3><p>用法：<code>JSON_INSERT(json_doc, path, val[, path, val] …)</code><br>事例：</p><figure class="highlight plain"><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">UPDATE dept set json_value&#x3D;JSON_INSERT(&#39;&#123;&quot;a&quot;: &quot;1&quot;, &quot;b&quot;: &quot;2&quot;&#125;&#39;, &#39;$.deptName&#39;, &#39;新增的部门2&#39;,&#39;$.newData2&#39;,&#39;新增的数据2&#39;) </span><br><span class="line">WHERE id&#x3D;2</span><br></pre></td></tr></table></figure><p>我们可以看到由于<code>json_doc</code>变化将之前的值覆盖了，新增了<code>deptName</code>和<code>newData2</code>.<br>如果我们再执行以下刚才的那个sql，只是换了value，我们会看到里面的key值不会发生变化。<br>因为这个函数只负责往json中插入新值，但不替换已经存在的旧值。</p><h3 id="1-3-11-JSON-REPLACE"><a href="#1-3-11-JSON-REPLACE" class="headerlink" title="1.3.11 JSON_REPLACE()"></a>1.3.11 JSON_REPLACE()</h3><p>用法：<code>JSON_REPLACE(json_doc, path, val[, path, val] …)</code><br>用例：<br>如果我们要更新id=2数据中newData2的值为：更新的数据2<br>sql语句如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">UPDATE dept set json_value&#x3D;JSON_REPLACE(&#39;&#123;&quot;a&quot;: &quot;1&quot;, &quot;b&quot;: &quot;2&quot;, &quot;deptName&quot;: &quot;新增的部门2&quot;, &quot;newData2&quot;: &quot;新增的数据2&quot;&#125;&#39;, &#39;$.newData2&#39;, &#39;更新的数据2&#39;) WHERE id &#x3D;2;</span><br></pre></td></tr></table></figure><h3 id="1-3-12-JSON-REMOVE-从JSON文档中删除数据"><a href="#1-3-12-JSON-REMOVE-从JSON文档中删除数据" class="headerlink" title="1.3.12 JSON_REMOVE():从JSON文档中删除数据"></a>1.3.12 JSON_REMOVE():从JSON文档中删除数据</h3><p>用法：<code>JSON_REMOVE(json_doc, path[, path] …)</code><br>举例：删除key为a的字段。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">UPDATE dept set json_value&#x3D;JSON_REMOVE(&#39;&#123;&quot;a&quot;: &quot;1&quot;, &quot;b&quot;: &quot;2&quot;, &quot;deptName&quot;: &quot;新增的部门2&quot;, &quot;newData2&quot;: &quot;更新的数据2&quot;&#125;&#39;,&#39;$.a&#39;) WHERE id &#x3D;2;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;mysql5.7以上提供了一种新的字段格式-json，大概是mysql想把非关系型和关系型数据库一口通吃，所以推出了这种非常好用的格式，这样，我们的很多基于mongoDb或者clickHouse的业务都可以用mysql去实现了。当然了，5.7的版本</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Docker Compose 网络设置</title>
    <link href="http://example.com/2023/01/12/Docker%20Compose%20%E7%BD%91%E7%BB%9C%E8%AE%BE%E7%BD%AE/"/>
    <id>http://example.com/2023/01/12/Docker%20Compose%20%E7%BD%91%E7%BB%9C%E8%AE%BE%E7%BD%AE/</id>
    <published>2023-01-12T07:36:26.000Z</published>
    <updated>2023-06-06T15:56:09.499Z</updated>
    
    <content type="html"><![CDATA[<h3 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h3><p>默认情况下，Compose会为我们的应用创建一个网络，服务的每个容器都会加入该网络中。这样，容器就可被该网络中的其他容器访问，不仅如此，该容器还能以服务名称作为hostname被其他容器访问。</p><p>默认情况下，应用程序的网络名称基于Compose的工程名称，而项目名称基于docker-compose.yml所在目录的名称。如需修改工程名称，可使用–project-name标识或COMPOSE_PORJECT_NAME环境变量。</p><p>举个例子，假如一个应用程序在名为myapp的目录中，并且docker-compose.yml如下所示：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">version: &#39;2&#39;</span><br><span class="line">services:</span><br><span class="line">  web:</span><br><span class="line">    build: .</span><br><span class="line">    ports:</span><br><span class="line">      - &quot;8000:8000&quot;</span><br><span class="line">  db:</span><br><span class="line">    image: postgres</span><br></pre></td></tr></table></figure><p>当我们运行docker-compose up时，将会执行以下几步：</p><ul><li>创建一个名为myapp_default的网络；</li><li>使用web服务的配置创建容器，它以“web”这个名称加入网络myapp_default；</li><li>使用db服务的配置创建容器，它以“db”这个名称加入网络myapp_default。</li></ul><p>容器间可使用服务名称（web或db）作为hostname相互访问。例如，web这个服务可使用<code>postgres://db:5432</code> 访问db容器。</p><h3 id="更新容器"><a href="#更新容器" class="headerlink" title="更新容器"></a>更新容器</h3><p>当服务的配置发生更改时，可使用docker-compose up命令更新配置。</p><p>此时，Compose会删除旧容器并创建新容器。新容器会以不同的IP地址加入网络，名称保持不变。任何指向旧容器的连接都会被关闭，容器会重新找到新容器并连接上去。</p><h3 id="links"><a href="#links" class="headerlink" title="links"></a>links</h3><p>前文讲过，默认情况下，服务之间可使用服务名称相互访问。links允许我们定义一个别名，从而使用该别名访问其他服务。举个例子：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">version: &#39;2&#39;</span><br><span class="line">services:</span><br><span class="line">  web:</span><br><span class="line">    build: .</span><br><span class="line">    links:</span><br><span class="line">      - &quot;db:database&quot;</span><br><span class="line">  db:</span><br><span class="line">    image: postgres</span><br></pre></td></tr></table></figure><p>这样web服务就可使用db或database作为hostname访问db服务了。</p><h3 id="指定自定义网络"><a href="#指定自定义网络" class="headerlink" title="指定自定义网络"></a>指定自定义网络</h3><p>一些场景下，默认的网络配置满足不了我们的需求，此时我们可使用networks命令自定义网络。networks命令允许我们创建更加复杂的网络拓扑并指定自定义网络驱动和选项。不仅如此，我们还可使用networks将服务连接到不是由Compose管理的、外部创建的网络。</p><p>如下，我们在其中定义了两个自定义网络。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">version: &#39;2&#39;</span><br><span class="line"></span><br><span class="line">services:</span><br><span class="line">  proxy:</span><br><span class="line">    build: .&#x2F;proxy</span><br><span class="line">    networks:</span><br><span class="line">      - front</span><br><span class="line">  app:</span><br><span class="line">    build: .&#x2F;app</span><br><span class="line">    networks:</span><br><span class="line">      - front</span><br><span class="line">      - back</span><br><span class="line">  db:</span><br><span class="line">    image: postgres</span><br><span class="line">    networks:</span><br><span class="line">      - back</span><br><span class="line"></span><br><span class="line">networks:</span><br><span class="line">  front:</span><br><span class="line">    # Use a custom driver</span><br><span class="line">    driver: custom-driver-1</span><br><span class="line">  back:</span><br><span class="line">    # Use a custom driver which takes special options</span><br><span class="line">    driver: custom-driver-2</span><br><span class="line">    driver_opts:</span><br><span class="line">      foo: &quot;1&quot;</span><br><span class="line">      bar: &quot;2&quot;</span><br></pre></td></tr></table></figure><p>其中，proxy服务与db服务隔离，两者分别使用自己的网络；app服务可与两者通信。</p><p>由本例不难发现，使用networks命令，即可方便实现服务间的网络隔离与连接。</p><h3 id="配置默认网络"><a href="#配置默认网络" class="headerlink" title="配置默认网络"></a>配置默认网络</h3><p>除自定义网络外，我们也可为默认网络自定义配置。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">version: &#39;2&#39;</span><br><span class="line"></span><br><span class="line">services:</span><br><span class="line">  web:</span><br><span class="line">    build: .</span><br><span class="line">    ports:</span><br><span class="line">      - &quot;8000:8000&quot;</span><br><span class="line">  db:</span><br><span class="line">    image: postgres</span><br><span class="line"></span><br><span class="line">networks:</span><br><span class="line">  default:</span><br><span class="line">    # Use a custom driver</span><br><span class="line">    driver: custom-driver-1</span><br></pre></td></tr></table></figure><p>这样，就可为该应用指定自定义的网络驱动。</p><h3 id="使用已存在的网络"><a href="#使用已存在的网络" class="headerlink" title="使用已存在的网络"></a>使用已存在的网络</h3><p>一些场景下，我们并不需要创建新的网络，而只需加入已存在的网络，此时可使用external选项。示例：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">networks:</span><br><span class="line">  default:</span><br><span class="line">    external:</span><br><span class="line">      name: my-pre-existing-network</span><br></pre></td></tr></table></figure><h3 id="Docker-Compose-链接外部容器的几种方式"><a href="#Docker-Compose-链接外部容器的几种方式" class="headerlink" title="Docker Compose 链接外部容器的几种方式"></a>Docker Compose 链接外部容器的几种方式</h3><p>在Docker中，容器之间的链接是一种很常见的操作：它提供了访问其中的某个容器的网络服务而不需要将所需的端口暴露给Docker Host主机的功能。Docker Compose中对该特性的支持同样是很方便的。然而，如果需要链接的容器没有定义在同一个<code>docker-compose.yml</code>中的时候，这个时候就稍微麻烦复杂了点。</p><p>在不使用Docker Compose的时候，将两个容器链接起来使用<code>—link</code>参数，相对来说比较简单，以<code>nginx</code>镜像为例子：</p><figure class="highlight plain"><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">docker run --rm --name test1 -d nginx  #开启一个实例test1</span><br><span class="line">docker run --rm --name test2 --link test1 -d nginx #开启一个实例test2并与test1建立链接</span><br></pre></td></tr></table></figure><p>这样，<code>test2</code>与<code>test1</code>便建立了链接，就可以在<code>test2</code>中使用访问<code>test1</code>中的服务了。</p><p>如果使用Docker Compose，那么这个事情就更简单了，还是以上面的<code>nginx</code>镜像为例子，编辑<code>docker-compose.yml</code>文件为：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">version: &quot;3&quot;</span><br><span class="line">services:</span><br><span class="line">  test2:</span><br><span class="line">    image: nginx</span><br><span class="line">    depends_on:</span><br><span class="line">      - test1</span><br><span class="line">    links:</span><br><span class="line">      - test1</span><br><span class="line">  test1:</span><br><span class="line">    image: nginx</span><br></pre></td></tr></table></figure><p>最终效果与使用普通的Docker命令<code>docker run xxxx</code>建立的链接并无区别。这只是一种最为理想的情况。</p><ol><li>如果容器没有定义在同一个<code>docker-compose.yml</code>文件中，应该如何链接它们呢？</li><li>又如果定义在<code>docker-compose.yml</code>文件中的容器需要与<code>docker run xxx</code>启动的容器链接，需要如何处理？</li></ol><p>针对这两种典型的情况，下面给出我个人测试可行的办法：</p><h3 id="方式一：让需要链接的容器同属一个外部网络"><a href="#方式一：让需要链接的容器同属一个外部网络" class="headerlink" title="方式一：让需要链接的容器同属一个外部网络"></a>方式一：让需要链接的容器同属一个外部网络</h3><p>我们还是使用nginx镜像来模拟这样的一个情景：假设我们需要将两个使用Docker Compose管理的nignx容器（<code>test1</code>和<code>test2</code>）链接起来，使得<code>test2</code>能够访问<code>test1</code>中提供的服务，这里我们以能ping通为准。</p><p>首先，我们定义容器<code>test1</code>的<code>docker-compose.yml</code>文件内容为：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">test2:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">nginx</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">test1</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">default</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">app_net</span></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">app_net:</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>容器<code>test2</code>内容与<code>test1</code>基本一样，只是多了一个<code>external_links</code>,需要特别说明的是：<strong>最近发布的Docker版本已经不需要使用external_links来链接容器，容器的DNS服务可以正确的作出判断</strong>，因此如果你你需要兼容较老版本的Docker的话，那么容器<code>test2</code>的<code>docker-compose.yml</code>文件内容为：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">test2:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">nginx</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">default</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">app_net</span></span><br><span class="line">    <span class="attr">external_links:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">test1</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">test2</span></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">app_net:</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>否则的话，<code>test2</code>的<code>docker-compose.yml</code>和<code>test1</code>的定义完全一致，不需要额外多指定一个<code>external_links</code>。相关的问题请参见stackoverflow上的相关问题：<a href="https://stackoverflow.com/questions/39067295/docker-compose-external-container">docker-compose + external container</a></p><p>正如你看到的那样，这里两个容器的定义里都使用了同一个外部网络<code>app_net</code>,因此，我们需要在启动这两个容器之前通过以下命令再创建外部网络：</p><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">docker network create app_net</span><br></pre></td></tr></table></figure><p>之后，通过<code>docker-compose up -d</code>命令启动这两个容器，然后执行<code>docker exec -it test2 ping test1</code>,你将会看到如下的输出：</p><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></pre></td><td class="code"><pre><span class="line">docker exec -it test2 ping test1</span><br><span class="line">PING test1 (172.18.0.2): 56 data bytes</span><br><span class="line">64 bytes from 172.18.0.2: icmp_seq=0 ttl=64 time=0.091 ms</span><br><span class="line">64 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.146 ms</span><br><span class="line">64 bytes from 172.18.0.2: icmp_seq=2 ttl=64 time=0.150 ms</span><br><span class="line">64 bytes from 172.18.0.2: icmp_seq=3 ttl=64 time=0.145 ms</span><br><span class="line">64 bytes from 172.18.0.2: icmp_seq=4 ttl=64 time=0.126 ms</span><br><span class="line">64 bytes from 172.18.0.2: icmp_seq=5 ttl=64 time=0.147 ms</span><br></pre></td></tr></table></figure><p>证明这两个容器是成功链接了，反过来在<code>test1</code>中ping<code>test2</code>也是能够正常ping通的。</p><p>如果我们通过<code>docker run --rm --name test3 -d nginx</code>这种方式来先启动了一个容器(<code>test3</code>)并且没有指定它所属的外部网络，而需要将其与<code>test1</code>或者<code>test2</code>链接的话，这个时候手动链接外部网络即可：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker network connect app_net test3</span><br></pre></td></tr></table></figure><p>这样，三个容器都可以相互访问了。</p><h3 id="方式二：更改需要链接的容器的网络模式"><a href="#方式二：更改需要链接的容器的网络模式" class="headerlink" title="方式二：更改需要链接的容器的网络模式"></a>方式二：更改需要链接的容器的网络模式</h3><p>通过更改你想要相互链接的容器的网络模式为<code>bridge</code>,并指定需要链接的外部容器（<code>external_links</code>)即可。与同属外部网络的容器可以相互访问的链接方式一不同，这种方式的访问是单向的。</p><p>还是以nginx容器镜像为例子，如果容器实例<code>nginx1</code>需要访问容器实例<code>nginx2</code>，那么<code>nginx2</code>的<code>doker-compose.yml</code>定义为：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">version: &quot;3&quot;</span><br><span class="line">services:</span><br><span class="line">  nginx2:</span><br><span class="line">    image: nginx</span><br><span class="line">    container_name: nginx2</span><br><span class="line">    network_mode: bridge</span><br></pre></td></tr></table></figure><p>与其对应的，<code>nginx1</code>的<code>docker-compose.yml</code>定义为：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">version: &quot;3&quot;</span><br><span class="line">services:</span><br><span class="line">  nginx1:</span><br><span class="line">    image: nginx</span><br><span class="line">    external_links:</span><br><span class="line">      - nginx2</span><br><span class="line">    container_name: nginx1</span><br><span class="line">    network_mode: bridge</span><br></pre></td></tr></table></figure><blockquote><p>需要特别说明的是，这里的<code>external_links</code>是不能省略的，而且<code>nginx1</code>的启动必须要在<code>nginx2</code>之后，否则可能会报找不到容器<code>nginx2</code>的错误。</p></blockquote><p>接着我们使用ping来测试下连通性：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ docker exec -it nginx1 ping nginx2  # nginx1 to nginx2</span><br><span class="line">PING nginx2 (172.17.0.4): 56 data bytes</span><br><span class="line">64 bytes from 172.17.0.4: icmp_seq&#x3D;0 ttl&#x3D;64 time&#x3D;0.141 ms</span><br><span class="line">64 bytes from 172.17.0.4: icmp_seq&#x3D;1 ttl&#x3D;64 time&#x3D;0.139 ms</span><br><span class="line">64 bytes from 172.17.0.4: icmp_seq&#x3D;2 ttl&#x3D;64 time&#x3D;0.145 ms</span><br><span class="line"></span><br><span class="line">$ docker exec -it nginx2 ping nginx1 #nginx2 to nginx1</span><br><span class="line">ping: unknown host</span><br></pre></td></tr></table></figure><p>以上也能充分证明这种方式是属于单向联通的。</p><p>在实际应用中根据自己的需要灵活的选择这两种链接方式，如果想偷懒的话，大可选择第二种。不过我更推荐第一种，不难看出无论是联通性还是灵活性，第一种都比较好。</p><p>附docker-compose.yml文件详解</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br></pre></td><td class="code"><pre><span class="line">Compose和Docker兼容性：</span><br><span class="line">    Compose 文件格式有3个版本,分别为1, 2.x 和 3.x</span><br><span class="line">    目前主流的为 3.x 其支持 docker 1.13.0 及其以上的版本</span><br><span class="line"></span><br><span class="line">常用参数：</span><br><span class="line">    version           # 指定 compose 文件的版本</span><br><span class="line">    services          # 定义所有的 service 信息, services 下面的第一级别的 key 是一个 service 的名称</span><br><span class="line"></span><br><span class="line">        build                 # 指定包含构建上下文的路径, 或作为一个对象，该对象具有 context 和指定的 dockerfile 文件以及 args 参数值</span><br><span class="line">            context               # context: 指定 Dockerfile 文件所在的路径</span><br><span class="line">            dockerfile            # dockerfile: 指定 context 指定的目录下面的 Dockerfile 的名称(默认为 Dockerfile)</span><br><span class="line">            args                  # args: Dockerfile 在 build 过程中需要的参数 (等同于 docker container build --build-arg 的作用)</span><br><span class="line">            cache_from            # v3.2中新增的参数, 指定缓存的镜像列表 (等同于 docker container build --cache_from 的作用)</span><br><span class="line">            labels                # v3.3中新增的参数, 设置镜像的元数据 (等同于 docker container build --labels 的作用)</span><br><span class="line">            shm_size              # v3.5中新增的参数, 设置容器 &#x2F;dev&#x2F;shm 分区的大小 (等同于 docker container build --shm-size 的作用)</span><br><span class="line"></span><br><span class="line">        command               # 覆盖容器启动后默认执行的命令, 支持 shell 格式和 [] 格式</span><br><span class="line"></span><br><span class="line">        configs               # 不知道怎么用</span><br><span class="line"></span><br><span class="line">        cgroup_parent         # 不知道怎么用</span><br><span class="line"></span><br><span class="line">        container_name        # 指定容器的名称 (等同于 docker run --name 的作用)</span><br><span class="line"></span><br><span class="line">        credential_spec       # 不知道怎么用</span><br><span class="line"></span><br><span class="line">        deploy                # v3 版本以上, 指定与部署和运行服务相关的配置, deploy 部分是 docker stack 使用的, docker stack 依赖 docker swarm</span><br><span class="line">            endpoint_mode         # v3.3 版本中新增的功能, 指定服务暴露的方式</span><br><span class="line">                vip                   # Docker 为该服务分配了一个虚拟 IP(VIP), 作为客户端的访问服务的地址</span><br><span class="line">                dnsrr                 # DNS轮询, Docker 为该服务设置 DNS 条目, 使得服务名称的 DNS 查询返回一个 IP 地址列表, 客户端直接访问其中的一个地址</span><br><span class="line">            labels                # 指定服务的标签，这些标签仅在服务上设置</span><br><span class="line">            mode                  # 指定 deploy 的模式</span><br><span class="line">                global                # 每个集群节点都只有一个容器</span><br><span class="line">                replicated            # 用户可以指定集群中容器的数量(默认)</span><br><span class="line">            placement             # 不知道怎么用</span><br><span class="line">            replicas              # deploy 的 mode 为 replicated 时, 指定容器副本的数量</span><br><span class="line">            resources             # 资源限制</span><br><span class="line">                limits                # 设置容器的资源限制</span><br><span class="line">                    cpus: &quot;0.5&quot;           # 设置该容器最多只能使用 50% 的 CPU </span><br><span class="line">                    memory: 50M           # 设置该容器最多只能使用 50M 的内存空间 </span><br><span class="line">                reservations          # 设置为容器预留的系统资源(随时可用)</span><br><span class="line">                    cpus: &quot;0.2&quot;           # 为该容器保留 20% 的 CPU</span><br><span class="line">                    memory: 20M           # 为该容器保留 20M 的内存空间</span><br><span class="line">            restart_policy        # 定义容器重启策略, 用于代替 restart 参数</span><br><span class="line">                condition             # 定义容器重启策略(接受三个参数)</span><br><span class="line">                    none                  # 不尝试重启</span><br><span class="line">                    on-failure            # 只有当容器内部应用程序出现问题才会重启</span><br><span class="line">                    any                   # 无论如何都会尝试重启(默认)</span><br><span class="line">                delay                 # 尝试重启的间隔时间(默认为 0s)</span><br><span class="line">                max_attempts          # 尝试重启次数(默认一直尝试重启)</span><br><span class="line">                window                # 检查重启是否成功之前的等待时间(即如果容器启动了, 隔多少秒之后去检测容器是否正常, 默认 0s)</span><br><span class="line">            update_config         # 用于配置滚动更新配置</span><br><span class="line">                parallelism           # 一次性更新的容器数量</span><br><span class="line">                delay                 # 更新一组容器之间的间隔时间</span><br><span class="line">                failure_action        # 定义更新失败的策略</span><br><span class="line">                    continue              # 继续更新</span><br><span class="line">                    rollback              # 回滚更新</span><br><span class="line">                    pause                 # 暂停更新(默认)</span><br><span class="line">                monitor               # 每次更新后的持续时间以监视更新是否失败(单位: ns|us|ms|s|m|h) (默认为0)</span><br><span class="line">                max_failure_ratio     # 回滚期间容忍的失败率(默认值为0)</span><br><span class="line">                order                 # v3.4 版本中新增的参数, 回滚期间的操作顺序</span><br><span class="line">                    stop-first            #旧任务在启动新任务之前停止(默认)</span><br><span class="line">                    start-first           #首先启动新任务, 并且正在运行的任务暂时重叠</span><br><span class="line">            rollback_config       # v3.7 版本中新增的参数, 用于定义在 update_config 更新失败的回滚策略</span><br><span class="line">                parallelism           # 一次回滚的容器数, 如果设置为0, 则所有容器同时回滚</span><br><span class="line">                delay                 # 每个组回滚之间的时间间隔(默认为0)</span><br><span class="line">                failure_action        # 定义回滚失败的策略</span><br><span class="line">                    continue              # 继续回滚</span><br><span class="line">                    pause                 # 暂停回滚</span><br><span class="line">                monitor               # 每次回滚任务后的持续时间以监视失败(单位: ns|us|ms|s|m|h) (默认为0)</span><br><span class="line">                max_failure_ratio     # 回滚期间容忍的失败率(默认值0)</span><br><span class="line">                order                 # 回滚期间的操作顺序</span><br><span class="line">                    stop-first            # 旧任务在启动新任务之前停止(默认)</span><br><span class="line">                    start-first           # 首先启动新任务, 并且正在运行的任务暂时重叠</span><br><span class="line"></span><br><span class="line">            注意：</span><br><span class="line">                支持 docker-compose up 和 docker-compose run 但不支持 docker stack deploy 的子选项</span><br><span class="line">                security_opt  container_name  devices  tmpfs  stop_signal  links    cgroup_parent</span><br><span class="line">                network_mode  external_links  restart  build  userns_mode  sysctls</span><br><span class="line"></span><br><span class="line">        devices               # 指定设备映射列表 (等同于 docker run --device 的作用)</span><br><span class="line"></span><br><span class="line">        depends_on            # 定义容器启动顺序 (此选项解决了容器之间的依赖关系， 此选项在 v3 版本中 使用 swarm 部署时将忽略该选项)</span><br><span class="line">            示例：</span><br><span class="line">                docker-compose up 以依赖顺序启动服务，下面例子中 redis 和 db 服务在 web 启动前启动</span><br><span class="line">                默认情况下使用 docker-compose up web 这样的方式启动 web 服务时，也会启动 redis 和 db 两个服务，因为在配置文件中定义了依赖关系</span><br><span class="line">                version: &#39;3&#39;</span><br><span class="line">                services:</span><br><span class="line">                    web:</span><br><span class="line">                        build: .</span><br><span class="line">                        depends_on:</span><br><span class="line">                            - db      </span><br><span class="line">                            - redis  </span><br><span class="line">                    redis:</span><br><span class="line">                        image: redis</span><br><span class="line">                    db:</span><br><span class="line">                        image: postgres                             </span><br><span class="line"></span><br><span class="line">        dns                   # 设置 DNS 地址(等同于 docker run --dns 的作用)</span><br><span class="line"></span><br><span class="line">        dns_search            # 设置 DNS 搜索域(等同于 docker run --dns-search 的作用)</span><br><span class="line"></span><br><span class="line">        tmpfs                 # v2 版本以上, 挂载目录到容器中, 作为容器的临时文件系统(等同于 docker run --tmpfs 的作用, 在使用 swarm 部署时将忽略该选项)</span><br><span class="line"></span><br><span class="line">        entrypoint            # 覆盖容器的默认 entrypoint 指令 (等同于 docker run --entrypoint 的作用)</span><br><span class="line"></span><br><span class="line">        env_file              # 从指定文件中读取变量设置为容器中的环境变量, 可以是单个值或者一个文件列表, 如果多个文件中的变量重名则后面的变量覆盖前面的变量, environment 的值覆盖 env_file 的值</span><br><span class="line">            文件格式：</span><br><span class="line">                RACK_ENV&#x3D;development </span><br><span class="line"></span><br><span class="line">        environment           # 设置环境变量， environment 的值可以覆盖 env_file 的值 (等同于 docker run --env 的作用)</span><br><span class="line"></span><br><span class="line">        expose                # 暴露端口, 但是不能和宿主机建立映射关系, 类似于 Dockerfile 的 EXPOSE 指令</span><br><span class="line"></span><br><span class="line">        external_links        # 连接不在 docker-compose.yml 中定义的容器或者不在 compose 管理的容器(docker run 启动的容器, 在 v3 版本中使用 swarm 部署时将忽略该选项)</span><br><span class="line"></span><br><span class="line">        extra_hosts           # 添加 host 记录到容器中的 &#x2F;etc&#x2F;hosts 中 (等同于 docker run --add-host 的作用)</span><br><span class="line"></span><br><span class="line">        healthcheck           # v2.1 以上版本, 定义容器健康状态检查, 类似于 Dockerfile 的 HEALTHCHECK 指令</span><br><span class="line">            test                  # 检查容器检查状态的命令, 该选项必须是一个字符串或者列表, 第一项必须是 NONE, CMD 或 CMD-SHELL, 如果其是一个字符串则相当于 CMD-SHELL 加该字符串</span><br><span class="line">                NONE                  # 禁用容器的健康状态检测</span><br><span class="line">                CMD                   # test: [&quot;CMD&quot;, &quot;curl&quot;, &quot;-f&quot;, &quot;http:&#x2F;&#x2F;localhost&quot;]</span><br><span class="line">                CMD-SHELL             # test: [&quot;CMD-SHELL&quot;, &quot;curl -f http:&#x2F;&#x2F;localhost || exit 1&quot;] 或者　test: curl -f https:&#x2F;&#x2F;localhost || exit 1</span><br><span class="line">            interval: 1m30s       # 每次检查之间的间隔时间</span><br><span class="line">            timeout: 10s          # 运行命令的超时时间</span><br><span class="line">            retries: 3            # 重试次数</span><br><span class="line">            start_period: 40s     # v3.4 以上新增的选项, 定义容器启动时间间隔</span><br><span class="line">            disable: true         # true 或 false, 表示是否禁用健康状态检测和　test: NONE 相同</span><br><span class="line"></span><br><span class="line">        image                 # 指定 docker 镜像, 可以是远程仓库镜像、本地镜像</span><br><span class="line"></span><br><span class="line">        init                  # v3.7 中新增的参数, true 或 false 表示是否在容器中运行一个 init, 它接收信号并传递给进程</span><br><span class="line"></span><br><span class="line">        isolation             # 隔离容器技术, 在 Linux 中仅支持 default 值</span><br><span class="line"></span><br><span class="line">        labels                # 使用 Docker 标签将元数据添加到容器, 与 Dockerfile 中的 LABELS 类似</span><br><span class="line"></span><br><span class="line">        links                 # 链接到其它服务中的容器, 该选项是 docker 历史遗留的选项, 目前已被用户自定义网络名称空间取代, 最终有可能被废弃 (在使用 swarm 部署时将忽略该选项)</span><br><span class="line"></span><br><span class="line">        logging               # 设置容器日志服务</span><br><span class="line">            driver                # 指定日志记录驱动程序, 默认 json-file (等同于 docker run --log-driver 的作用)</span><br><span class="line">            options               # 指定日志的相关参数 (等同于 docker run --log-opt 的作用)</span><br><span class="line">                max-size              # 设置单个日志文件的大小, 当到达这个值后会进行日志滚动操作</span><br><span class="line">                max-file              # 日志文件保留的数量</span><br><span class="line"></span><br><span class="line">        network_mode          # 指定网络模式 (等同于 docker run --net 的作用, 在使用 swarm 部署时将忽略该选项)         </span><br><span class="line"></span><br><span class="line">        networks              # 将容器加入指定网络 (等同于 docker network connect 的作用), networks 可以位于 compose 文件顶级键和 services 键的二级键</span><br><span class="line">            aliases               # 同一网络上的容器可以使用服务名称或别名连接到其中一个服务的容器</span><br><span class="line">            ipv4_address      # IP V4 格式</span><br><span class="line">            ipv6_address      # IP V6 格式</span><br><span class="line"></span><br><span class="line">            示例:</span><br><span class="line">                version: &#39;3.7&#39;</span><br><span class="line">                services: </span><br><span class="line">                    test: </span><br><span class="line">                        image: nginx:1.14-alpine</span><br><span class="line">                        container_name: mynginx</span><br><span class="line">                        command: ifconfig</span><br><span class="line">                        networks: </span><br><span class="line">                            app_net:                                # 调用下面 networks 定义的 app_net 网络</span><br><span class="line">                            ipv4_address: 172.16.238.10</span><br><span class="line">                networks:</span><br><span class="line">                    app_net:</span><br><span class="line">                        driver: bridge</span><br><span class="line">                        ipam:</span><br><span class="line">                            driver: default</span><br><span class="line">                            config:</span><br><span class="line">                                - subnet: 172.16.238.0&#x2F;24</span><br><span class="line"></span><br><span class="line">        pid: &#39;host&#39;           # 共享宿主机的 进程空间(PID)</span><br><span class="line"></span><br><span class="line">        ports                 # 建立宿主机和容器之间的端口映射关系, ports 支持两种语法格式</span><br><span class="line">            SHORT 语法格式示例:</span><br><span class="line">                - &quot;3000&quot;                            # 暴露容器的 3000 端口, 宿主机的端口由 docker 随机映射一个没有被占用的端口</span><br><span class="line">                - &quot;3000-3005&quot;                       # 暴露容器的 3000 到 3005 端口, 宿主机的端口由 docker 随机映射没有被占用的端口</span><br><span class="line">                - &quot;8000:8000&quot;                       # 容器的 8000 端口和宿主机的 8000 端口建立映射关系</span><br><span class="line">                - &quot;9090-9091:8080-8081&quot;</span><br><span class="line">                - &quot;127.0.0.1:8001:8001&quot;             # 指定映射宿主机的指定地址的</span><br><span class="line">                - &quot;127.0.0.1:5000-5010:5000-5010&quot;   </span><br><span class="line">                - &quot;6060:6060&#x2F;udp&quot;                   # 指定协议</span><br><span class="line"></span><br><span class="line">            LONG 语法格式示例:(v3.2 新增的语法格式)</span><br><span class="line">                ports:</span><br><span class="line">                    - target: 80                    # 容器端口</span><br><span class="line">                      published: 8080               # 宿主机端口</span><br><span class="line">                      protocol: tcp                 # 协议类型</span><br><span class="line">                      mode: host                    # host 在每个节点上发布主机端口,  ingress 对于群模式端口进行负载均衡</span><br><span class="line"></span><br><span class="line">        secrets               # 不知道怎么用</span><br><span class="line"></span><br><span class="line">        security_opt          # 为每个容器覆盖默认的标签 (在使用 swarm 部署时将忽略该选项)</span><br><span class="line"></span><br><span class="line">        stop_grace_period     # 指定在发送了 SIGTERM 信号之后, 容器等待多少秒之后退出(默认 10s)</span><br><span class="line"></span><br><span class="line">        stop_signal           # 指定停止容器发送的信号 (默认为 SIGTERM 相当于 kill PID; SIGKILL 相当于 kill -9 PID; 在使用 swarm 部署时将忽略该选项)</span><br><span class="line"></span><br><span class="line">        sysctls               # 设置容器中的内核参数 (在使用 swarm 部署时将忽略该选项)</span><br><span class="line"></span><br><span class="line">        ulimits               # 设置容器的 limit</span><br><span class="line"></span><br><span class="line">        userns_mode           # 如果Docker守护程序配置了用户名称空间, 则禁用此服务的用户名称空间 (在使用 swarm 部署时将忽略该选项)</span><br><span class="line"></span><br><span class="line">        volumes               # 定义容器和宿主机的卷映射关系, 其和 networks 一样可以位于 services 键的二级键和 compose 顶级键, 如果需要跨服务间使用则在顶级键定义, 在 services 中引用</span><br><span class="line">            SHORT 语法格式示例:</span><br><span class="line">                volumes:</span><br><span class="line">                    - &#x2F;var&#x2F;lib&#x2F;mysql                # 映射容器内的 &#x2F;var&#x2F;lib&#x2F;mysql 到宿主机的一个随机目录中</span><br><span class="line">                    - &#x2F;opt&#x2F;data:&#x2F;var&#x2F;lib&#x2F;mysql      # 映射容器内的 &#x2F;var&#x2F;lib&#x2F;mysql 到宿主机的 &#x2F;opt&#x2F;data</span><br><span class="line">                    - .&#x2F;cache:&#x2F;tmp&#x2F;cache            # 映射容器内的 &#x2F;var&#x2F;lib&#x2F;mysql 到宿主机 compose 文件所在的位置</span><br><span class="line">                    - ~&#x2F;configs:&#x2F;etc&#x2F;configs&#x2F;:ro    # 映射容器宿主机的目录到容器中去, 权限只读</span><br><span class="line">                    - datavolume:&#x2F;var&#x2F;lib&#x2F;mysql     # datavolume 为 volumes 顶级键定义的目录, 在此处直接调用</span><br><span class="line"></span><br><span class="line">            LONG 语法格式示例:(v3.2 新增的语法格式)</span><br><span class="line">                version: &quot;3.2&quot;</span><br><span class="line">                services:</span><br><span class="line">                    web:</span><br><span class="line">                        image: nginx:alpine</span><br><span class="line">                        ports:</span><br><span class="line">                            - &quot;80:80&quot;</span><br><span class="line">                        volumes:</span><br><span class="line">                            - type: volume                  # mount 的类型, 必须是 bind、volume 或 tmpfs</span><br><span class="line">                                source: mydata              # 宿主机目录</span><br><span class="line">                                target: &#x2F;data               # 容器目录</span><br><span class="line">                                volume:                     # 配置额外的选项, 其 key 必须和 type 的值相同</span><br><span class="line">                                    nocopy: true                # volume 额外的选项, 在创建卷时禁用从容器复制数据</span><br><span class="line">                            - type: bind                    # volume 模式只指定容器路径即可, 宿主机路径随机生成; bind 需要指定容器和数据机的映射路径</span><br><span class="line">                                source: .&#x2F;static</span><br><span class="line">                                target: &#x2F;opt&#x2F;app&#x2F;static</span><br><span class="line">                                read_only: true             # 设置文件系统为只读文件系统</span><br><span class="line">                volumes:</span><br><span class="line">                    mydata:                                 # 定义在 volume, 可在所有服务中调用</span><br><span class="line"></span><br><span class="line">        restart               # 定义容器重启策略(在使用 swarm 部署时将忽略该选项, 在 swarm 使用 restart_policy 代替 restart)</span><br><span class="line">            no                    # 禁止自动重启容器(默认)</span><br><span class="line">            always                # 无论如何容器都会重启</span><br><span class="line">            on-failure            # 当出现 on-failure 报错时, 容器重新启动</span><br><span class="line"></span><br><span class="line">        其他选项：</span><br><span class="line">            domainname, hostname, ipc, mac_address, privileged, read_only, shm_size, stdin_open, tty, user, working_dir</span><br><span class="line">            上面这些选项都只接受单个值和 docker run 的对应参数类似</span><br><span class="line"></span><br><span class="line">        对于值为时间的可接受的值：</span><br><span class="line">            2.5s</span><br><span class="line">            10s</span><br><span class="line">            1m30s</span><br><span class="line">            2h32m</span><br><span class="line">            5h34m56s</span><br><span class="line">            时间单位: us, ms, s, m， h</span><br><span class="line">        对于值为大小的可接受的值：</span><br><span class="line">            2b</span><br><span class="line">            1024kb</span><br><span class="line">            2048k</span><br><span class="line">            300m</span><br><span class="line">            1gb</span><br><span class="line">            单位: b, k, m, g 或者 kb, mb, gb</span><br><span class="line">    networks          # 定义 networks 信息</span><br><span class="line">        driver                # 指定网络模式, 大多数情况下, 它 bridge 于单个主机和 overlay Swarm 上</span><br><span class="line">            bridge                # Docker 默认使用 bridge 连接单个主机上的网络</span><br><span class="line">            overlay               # overlay 驱动程序创建一个跨多个节点命名的网络</span><br><span class="line">            host                  # 共享主机网络名称空间(等同于 docker run --net&#x3D;host)</span><br><span class="line">            none                  # 等同于 docker run --net&#x3D;none</span><br><span class="line">        driver_opts           # v3.2以上版本, 传递给驱动程序的参数, 这些参数取决于驱动程序</span><br><span class="line">        attachable            # driver 为 overlay 时使用, 如果设置为 true 则除了服务之外，独立容器也可以附加到该网络; 如果独立容器连接到该网络，则它可以与其他 Docker 守护进程连接到的该网络的服务和独立容器进行通信</span><br><span class="line">        ipam                  # 自定义 IPAM 配置. 这是一个具有多个属性的对象, 每个属性都是可选的</span><br><span class="line">            driver                # IPAM 驱动程序, bridge 或者 default</span><br><span class="line">            config                # 配置项</span><br><span class="line">                subnet                # CIDR格式的子网，表示该网络的网段</span><br><span class="line">        external              # 外部网络, 如果设置为 true 则 docker-compose up 不会尝试创建它, 如果它不存在则引发错误</span><br><span class="line">        name                  # v3.5 以上版本, 为此网络设置名称</span><br><span class="line">文件格式示例：</span><br><span class="line">    version: &quot;3&quot;</span><br><span class="line">    services:</span><br><span class="line">      redis:</span><br><span class="line">        image: redis:alpine</span><br><span class="line">        ports:</span><br><span class="line">          - &quot;6379&quot;</span><br><span class="line">        networks:</span><br><span class="line">          - frontend</span><br><span class="line">        deploy:</span><br><span class="line">          replicas: 2</span><br><span class="line">          update_config:</span><br><span class="line">            parallelism: 2</span><br><span class="line">            delay: 10s</span><br><span class="line">          restart_policy:</span><br><span class="line">            condition: on-failure</span><br><span class="line">      db:</span><br><span class="line">        image: postgres:9.4</span><br><span class="line">        volumes:</span><br><span class="line">          - db-data:&#x2F;var&#x2F;lib&#x2F;postgresql&#x2F;data</span><br><span class="line">        networks:</span><br><span class="line">          - backend</span><br><span class="line">        deploy:</span><br><span class="line">          placement:</span><br><span class="line">            constraints: [node.role &#x3D;&#x3D; manager]</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;基本概念&quot;&gt;&lt;a href=&quot;#基本概念&quot; class=&quot;headerlink&quot; title=&quot;基本概念&quot;&gt;&lt;/a&gt;基本概念&lt;/h3&gt;&lt;p&gt;默认情况下，Compose会为我们的应用创建一个网络，服务的每个容器都会加入该网络中。这样，容器就可被该网络中的其他容器访问</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>MYSQL  插入数据避免主键冲突</title>
    <link href="http://example.com/2022/11/24/MYSQL%20%20%E6%8F%92%E5%85%A5%E6%95%B0%E6%8D%AE%E9%81%BF%E5%85%8D%E4%B8%BB%E9%94%AE%E5%86%B2%E7%AA%81/"/>
    <id>http://example.com/2022/11/24/MYSQL%20%20%E6%8F%92%E5%85%A5%E6%95%B0%E6%8D%AE%E9%81%BF%E5%85%8D%E4%B8%BB%E9%94%AE%E5%86%B2%E7%AA%81/</id>
    <published>2022-11-24T06:18:39.000Z</published>
    <updated>2023-06-06T15:56:09.499Z</updated>
    
    <content type="html"><![CDATA[<h3 id="1-不存在则插入，存在则更新"><a href="#1-不存在则插入，存在则更新" class="headerlink" title="1.不存在则插入，存在则更新"></a>1.不存在则插入，存在则更新</h3><h4 id="1-1-on-duplicate-key-update"><a href="#1-1-on-duplicate-key-update" class="headerlink" title="1.1 on duplicate key update"></a>1.1 on duplicate key update</h4><p>如果插入的数据会导致UNIQUE 索引或PRIMARY KEY发生冲突/重复，则执行UPDATE语句，例：</p><figure class="highlight sql"><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">INSERT</span> <span class="keyword">INTO</span> `student`(`name`, `age`) <span class="keyword">VALUES</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">19</span>)</span><br><span class="line">  <span class="keyword">ON</span> DUPLICATE KEY </span><br><span class="line">  <span class="keyword">UPDATE</span> `age`<span class="operator">=</span><span class="number">19</span>; <span class="comment">-- If will happen conflict, the update statement is executed</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="1-2-replace-into"><a href="#1-2-replace-into" class="headerlink" title="1.2 replace into"></a>1.2 replace into</h3><p>如果插入的数据会导致<code>UNIQUE 索引</code>或<code>PRIMARY KEY</code>发生冲突/重复，则<strong>先删除旧数据再插入最新的数据</strong>，例：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">REPLACE <span class="keyword">INTO</span> `student`(`name`, `age`) <span class="keyword">VALUES</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">18</span>);</span><br></pre></td></tr></table></figure><h3 id="2-避免重复插入"><a href="#2-避免重复插入" class="headerlink" title="2. 避免重复插入"></a>2. 避免重复插入</h3><p>关键字/句：<code>insert ignore into</code>，如果插入的数据会导致<code>UNIQUE索引</code>或<code>PRIMARY KEY</code>发生冲突/重复，则忽略此次操作/不插入数据，例：</p><figure class="highlight sql"><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">INSERT</span> IGNORE <span class="keyword">INTO</span> `student`(`name`, `age`) <span class="keyword">VALUES</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">18</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 0 row(s) affected</span></span><br></pre></td></tr></table></figure><p>这里已经存在<code>name=&#39;Jack&#39;</code>的数据，所以会忽略掉新插入的数据，受影响行数为<code>0</code>，表数据不变。</p><p>以上。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;1-不存在则插入，存在则更新&quot;&gt;&lt;a href=&quot;#1-不存在则插入，存在则更新&quot; class=&quot;headerlink&quot; title=&quot;1.不存在则插入，存在则更新&quot;&gt;&lt;/a&gt;1.不存在则插入，存在则更新&lt;/h3&gt;&lt;h4 id=&quot;1-1-on-duplicate-</summary>
      
    
    
    
    <category term="问题" scheme="http://example.com/categories/%E9%97%AE%E9%A2%98/"/>
    
    
    <category term="mysql" scheme="http://example.com/tags/mysql/"/>
    
  </entry>
  
  <entry>
    <title>Go文件操作大全</title>
    <link href="http://example.com/2022/07/22/Go%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C%E5%A4%A7%E5%85%A8/"/>
    <id>http://example.com/2022/07/22/Go%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C%E5%A4%A7%E5%85%A8/</id>
    <published>2022-07-22T06:41:52.000Z</published>
    <updated>2023-06-06T15:56:09.499Z</updated>
    
    <content type="html"><![CDATA[<h1 id=""><a href="#" class="headerlink" title=""></a></h1><h4 id="万物皆文件"><a href="#万物皆文件" class="headerlink" title="万物皆文件"></a>万物皆文件</h4><p>UNIX 的一个基础设计就是”万物皆文件”(everything is a file)。我们不必知道一个文件到底映射成什么，操作系统的设备驱动抽象成文件。操作系统为设备提供了文件格式的接口。</p><p>Go语言中的reader和writer接口也类似。我们只需简单的读写字节，不必知道reader的数据来自哪里，也不必知道writer将数据发送到哪里。<br>你可以在<code>/dev</code>下查看可用的设备，有些可能需要较高的权限才能访问。</p><h3 id="基本操作"><a href="#基本操作" class="headerlink" title="基本操作"></a>基本操作</h3><h4 id="创建空文件"><a href="#创建空文件" class="headerlink" title="创建空文件"></a>创建空文件</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">var (</span><br><span class="line">    newFile *os.File</span><br><span class="line">    err     error</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    newFile, err &#x3D; os.Create(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    log.Println(newFile)</span><br><span class="line">    newFile.Close()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Truncate文件"><a href="#Truncate文件" class="headerlink" title="Truncate文件"></a>Truncate文件</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 裁剪一个文件到100个字节。</span><br><span class="line">    &#x2F;&#x2F; 如果文件本来就少于100个字节，则文件中原始内容得以保留，剩余的字节以null字节填充。</span><br><span class="line">    &#x2F;&#x2F; 如果文件本来超过100个字节，则超过的字节会被抛弃。</span><br><span class="line">    &#x2F;&#x2F; 这样我们总是得到精确的100个字节的文件。</span><br><span class="line">    &#x2F;&#x2F; 传入0则会清空文件。</span><br><span class="line"></span><br><span class="line">    err :&#x3D; os.Truncate(&quot;test.txt&quot;, 100)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="得到文件信息"><a href="#得到文件信息" class="headerlink" title="得到文件信息"></a>得到文件信息</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;fmt&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">var (</span><br><span class="line">    fileInfo os.FileInfo</span><br><span class="line">    err      error</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 如果文件不存在，则返回错误</span><br><span class="line">    fileInfo, err &#x3D; os.Stat(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(&quot;File name:&quot;, fileInfo.Name())</span><br><span class="line">    fmt.Println(&quot;Size in bytes:&quot;, fileInfo.Size())</span><br><span class="line">    fmt.Println(&quot;Permissions:&quot;, fileInfo.Mode())</span><br><span class="line">    fmt.Println(&quot;Last modified:&quot;, fileInfo.ModTime())</span><br><span class="line">    fmt.Println(&quot;Is Directory: &quot;, fileInfo.IsDir())</span><br><span class="line">    fmt.Printf(&quot;System interface type: %T\n&quot;, fileInfo.Sys())</span><br><span class="line">    fmt.Printf(&quot;System info: %+v\n\n&quot;, fileInfo.Sys())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="重命名和移动"><a href="#重命名和移动" class="headerlink" title="重命名和移动"></a>重命名和移动</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    originalPath :&#x3D; &quot;test.txt&quot;</span><br><span class="line">    newPath :&#x3D; &quot;test2.txt&quot;</span><br><span class="line">    err :&#x3D; os.Rename(originalPath, newPath)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>译者按： rename 和 move 原理一样</p></blockquote><h4 id="删除文件"><a href="#删除文件" class="headerlink" title="删除文件"></a>删除文件</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    err :&#x3D; os.Remove(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="打开和关闭文件"><a href="#打开和关闭文件" class="headerlink" title="打开和关闭文件"></a>打开和关闭文件</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 简单地以只读的方式打开。下面的例子会介绍读写的例子。</span><br><span class="line">    file, err :&#x3D; os.Open(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    file.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; OpenFile提供更多的选项。</span><br><span class="line">    &#x2F;&#x2F; 最后一个参数是权限模式permission mode</span><br><span class="line">    &#x2F;&#x2F; 第二个是打开时的属性    </span><br><span class="line">    file, err &#x3D; os.OpenFile(&quot;test.txt&quot;, os.O_APPEND, 0666)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    file.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 下面的属性可以单独使用，也可以组合使用。</span><br><span class="line">    &#x2F;&#x2F; 组合使用时可以使用 OR 操作设置 OpenFile的第二个参数，例如：</span><br><span class="line">    &#x2F;&#x2F; os.O_CREATE|os.O_APPEND</span><br><span class="line">    &#x2F;&#x2F; 或者 os.O_CREATE|os.O_TRUNC|os.O_WRONLY</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; os.O_RDONLY &#x2F;&#x2F; 只读</span><br><span class="line">    &#x2F;&#x2F; os.O_WRONLY &#x2F;&#x2F; 只写</span><br><span class="line">    &#x2F;&#x2F; os.O_RDWR &#x2F;&#x2F; 读写</span><br><span class="line">    &#x2F;&#x2F; os.O_APPEND &#x2F;&#x2F; 往文件中添建（Append）</span><br><span class="line">    &#x2F;&#x2F; os.O_CREATE &#x2F;&#x2F; 如果文件不存在则先创建</span><br><span class="line">    &#x2F;&#x2F; os.O_TRUNC &#x2F;&#x2F; 文件打开时裁剪文件</span><br><span class="line">    &#x2F;&#x2F; os.O_EXCL &#x2F;&#x2F; 和O_CREATE一起使用，文件不能存在</span><br><span class="line">    &#x2F;&#x2F; os.O_SYNC &#x2F;&#x2F; 以同步I&#x2F;O的方式打开</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>译者按：熟悉Linux的读者应该很熟悉权限模式，通过Linux命令<code>chmod</code>可以更改文件的权限<br><a href="https://www.linux.com/learn/understanding-linux-file-permissions">https://www.linux.com/learn/understanding-linux-file-permissions</a></p><p>补充了原文未介绍的flag</p></blockquote><h4 id="检查文件是否存在"><a href="#检查文件是否存在" class="headerlink" title="检查文件是否存在"></a>检查文件是否存在</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">var (</span><br><span class="line">    fileInfo *os.FileInfo</span><br><span class="line">    err      error</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 文件不存在则返回error</span><br><span class="line">    fileInfo, err :&#x3D; os.Stat(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        if os.IsNotExist(err) &#123;</span><br><span class="line">            log.Fatal(&quot;File does not exist.&quot;)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    log.Println(&quot;File does exist. File information:&quot;)</span><br><span class="line">    log.Println(fileInfo)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="检查读写权限"><a href="#检查读写权限" class="headerlink" title="检查读写权限"></a>检查读写权限</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 这个例子测试写权限，如果没有写权限则返回error。</span><br><span class="line">    &#x2F;&#x2F; 注意文件不存在也会返回error，需要检查error的信息来获取到底是哪个错误导致。</span><br><span class="line">    file, err :&#x3D; os.OpenFile(&quot;test.txt&quot;, os.O_WRONLY, 0666)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        if os.IsPermission(err) &#123;</span><br><span class="line">            log.Println(&quot;Error: Write permission denied.&quot;)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    file.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 测试读权限</span><br><span class="line">    file, err &#x3D; os.OpenFile(&quot;test.txt&quot;, os.O_RDONLY, 0666)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        if os.IsPermission(err) &#123;</span><br><span class="line">            log.Println(&quot;Error: Read permission denied.&quot;)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    file.Close()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="改变权限、拥有者、时间戳"><a href="#改变权限、拥有者、时间戳" class="headerlink" title="改变权限、拥有者、时间戳"></a>改变权限、拥有者、时间戳</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;time&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 使用Linux风格改变文件权限</span><br><span class="line">    err :&#x3D; os.Chmod(&quot;test.txt&quot;, 0777)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Println(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 改变文件所有者</span><br><span class="line">    err &#x3D; os.Chown(&quot;test.txt&quot;, os.Getuid(), os.Getgid())</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Println(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 改变时间戳</span><br><span class="line">    twoDaysFromNow :&#x3D; time.Now().Add(48 * time.Hour)</span><br><span class="line">    lastAccessTime :&#x3D; twoDaysFromNow</span><br><span class="line">    lastModifyTime :&#x3D; twoDaysFromNow</span><br><span class="line">    err &#x3D; os.Chtimes(&quot;test.txt&quot;, lastAccessTime, lastModifyTime)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Println(err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="硬链接和软链接"><a href="#硬链接和软链接" class="headerlink" title="硬链接和软链接"></a>硬链接和软链接</h4><p>一个普通的文件是一个指向硬盘的inode的地方。<br>硬链接创建一个新的指针指向同一个地方。只有所有的链接被删除后文件才会被删除。硬链接只在相同的文件系统中才工作。你可以认为一个硬链接是一个正常的链接。</p><p>symbolic link，又叫软连接，和硬链接有点不一样，它不直接指向硬盘中的相同的地方，而是通过名字引用其它文件。他们可以指向不同的文件系统中的不同文件。并不是所有的操作系统都支持软链接。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;fmt&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 创建一个硬链接。</span><br><span class="line">    &#x2F;&#x2F; 创建后同一个文件内容会有两个文件名，改变一个文件的内容会影响另一个。</span><br><span class="line">    &#x2F;&#x2F; 删除和重命名不会影响另一个。</span><br><span class="line">    err :&#x3D; os.Link(&quot;original.txt&quot;, &quot;original_also.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    fmt.Println(&quot;creating sym&quot;)</span><br><span class="line">    &#x2F;&#x2F; Create a symlink</span><br><span class="line">    err &#x3D; os.Symlink(&quot;original.txt&quot;, &quot;original_sym.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; Lstat返回一个文件的信息，但是当文件是一个软链接时，它返回软链接的信息，而不是引用的文件的信息。</span><br><span class="line">    &#x2F;&#x2F; Symlink在Windows中不工作。</span><br><span class="line">    fileInfo, err :&#x3D; os.Lstat(&quot;original_sym.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(&quot;Link info: %+v&quot;, fileInfo)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F;改变软链接的拥有者不会影响原始文件。</span><br><span class="line">    err &#x3D; os.Lchown(&quot;original_sym.txt&quot;, os.Getuid(), os.Getgid())</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</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 plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;io&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 打开原始文件</span><br><span class="line">    originalFile, err :&#x3D; os.Open(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    defer originalFile.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 创建新的文件作为目标文件</span><br><span class="line">    newFile, err :&#x3D; os.Create(&quot;test_copy.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    defer newFile.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 从源中复制字节到目标文件</span><br><span class="line">    bytesWritten, err :&#x3D; io.Copy(newFile, originalFile)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    log.Printf(&quot;Copied %d bytes.&quot;, bytesWritten)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 将文件内容flush到硬盘中</span><br><span class="line">    err &#x3D; newFile.Sync()</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="跳转到文件指定位置-Seek"><a href="#跳转到文件指定位置-Seek" class="headerlink" title="跳转到文件指定位置(Seek)"></a>跳转到文件指定位置(Seek)</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;fmt&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    file, _ :&#x3D; os.Open(&quot;test.txt&quot;)</span><br><span class="line">    defer file.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 偏离位置，可以是正数也可以是负数</span><br><span class="line">    var offset int64 &#x3D; 5</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 用来计算offset的初始位置</span><br><span class="line">    &#x2F;&#x2F; 0 &#x3D; 文件开始位置</span><br><span class="line">    &#x2F;&#x2F; 1 &#x3D; 当前位置</span><br><span class="line">    &#x2F;&#x2F; 2 &#x3D; 文件结尾处</span><br><span class="line">    var whence int &#x3D; 0</span><br><span class="line">    newPosition, err :&#x3D; file.Seek(offset, whence)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(&quot;Just moved to 5:&quot;, newPosition)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 从当前位置回退两个字节</span><br><span class="line">    newPosition, err &#x3D; file.Seek(-2, 1)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(&quot;Just moved back two:&quot;, newPosition)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 使用下面的技巧得到当前的位置</span><br><span class="line">    currentPosition, err :&#x3D; file.Seek(0, 1)</span><br><span class="line">    fmt.Println(&quot;Current position:&quot;, currentPosition)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 转到文件开始处</span><br><span class="line">    newPosition, err &#x3D; file.Seek(0, 0)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(&quot;Position after seeking 0,0:&quot;, newPosition)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="写文件"><a href="#写文件" class="headerlink" title="写文件"></a>写文件</h4><p>可以使用<code>os</code>包写入一个打开的文件。<br>因为Go可执行包是静态链接的可执行文件，你import的每一个包都会增加你的可执行文件的大小。其它的包如<code>io</code>、｀ioutil｀、｀bufio｀提供了一些方法，但是它们不是必须的。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 可写方式打开文件</span><br><span class="line">    file, err :&#x3D; os.OpenFile(</span><br><span class="line">        &quot;test.txt&quot;,</span><br><span class="line">        os.O_WRONLY|os.O_TRUNC|os.O_CREATE,</span><br><span class="line">        0666,</span><br><span class="line">    )</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    defer file.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 写字节到文件中</span><br><span class="line">    byteSlice :&#x3D; []byte(&quot;Bytes!\n&quot;)</span><br><span class="line">    bytesWritten, err :&#x3D; file.Write(byteSlice)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    log.Printf(&quot;Wrote %d bytes.\n&quot;, bytesWritten)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="快写文件"><a href="#快写文件" class="headerlink" title="快写文件"></a>快写文件</h4><p><code>ioutil</code>包有一个非常有用的方法<code>WriteFile()</code>可以处理创建／打开文件、写字节slice和关闭文件一系列的操作。如果你需要简洁快速地写字节slice到文件中，你可以使用它。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;io&#x2F;ioutil&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    err :&#x3D; ioutil.WriteFile(&quot;test.txt&quot;, []byte(&quot;Hi\n&quot;), 0666)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="使用缓存写"><a href="#使用缓存写" class="headerlink" title="使用缓存写"></a>使用缓存写</h4><p><code>bufio</code>包提供了带缓存功能的writer，所以你可以在写字节到硬盘前使用内存缓存。当你处理很多的数据很有用，因为它可以节省操作硬盘I/O的时间。在其它一些情况下它也很有用，比如你每次写一个字节，把它们攒在内存缓存中，然后一次写入到硬盘中，减少硬盘的磨损以及提升性能。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;bufio&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 打开文件，只写</span><br><span class="line">    file, err :&#x3D; os.OpenFile(&quot;test.txt&quot;, os.O_WRONLY, 0666)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    defer file.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 为这个文件创建buffered writer</span><br><span class="line">    bufferedWriter :&#x3D; bufio.NewWriter(file)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 写字节到buffer</span><br><span class="line">    bytesWritten, err :&#x3D; bufferedWriter.Write(</span><br><span class="line">        []byte&#123;65, 66, 67&#125;,</span><br><span class="line">    )</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    log.Printf(&quot;Bytes written: %d\n&quot;, bytesWritten)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 写字符串到buffer</span><br><span class="line">    &#x2F;&#x2F; 也可以使用 WriteRune() 和 WriteByte()   </span><br><span class="line">    bytesWritten, err &#x3D; bufferedWriter.WriteString(</span><br><span class="line">        &quot;Buffered string\n&quot;,</span><br><span class="line">    )</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    log.Printf(&quot;Bytes written: %d\n&quot;, bytesWritten)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 检查缓存中的字节数</span><br><span class="line">    unflushedBufferSize :&#x3D; bufferedWriter.Buffered()</span><br><span class="line">    log.Printf(&quot;Bytes buffered: %d\n&quot;, unflushedBufferSize)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 还有多少字节可用（未使用的缓存大小）</span><br><span class="line">    bytesAvailable :&#x3D; bufferedWriter.Available()</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    log.Printf(&quot;Available buffer: %d\n&quot;, bytesAvailable)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 写内存buffer到硬盘</span><br><span class="line">    bufferedWriter.Flush()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 丢弃还没有flush的缓存的内容，清除错误并把它的输出传给参数中的writer</span><br><span class="line">    &#x2F;&#x2F; 当你想将缓存传给另外一个writer时有用</span><br><span class="line">    bufferedWriter.Reset(bufferedWriter)</span><br><span class="line"></span><br><span class="line">    bytesAvailable &#x3D; bufferedWriter.Available()</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    log.Printf(&quot;Available buffer: %d\n&quot;, bytesAvailable)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 重新设置缓存的大小。</span><br><span class="line">    &#x2F;&#x2F; 第一个参数是缓存应该输出到哪里，这个例子中我们使用相同的writer。</span><br><span class="line">    &#x2F;&#x2F; 如果我们设置的新的大小小于第一个参数writer的缓存大小， 比如10，我们不会得到一个10字节大小的缓存，</span><br><span class="line">    &#x2F;&#x2F; 而是writer的原始大小的缓存，默认是4096。</span><br><span class="line">    &#x2F;&#x2F; 它的功能主要还是为了扩容。</span><br><span class="line">    bufferedWriter &#x3D; bufio.NewWriterSize(</span><br><span class="line">        bufferedWriter,</span><br><span class="line">        8000,</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; resize后检查缓存的大小</span><br><span class="line">    bytesAvailable &#x3D; bufferedWriter.Available()</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    log.Printf(&quot;Available buffer: %d\n&quot;, bytesAvailable)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="读取最多N个字节"><a href="#读取最多N个字节" class="headerlink" title="读取最多N个字节"></a>读取最多N个字节</h4><p><code>os.File</code>提供了文件操作的基本功能， 而<code>io</code>、<code>ioutil</code>、<code>bufio</code>提供了额外的辅助函数。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 打开文件，只读</span><br><span class="line">    file, err :&#x3D; os.Open(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    defer file.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 从文件中读取len(b)字节的文件。</span><br><span class="line">    &#x2F;&#x2F; 返回0字节意味着读取到文件尾了</span><br><span class="line">    &#x2F;&#x2F; 读取到文件会返回io.EOF的error</span><br><span class="line">    byteSlice :&#x3D; make([]byte, 16)</span><br><span class="line">    bytesRead, err :&#x3D; file.Read(byteSlice)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    log.Printf(&quot;Number of bytes read: %d\n&quot;, bytesRead)</span><br><span class="line">    log.Printf(&quot;Data read: %s\n&quot;, byteSlice)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="读取正好N个字节"><a href="#读取正好N个字节" class="headerlink" title="读取正好N个字节"></a>读取正好N个字节</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;io&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; Open file for reading</span><br><span class="line">    file, err :&#x3D; os.Open(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; file.Read()可以读取一个小文件到大的byte slice中，</span><br><span class="line">    &#x2F;&#x2F; 但是io.ReadFull()在文件的字节数小于byte slice字节数的时候会返回错误</span><br><span class="line">    byteSlice :&#x3D; make([]byte, 2)</span><br><span class="line">    numBytesRead, err :&#x3D; io.ReadFull(file, byteSlice)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    log.Printf(&quot;Number of bytes read: %d\n&quot;, numBytesRead)</span><br><span class="line">    log.Printf(&quot;Data read: %s\n&quot;, byteSlice)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="读取至少N个字节"><a href="#读取至少N个字节" class="headerlink" title="读取至少N个字节"></a>读取至少N个字节</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;io&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 打开文件，只读</span><br><span class="line">    file, err :&#x3D; os.Open(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    byteSlice :&#x3D; make([]byte, 512)</span><br><span class="line">    minBytes :&#x3D; 8</span><br><span class="line">    &#x2F;&#x2F; io.ReadAtLeast()在不能得到最小的字节的时候会返回错误，但会把已读的文件保留</span><br><span class="line">    numBytesRead, err :&#x3D; io.ReadAtLeast(file, byteSlice, minBytes)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    log.Printf(&quot;Number of bytes read: %d\n&quot;, numBytesRead)</span><br><span class="line">    log.Printf(&quot;Data read: %s\n&quot;, byteSlice)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="读取全部字节"><a href="#读取全部字节" class="headerlink" title="读取全部字节"></a>读取全部字节</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;fmt&quot;</span><br><span class="line">    &quot;io&#x2F;ioutil&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    file, err :&#x3D; os.Open(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; os.File.Read(), io.ReadFull() 和</span><br><span class="line">    &#x2F;&#x2F; io.ReadAtLeast() 在读取之前都需要一个固定大小的byte slice。</span><br><span class="line">    &#x2F;&#x2F; 但ioutil.ReadAll()会读取reader(这个例子中是file)的每一个字节，然后把字节slice返回。</span><br><span class="line">    data, err :&#x3D; ioutil.ReadAll(file)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    fmt.Printf(&quot;Data as hex: %x\n&quot;, data)</span><br><span class="line">    fmt.Printf(&quot;Data as string: %s\n&quot;, data)</span><br><span class="line">    fmt.Println(&quot;Number of bytes read:&quot;, len(data))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="快读到内存"><a href="#快读到内存" class="headerlink" title="快读到内存"></a>快读到内存</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;io&#x2F;ioutil&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 读取文件到byte slice中</span><br><span class="line">    data, err :&#x3D; ioutil.ReadFile(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    log.Printf(&quot;Data read: %s\n&quot;, data)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="使用缓存读"><a href="#使用缓存读" class="headerlink" title="使用缓存读"></a>使用缓存读</h4><p>有缓存写也有缓存读。<br>缓存reader会把一些内容缓存在内存中。它会提供比<code>os.File</code>和<code>io.Reader</code>更多的函数,缺省的缓存大小是4096，最小缓存是16。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;bufio&quot;</span><br><span class="line">    &quot;fmt&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 打开文件，创建buffered reader</span><br><span class="line">    file, err :&#x3D; os.Open(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    bufferedReader :&#x3D; bufio.NewReader(file)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 得到字节，当前指针不变</span><br><span class="line">    byteSlice :&#x3D; make([]byte, 5)</span><br><span class="line">    byteSlice, err &#x3D; bufferedReader.Peek(5)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(&quot;Peeked at 5 bytes: %s\n&quot;, byteSlice)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 读取，指针同时移动</span><br><span class="line">    numBytesRead, err :&#x3D; bufferedReader.Read(byteSlice)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(&quot;Read %d bytes: %s\n&quot;, numBytesRead, byteSlice)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 读取一个字节, 如果读取不成功会返回Error</span><br><span class="line">    myByte, err :&#x3D; bufferedReader.ReadByte()</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(&quot;Read 1 byte: %c\n&quot;, myByte)     </span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 读取到分隔符，包含分隔符，返回byte slice</span><br><span class="line">    dataBytes, err :&#x3D; bufferedReader.ReadBytes(&#39;\n&#39;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(&quot;Read bytes: %s\n&quot;, dataBytes)           </span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 读取到分隔符，包含分隔符，返回字符串</span><br><span class="line">    dataString, err :&#x3D; bufferedReader.ReadString(&#39;\n&#39;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(&quot;Read string: %s\n&quot;, dataString)     </span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F;这个例子读取了很多行，所以test.txt应该包含多行文本才不至于出错</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="使用-scanner"><a href="#使用-scanner" class="headerlink" title="使用 scanner"></a>使用 scanner</h4><p><code>Scanner</code>是<code>bufio</code>包下的类型,在处理文件中以分隔符分隔的文本时很有用。<br>通常我们使用换行符作为分隔符将文件内容分成多行。在CSV文件中，逗号一般作为分隔符。<br><code>os.File</code>文件可以被包装成<code>bufio.Scanner</code>，它就像一个缓存reader。<br>我们会调用<code>Scan()</code>方法去读取下一个分隔符，使用<code>Text()</code>或者<code>Bytes()</code>获取读取的数据。</p><p>分隔符可以不是一个简单的字节或者字符，有一个特殊的方法可以实现分隔符的功能，以及将指针移动多少，返回什么数据。<br>如果没有定制的<code>SplitFunc</code>提供，缺省的<code>ScanLines</code>会使用<code>newline</code>字符作为分隔符，其它的分隔函数还包括<code>ScanRunes</code>和<code>ScanWords</code>,皆在<code>bufio</code>包中。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; To define your own split function, match this fingerprint</span><br><span class="line">type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; Returning (0, nil, nil) will tell the scanner</span><br><span class="line">&#x2F;&#x2F; to scan again, but with a bigger buffer because</span><br><span class="line">&#x2F;&#x2F; it wasn&#39;t enough data to reach the delimiter</span><br></pre></td></tr></table></figure><p>下面的例子中，为一个文件创建了<code>bufio.Scanner</code>，并按照单词逐个读取：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;fmt&quot;</span><br><span class="line">    &quot;bufio&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    file, err :&#x3D; os.Open(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    scanner :&#x3D; bufio.NewScanner(file)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 缺省的分隔函数是bufio.ScanLines,我们这里使用ScanWords。</span><br><span class="line">    &#x2F;&#x2F; 也可以定制一个SplitFunc类型的分隔函数</span><br><span class="line">    scanner.Split(bufio.ScanWords)</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; scan下一个token.</span><br><span class="line">    success :&#x3D; scanner.Scan()</span><br><span class="line">    if success &#x3D;&#x3D; false &#123;</span><br><span class="line">        &#x2F;&#x2F; 出现错误或者EOF是返回Error</span><br><span class="line">        err &#x3D; scanner.Err()</span><br><span class="line">        if err &#x3D;&#x3D; nil &#123;</span><br><span class="line">            log.Println(&quot;Scan completed and reached EOF&quot;)</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            log.Fatal(err)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 得到数据，Bytes() 或者 Text()</span><br><span class="line">    fmt.Println(&quot;First word found:&quot;, scanner.Text())</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 再次调用scanner.Scan()发现下一个token</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="压缩"><a href="#压缩" class="headerlink" title="压缩"></a>压缩</h3><h4 id="打包-zip-文件"><a href="#打包-zip-文件" class="headerlink" title="打包(zip) 文件"></a>打包(zip) 文件</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; This example uses zip but standard library</span><br><span class="line">&#x2F;&#x2F; also supports tar archives</span><br><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;archive&#x2F;zip&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 创建一个打包文件</span><br><span class="line">    outFile, err :&#x3D; os.Create(&quot;test.zip&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    defer outFile.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 创建zip writer</span><br><span class="line">    zipWriter :&#x3D; zip.NewWriter(outFile)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 往打包文件中写文件。</span><br><span class="line">    &#x2F;&#x2F; 这里我们使用硬编码的内容，你可以遍历一个文件夹，把文件夹下的文件以及它们的内容写入到这个打包文件中。</span><br><span class="line">    var filesToArchive &#x3D; []struct &#123;</span><br><span class="line">        Name, Body string</span><br><span class="line">    &#125; &#123;</span><br><span class="line">        &#123;&quot;test.txt&quot;, &quot;String contents of file&quot;&#125;,</span><br><span class="line">        &#123;&quot;test2.txt&quot;, &quot;\x61\x62\x63\n&quot;&#125;,</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 下面将要打包的内容写入到打包文件中，依次写入。</span><br><span class="line">    for _, file :&#x3D; range filesToArchive &#123;</span><br><span class="line">            fileWriter, err :&#x3D; zipWriter.Create(file.Name)</span><br><span class="line">            if err !&#x3D; nil &#123;</span><br><span class="line">                    log.Fatal(err)</span><br><span class="line">            &#125;</span><br><span class="line">            _, err &#x3D; fileWriter.Write([]byte(file.Body))</span><br><span class="line">            if err !&#x3D; nil &#123;</span><br><span class="line">                    log.Fatal(err)</span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 清理</span><br><span class="line">    err &#x3D; zipWriter.Close()</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">            log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="抽取-unzip-文件"><a href="#抽取-unzip-文件" class="headerlink" title="抽取(unzip) 文件"></a>抽取(unzip) 文件</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; This example uses zip but standard library</span><br><span class="line">&#x2F;&#x2F; also supports tar archives</span><br><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;archive&#x2F;zip&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;io&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;path&#x2F;filepath&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    zipReader, err :&#x3D; zip.OpenReader(&quot;test.zip&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    defer zipReader.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 遍历打包文件中的每一文件&#x2F;文件夹</span><br><span class="line">    for _, file :&#x3D; range zipReader.Reader.File &#123;</span><br><span class="line">        &#x2F;&#x2F; 打包文件中的文件就像普通的一个文件对象一样</span><br><span class="line">        zippedFile, err :&#x3D; file.Open()</span><br><span class="line">        if err !&#x3D; nil &#123;</span><br><span class="line">            log.Fatal(err)</span><br><span class="line">        &#125;</span><br><span class="line">        defer zippedFile.Close()</span><br><span class="line"></span><br><span class="line">        &#x2F;&#x2F; 指定抽取的文件名。</span><br><span class="line">        &#x2F;&#x2F; 你可以指定全路径名或者一个前缀，这样可以把它们放在不同的文件夹中。</span><br><span class="line">        &#x2F;&#x2F; 我们这个例子使用打包文件中相同的文件名。</span><br><span class="line">        targetDir :&#x3D; &quot;.&#x2F;&quot;</span><br><span class="line">        extractedFilePath :&#x3D; filepath.Join(</span><br><span class="line">            targetDir,</span><br><span class="line">            file.Name,</span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">        &#x2F;&#x2F; 抽取项目或者创建文件夹</span><br><span class="line">        if file.FileInfo().IsDir() &#123;</span><br><span class="line">            &#x2F;&#x2F; 创建文件夹并设置同样的权限</span><br><span class="line">            log.Println(&quot;Creating directory:&quot;, extractedFilePath)</span><br><span class="line">            os.MkdirAll(extractedFilePath, file.Mode())</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            &#x2F;&#x2F;抽取正常的文件</span><br><span class="line">            log.Println(&quot;Extracting file:&quot;, file.Name)</span><br><span class="line"></span><br><span class="line">            outputFile, err :&#x3D; os.OpenFile(</span><br><span class="line">                extractedFilePath,</span><br><span class="line">                os.O_WRONLY|os.O_CREATE|os.O_TRUNC,</span><br><span class="line">                file.Mode(),</span><br><span class="line">            )</span><br><span class="line">            if err !&#x3D; nil &#123;</span><br><span class="line">                log.Fatal(err)</span><br><span class="line">            &#125;</span><br><span class="line">            defer outputFile.Close()</span><br><span class="line"></span><br><span class="line">            &#x2F;&#x2F; 通过io.Copy简洁地复制文件内容</span><br><span class="line">            _, err &#x3D; io.Copy(outputFile, zippedFile)</span><br><span class="line">            if err !&#x3D; nil &#123;</span><br><span class="line">                log.Fatal(err)</span><br><span class="line">            &#125;</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><h4 id="压缩文件"><a href="#压缩文件" class="headerlink" title="压缩文件"></a>压缩文件</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">&#x2F;&#x2F; 这个例子中使用gzip压缩格式，标准库还支持zlib, bz2, flate, lzw</span><br><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">    &quot;compress&#x2F;gzip&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    outputFile, err :&#x3D; os.Create(&quot;test.txt.gz&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    gzipWriter :&#x3D; gzip.NewWriter(outputFile)</span><br><span class="line">    defer gzipWriter.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 当我们写如到gizp writer数据时，它会依次压缩数据并写入到底层的文件中。</span><br><span class="line">    &#x2F;&#x2F; 我们不必关心它是如何压缩的，还是像普通的writer一样操作即可。</span><br><span class="line">    _, err &#x3D; gzipWriter.Write([]byte(&quot;Gophers rule!\n&quot;))</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    log.Println(&quot;Compressed data written to file.&quot;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="解压缩文件"><a href="#解压缩文件" class="headerlink" title="解压缩文件"></a>解压缩文件</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 这个例子中使用gzip压缩格式，标准库还支持zlib, bz2, flate, lzw</span><br><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;compress&#x2F;gzip&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;io&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 打开一个gzip文件。</span><br><span class="line">    &#x2F;&#x2F; 文件是一个reader,但是我们可以使用各种数据源，比如web服务器返回的gzipped内容，</span><br><span class="line">    &#x2F;&#x2F; 它的内容不是一个文件，而是一个内存流</span><br><span class="line">    gzipFile, err :&#x3D; os.Open(&quot;test.txt.gz&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    gzipReader, err :&#x3D; gzip.NewReader(gzipFile)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    defer gzipReader.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 解压缩到一个writer,它是一个file writer</span><br><span class="line">    outfileWriter, err :&#x3D; os.Create(&quot;unzipped.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    defer outfileWriter.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 复制内容</span><br><span class="line">    _, err &#x3D; io.Copy(outfileWriter, gzipReader)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</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>ioutil</code>提供了两个函数: <code>TempDir()</code> 和 <code>TempFile()</code>。<br>使用完毕后，调用者负责删除这些临时文件和文件夹。<br>有一点好处就是当你传递一个空字符串作为文件夹名的时候，它会在操作系统的临时文件夹中创建这些项目（/tmp on Linux）。<br><code>os.TempDir()</code>返回当前操作系统的临时文件夹。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">     &quot;os&quot;</span><br><span class="line">     &quot;io&#x2F;ioutil&quot;</span><br><span class="line">     &quot;log&quot;</span><br><span class="line">     &quot;fmt&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">     &#x2F;&#x2F; 在系统临时文件夹中创建一个临时文件夹</span><br><span class="line">     tempDirPath, err :&#x3D; ioutil.TempDir(&quot;&quot;, &quot;myTempDir&quot;)</span><br><span class="line">     if err !&#x3D; nil &#123;</span><br><span class="line">          log.Fatal(err)</span><br><span class="line">     &#125;</span><br><span class="line">     fmt.Println(&quot;Temp dir created:&quot;, tempDirPath)</span><br><span class="line"></span><br><span class="line">     &#x2F;&#x2F; 在临时文件夹中创建临时文件</span><br><span class="line">     tempFile, err :&#x3D; ioutil.TempFile(tempDirPath, &quot;myTempFile.txt&quot;)</span><br><span class="line">     if err !&#x3D; nil &#123;</span><br><span class="line">          log.Fatal(err)</span><br><span class="line">     &#125;</span><br><span class="line">     fmt.Println(&quot;Temp file created:&quot;, tempFile.Name())</span><br><span class="line"></span><br><span class="line">     &#x2F;&#x2F; ... 做一些操作 ...</span><br><span class="line"></span><br><span class="line">     &#x2F;&#x2F; 关闭文件</span><br><span class="line">     err &#x3D; tempFile.Close()</span><br><span class="line">     if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 删除我们创建的资源</span><br><span class="line">     err &#x3D; os.Remove(tempFile.Name())</span><br><span class="line">     if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">     err &#x3D; os.Remove(tempDirPath)</span><br><span class="line">     if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="通过HTTP下载文件"><a href="#通过HTTP下载文件" class="headerlink" title="通过HTTP下载文件"></a>通过HTTP下载文件</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">     &quot;os&quot;</span><br><span class="line">     &quot;io&quot;</span><br><span class="line">     &quot;log&quot;</span><br><span class="line">     &quot;net&#x2F;http&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">     newFile, err :&#x3D; os.Create(&quot;devdungeon.html&quot;)</span><br><span class="line">     if err !&#x3D; nil &#123;</span><br><span class="line">          log.Fatal(err)</span><br><span class="line">     &#125;</span><br><span class="line">     defer newFile.Close()</span><br><span class="line"></span><br><span class="line">     url :&#x3D; &quot;http:&#x2F;&#x2F;www.devdungeon.com&#x2F;archive&quot;</span><br><span class="line">     response, err :&#x3D; http.Get(url)</span><br><span class="line">     defer response.Body.Close()</span><br><span class="line"></span><br><span class="line">     &#x2F;&#x2F; 将HTTP response Body中的内容写入到文件</span><br><span class="line">     &#x2F;&#x2F; Body满足reader接口，因此我们可以使用ioutil.Copy</span><br><span class="line">     numBytesWritten, err :&#x3D; io.Copy(newFile, response.Body)</span><br><span class="line">     if err !&#x3D; nil &#123;</span><br><span class="line">          log.Fatal(err)</span><br><span class="line">     &#125;</span><br><span class="line">     log.Printf(&quot;Downloaded %d byte file.\n&quot;, numBytesWritten)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="哈希和摘要"><a href="#哈希和摘要" class="headerlink" title="哈希和摘要"></a>哈希和摘要</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;crypto&#x2F;md5&quot;</span><br><span class="line">    &quot;crypto&#x2F;sha1&quot;</span><br><span class="line">    &quot;crypto&#x2F;sha256&quot;</span><br><span class="line">    &quot;crypto&#x2F;sha512&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;fmt&quot;</span><br><span class="line">    &quot;io&#x2F;ioutil&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    &#x2F;&#x2F; 得到文件内容</span><br><span class="line">    data, err :&#x3D; ioutil.ReadFile(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 计算Hash</span><br><span class="line">    fmt.Printf(&quot;Md5: %x\n\n&quot;, md5.Sum(data))</span><br><span class="line">    fmt.Printf(&quot;Sha1: %x\n\n&quot;, sha1.Sum(data))</span><br><span class="line">    fmt.Printf(&quot;Sha256: %x\n\n&quot;, sha256.Sum256(data))</span><br><span class="line">    fmt.Printf(&quot;Sha512: %x\n\n&quot;, sha512.Sum512(data))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的例子复制整个文件内容到内存中，传递给hash函数。<br>另一个方式是创建一个hash writer, 使用<code>Write</code>、<code>WriteString</code>、<code>Copy</code>将数据传给它。<br>下面的例子使用 md5 hash,但你可以使用其它的Writer。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;crypto&#x2F;md5&quot;</span><br><span class="line">    &quot;log&quot;</span><br><span class="line">    &quot;fmt&quot;</span><br><span class="line">    &quot;io&quot;</span><br><span class="line">    &quot;os&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    file, err :&#x3D; os.Open(&quot;test.txt&quot;)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line">    defer file.Close()</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F;创建一个新的hasher,满足writer接口</span><br><span class="line">    hasher :&#x3D; md5.New()</span><br><span class="line">    _, err &#x3D; io.Copy(hasher, file)</span><br><span class="line">    if err !&#x3D; nil &#123;</span><br><span class="line">        log.Fatal(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 计算hash并打印结果。</span><br><span class="line">    &#x2F;&#x2F; 传递 nil 作为参数，因为我们不通参数传递数据，而是通过writer接口。</span><br><span class="line">    sum :&#x3D; hasher.Sum(nil)</span><br><span class="line">    fmt.Printf(&quot;Md5 checksum: %x\n&quot;, sum)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;&quot;&gt;&lt;a href=&quot;#&quot; class=&quot;headerlink&quot; title=&quot;&quot;&gt;&lt;/a&gt;&lt;/h1&gt;&lt;h4 id=&quot;万物皆文件&quot;&gt;&lt;a href=&quot;#万物皆文件&quot; class=&quot;headerlink&quot; title=&quot;万物皆文件&quot;&gt;&lt;/a&gt;万物皆文件&lt;/h4&gt;&lt;p</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>centos7环境 的 k8s安装helm 3.7.1</title>
    <link href="http://example.com/2022/07/11/centos7%E7%8E%AF%E5%A2%83%20%E7%9A%84%20k8s%E5%AE%89%E8%A3%85helm%203.7.1/"/>
    <id>http://example.com/2022/07/11/centos7%E7%8E%AF%E5%A2%83%20%E7%9A%84%20k8s%E5%AE%89%E8%A3%85helm%203.7.1/</id>
    <published>2022-07-11T04:18:41.000Z</published>
    <updated>2023-06-06T15:56:09.499Z</updated>
    
    <content type="html"><![CDATA[<h1 id=""><a href="#" class="headerlink" title=""></a></h1><p><strong>一、 为什么要有helm?</strong></p><p>K8S 上的应用对象，都是由特定的资源描述组成，包括 deployment、service 等。都保存 各自文件中或者集中写到一个配置文件。然后 kubectl apply –f 部署。如果应用只由一 个或几个这样的服务组成，上面部署方式足够了。而对于一个复杂的应用，会有很多类似 上面的资源描述文件，例如微服务架构应用，组成应用的服务可能多达十个，几十个。如 果有更新或回滚应用的需求，可能要修改和维护所涉及的大量资源文件，而这种组织和管 理应用的方式就显得力不从心了。且由于缺少对发布过的应用版本管理和控制，使 Kubernetes 上的应用维护和更新等面临诸多的挑战，主要面临以下问题：（1）如何将这 些服务作为一个整体管理 （2）这些资源文件如何高效复用 （3）不支持应用级别的版本 管理</p><p><strong>二、 helm 解决的问题</strong></p><p>Helm 是一个 Kubernetes 的包管理工具，就像 Linux 下的包管理器，如 yum/apt 等，可以 很方便的将之前打包好的 yaml 文件部署到 kubernetes 上。<br>Helm 有 3 个重要概念：<br>（1）helm：一个命令行客户端工具，主要用于 Kubernetes 应用 chart 的创建、打包、发 布和管理。<br>（2）Chart：应用描述，一系列用于描述 k8s 资源相关文件的集合。<br>（3）Release：基于 Chart 的部署实体，一个 chart 被 Helm 运行后将会生成对应的一个 release；将在 k8s 中创建出真实运行的资源对象。</p><p><strong>三、安装helm</strong></p><p>helm的官方网址：<a href="https://helm.sh/">https://helm.sh/</a></p><p>heml需要在k8s的主节点上安装。</p><p>我们下载安装包进行安装，helm发布的版本地址如下：</p><p><a href="https://github.com/helm/helm/releases">https://github.com/helm/helm/releases</a></p><p>centos7环境，则选择  Linux amd64 这个版本，下载地址如下：</p><p><a href="https://get.helm.sh/helm-v3.7.1-linux-amd64.tar.gz">https://get.helm.sh/helm-v3.7.1-linux-amd64.tar.gz</a></p><p><img src="https://img2020.cnblogs.com/blog/98796/202112/98796-20211205153906611-2131384890.png" alt="img"></p><p>我们在K8s主节点上先创建个目录。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkdir myhelm</span><br></pre></td></tr></table></figure><p>进入该目录：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cd myhelm</span><br></pre></td></tr></table></figure><p>下载：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -SLO https:&#x2F;&#x2F;get.helm.sh&#x2F;helm-v3.7.1-linux-amd64.tar.gz</span><br></pre></td></tr></table></figure><p>解压：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tar -zxvf  helm-v3.7.1-linux-amd64.tar.gz</span><br></pre></td></tr></table></figure><p>将helm移至 /bin 目录</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mv  linux-amd64&#x2F;helm  &#x2F;usr&#x2F;local&#x2F;bin&#x2F;helm</span><br></pre></td></tr></table></figure><p>这样就可以了。我们查看下版本号：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm version</span><br></pre></td></tr></table></figure><p><img src="https://img2020.cnblogs.com/blog/98796/202112/98796-20211205154349309-283858203.png" alt="img"></p><p> 说明安装成功了。</p><p>添加国内 阿里云的 镜像源：</p><p><a href="javascript:void(0);"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码"></a></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">helm repo remove stable</span><br><span class="line">helm repo add stable http:&#x2F;&#x2F;mirror.azure.cn&#x2F;kubernetes&#x2F;charts&#x2F;</span><br><span class="line">helm repo update </span><br><span class="line"> </span><br></pre></td></tr></table></figure><p><a href="javascript:void(0);"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码"></a></p><p> 也可以添加azure的源：（可选）</p><p><a href="javascript:void(0);"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码"></a></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">helm repo remove stable</span><br><span class="line"></span><br><span class="line">helm repo add stable http:&#x2F;&#x2F;mirror.azure.cn&#x2F;kubernetes&#x2F;charts&#x2F;</span><br><span class="line"></span><br><span class="line">helm repo add incubator http:&#x2F;&#x2F;mirror.azure.cn&#x2F;kubernetes&#x2F;charts-incubator&#x2F;</span><br><span class="line"></span><br><span class="line">helm repo update </span><br></pre></td></tr></table></figure><p><a href="javascript:void(0);"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码"></a></p><p>搜索：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm search repo redis</span><br></pre></td></tr></table></figure><p><strong>四、helm 常用命令</strong></p><p>1、 create</p><p>创建一个 chart 并指定名字</p><p>2、 dependency</p><p>管理 chart 依赖</p><p>3、get</p><p>下载一个 release。可用子命令：all、hooks、manifest、notes、values</p><p>4、history</p><p>获取 release 历史</p><p>5、install</p><p>安装一个 chart</p><p>6、list</p><p>列出 release</p><p>7、package</p><p>将 chart 目录打包到 chart 存档文件中</p><p>8、pull</p><p>从远程仓库中下载 chart 并解压到本地 # helm pull stable/mysql –untar</p><p>9、repo</p><p>添加，列出，移除，更新和索引 chart 仓库。可用子命令：add、index、list、remove、update</p><p>10、rollback</p><p>从之前版本回滚</p><p>11、search</p><p>根据关键字搜索 chart。可用子命令：hub、repo</p><p>12、show</p><p>查看 chart 详细信息。可用子命令：all、chart、readme、values</p><p>13、status</p><p>显示已命名版本的状态</p><p>14、template</p><p>本地呈现模板</p><p>15、uninstall</p><p>卸载一个 release</p><p>16、upgrade</p><p>更新一个 release</p><p>16、version</p><p>查看 helm 客户端版本</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;&quot;&gt;&lt;a href=&quot;#&quot; class=&quot;headerlink&quot; title=&quot;&quot;&gt;&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;一、 为什么要有helm?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;K8S 上的应用对象，都是由特定的资源描述组成，包括 deployment、se</summary>
      
    
    
    
    <category term="问题" scheme="http://example.com/categories/%E9%97%AE%E9%A2%98/"/>
    
    
    <category term="centos" scheme="http://example.com/tags/centos/"/>
    
  </entry>
  
  <entry>
    <title>docker安装ES(elasticsearch:7.4.2)</title>
    <link href="http://example.com/2022/06/11/docker%E5%AE%89%E8%A3%85ES-elasticsearch-7-4-2/"/>
    <id>http://example.com/2022/06/11/docker%E5%AE%89%E8%A3%85ES-elasticsearch-7-4-2/</id>
    <published>2022-06-11T06:11:22.000Z</published>
    <updated>2023-06-06T15:56:09.500Z</updated>
    
    <content type="html"><![CDATA[<p>1.拉取ES镜像 本人安装的是7.4.2 可根据自己实际需求安装 命令：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker pull elasticsearch:7.4.2</span><br></pre></td></tr></table></figure><p>2.创建docker容器挂在的目录</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sudo docker pull elasticsearch:7.4.2</span><br><span class="line">mkdir -p ~&#x2F;.elasticsearch&#x2F;config&#x2F;</span><br><span class="line">mkdir -p ~&#x2F;.elasticsearch&#x2F;data&#x2F;</span><br><span class="line">echo &quot;http.host: 0.0.0.0&quot;&gt;&gt;~&#x2F;.elasticsearch&#x2F;config&#x2F;elasticsearch.yml</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>3.创建实例并启动ES</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">sudo docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \</span><br><span class="line">-e ES_JAVA_OPS&#x3D;&quot;-Xms256m -Xmx256m&quot; \</span><br><span class="line">-v ~&#x2F;.elasticsearch&#x2F;config&#x2F;elasticsearch.yml:&#x2F;usr&#x2F;share&#x2F;elasticsearch&#x2F;config&#x2F;elasticsearch.yml \</span><br><span class="line">-v ~&#x2F;.elasticsearch&#x2F;data:&#x2F;usr&#x2F;share&#x2F;elasticsearch&#x2F;data \</span><br><span class="line">-v ~&#x2F;.elasticsearch&#x2F;plugins:&#x2F;usr&#x2F;share&#x2F;elasticsearch&#x2F;plugins \</span><br><span class="line">-d elasticsearch:7.4.2</span><br></pre></td></tr></table></figure><p>参数说明:</p><blockquote><p>-p 9200:9200 将容器的9200端口映射到主机的9200端口;<br>–name elasticsearch 给当前启动的容器取名叫 elasticsearch<br>-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data 将数据文件夹挂载到主机;<br>-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml 将配置文件挂载到主机;<br>-d 以后台方式运行(daemon)<br>-e ES_JAVA_OPS=”-Xms256m -Xmx256m” 测试时限定内存小一点</p></blockquote><p>4.查看ES启动状态 命令 ：docker ps</p><p>5.访问elasticsearch 注意关闭防火墙 访问地址：<a href="http://localhost:9200/">http://localhost:9200/</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;1.拉取ES镜像 本人安装的是7.4.2 可根据自己实际需求安装 命令：&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;b</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>golang fmt格式&quot;占位符&quot;</title>
    <link href="http://example.com/2022/06/10/golang-fmt%E6%A0%BC%E5%BC%8F-%E5%8D%A0%E4%BD%8D%E7%AC%A6/"/>
    <id>http://example.com/2022/06/10/golang-fmt%E6%A0%BC%E5%BC%8F-%E5%8D%A0%E4%BD%8D%E7%AC%A6/</id>
    <published>2022-06-10T06:30:41.000Z</published>
    <updated>2023-06-06T15:56:09.500Z</updated>
    
    <content type="html"><![CDATA[<p>golang 的fmt 包实现了格式化I/O函数，类似于C的 printf 和 scanf。</p><figure class="highlight cpp"><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><br><span class="line">type Human <span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    Name <span class="built_in">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">var people = Human&#123;Name:<span class="string">&quot;zhangsan&quot;</span>&#125;</span><br></pre></td></tr></table></figure><h4 id="普通占位符"><a href="#普通占位符" class="headerlink" title="普通占位符"></a>普通占位符</h4><table><thead><tr><th>占位符</th><th>说明</th><th>举例</th><th>输出</th></tr></thead><tbody><tr><td>%v</td><td>相应值的默认格式。</td><td>Printf(“%v”, people)</td><td>{zhangsan}，</td></tr><tr><td>%+v</td><td>打印结构体时，会添加字段名</td><td>Printf(“%+v”, people)</td><td>{Name:zhangsan}</td></tr><tr><td>%#v</td><td>相应值的Go语法表示</td><td>Printf(“#v”, people)</td><td>main.Human{Name:”zhangsan”}</td></tr><tr><td>%T</td><td>相应值的类型的Go语法表示</td><td>Printf(“%T”, people)</td><td>main.Human</td></tr><tr><td>%%</td><td>字面上的百分号，并非值的占位符</td><td>Printf(“%%”)</td><td>%</td></tr></tbody></table><h4 id="布尔占位符"><a href="#布尔占位符" class="headerlink" title="布尔占位符"></a>布尔占位符</h4><table><thead><tr><th>占位符</th><th>说明</th><th>举例</th><th>输出</th></tr></thead><tbody><tr><td>%t</td><td>true 或 false。</td><td>Printf(“%t”, true)</td><td>TRUE</td></tr></tbody></table><h4 id="整数占位符"><a href="#整数占位符" class="headerlink" title="整数占位符"></a>整数占位符</h4><table><thead><tr><th>占位符</th><th>说明</th><th>举例</th><th>输出</th></tr></thead><tbody><tr><td>%b</td><td>二进制表示</td><td>Printf(“%b”, 5)</td><td>101</td></tr><tr><td>%c</td><td>相应Unicode码点所表示的字符</td><td>Printf(“%c”, 0x4E2D)</td><td>中</td></tr><tr><td>%d</td><td>十进制表示</td><td>Printf(“%d”, 0x12)</td><td>18</td></tr><tr><td>%o</td><td>八进制表示</td><td>Printf(“%d”, 10)</td><td>12</td></tr><tr><td>%q</td><td>单引号围绕的字符字面值，由Go语法安全地转义</td><td>Printf(“%q”, 0x4E2D)</td><td>‘中’</td></tr><tr><td>%x</td><td>十六进制表示，字母形式为小写a-f</td><td>Printf(“%x”, 13)</td><td>d</td></tr><tr><td>%X</td><td>十六进制表示，字母形式为大写A-F</td><td>Printf(“%x”, 13)</td><td>D</td></tr><tr><td>%U</td><td>Unicode格式：U+1234，等同于 “U+%04X”</td><td>Printf(“%U”, 0x4E2D)</td><td>U+4E2D</td></tr></tbody></table><h4 id="浮点数和复数的组成部分（实部和虚部）"><a href="#浮点数和复数的组成部分（实部和虚部）" class="headerlink" title="浮点数和复数的组成部分（实部和虚部）"></a>浮点数和复数的组成部分（实部和虚部）</h4><table><thead><tr><th>占位符</th><th>说明</th><th>举例</th><th>输出</th></tr></thead><tbody><tr><td>%b</td><td>无小数部分的，指数为二的幂的科学计数法，与 strconv.FormatFloat 的 ‘b’ 转换格式一致。</td><td>例如 -123456p</td><td>-78</td></tr><tr><td>%e</td><td>科学计数法，例如 -1234.456e+78</td><td>Printf(“%e”, 10.2)</td><td>1.02E+01</td></tr><tr><td>%E</td><td>科学计数法，例如 -1234.456E+78</td><td>Printf(“%e”, 10.2)</td><td>1.02E+01</td></tr><tr><td>%f</td><td>有小数点而无指数，例如 123.456</td><td>Printf(“%f”, 10.2)</td><td>10.2</td></tr><tr><td>%g</td><td>根据情况选择 %e 或 %f 以产生更紧凑的（无末尾的0）输出</td><td>Printf(“%g”, 10.20)</td><td>10.2</td></tr><tr><td>%G</td><td>根据情况选择 %E 或 %f 以产生更紧凑的（无末尾的0）输出</td><td>Printf(“%G”,10.20+2i)</td><td>(10.2+2i)</td></tr></tbody></table><h4 id="字符串与字节切片"><a href="#字符串与字节切片" class="headerlink" title="字符串与字节切片"></a>字符串与字节切片</h4><table><thead><tr><th>占位符</th><th>说明</th><th>举例</th><th>输出</th></tr></thead><tbody><tr><td>%s</td><td>输出字符串表示（string类型或[]byte)</td><td>Printf(“%s”, []byte(“Go语言”))</td><td>Go语言</td></tr><tr><td>%q</td><td>双引号围绕的字符串，由Go语法安全地转义</td><td>Printf(“%q”, “Go语言”)</td><td>“Go语言”</td></tr><tr><td>%x</td><td>十六进制，小写字母，每字节两个字符</td><td>Printf(“%x”, “golang”)</td><td>676f6c616e67</td></tr><tr><td>%X</td><td>十六进制，大写字母，每字节两个字符</td><td>Printf(“%X”, “golang”)</td><td>676F6C616E67</td></tr></tbody></table><h4 id="指针"><a href="#指针" class="headerlink" title="指针"></a>指针</h4><table><thead><tr><th>占位符</th><th>说明</th><th>举例</th><th>输出</th></tr></thead><tbody><tr><td>%p</td><td>十六进制表示，前缀 0x</td><td>Printf(“%p”, &amp;people)</td><td>0x4f57f0</td></tr></tbody></table><h4 id="其它标记"><a href="#其它标记" class="headerlink" title="其它标记"></a>其它标记</h4><table><thead><tr><th>占位符</th><th>说明</th><th>举例</th><th>输出</th></tr></thead><tbody><tr><td>+</td><td>总打印数值的正负号；对于%q（%+q）保证只输出ASCII编码的字符。</td><td>Printf(“%+q”, “中文”)</td><td>“\u4e2d\u6587”</td></tr><tr><td>-</td><td>在右侧而非左侧填充空格（左对齐该区域）</td><td></td><td></td></tr><tr><td>#</td><td>备用格式：为八进制添加前导 0（%#o）</td><td>Printf(“%#U”, ‘中’)</td><td>U+4E2D</td></tr><tr><td></td><td>为十六进制添加前导 0x（%#x）或 0X（%#X），为 %p（%#p）去掉前导 0x；</td><td></td><td></td></tr><tr><td></td><td>如果可能的话，%q（%#q）会打印原始 （即反引号围绕的）字符串；</td><td></td><td></td></tr><tr><td></td><td>如果是可打印字符，%U（%#U）会写出该字符的</td><td></td><td></td></tr><tr><td></td><td>Unicode 编码形式（如字符 x 会被打印成 U+0078 ‘x’）。</td><td></td><td></td></tr><tr><td>‘ ‘</td><td>(空格)为数值中省略的正负号留出空白（% d）；</td><td></td><td></td></tr><tr><td></td><td>以十六进制（% x, % X）打印字符串或切片时，在字节之间用空格隔开</td><td></td><td></td></tr><tr><td>0</td><td>填充前导的0而非空格；对于数字，这会将填充移到正负号之后</td><td></td><td></td></tr></tbody></table><h4 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h4><p>golang没有 ‘%u’ 点位符，若整数为无符号类型，默认就会被打印成无符号的。</p><p>宽度与精度的控制格式以Unicode码点为单位。宽度为该数值占用区域的最小宽度；精度为小数点之后的位数。<br>操作数的类型为int时，宽度与精度都可用字符 ‘*’ 表示。</p><p>对于 %g/%G 而言，精度为所有数字的总数，例如：123.45，%.4g 会打印123.5，（而 %6.2f 会打印123.45）。</p><p>%e 和 %f 的默认精度为6</p><p>对大多数的数值类型而言，宽度为输出的最小字符数，如果必要的话会为已格式化的形式填充空格。</p><p>而以字符串类型，精度为输出的最大字符数，如果必要的话会直接截断。 </p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;golang 的fmt 包实现了格式化I/O函数，类似于C的 printf 和 scanf。&lt;/p&gt;
&lt;figure class=&quot;highlight cpp&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Mac redis 开机自启动</title>
    <link href="http://example.com/2022/05/24/Mac-redis-%E5%BC%80%E6%9C%BA%E8%87%AA%E5%90%AF%E5%8A%A8/"/>
    <id>http://example.com/2022/05/24/Mac-redis-%E5%BC%80%E6%9C%BA%E8%87%AA%E5%90%AF%E5%8A%A8/</id>
    <published>2022-05-24T08:56:05.000Z</published>
    <updated>2023-06-06T15:56:09.499Z</updated>
    
    <content type="html"><![CDATA[<h3 id="1-创建-plist配置文件"><a href="#1-创建-plist配置文件" class="headerlink" title="1 创建.plist配置文件"></a>1 创建.plist配置文件</h3><p>为了让 Redis 在启动时自动启动，我使用的是 launchd。在 <code>/Library/LaunchDaemons</code> 中创建一个简单的xml文档。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo vim &#x2F;Library&#x2F;LaunchDaemons&#x2F;redis-server.plist</span><br></pre></td></tr></table></figure><p><strong>注意：</strong></p><ul><li>先执行  <code>which redis-server</code> 查看redis的安装位置；</li><li>再执行 <code>sudo find / -name redis.conf</code> 查看reids.conf的位置。</li></ul><p>然后正确填写以下代码：</p><figure class="highlight xml"><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="meta">&lt;!DOCTYPE <span class="meta-keyword">plist</span> <span class="meta-keyword">PUBLIC</span> <span class="meta-string">&quot;-//Apple//DTD PLIST 1.0//EN&quot;</span> <span class="meta-string">&quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">plist</span> <span class="attr">version</span>=<span class="string">&quot;1.0&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">key</span>&gt;</span>Label<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">string</span>&gt;</span>redis-server<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">key</span>&gt;</span>ProgramArguments<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">array</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>/usr/local/bin/redis-server<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>/usr/local/etc/redis.conf<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">array</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">key</span>&gt;</span>RunAtLoad<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">true</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plist</span>&gt;</span></span><br></pre></td></tr></table></figure><p>如果您没有 <code>redis.conf</code> ，则只需将其从此xml文件中删除即可。</p><h3 id="2-将-redis-server-plist-加载到-launchd-中"><a href="#2-将-redis-server-plist-加载到-launchd-中" class="headerlink" title="2 将 redis.server.plist 加载到 launchd 中"></a>2 将 redis.server.plist 加载到 launchd 中</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo launchctl load &#x2F;Library&#x2F;LaunchDaemons&#x2F;redis-server.plist</span><br></pre></td></tr></table></figure><p>此时 Mac 开机或重启都会自动启动 redis</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;1-创建-plist配置文件&quot;&gt;&lt;a href=&quot;#1-创建-plist配置文件&quot; class=&quot;headerlink&quot; title=&quot;1 创建.plist配置文件&quot;&gt;&lt;/a&gt;1 创建.plist配置文件&lt;/h3&gt;&lt;p&gt;为了让 Redis 在启动时自动启动，我使用</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>golang结构体标签</title>
    <link href="http://example.com/2022/05/20/golang%E7%BB%93%E6%9E%84%E4%BD%93%E6%A0%87%E7%AD%BE/"/>
    <id>http://example.com/2022/05/20/golang%E7%BB%93%E6%9E%84%E4%BD%93%E6%A0%87%E7%AD%BE/</id>
    <published>2022-05-20T06:04:53.000Z</published>
    <updated>2023-06-06T15:56:09.500Z</updated>
    
    <content type="html"><![CDATA[<h2 id="结构体标签"><a href="#结构体标签" class="headerlink" title="结构体标签"></a>结构体标签</h2><h3 id="标签定义"><a href="#标签定义" class="headerlink" title="标签定义"></a>标签定义</h3><p>Tag是结构体在编译阶段关联到成员的元信息字符串，在运行的时候通过反射的机制读取出来。</p><p>结构体标签由一个或多个键值对组成。键与值使用冒号分隔，值用双引号括起来。键值对之间使用一个空格分隔，具体的格式如下：</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">`key1:&quot;value1&quot; key2:&quot;value2&quot; key3:&quot;value3&quot;...`</span>  <span class="comment">// 键值对用空格分隔</span></span><br></pre></td></tr></table></figure><p>key会指定反射的解析方式，如下： json(JSON标签) orm(Beego标签)、gorm(GORM标签)、bson(MongoDB标签)、form(表单标签)、binding(表单验证标签)</p><h3 id="标签选项"><a href="#标签选项" class="headerlink" title="标签选项"></a>标签选项</h3><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></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">    ID   <span class="keyword">int</span>     <span class="string">`json:&quot;-&quot;`</span>            <span class="comment">// 该字段不进行序列化</span></span><br><span class="line">    Name <span class="keyword">string</span>  <span class="string">`json:name,omitempy`</span>  <span class="comment">// 如果为类型零值或空值，序列化时忽略该字段</span></span><br><span class="line">    Age  <span class="keyword">int</span>     <span class="string">`json:age,string`</span>     <span class="comment">// 指定类型，支持string、number、boolen</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注：encoding/json<a href="https://studygolang.com/static/pkgdoc/pkg/encoding_json.htm">官方文档</a></p><h2 id="json标签"><a href="#json标签" class="headerlink" title="json标签"></a>json标签</h2><h3 id="JSON说明"><a href="#JSON说明" class="headerlink" title="JSON说明"></a>JSON说明</h3><p>JSON<code>数组</code>可以用于编码Go语言的<code>数组</code>和<code>slice</code>；由于JSON<code>对象</code>是一个字符串到值的映射，写成一系列的<code>name:value</code>对形式，因此JSON的<code>对象</code>类型可以用于编码Go语言的<code>map</code>和<code>结构体</code>。</p><p>将Go语言中结构体<code>slice</code>转为JSON的过程叫<code>编组</code>(marshaling)，编组通过<code>json.Marshal</code>函数完成。在编码时，默认使用Go语言结构体的成员名字作为JSON的对象（通过reflect反射技术）。只有导出的结构体成员才会被编码。</p><p>如果在结构体slice编码成JSON的时候使用自定义的成员名，可以使用<code>结构体成员Tag</code>来实现。</p><h3 id="JSON标签"><a href="#JSON标签" class="headerlink" title="JSON标签"></a>JSON标签</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">    ID   <span class="keyword">int</span> <span class="string">`json:&quot;id&quot;`</span>  <span class="comment">// 编码后的字段名为 id</span></span><br><span class="line">    Name <span class="keyword">string</span>           <span class="comment">// 编码后的字段名为 自定义成员名 Name</span></span><br><span class="line">    age  <span class="keyword">int</span>              <span class="comment">// 未导出字段不能编码</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>json</code>为键名的标签对应的值用于控制<code>encoding/json</code>包的编码和解码的行为，并且<code>encoding/...</code>下面其它的包也遵循这个约定。</p><table><thead><tr><th>标签选项</th><th>使用说明</th></tr></thead><tbody><tr><td>-</td><td>字段不进行序列化 例：<code>json:&quot;-&quot;</code></td></tr><tr><td>omitempy</td><td>类型零值或空值，序列化时忽略该字段 例：<code>json:&quot;,omitempy&quot;</code> 字段名省略的话用结构体字段名</td></tr><tr><td>type</td><td>重新指定字段类型 例：<code>json:&quot;age,string&quot;</code></td></tr></tbody></table><h2 id="gorm标签"><a href="#gorm标签" class="headerlink" title="gorm标签"></a>gorm标签</h2><h3 id="模型定义"><a href="#模型定义" class="headerlink" title="模型定义"></a>模型定义</h3><p>模型是标准的 struct,由基本数据类型以及实现了<em>Scanner</em>和<em>Valuer</em>接口的自定义类型及其指针或别名组成。</p><p>GORM 定义一个 gorm.Model 结构体，如下所示：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Model <span class="keyword">struct</span> &#123;</span><br><span class="line">  ID        <span class="keyword">uint</span>           <span class="string">`gorm:&quot;primaryKey&quot;`</span></span><br><span class="line">  CreatedAt time.Time</span><br><span class="line">  UpdatedAt time.Time</span><br><span class="line">  DeletedAt gorm.DeletedAt <span class="string">`gorm:&quot;index&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">gorm<span class="string">`为键名的标签遵循GORM的解析规则，GORM支持如下tag，tag名大小写不敏感，建议使用`</span>camelCase<span class="string">`风格，多个标签定义用`</span>分号(;)分隔</span><br></pre></td></tr></table></figure><p><code>[知识点]</code> Gorm建表时 AUTO_INCREMENT 不生效的问题</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AUTO_INCREMENT 不生效</span></span><br><span class="line">Id  <span class="keyword">uint64</span> <span class="string">`gorm:&quot;column:id;primaryKey;type:bigint(20);autoIncrement;comment:&#x27;主键&#x27;&quot;`</span></span><br><span class="line"><span class="comment">// AUTO_INCREMENT 不生效</span></span><br><span class="line">Id  <span class="keyword">uint64</span> <span class="string">`gorm:&quot;column:id;type:bigint(20);autoIncrement;comment:&#x27;主键&#x27;&quot;`</span></span><br><span class="line"><span class="comment">// AUTO_INCREMENT 生效 gorm会自动根据字段类型设置数据库字段类型并设置为主键</span></span><br><span class="line">Id  <span class="keyword">uint64</span> <span class="string">`gorm:&quot;column:id;autoIncrement;comment:&#x27;主键&#x27;&quot;`</span> <span class="comment">//写成AUTO_INCREMENT也可以</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><table><thead><tr><th>标签选项</th><th>使用说明</th></tr></thead><tbody><tr><td>column</td><td>指定 db 列名</td></tr><tr><td>type</td><td>列数据类型，推荐使用兼容性好的通用类型，例如：所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用，例如：<code>not null</code>、<code>size</code>, <code>autoIncrement</code>… 像 <code>varbinary(8)</code> 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时，它需要是完整的数据库数据类型，如：<code>MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT</code></td></tr><tr><td>size</td><td>指定列大小，例如：<code>size:256</code></td></tr><tr><td>primaryKey</td><td>指定列为主键</td></tr><tr><td>unique</td><td>指定列为唯一</td></tr><tr><td>default</td><td>指定列的默认值，字符串默认值用单引号，例：<code>gorm:&quot;default:&#39;cn&#39;&quot;</code></td></tr><tr><td>precision</td><td>指定列的精度</td></tr><tr><td>scale</td><td>指定列大小</td></tr><tr><td>not null</td><td>指定列为 NOT NULL</td></tr><tr><td>autoIncrement</td><td>指定列为自动增长，不可与<code>primaryKey</code>、<code>type</code>同时使用否则不生效，看上面<code>知识点</code>部分</td></tr><tr><td>autoIncrementIncrement</td><td>自动步长，控制连续记录之间的间隔</td></tr><tr><td>embedded</td><td>嵌套字段</td></tr><tr><td>embeddedPrefix</td><td>嵌入字段的列名前缀</td></tr><tr><td>autoCreateTime</td><td>创建时追踪当前时间，对于 <code>int</code> 字段，它会追踪秒级时间戳，您可以使用 <code>nano</code>/<code>milli</code> 来追踪纳秒、毫秒时间戳，例如：<code>autoCreateTime:nano</code></td></tr><tr><td>autoUpdateTime</td><td>创建/更新时追踪当前时间，对于 <code>int</code> 字段，它会追踪秒级时间戳，您可以使用 <code>nano</code>/<code>milli</code> 来追踪纳秒、毫秒时间戳，例如：<code>autoUpdateTime:milli</code></td></tr><tr><td>index</td><td>根据参数创建索引，多个字段使用相同的名称则创建复合索引，查看<a href="https://gorm.io/zh_CN/docs/indexes.html">索引</a>获取详情</td></tr><tr><td>uniqueIndex</td><td>与 <code>index</code> 相同，但创建的是唯一索引</td></tr><tr><td>check</td><td>创建检查约束，例如 <code>check:age &gt; 13</code>，查看 <a href="https://gorm.io/zh_CN/docs/constraints.html">约束</a> 获取详情</td></tr><tr><td>&lt;-</td><td>设置字段写入的权限， <code>&lt;-:create</code> 只创建、<code>&lt;-:update</code> 只更新、<code>&lt;-:false</code> 无写入权限、<code>&lt;-</code> 创建和更新权限</td></tr><tr><td>-&gt;</td><td>设置字段读的权限，<code>-&gt;:false</code> 无读权限</td></tr><tr><td>-</td><td>忽略该字段，<code>-</code> 无读写权限</td></tr><tr><td>comment</td><td>迁移时为字段添加注释</td></tr></tbody></table><p>示例：</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 内容模型</span></span><br><span class="line"><span class="keyword">type</span> Content <span class="keyword">struct</span> &#123;</span><br><span class="line">    Model</span><br><span class="line">    NewsId   <span class="keyword">uint64</span>  <span class="string">`gorm:&quot;column:news_id&quot;`</span></span><br><span class="line">    Content  <span class="keyword">string</span>  <span class="string">`gorm:&quot;column:content&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>知识点</code> 自定义唯一索引</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Go 版本：go1.16.6   Gorm 版本：v1.9.16</span></span><br><span class="line"><span class="comment">// 尝试用 uniqueIndex 创建不生效,有解决方法的同学欢迎评论区留言 普通索引是生效的</span></span><br><span class="line">Email <span class="keyword">string</span> <span class="string">`gorm:&quot;column:email;type:varchar(50);uniqueIndex:uidx_email&quot;`</span> <span class="comment">// 不生效</span></span><br><span class="line">Email <span class="keyword">string</span> <span class="string">`gorm:&quot;column:email;type:varchar(50);index:idx_email&quot;`</span>        <span class="comment">// 生效</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建唯一索引 建表时会创建名称为 email 的唯一索引</span></span><br><span class="line">Email <span class="keyword">string</span> <span class="string">`gorm:&quot;column:email;type:varchar(50);unique&quot;`</span></span><br><span class="line"><span class="comment">// 创建自定义名称 uidx_email 的唯一索引</span></span><br><span class="line">Email <span class="keyword">string</span> <span class="string">`gorm:&quot;column:email;type:varchar(50)&quot; sql:&quot;unique_index:uidx_email&quot;`</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>知识点</code> 自动更新时间</p><p><code>GORM</code>约定使用<code>CreatedAt</code>、<code>UpdatedAt</code>追踪创建/更新时间。如果定义了这种字段，且默认值为零值，<code>GORM</code>在创建、更新时会自动填充当前时间。要使用不同名称的字段，您可以配置<code>autoCreateTime</code>、<code>autoUpdateTime</code>标签，如果想要保存 UNIX（毫/纳）秒时间戳，而不是 time，只需简单地将 time.Time 修改为 int 即可，毫/纳秒参数可以看上面表格示例。</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><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="comment">// 时间自动创建和更新</span></span><br><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123; </span><br><span class="line">    <span class="comment">// 自定义字段  使用时间戳填纳秒数充更新时间 </span></span><br><span class="line">    Updated   <span class="keyword">int64</span> <span class="string">`gorm:&quot;autoUpdateTime:nano&quot;`</span> </span><br><span class="line">    <span class="comment">//自定义字段  使用时间戳毫秒数填充更新时间 </span></span><br><span class="line">    Updated   <span class="keyword">int64</span> <span class="string">`gorm:&quot;autoUpdateTime:milli&quot;`</span> </span><br><span class="line">    <span class="comment">//自定义字段  使用时间戳秒数填充创建时间 </span></span><br><span class="line">    Created   <span class="keyword">int64</span> <span class="string">`gorm:&quot;autoCreateTime&quot;`</span> </span><br><span class="line">    <span class="comment">// 默认创建时间字段  在创建时如果该字段值为零值，则使用当前时间填充 </span></span><br><span class="line">    CreatedAt time.Time </span><br><span class="line">    <span class="comment">// 默认更新时间字段  在创建时该字段值为零值或者在更新时，使用当前时间戳秒数填充 </span></span><br><span class="line">    UpdatedAt <span class="keyword">int</span>      </span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>注：GORM模型 <a href="https://link.juejin.cn/?target=https://gorm.io/zh_CN/docs/models.html">官方文档</a></p><h3 id="关联标签"><a href="#关联标签" class="headerlink" title="关联标签"></a>关联标签</h3><p>GORM的关联类型有多重类型：<code>belongs to</code>、<code>has one</code>、<code>has many</code>、<code>many to many</code>具体结构体定义可参考问<a href="https://gorm.io/zh_CN/docs/belongs_to.html">文档</a>，关联模式使用的标签选项如下所示：</p><table><thead><tr><th>标签选项</th><th>使用说明</th></tr></thead><tbody><tr><td>foreignKey</td><td>指定当前模型的列作为连接表的外键 例：<code>gorm:&quot;foreignKey:FieldId&quot;</code> 其中FieldID是外键字段名</td></tr><tr><td>references</td><td>指定引用表的列名，其将被映射为连接表外键</td></tr><tr><td>polymorphic</td><td>指定多态类型，比如模型名</td></tr><tr><td>polymorphicValue</td><td>指定多态值、默认表名</td></tr><tr><td>many2many</td><td>指定连接表表名</td></tr><tr><td>joinForeignKey</td><td>指定连接表的外键列名，其将被映射到当前表</td></tr><tr><td>joinReferences</td><td>指定连接表的外键列名，其将被映射到引用表</td></tr><tr><td>constraint</td><td>关系约束，例如：<code>OnUpdate</code>、<code>OnDelete</code></td></tr></tbody></table><p>示例：</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 新闻模型</span></span><br><span class="line"><span class="keyword">type</span> News <span class="keyword">struct</span> &#123;</span><br><span class="line">    Model</span><br><span class="line">    Title   <span class="keyword">string</span>   <span class="string">`gorm:&quot;column:title;type:string;not null,default:&#x27;&#x27;&quot;`</span></span><br><span class="line">    Content Content  <span class="string">`gorm:&quot;foreignKey:NewsId&quot; json:&quot;content&quot;`</span> <span class="comment">//指定外键</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>注：GORM关联模型 <a href="https://link.juejin.cn/?target=https://gorm.io/zh_CN/docs/associations.html%23tags">官方文档</a></p><h2 id="form标签"><a href="#form标签" class="headerlink" title="form标签"></a>form标签</h2><p>Gin中提供了模型绑定，将表单数据和模型进行绑定，方便参数校验和使用。</p><h3 id="模型绑定"><a href="#模型绑定" class="headerlink" title="模型绑定"></a>模型绑定</h3><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><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 表单数据</span></span><br><span class="line"><span class="keyword">type</span> LoginForm <span class="keyword">struct</span> &#123;</span><br><span class="line">    Email     <span class="keyword">string</span>    <span class="string">`form:&quot;emial&quot;`</span>    </span><br><span class="line">    Password  <span class="keyword">string</span>    <span class="string">`form:&quot;password&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// model 或 service 层Model</span></span><br><span class="line"><span class="keyword">type</span> Email <span class="keyword">struct</span> &#123;</span><br><span class="line">    Email       <span class="keyword">string</span></span><br><span class="line">    Password    <span class="keyword">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>通过 form:”email” 对表单email数据进行绑定。然后通过Bind()、ShouldBind()等方法获取参数值。</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><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">EmailLogin</span> <span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> email LoginForm</span><br><span class="line">    <span class="keyword">if</span> err := c.ShouldBind(&amp;email); err != <span class="literal">nil</span> &#123;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 获取表单数据局</span></span><br><span class="line">    args := Email &#123;</span><br><span class="line">        Email:     email.Email,</span><br><span class="line">        Password:  email.Password,</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 对参数进行后续使用</span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="binding标签"><a href="#binding标签" class="headerlink" title="binding标签"></a>binding标签</h2><p>Gin对于数据的校验使用的是 <code>validator.v10</code> <a href="https://github.com/go-playground/validator">包</a>，该包提供多种数据校验方法，通过<code>binding:&quot;&quot;</code>标签来进行数据校验。</p><p>我们对上面的表单模型添加数据校验标签如下：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> LoginForm <span class="keyword">struct</span> &#123;</span><br><span class="line">    Email     <span class="keyword">string</span>    <span class="string">`form:&quot;emial&quot; binding:&quot;email&quot;`</span>    </span><br><span class="line">    Password  <span class="keyword">string</span>    <span class="string">`form:&quot;password&quot; binging:&quot;required,min=6,max=10&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>特殊符号说明：</p><ul><li>逗号（,）:分隔多个标签选项，逗号之间不能有空格，否则panic；</li><li>横线（-）:跳过该字段不做校验；</li><li>竖线（|）:使用多个选项，满足其中一个即可。</li></ul><p>binding标签选项：</p><h3 id="必需校验"><a href="#必需校验" class="headerlink" title="必需校验"></a>必需校验</h3><table><thead><tr><th>标签选项</th><th>使用说明</th><th>示例</th></tr></thead><tbody><tr><td>required</td><td>表示该字段值必输设置，且不能为默认值</td><td><code>binding:required</code></td></tr><tr><td>omitempty</td><td>如果字段未设置，则忽略它</td><td><code>binding:reqomitemptyuired</code></td></tr></tbody></table><h3 id="范围校验"><a href="#范围校验" class="headerlink" title="范围校验"></a>范围校验</h3><p>范围验证: 切片、数组和map、字符串，验证其长度；数值，验证大小范围</p><table><thead><tr><th>标签选项</th><th>使用说明</th><th>示例</th></tr></thead><tbody><tr><td>len</td><td>参数值等于给定值</td><td><code>binding:&quot;len=8&quot;</code>等于8</td></tr><tr><td>ne</td><td>不等于</td><td><code>binding:&quot;ne=8&quot;</code>不等于8</td></tr><tr><td>max</td><td>最大值，小于等于参数值</td><td><code>binding:&quot;max=8&quot;</code>小于等于8</td></tr><tr><td>min</td><td>最小值，大于等于参数值</td><td><code>binding:&quot;min=8&quot;</code>大于等于8</td></tr><tr><td>lte</td><td>参数值小于等于给定值</td><td><code>binding:&quot;lte=8&quot;</code>小于等于8</td></tr><tr><td>gte</td><td>参数值大于等于给定值</td><td><code>binding:&quot;gte=8&quot;</code>大于等于8</td></tr><tr><td>lt</td><td>参数值小于给定值</td><td><code>binding:&quot;lt=8&quot;</code>小于8</td></tr><tr><td>gt</td><td>参数值大于给定值</td><td><code>binding:&quot;gt=8&quot;</code>大于8</td></tr><tr><td>oneof</td><td>参数值只能是枚举值中的一个，值必须是数值或字符串，以空格分隔，如果字符串中有空格，将字符串用单引号包围</td><td><code>binding:&quot;oneof=red green&quot;</code></td></tr></tbody></table><p>示例：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">    Name <span class="keyword">string</span> <span class="string">`form:&quot;name&quot; binding:&quot;required,min=1,max=10&quot;`</span></span><br><span class="line">    Age  unit8  <span class="string">`form:&quot;age&quot; binding:&quot;lte=150,gte=0&quot;`</span></span><br><span class="line">    sex  <span class="keyword">string</span> <span class="string">`form:&quot;sex&quot; binding:&quot;oneof=male female&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>注：<a href="https://github.com/go-playground/validator/blob/master/README.md#comparisons">文档地址</a></p><h3 id="字符串校验"><a href="#字符串校验" class="headerlink" title="字符串校验"></a>字符串校验</h3><table><thead><tr><th>标签选项</th><th>使用说明</th><th>示例</th></tr></thead><tbody><tr><td>contains</td><td>参数值包含设置子串</td><td><code>binding:&quot;contains=tom&quot;</code>是否包含tom字符串</td></tr><tr><td>excludes</td><td>参数值不包含设置子串</td><td><code>binding:&quot;excludes=tom&quot;</code>是否不包含tom字符串</td></tr><tr><td>startswith</td><td>字符串前缀</td><td><code>binding:&quot;startswith=tom&quot;</code>是否以tom开头</td></tr><tr><td>endswith</td><td>字符串前缀</td><td><code>binding:&quot;endswith=tom&quot;</code>是否以tom结尾</td></tr></tbody></table><p>示例：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">    Name <span class="keyword">string</span> <span class="string">`form:&quot;name&quot; binding:&quot;required,contains=ac,endswith=ck&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>注：<a href="https://github.com/go-playground/validator/blob/master/README.md#strings">文档地址</a></p><h3 id="字段校验"><a href="#字段校验" class="headerlink" title="字段校验"></a>字段校验</h3><table><thead><tr><th>标签选项</th><th>使用说明</th></tr></thead><tbody><tr><td>eqcsfield</td><td>跨不同结构体字段相等，比如<code>struct1 field1</code> 是否等于<code>struct2 field2</code></td></tr><tr><td>necsfield</td><td>跨不同结构体字段不相等</td></tr><tr><td>eqfield</td><td>同一结构体字段相等验证，例如：输入两次密码</td></tr><tr><td>nefield</td><td>同一结构体字段不相等验证</td></tr><tr><td>gtefield</td><td>大于等于同一结构体字段</td></tr><tr><td>ltefield</td><td>小于等于同一结构体字段</td></tr></tbody></table><p>示例：</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 跨不同结构体字段相等</span></span><br><span class="line"><span class="keyword">type</span> Struct1 <span class="keyword">struct</span> &#123; </span><br><span class="line">    Field1 <span class="keyword">string</span> <span class="string">`validate:eqcsfield=Struct2.Field2`</span> </span><br><span class="line">    Struct2 <span class="keyword">struct</span> &#123; </span><br><span class="line">        Field2 <span class="keyword">string</span> </span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 同一结构体字段相等验证</span></span><br><span class="line"><span class="keyword">type</span> Email <span class="keyword">struct</span> &#123; </span><br><span class="line">    Email  <span class="keyword">string</span> <span class="string">`validate:&quot;lte=4&quot;`</span> </span><br><span class="line">    Pwd    <span class="keyword">string</span> <span class="string">`validate:&quot;min=10&quot;`</span> </span><br><span class="line">    Pwd2   <span class="keyword">string</span> <span class="string">`validate:&quot;eqfield=Pwd&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 同一结构体字段验证不相等</span></span><br><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123; </span><br><span class="line">    Name     <span class="keyword">string</span> <span class="string">`validate:&quot;lte=4&quot;`</span> </span><br><span class="line">    Age      <span class="keyword">int</span> <span class="string">`validate:&quot;min=20&quot;`</span> </span><br><span class="line">    Password <span class="keyword">string</span> <span class="string">`validate:&quot;min=10,nefield=Name&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="其他校验"><a href="#其他校验" class="headerlink" title="其他校验"></a>其他校验</h3><table><thead><tr><th>标签选项</th><th>使用说明</th><th>示例</th></tr></thead><tbody><tr><td>ip</td><td>合法IP地址校验</td><td><code>binding:&quot;ip&quot;</code></td></tr><tr><td>email</td><td>合法邮箱校验</td><td><code>binding:&quot;email&quot;</code></td></tr><tr><td>url</td><td>合法的URL</td><td><code>binding:&quot;url&quot;</code></td></tr><tr><td>uri</td><td>合法的URI</td><td><code>binding:&quot;uri&quot;</code></td></tr><tr><td>uuid</td><td>uuid验证</td><td><code>binding:&quot;uuid&quot;</code></td></tr><tr><td>datetime</td><td>合法时间格式值校验</td><td><code>binding:&quot;datetime=2006-01-02&quot;</code></td></tr><tr><td>json</td><td>JSON数据验证</td><td><code>validate:&quot;json&quot;</code></td></tr><tr><td>numeric</td><td>数值验证 正则：<code>^[-+]?[0-9]+(?:\\.[0-9]+)?$</code></td><td><code>validate:&quot;numeric&quot;</code></td></tr><tr><td>number</td><td>整数验证 正则：<code>^[0-9]+$</code></td><td><code>validate:&quot;number&quot;</code></td></tr><tr><td>alpha</td><td>字母字符串验证 正则：<code>^[a-zA-Z]+$</code></td><td><code>validate:&quot;alpha&quot;</code></td></tr><tr><td>alphanum</td><td>字母数字字符串验证 正则：<code>^[a-zA-Z0-9]+$</code></td><td><code>validate:&quot;alphanum&quot;</code></td></tr><tr><td>ascii</td><td>Ascii 字符验证</td><td><code>validate:&quot;ascii&quot;</code></td></tr></tbody></table><p>示例：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123; </span><br><span class="line">    Name     <span class="keyword">string</span>  <span class="string">`validate:&quot;required,min=1,max=10&quot;`</span> </span><br><span class="line">    Email    <span class="keyword">int</span>     <span class="string">`validate:&quot;required,email&quot;`</span></span><br><span class="line">    birthday <span class="keyword">string</span>  <span class="string">`validate:&quot;datetime=2006-01-02&quot;`</span></span><br><span class="line">    Pwd      <span class="keyword">string</span>  <span class="string">`validate:&quot;required,alphanum&quot;`</span></span><br><span class="line">    Score    srring  <span class="string">`validate:&quot;numeric&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>注：<a href="https://github.com/go-playground/validator/blob/master/README.md#network">文档地址</a></p><h2 id="ini标签"><a href="#ini标签" class="headerlink" title="ini标签"></a>ini标签</h2><p>在使用<code>go-ini</code>库操作.ini配置文件的时候，如果需要将配置文件字段映射到结构体变量，如果键名与字段名不相同，那么需要在结构标签中指定对应的键名。标准库<code>encoding/json</code>、<code>encoding/xml</code>解析时可以将键名<code>app_name</code>对应到字段名<code>AppName</code>，而go-ini库不可以，所以需要在结构体标签指定对应键名。</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><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="comment">## 配置文件 cnf.ini</span></span><br><span class="line"><span class="attr">app_name</span>  = awesome web</span><br><span class="line"><span class="attr">log_level</span> = DEBUG</span><br><span class="line"></span><br><span class="line">// 配置文件映射 结构体</span><br><span class="line">type Config struct &#123;</span><br><span class="line">  AppName   string `ini:&quot;app_name&quot;`  // ini标签指定下键名</span><br><span class="line">  LogLevel  string `ini:&quot;log_level&quot;`</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;结构体标签&quot;&gt;&lt;a href=&quot;#结构体标签&quot; class=&quot;headerlink&quot; title=&quot;结构体标签&quot;&gt;&lt;/a&gt;结构体标签&lt;/h2&gt;&lt;h3 id=&quot;标签定义&quot;&gt;&lt;a href=&quot;#标签定义&quot; class=&quot;headerlink&quot; title=&quot;标签定义&quot;</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>docker 安装gitea</title>
    <link href="http://example.com/2022/05/12/docker%20%E5%AE%89%E8%A3%85gitea/"/>
    <id>http://example.com/2022/05/12/docker%20%E5%AE%89%E8%A3%85gitea/</id>
    <published>2022-05-12T02:13:36.000Z</published>
    <updated>2023-06-06T15:56:09.499Z</updated>
    
    <content type="html"><![CDATA[<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">version: &quot;3&quot;</span><br><span class="line"></span><br><span class="line">networks:</span><br><span class="line">  gitea:</span><br><span class="line">    external: false</span><br><span class="line">services:</span><br><span class="line">  server:</span><br><span class="line">    image: gitea&#x2F;gitea:latest</span><br><span class="line">    container_name: gitea</span><br><span class="line">    environment:</span><br><span class="line">      - USER_UID&#x3D;1000</span><br><span class="line">      - USER_GID&#x3D;1000</span><br><span class="line">      - DB_TYPE&#x3D;mysql</span><br><span class="line">      - DB_HOST&#x3D;db:3306</span><br><span class="line">      - DB_NAME&#x3D;gitea</span><br><span class="line">      - DB_USER&#x3D;gitea</span><br><span class="line">      - DB_PASSWD&#x3D;gitea</span><br><span class="line">    restart: always</span><br><span class="line">    networks:</span><br><span class="line">      - gitea</span><br><span class="line">    volumes:</span><br><span class="line">      - .&#x2F;data:&#x2F;data</span><br><span class="line">      - &#x2F;etc&#x2F;timezone:&#x2F;etc&#x2F;timezone:ro</span><br><span class="line">      - &#x2F;etc&#x2F;localtime:&#x2F;etc&#x2F;localtime:ro</span><br><span class="line">    ports:</span><br><span class="line">      - &quot;222:22&quot;</span><br><span class="line">    depends_on:</span><br><span class="line">      - db</span><br><span class="line"></span><br><span class="line">  db:</span><br><span class="line">    image: mysql:8</span><br><span class="line">    restart: always</span><br><span class="line">    environment:</span><br><span class="line">      - MYSQL_ROOT_PASSWORD&#x3D;gitea</span><br><span class="line">      - MYSQL_USER&#x3D;gitea</span><br><span class="line">      - MYSQL_PASSWORD&#x3D;gitea</span><br><span class="line">      - MYSQL_DATABASE&#x3D;gitea</span><br><span class="line">    networks:</span><br><span class="line">      - gitea</span><br><span class="line">    volumes:</span><br><span class="line">      - .&#x2F;mysql:&#x2F;var&#x2F;lib&#x2F;mysql</span><br><span class="line">  </span><br><span class="line">  nginx:</span><br><span class="line">    image: nginx:latest</span><br><span class="line">    container_name: nginx-web</span><br><span class="line">    restart: always</span><br><span class="line">    ports:</span><br><span class="line">      - 80:80</span><br><span class="line">      - 443:443</span><br><span class="line">    networks:</span><br><span class="line">      - gitea</span><br><span class="line">    volumes:</span><br><span class="line">      - .&#x2F;nginx&#x2F;cert:&#x2F;etc&#x2F;nginx&#x2F;cert</span><br><span class="line">      - .&#x2F;nginx&#x2F;nginx.conf:&#x2F;etc&#x2F;nginx&#x2F;conf.d&#x2F;default.conf</span><br><span class="line">      - .&#x2F;nginx&#x2F;log:&#x2F;var&#x2F;log&#x2F;nginx</span><br><span class="line">      - &#x2F;etc&#x2F;localtime:&#x2F;etc&#x2F;localtime:ro</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">client_max_body_size 100m;</span><br><span class="line">server &#123;</span><br><span class="line">    listen 80;</span><br><span class="line">    listen 443 ssl;</span><br><span class="line">    server_name git.shenglei.top;</span><br><span class="line">    ssl_certificate &#x2F;etc&#x2F;nginx&#x2F;cert&#x2F;www.shenglei.top_bundle.crt;</span><br><span class="line">    ssl_certificate_key &#x2F;etc&#x2F;nginx&#x2F;cert&#x2F;www.shenglei.top.key;</span><br><span class="line">    ssl_session_timeout 5m;</span><br><span class="line">    ssl_protocols TLSv1.2 TLSv1.3;</span><br><span class="line">    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;</span><br><span class="line">    ssl_prefer_server_ciphers on;</span><br><span class="line">    location &#x2F; &#123;</span><br><span class="line">        proxy_pass http:&#x2F;&#x2F;gitea:3000;</span><br><span class="line">        proxy_set_header Host $host;</span><br><span class="line">        proxy_set_header X-Real-IP $remote_addr;</span><br><span class="line">        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</span><br><span class="line">        proxy_set_header X-Forwarded-Proto $scheme;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">ssl_certificate &#x2F;etc&#x2F;nginx&#x2F;cert&#x2F;www.shenglei.top_bundle.crt;</span><br><span class="line">ssl_certificate_key &#x2F;etc&#x2F;nginx&#x2F;cert&#x2F;www.shenglei.top.key;</span><br><span class="line">ssl_session_timeout 5m;</span><br><span class="line">ssl_protocols TLSv1.2 TLSv1.3;</span><br><span class="line">ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;</span><br><span class="line">ssl_prefer_server_ciphers on;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">version: &quot;3&quot;</span><br><span class="line"></span><br><span class="line">networks:</span><br><span class="line">  tinode-net:</span><br><span class="line">    external: false</span><br><span class="line">services:</span><br><span class="line">  server:</span><br><span class="line">    image: tinode&#x2F;tinode-mysql:latest</span><br><span class="line">    container_name: tinode</span><br><span class="line">    environment:</span><br><span class="line">      - EXT_CONFIG&#x3D;&#x2F;tinode.conf </span><br><span class="line">    restart: always</span><br><span class="line">    networks:</span><br><span class="line">      - tinode-net</span><br><span class="line">    volumes:</span><br><span class="line">      - .&#x2F;tinode.conf:&#x2F;tinode.conf</span><br><span class="line">      - &#x2F;etc&#x2F;timezone:&#x2F;etc&#x2F;timezone:ro</span><br><span class="line">      - &#x2F;etc&#x2F;localtime:&#x2F;etc&#x2F;localtime:ro</span><br><span class="line">    depends_on:</span><br><span class="line">      - mysql</span><br><span class="line">  mysql:</span><br><span class="line">    image: mysql:5.7</span><br><span class="line">    restart: always</span><br><span class="line">    environment:</span><br><span class="line">      - MYSQL_ALLOW_EMPTY_PASSWORD&#x3D;yes</span><br><span class="line">    networks:</span><br><span class="line">      - tinode-net</span><br><span class="line">    volumes:</span><br><span class="line">      - .&#x2F;mysql:&#x2F;var&#x2F;lib&#x2F;mysql</span><br><span class="line">  </span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class</summary>
      
    
    
    
    
    <category term="-docker" scheme="http://example.com/tags/docker/"/>
    
  </entry>
  
  <entry>
    <title>golang-migrate 使用</title>
    <link href="http://example.com/2022/05/07/golang-migrate-%E4%BD%BF%E7%94%A8/"/>
    <id>http://example.com/2022/05/07/golang-migrate-%E4%BD%BF%E7%94%A8/</id>
    <published>2022-05-07T02:12:42.000Z</published>
    <updated>2023-06-06T15:56:09.500Z</updated>
    
    <content type="html"><![CDATA[<h3 id="一、CLI方式使用"><a href="#一、CLI方式使用" class="headerlink" title="一、CLI方式使用"></a>一、CLI方式使用</h3><p>需要下载工具,之后在GOPATH目录下会多一个migrate程序</p><figure class="highlight golang"><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> install -tags <span class="string">&#x27;mysql&#x27;</span> github.com/golang-migrate/migrate/v4/cmd/migrate@latest</span><br></pre></td></tr></table></figure><p>创建migrate项目，创建migration文件夹（用于存放迁移文件）</p><p>在migrate目录下用命令生成要迁移的up和down文件文件</p><blockquote><p> -format string</p><p> 要使用的时间格式字符串。如果指定了字符串”unix”或”unixNano”，则将分别使用自1970年UTC 1月1日起的秒或纳秒。注意，由于time.Time.Format()的行为，无效的格式字符串将不会出错(默认为”20060102150405”)</p></blockquote><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">migrate create -ext sql -dir migration -format unix create_test_table</span><br></pre></td></tr></table></figure><p>文件为空，需要自行补充sql命令</p><p>编辑up文件</p><p>例如：</p><figure class="highlight sql"><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="keyword">CREATE</span> <span class="keyword">TABLE</span> IF  <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> test(</span><br><span class="line">    id <span class="type">int</span>(<span class="number">10</span>) unsigned <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT,</span><br><span class="line">   name <span class="type">VARCHAR</span>(<span class="number">100</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line">   password <span class="type">VARCHAR</span>(<span class="number">40</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line">   <span class="keyword">PRIMARY</span> <span class="keyword">KEY</span> ( id )</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>编辑down文件</p><p>例如：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DROP</span>` `<span class="keyword">table</span>` `IF <span class="keyword">EXISTS</span> test;</span><br></pre></td></tr></table></figure><p>运行1个迁移文件：</p><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">migrate -verbose -source file://migration mysql://user:password@tcp(ip:port)/database_name up 1</span><br></pre></td></tr></table></figure><p>version 如果-1表示版本脏了，需要加上force 20*****（生成的表前缀） 来强制版本索引，例如：</p><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">migrate -verbose -source file://migration -database mysql://user:password@tcp(ip:port)/database_name force 20210730160233</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 get -u github.com/golang-migrate/migrate</span><br></pre></td></tr></table></figure><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><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></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;database/sql&quot;</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/go-sql-driver/mysql&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/golang-migrate/migrate&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/golang-migrate/migrate/database/mysql&quot;</span></span><br><span class="line">_ <span class="string">&quot;github.com/golang-migrate/migrate/database/mysql&quot;</span></span><br><span class="line">_ <span class="string">&quot;github.com/golang-migrate/migrate/source/file&quot;</span></span><br><span class="line">_ <span class="string">&quot;github.com/golang-migrate/migrate/source/github&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">db, err := sql.Open(<span class="string">&quot;mysql&quot;</span>, <span class="string">&quot;user:password@tcp(ip:port)/migrate?multiStatements=true&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatal(err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">defer</span> db.Close()</span><br><span class="line"></span><br><span class="line">driver, err := mysql.WithInstance(db, &amp;mysql.Config&#123;&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatal(err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">m, err := migrate.NewWithDatabaseInstance(</span><br><span class="line"><span class="string">&quot;file://migration&quot;</span>,</span><br><span class="line"><span class="string">&quot;migrate&quot;</span>,</span><br><span class="line">driver,</span><br><span class="line">)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatal(err)</span><br><span class="line">&#125;</span><br><span class="line">err = m.Up() <span class="comment">//or m.Down()</span></span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatal(err)</span><br><span class="line">&#125;</span><br><span class="line">_ = m.Steps(<span class="number">1</span>) <span class="comment">//执行的文件数</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>切记要注意import包，否则报错</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;一、CLI方式使用&quot;&gt;&lt;a href=&quot;#一、CLI方式使用&quot; class=&quot;headerlink&quot; title=&quot;一、CLI方式使用&quot;&gt;&lt;/a&gt;一、CLI方式使用&lt;/h3&gt;&lt;p&gt;需要下载工具,之后在GOPATH目录下会多一个migrate程序&lt;/p&gt;
&lt;figu</summary>
      
    
    
    
    
    <category term="golang" scheme="http://example.com/tags/golang/"/>
    
  </entry>
  
  <entry>
    <title>wget的基本使用</title>
    <link href="http://example.com/2022/04/25/wget%E7%9A%84%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/"/>
    <id>http://example.com/2022/04/25/wget%E7%9A%84%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/</id>
    <published>2022-04-25T09:35:21.000Z</published>
    <updated>2022-05-06T07:20:02.732Z</updated>
    
    <content type="html"><![CDATA[<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">需要下载某个目录下面的所有文件。</span><br><span class="line">命令如下</span><br><span class="line">wget -c -r -np -k -L -p  http://docs.openstack.org/liberty/install-guide-rdo/</span><br><span class="line"></span><br><span class="line">在下载时。有用到外部域名的图片或连接。如果需要同时下载就要用-H参数。</span><br><span class="line">wget -np -nH -r –span-hosts www.xianren.org/pub/path/</span><br><span class="line">-c 断点续传</span><br><span class="line">-r 递归下载，下载指定网页某一目录下（包括子目录）的所有文件</span><br><span class="line">-nd 递归下载时不创建一层一层的目录，把所有的文件下载到当前目录</span><br><span class="line">-np 递归下载时不搜索上层目录，如wget -c -r www.xianren.org/pub/path/</span><br><span class="line">没有加参数-np，就会同时下载path的上一级目录pub下的其它文件</span><br><span class="line">-k 将绝对链接转为相对链接，下载整个站点后脱机浏览网页，最好加上这个参数</span><br><span class="line">-L 递归时不进入其它主机，如wget -c -r www.xianren.org/</span><br><span class="line">如果网站内有一个这样的链接：</span><br><span class="line">www.xianren.org，不加参数-L，就会像大火烧山一样，会递归下载www.xianren.org网站</span><br><span class="line">-p 下载网页所需的所有文件，如图片等</span><br><span class="line">-A 指定要下载的文件样式列表，多个样式用逗号分隔</span><br><span class="line">-i 后面跟一个文件，文件内指明要下载的URL</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class</summary>
      
    
    
    
    
    <category term="教程" scheme="http://example.com/tags/%E6%95%99%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>mac查看远程端口是否打开</title>
    <link href="http://example.com/2022/04/25/mac%E6%9F%A5%E7%9C%8B%E8%BF%9C%E7%A8%8B%E7%AB%AF%E5%8F%A3%E6%98%AF%E5%90%A6%E6%89%93%E5%BC%80/"/>
    <id>http://example.com/2022/04/25/mac%E6%9F%A5%E7%9C%8B%E8%BF%9C%E7%A8%8B%E7%AB%AF%E5%8F%A3%E6%98%AF%E5%90%A6%E6%89%93%E5%BC%80/</id>
    <published>2022-04-25T06:54:10.000Z</published>
    <updated>2022-05-06T07:20:02.732Z</updated>
    
    <content type="html"><![CDATA[<p>Mac使用telnet还需要安装，直接使用下面的方法：</p><p>查看 <a href="http://www.baidu.com/">www.baidu.com</a> 80端口是否可用</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nc -vz -w 2 www.baidu.com 80</span><br></pre></td></tr></table></figure><p>参数含义：</p><blockquote><p>-v 详细信息<br>-z 不发送包给对方<br>-w 秒，表示超时秒数，后面跟数字<br>-u udp 协议，默认是tcp。上图可以看到。如果是dns端口检测，用u</p></blockquote><p>Mac的telnet安装：apt install telnet</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Mac使用telnet还需要安装，直接使用下面的方法：&lt;/p&gt;
&lt;p&gt;查看 &lt;a href=&quot;http://www.baidu.com/&quot;&gt;www.baidu.com&lt;/a&gt; 80端口是否可用&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;tab</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>frp安装配置之内网穿透最佳方案</title>
    <link href="http://example.com/2022/04/24/frp%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AE%E4%B9%8B%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%E6%9C%80%E4%BD%B3%E6%96%B9%E6%A1%88/"/>
    <id>http://example.com/2022/04/24/frp%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AE%E4%B9%8B%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%E6%9C%80%E4%BD%B3%E6%96%B9%E6%A1%88/</id>
    <published>2022-04-24T02:29:10.000Z</published>
    <updated>2022-05-06T07:20:02.732Z</updated>
    
    <content type="html"><![CDATA[<p>Frp是一款流行的跨平台开源免费内网穿透反向代理应用，支持 Windows、macOS与 Linux，支持 TCP、UDP 协议，支持http 和 https 协议，在公网服务器安装一个server端，内网服务器安装一个客户端，起到一个中转转发的作用，从而实现内网暴露到外网，实际就是一个反向代理转发器。</p><h3 id="服务器端安装配置-Frp"><a href="#服务器端安装配置-Frp" class="headerlink" title="服务器端安装配置 Frp"></a>服务器端安装配置 Frp</h3><p>FRP 使用 Go 语言开发，可以支持 Windows、Linux、macOS、ARM 等多平台部署。FRP 安装非常容易，只需下载对应系统平台的软件包并解压就可用了。这里以 Linux 系统为例：</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="built_in">export</span> FRP_VERSION=0.41.0</span><br><span class="line">sudo mkdir -p /etc/frp</span><br><span class="line"><span class="built_in">cd</span> /etc/frp</span><br><span class="line">sudo wget <span class="string">&quot;https://github.com/fatedier/frp/releases/download/v<span class="variable">$&#123;FRP_VERSION&#125;</span>/frp_<span class="variable">$&#123;FRP_VERSION&#125;</span>_linux_amd64.tar.gz&quot;</span></span><br><span class="line">sudo tar xzvf frp_<span class="variable">$&#123;FRP_VERSION&#125;</span>_linux_amd64.tar.gz</span><br><span class="line">sudo mv frp_<span class="variable">$&#123;FRP_VERSION&#125;</span>_linux_amd64/* /etc/frp</span><br></pre></td></tr></table></figure><p>截止写这篇文章为止，github上的最新版本是0.41.0，如果以后出了更新的版本只要改一下上面的版本号就行了，可以去<a href="https://links.jianshu.com/go?to=https://github.com/fatedier/frp/">https://github.com/fatedier/frp/</a>查看最新版本信息。</p><p>FRP 默认提供了 2 个服务端配置文件，一个是简化版的 frps.ini，另一个是完整版的 frps_full.ini。初学者只需用简版配置即可，在简版 frps.ini 配置文件里，默认设置了监听端口为 7000，可以按需修改它。</p><p>需要将服务器的系统防火墙安全组放行，设置 <code>7000</code> 或修改过的对应端口的「允许入站和出站」，否则会一直连接不上的哦！！！这个切记！！</p><h3 id="启动-FRP-服务端-非后台启动，未配置开机自启的情况下"><a href="#启动-FRP-服务端-非后台启动，未配置开机自启的情况下" class="headerlink" title="启动 FRP 服务端(非后台启动，未配置开机自启的情况下)"></a>启动 FRP 服务端(非后台启动，未配置开机自启的情况下)</h3><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="operator">./</span>frps <span class="operator">-</span>c <span class="operator">./</span>frps.ini</span><br></pre></td></tr></table></figure><p>如服务器使用 Win 系统，假设解压到 c:\frp 文件夹，那么只需这样启动：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">c:\frp\frps.exe <span class="operator">-</span>c c:\frp\frps.exe</span><br></pre></td></tr></table></figure><p>当然，这样的启动一般测试可以，生产环境多数为后台启动，需要配置后台运行和开机自启</p><h3 id="使用systemctl配置后台运行和开机自启"><a href="#使用systemctl配置后台运行和开机自启" class="headerlink" title="使用systemctl配置后台运行和开机自启"></a>使用systemctl配置后台运行和开机自启</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo vim &#x2F;lib&#x2F;systemd&#x2F;system&#x2F;frps.service</span><br></pre></td></tr></table></figure><p>在frps.service里写入以下内容</p><figure class="highlight csharp"><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">[<span class="meta">Unit</span>]</span><br><span class="line">Description=fraps service</span><br><span class="line">After=network.target syslog.target</span><br><span class="line">Wants=network.target</span><br><span class="line"></span><br><span class="line">[<span class="meta">Service</span>]</span><br><span class="line">Type=simple</span><br><span class="line"><span class="meta">#启动服务的命令（此处写你的frps的实际安装目录）</span></span><br><span class="line">ExecStart=/etc/frp/frps -c /etc/frp/frps.ini</span><br><span class="line"></span><br><span class="line">[<span class="meta">Install</span>]</span><br><span class="line">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure><h3 id="启动frps"><a href="#启动frps" class="headerlink" title="启动frps"></a>启动frps</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></pre></td><td class="code"><pre><span class="line">sudo systemctl start frps</span><br><span class="line"><span class="comment">#打开自启动</span></span><br><span class="line">sudo systemctl <span class="built_in">enable</span> frps</span><br></pre></td></tr></table></figure><p>如果要重启应用，<code>sudo systemctl restart frps</code><br>如果要停止应用，<code>sudo systemctl stop frps</code><br>如果要查看应用的日志，<code>sudo systemctl status frps</code></p><p>如果启动时7000端口被占用</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="comment">#查看端口号 </span></span><br><span class="line">netstat -anp|grep 7000</span><br></pre></td></tr></table></figure><p>或</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="comment">#查看端口号</span></span><br><span class="line">lsof -i:7000</span><br></pre></td></tr></table></figure><p>或</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="comment">#查看程序进程 </span></span><br><span class="line">ps -ef|grep frps</span><br></pre></td></tr></table></figure><p>得到进程id（pid）之后，杀掉进程</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">kill</span> -9 进程号</span><br></pre></td></tr></table></figure><p>至此服务端安装完毕并已经启动</p><h3 id="配置frp客户端（内网机器上）"><a href="#配置frp客户端（内网机器上）" class="headerlink" title="配置frp客户端（内网机器上）"></a>配置frp客户端（内网机器上）</h3><p>可以将 Frp 客户端安装在内网的 Windows 电脑、Linux 设备 比如树莓派 或者 NAS，甚至部分路由器等设备上。Linux 客户端的安装和启动与服务器端没有太多区别，只是对应运行程序是 frpc 而不是 frps。</p><p>如果是linux设置，安装和设置重启过程这里就省略了，和上面一样的，如果是windows电脑，Frp 是绿色程序，下载软件包回来解压后，启动 frpc.exe 即可。</p><p>在启动前，我们需要先修改配置文件frpc.ini<br>比如服务器的公网ip是1.2.3.4.5</p><figure class="highlight csharp"><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="meta">common</span>]</span><br><span class="line">server_addr = <span class="number">1.2</span><span class="number">.3</span><span class="number">.4</span><span class="number">.5</span></span><br><span class="line">server_port = <span class="number">7000</span></span><br><span class="line"></span><br><span class="line">[<span class="meta">ssh</span>]</span><br><span class="line">type = tcp</span><br><span class="line">local_ip = <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span></span><br><span class="line">local_port = <span class="number">22</span></span><br><span class="line">remote_port = <span class="number">7001</span></span><br><span class="line"></span><br><span class="line">[<span class="meta">hexo</span>]</span><br><span class="line">type = tcp</span><br><span class="line">local_ip = <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span></span><br><span class="line">local_port = <span class="number">4000</span></span><br><span class="line">remote_port = <span class="number">7002</span></span><br></pre></td></tr></table></figure><p>如上，中括号里面的文字是标识，可以自定义，第一个<code>server-port = 7000</code>是服务器上的server端端口。<br>这里配置了四个应用，分别是<br>1.ssh远程登录22端口，映射公网的7001；<br>2.hexo应用，4000端口映射公网的7002</p><p>以上还可以配置更多端口，上面的7001到7002都要在服务器安全组放行</p><h3 id="启动frp客户端"><a href="#启动frp客户端" class="headerlink" title="启动frp客户端"></a>启动frp客户端</h3><p>linux和上面写的服务端启动方法一样<br>windows假设已将 Frp 的客户端解压缩到 c:\frp 目录中，那么启动 Frp 客户端的命令就是：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">c:\frp\frpc.exe <span class="operator">-</span>c c:\frp\frpc.ini</span><br></pre></td></tr></table></figure><p>Linux 启动 Frp 客户端命令(非后台启动，未配置开机自启的情况下)：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="operator">./</span>frpc <span class="operator">-</span>c <span class="operator">./</span>frpc.ini</span><br></pre></td></tr></table></figure><p>启动之后看到 “start proxy success”字样就表示启动成功了。</p><h3 id="远程访问"><a href="#远程访问" class="headerlink" title="远程访问"></a>远程访问</h3><p>公网ip或域名:7001就可以登录内网linux</p><p>公网ip或域名:7002就可以访问到内网的hexo应用</p><p>这样真的就完美舒服了。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Frp是一款流行的跨平台开源免费内网穿透反向代理应用，支持 Windows、macOS与 Linux，支持 TCP、UDP 协议，支持http 和 https 协议，在公网服务器安装一个server端，内网服务器安装一个客户端，起到一个中转转发的作用，从而实现内网暴露到外网</summary>
      
    
    
    
    
  </entry>
  
</feed>
